這幾天在用 spring-boot 2 的 webflux 重構(gòu)一個(gè)工程,寫到了一個(gè)需要獲得客戶端請(qǐng)求 IP 的地方,發(fā)現(xiàn)寫不下去了,在如下的 Handler(webflux 中 Handler 相當(dāng)于 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;/** * 某業(yè)務(wù) Handler */@Componentpublic class SplashHandler { private Mono<ServerResponse> execute(ServerRequest serverRequest) { ... 業(yè)務(wù)代碼 // serverRequest 獲得 IP ? ... 業(yè)務(wù)代碼 } @Configuration public static class RoutingConfiguration { @Bean public RouterFunction<ServerResponse> execute(SplashHandler handler) { return route( GET("/api/ad").and(accept(MediaType.TEXT_HTML)), handler::execute ); } }}
我發(fā)現(xiàn) org.springframework.web.reactive.function.server.ServerRequest
根本沒(méi)有暴露用于獲得客戶端 IP 的 API,想想這在傳統(tǒng) MVC 中是相當(dāng)基本的需求啊,竟然獲取不到,然后 Google 了一下,發(fā)現(xiàn)這個(gè)是 spring-webflux 的一個(gè) BUG ,這個(gè) BUG 在 spring-webflux 5.1 中解決了,但是,略有些尷尬的是當(dāng)前最新穩(wěn)定版的 spring-boot 還是依賴 5.0.x 的 spring-webflux 的。難道要等官方升級(jí)么,那不知道得等到什么時(shí)候,因此我接著搜了搜資料,看了看文檔和源碼,自己想了個(gè)曲線救國(guó)的辦法。
正文
在 spring-webflux 中,有一個(gè) org.springframework.web.server.WebFilter
接口,類似于 Servlet API 中的過(guò)濾器,這個(gè) API 提供了一個(gè)方法會(huì)將一個(gè)限定名為 org.springframework.web.server.ServerWebExchange
的類暴露出來(lái),而在這個(gè)類中就包含了對(duì)于請(qǐng)求端 IP 的獲取方法:
org.springframework.web.server.ServerWebExchange#getRequestorg.springframework.http.server.reactive.ServerHttpRequest#getRemoteAddress
因此,我們大可以實(shí)現(xiàn)一個(gè) WebFilter 在里面通過(guò)暴露的 ServerWebExchange 拿到客戶端 IP,然后再將其塞到請(qǐng)求的 header 中,這樣,后續(xù)過(guò)程就可以從 header 中取 IP 了。思路有了,我們開(kāi)始實(shí)現(xiàn)吧。
過(guò)濾、取 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. */@Configurationpublic 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 中不支持直接獲得請(qǐng)求 IP(https://jira.spring.io/browse/SPR-16681),因此寫了一個(gè)補(bǔ)丁曲線救國(guó), * 從 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); } }}
后續(xù)過(guò)程 header 取值:
private Mono<ServerResponse> execute(ServerRequest serverRequest) { String clientIp = serverRequest.headers().asHttpHeaders().getFirst("X-CLIENT-IP") ... 業(yè)務(wù)代碼}
通過(guò)上述解決方案(其實(shí)嚴(yán)格上說(shuō)是 hacking)就解決了我們遇到的問(wèn)題了。
總結(jié)
以上所述是小編給大家介紹的解決spring-boot2.0.6中webflux無(wú)法獲得請(qǐng)求IP的問(wèn)題,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)VeVb武林網(wǎng)網(wǎng)站的支持!
|
新聞熱點(diǎn)
疑難解答
圖片精選