這幾天在用 spring-boot 2 的 webflux 重構一個工程,寫到了一個需要獲得客戶端請求 ip 的地方,發現寫不下去了,在如下的 handler(webflux 中 handler 相當于 mvc 中的 controller)中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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 的獲取方法:
1
2
|
org.springframework.web.server.serverwebexchange#getrequest org.springframework.http.server.reactive.serverhttprequest#getremoteaddress |
因此,我們大可以實現一個 webfilter 在里面通過暴露的 serverwebexchange 拿到客戶端 ip,然后再將其塞到請求的 header 中,這樣,后續過程就可以從 header 中取 ip 了。思路有了,我們開始實現吧。
過濾、取 ip、放 header,一氣呵成:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
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 取值:
1
2
3
4
|
private mono<serverresponse> execute(serverrequest serverrequest) { string clientip = serverrequest.headers().ashttpheaders().getfirst( "x-client-ip" ) ... 業務代碼 } |
通過上述解決方案(其實嚴格上說是 hacking)就解決了我們遇到的問題了。
總結
以上所述是小編給大家介紹的解決spring-boot2.0.6中webflux無法獲得請求ip的問題,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:https://since1986.github.io/blog/5f5b006f.html