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

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

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

这几天在用 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)就解决了我们遇到的问题了。


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

查看所有标签

猜你喜欢:

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

PHP Cookbook

PHP Cookbook

Adam Trachtenberg、David Sklar / O'Reilly Media / 2006-08-01 / USD 44.99

When it comes to creating dynamic web sites, the open source PHP language is red-hot property: used on more than 20 million web sites today, PHP is now more popular than Microsoft's ASP.NET technology......一起来看看 《PHP Cookbook》 这本书的介绍吧!

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

多种字符组合密码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具