曲线救国,解决spring-boot2.0.6中webflux无法获得请求IP的问题

栏目: Java · 发布时间: 6年前

内容简介:这几天在用我发现在

这几天在用 spring-boot 2webflux 重构一个工程,写到了一个需要获得客户端请求 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)就解决了我们遇到的问题了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

30天自制操作系统

30天自制操作系统

[日] 川合秀实 / 周自恒、李黎明、曾祥江、张文旭 / 人民邮电出版社 / 2012-8 / 99.00元

自己编写一个操作系统,是许多程序员的梦想。也许有人曾经挑战过,但因为太难而放弃了。其实你错了,你的失败并不是因为编写操作系统太难,而是因为没有人告诉你那其实是一件很简单的事。那么,你想不想再挑战一次呢? 这是一本兼具趣味性、实用性与学习性的书籍。作者从计算机的构造、汇编语言、C语言开始解说,让你在实践中掌握算法。在这本书的指导下,从零编写所有代码,30天后就可以制作出一个具有窗口系统的32位......一起来看看 《30天自制操作系统》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具