解决spring-boot2.0.6中webflux无法获得请求IP的问题

这几天在用 spring-boot 2 的 webflux 重构一个工程,写到了一个需要获得客户端请求 IP 的地方,发现写不下去了,在如下的 Handler(webflux 中 Handler 相当于 mvc 中的 Controller)中

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
/**
 * 某业务 Handler
 */
@Component
public class SplashHandler {
  private Mono<ServerResponse> execute(ServerRequest serverRequest) {
    ... 业务代码
    // serverRequest 获得 IP ?
    ... 业务代码
  }
  @Configuration
  public static class RoutingConfiguration {
    @Bean
    public RouterFunction<ServerResponse> execute(SplashHandler handler) {
      return route(
          GET("/api/ad").and(accept(MediaType.TEXT_HTML)),
          handler::execute
      );
    }
  }
}

我发现 org.springframework.web.reactive.function.server.ServerRequest 根本没有暴露用于获得客户端 IP 的 API,想想这在传统 MVC 中是相当基本的需求啊,竟然获取不到,然后 Google 了一下,发现这个是 spring-webflux 的一个 BUG ,这个 BUG 在 spring-webflux 5.1 中解决了,但是,略有些尴尬的是当前最新稳定版的 spring-boot 还是依赖 5.0.x 的 spring-webflux 的。难道要等官方升级么,那不知道得等到什么时候,因此我接着搜了搜资料,看了看文档和源码,自己想了个曲线救国的办法。

正文

在 spring-webflux 中,有一个 org.springframework.web.server.WebFilter 接口,类似于 Servlet API 中的过滤器,这个 API 提供了一个方法会将一个限定名为 org.springframework.web.server.ServerWebExchange 的类暴露出来,而在这个类中就包含了对于请求端 IP 的获取方法:

org.springframework.web.server.ServerWebExchange#getRequest
org.springframework.http.server.reactive.ServerHttpRequest#getRemoteAddress

因此,我们大可以实现一个 WebFilter 在里面通过暴露的 ServerWebExchange 拿到客户端 IP,然后再将其塞到请求的 header 中,这样,后续过程就可以从 header 中取 IP 了。思路有了,我们开始实现吧。

过滤、取 IP、放 header,一气呵成:

import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.net.InetSocketAddress;
import java.util.Objects;
/*
If you want to keep Spring Boot WebFlux features and you want to add additional WebFlux configuration, you can add your own @Configuration class of type WebFluxConfigurer but without @EnableWebFlux.
If you want to take complete control of Spring WebFlux, you can add your own @Configuration annotated with @EnableWebFlux.
 */
@Configuration
public class WebConfiguration implements WebFluxConfigurer {
  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry
        .addMapping("/**")
        .allowedOrigins("*")
        .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTION")
        .allowedHeaders("header1", "header2", "header3")
        .exposedHeaders("header1", "header2")
        .allowCredentials(true)
        .maxAge(3600);
  }
  /**
   * https://stackoverflow.com/questions/51192630/how-do-you-get-clients-ip-address-spring-webflux-websocket?rq=1
   * https://stackoverflow.com/questions/50981136/how-to-get-client-ip-in-webflux
   * https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-filters
   * 由于在低版本的 spring-webflux 中不支持直接获得请求 IP(https://jira.spring.io/browse/SPR-16681),因此写了一个补丁曲线救国,
   * 从 org.springframework.web.server.ServerWebExchange 中获得 IP 后,在放到 header 里
   */
  @Component
  public static class RetrieveClientIpWebFilter implements WebFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
      InetSocketAddress remoteAddress = exchange.getRequest().getRemoteAddress();
      String clientIp = Objects.requireNonNull(remoteAddress).getAddress().getHostAddress();
      ServerHttpRequest mutatedServerHttpRequest = exchange.getRequest().mutate().header("X-CLIENT-IP", clientIp).build();
      ServerWebExchange mutatedServerWebExchange = exchange.mutate().request(mutatedServerHttpRequest).build();
      return chain.filter(mutatedServerWebExchange);
    }
  }
}

后续过程 header 取值:

private Mono<ServerResponse> execute(ServerRequest serverRequest) {
  String clientIp = serverRequest.headers().asHttpHeaders().getFirst("X-CLIENT-IP")
  ... 业务代码
}

通过上述解决方案(其实严格上说是 hacking)就解决了我们遇到的问题了。

总结

以上所述是小编给大家介绍的解决spring-boot2.0.6中webflux无法获得请求IP的问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 详解spring boot整合JMS(ActiveMQ实现)

    本文介绍了spring boot整合JMS(ActiveMQ实现),分享给大家,也给自己留个学习笔记. 一.安装ActiveMQ 具体的安装步骤,请参考我的另一篇文章:http://www.jb51.net/article/127117.htm 二.新建spring boot工程,并加入JMS(ActiveMQ)依赖 三.工程结构 pom依赖如下: <?xml version="1.0" encoding="UTF-8"?> <project xm

  • SpringBoot使用WebJars统一管理静态资源的方法

    传统管理静态资源主要依赖于复制粘贴,不利于后期维护,为了让大家往后更舒心,让WebJars给静态资源来一次搬家革命吧!! 学习目标 简单两步!快速学会使用WebJars统一管理前端依赖. 快速查阅 源码下载:SpringBoot Webjars Learning 使用教程 一.引入相关依赖 在 WebJars官网找到项目中需要的依赖,例如在项目中引入jQuery.BootStrap前端组件等.例如: 版本定位工具:webjars-locator-core 前端组件:jquery .bootstr

  • springboot登陆页面图片验证码简单的web项目实现

    写在前面 前段时间大家都说最近大环境不好,好多公司在裁员,换工作的话不推荐轻易的裸辞,但是我想说的是我所在的公司好流弊,有做不完的业务需求,还有就是招不完的人...... 最近我也是比较繁忙,但是还是要抽一点时间来进行自我复盘和记录,最近也写一个简单的小功能,就是登陆界面的图片验证码功能 环境:Tomcat9.Jdk1.8 1 生成验证码的工具类 public class RandomValidateCodeUtil { public static final String RANDOMCODE

  • SpringBoot深入理解之内置web容器及配置的总结

    前言 在学会基本运用SpringBoot同时,想必搭过SSH.SSM等开发框架的小伙伴都有疑惑,SpringBoot在spring的基础上做了些什么,使得使用SpringBoot搭建开发框架能如此简单,便捷,快速.本系列文章记录网罗博客.分析源码.结合微薄经验后的总结,以便日后翻阅自省. 正文 使用SpringBoot时,首先引人注意的便是其启动方式,我们熟知的web项目都是需要部署到服务容器上,例如tomcat.weblogic.widefly(以前叫JBoss),然后启动web容器真正运行我

  • spring整合JMS实现同步收发消息(基于ActiveMQ的实现)

    本文介绍了spring整合JMS实现同步收发消息(基于ActiveMQ的实现),分享给大家,具体如下: 1. 安装ActiveMQ 注意:JDK版本需要1.7及以上才行 到Apache官方网站下载最新的ActiveMQ的安装包,并解压到本地目录下,下载链接如下:http://activemq.apache.org/download.html,解压后的目录结构如下: bin目录结构如下: 如果我们是32位的机器,就双击win32目录下的activemq.bat,如果是64位机器,则双击win64目

  • Spring整合Weblogic jms实例详解

    本文主要介绍weblogic jms的配置,包括JMS 服务器和JMS 模块(连接工厂.队列.远程 SAF 上下文.SAF 导入目的地.SAF 错误处理)的配置:并在Spring环境下进行消息的监听及发送:为了更多的使用webloigc jms的功能,发送的队列使用saf配置的远程weblogic jms队列(两边的weblogic版本须一致),当然本地也是可以的.本文中demo所使用的软件环境为:weblogic 10.3.6.0.spring5.1.2.RELEASE 注:saf配置的远程队

  • spring boot 开发soap webservice的实现代码

    介绍 spring boot web模块提供了RestController实现restful,第一次看到这个名字的时候以为还有SoapController,很可惜没有,对于soap webservice提供了另外一个模块spring-boot-starter-web-services支持.本文介绍如何在spring boot中开发soap webservice接口,以及接口如何同时支持soap和restful两种协议. soap webservice Web service是一个平台独立的,低耦

  • 详解Spring Boot2 Webflux的全局异常处理

    本文首先将会回顾Spring 5之前的SpringMVC异常处理机制,然后主要讲解Spring Boot 2 Webflux的全局异常处理机制. SpringMVC的异常处理 Spring 统一异常处理有 3 种方式,分别为: 使用 @ExceptionHandler 注解 实现 HandlerExceptionResolver 接口 使用 @controlleradvice 注解 使用 @ExceptionHandler 注解 用于局部方法捕获,与抛出异常的方法处于同一个Controller类

  • 解决spring-boot2.0.6中webflux无法获得请求IP的问题

    这几天在用 spring-boot 2 的 webflux 重构一个工程,写到了一个需要获得客户端请求 IP 的地方,发现写不下去了,在如下的 Handler(webflux 中 Handler 相当于 mvc 中的 Controller)中 import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; im

  • 解决Spring boot2.0+配置拦截器拦截静态资源的问题

    第一次遇到这个问题的时候,简直是一脸蒙逼,写了一个拦截器以后,静态资源就不能访问了,到处查找才知道是版本问题 解决办法: 第一步:定义一个类实现 实现WebMvcConfigurer的类中拦截器中添加放行资源处添加静态资源文件路径: @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(sessionInterceptor).addPathPatterns("/&

  • Spring Boot2.0使用Spring Security的示例代码

    一.Spring Secutity简介 Spring 是一个非常流行和成功的 Java 应用开发框架.Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案.一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分.用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统.用户认证一般要求用户提供用户名和密码.系统通过校验用户名和密码来完成认证过程.用户授权指的是

  • 解决Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题

    LocalDate . LocalTime . LocalDateTime 是Java 8开始提供的时间日期API,主要用来优化Java 8以前对于时间日期的处理操作.然而,我们在使用Spring Boot或使用Spring Cloud Feign的时候,往往会发现使用请求参数或返回结果中有 LocalDate . LocalTime . LocalDateTime 的时候会发生各种问题.本文我们就来说说这种情况下出现的问题,以及如何解决. 问题现象 先来看看症状.比如下面的例子: @Sprin

  • Spring boot2.0 实现日志集成的方法(2)

    目录 前言: logback.xml配置文件定义 引用自定义logback.xml文件 附加说明 前言: 上一章Spring boot2.0 日志集成方法分享(1)讲解了spring boot日志简单集成,将所有的日志都输出到一个文件中,但是在实际的项目中,我们需要将日志进行分类,常规日志.异常日志.监控日志等,需要将日志输出到不同的文件中.spring boot 日志默认采用的是sf4j+logback实现,默认配置文件为logback-spring.xml,如果需要输出到不同的文件,需要自定

  • Spring boot2.0 日志集成方法分享(1)

    目录 前言: 1.基本引用 2.基础配置 3.基本使用 前言: 项目开发中日志是不可缺少的一部分,通过日志能够定位和分析事故原因.目前流行日志框架包含了log4j.log4j2.logback等,另外 slf4j(Simple Logging Facade for Java) 则是一个日志门面框架,提供了日志系统中常用的接口,logback 和 log4j 则对slf4j 进行了实现.本文将讲述spring boot 中如何使用logback+slf4j实现日志. Java应用中,日志一般分为以

  • Spring boot2.0 实现日志集成的方法(3)

    目录 前言 具体实现 定义日志注解 定义日志切面 基本使用 输出信息 总结 前言 上一章Spring boot2.0 实现日志集成的方法(2)主要讲解了将日志信息根据类别输出到不同的文件中,实际开发中我们需要通过日志来监控用户的操作行为.请求的耗时情况,针对耗时久的请求进行性能分析,提升系统性能. 具体实现 采用的Spring Aop切面技术来实现控用户的操作行为.请求的耗时情况. 定义日志注解 @Target({ ElementType.METHOD }) @Retention(Retenti

  • 使用Spring-Retry解决Spring Boot应用程序中的重试问题

    目录 1.背景 2.引入依赖 3.开启spring-retry 4.在方法上添加@Retryable 5.重试完 6.注意事项 1.背景 在日常开发过程中,难免会与第三方接口发生交互,例如:远程服务调用当正常调用发生异常时,例如:网络抖动等短时间内的临时问题,程序为了更加健壮并且更不容易出现故障.需要重新触发业务操作,以防止间歇性的异常对程序照成的影响.这个可以就可以用重试来解决. 2.引入依赖 <!--因为spring-retry是基于aop实现,所以需要引入aop--> <depen

  • spring boot2.0图片上传至本地或服务器并配置虚拟路径的方法

    最近写了关于图片上传至本地文件夹或服务器,上传路径到数据库,并在上传时预览图片.使用到的工具如下: 框架:spring boot 2.0 前端模板:thymeleaf 图片预览:js 首先,上传以及预览,js以及<input type="file">,以及预览图片的JS function Img(obj){ var imgFile = obj.files[0]; console.log(imgFile); var img = new Image(); var fr = ne

  • 解决spring boot2集成activiti6踩过的坑

    spring boot2集成activiti6踩过的坑 1.activiti中的mybaitis版本冲突 错误信息 Caused by: java.lang.NoSuchFieldError: INSTANCE at com.baomidou.mybatisplus.MybatisMapperAnnotationBuilder.parseStatement(MybatisMapperAnnotationBuilder.java:332) ~[mybatis-plus-core-2.3.jar:?

随机推荐