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

 更新时间:2018年10月24日 10:59:55   作者:Posted on  
这几天在用 spring-boot 2 的 webflux 重构一个工程,写到了一个需要获得客户端请求 IP 的地方,在写的过程中遇到很多问题,下面小编通过一段代码给大家介绍解决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的问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

相关文章

  • java多线程实现文件下载

    java多线程实现文件下载

    这篇文章主要为大家详细介绍了java多线程实现文件下载,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07
  • SpringBoot中实现代理方式

    SpringBoot中实现代理方式

    这篇文章主要介绍了SpringBoot中实现代理方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-06-06
  • mybatis-plus 处理大数据插入太慢的解决

    mybatis-plus 处理大数据插入太慢的解决

    这篇文章主要介绍了mybatis-plus 处理大数据插入太慢的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • spring jpa 审计功能自定义填充字段方式

    spring jpa 审计功能自定义填充字段方式

    这篇文章主要介绍了spring jpa审计功能自定义填充字段方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • java获取当前时间和前一天日期(实现代码)

    java获取当前时间和前一天日期(实现代码)

    java获取当前时间和前一天日期的实现代码。需要的朋友可以过来参考下,希望对大家有所帮助
    2013-10-10
  • JAVA自定义异常使用方法实例详解

    JAVA自定义异常使用方法实例详解

    这篇文章主要介绍了JAVA自定义异常使用方法实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • Spring Boot自定义favicon实现方法实例解析

    Spring Boot自定义favicon实现方法实例解析

    这篇文章主要介绍了Spring Boot自定义favicon实现方法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Java SpringBoot高级用法详解

    Java SpringBoot高级用法详解

    这篇文章主要为大家详细介绍了Java Spring Boot的一些高级用法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-09-09
  • Hadoop使用hdfs指令查看hdfs目录的根目录显示被拒的原因及解决方案

    Hadoop使用hdfs指令查看hdfs目录的根目录显示被拒的原因及解决方案

    这篇文章主要介绍了Hadoop使用hdfs指令查看hdfs目录的根目录显示被拒的原因及解决方案,分布式部署hadoop,服务机只有namenode节点,主机包含其他所有节点,本文给大家介绍的非常详细,需要的朋友可以参考下
    2023-10-10
  • Java整合RabbitMQ实现五种常见消费模型

    Java整合RabbitMQ实现五种常见消费模型

    本文将深入介绍RabbitMQ的五种常见消费模型,包括简单队列模型、工作队列模型、发布/订阅模型、路由模型和主题模型,删除线格式并探讨它们各自的优缺点和适用场景,感兴趣的可以了解一下
    2023-11-11

最新评论