问题描述

在使用WebFlux时,因为业务需要,要获取请求 IP 并作为日志输出。我使用的是RouterFunction方式的路由:

1
2
3
4
5
6
7
8
@Override
@NotNull
public Mono<ServerResponse> search(ServerRequest request) {
    Mono<Query> query = request.bodyToMono(Query.class);
    Mono<SearchResult> result = query.map(processQuery(request))
        .map(indexerService::search);
    return resultOk(result, SearchResult.class);
}

可以看到,这里接收一个ServerRequest,但是并没有获取请求 IP 的 API,搜索一番,结果是Spring的一个 BUG,详情见SPR-16681,已经在5.1版本中修复。但生产的版本没法随意升级,所以只能另寻他法了。

解决方案

其实,在WebFlux中的Filter的方法中,ServerWebExchange对象是可以通过 API 获取请求 IP 的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@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();
        IpThreadLocal.setIp(clientIp);
        ServerHttpRequest mutatedServerHttpRequest = exchange.getRequest().mutate().header("X-CLIENT-IP", clientIp).build();
        ServerWebExchange mutatedServerWebExchange = exchange.mutate().request(mutatedServerHttpRequest).build();
        return chain.filter(mutatedServerWebExchange);
    }
}

可以把 IP 放到Header中,通过ServerRequest来获取,也可以放到全局的线程变量中。

写在最后

不得不感叹,往往很多时候我都是面向搜索编程,碰到问题,就到处搜索,到处翻文章,找到方案了,就直接CV,这其实很难有所进步。问题是解决了,但下次再碰到,同样没记住,因为只是做了一次CV而已。那如何才能有效提高每一次的问题解决呢?我的方法就是记录下来,做一次输出,加深自己的印象,同时也能够在以后随时复盘,逐渐掌握。

参考

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