spring全家桶系列之spring boot 2.2-Spring WebFlux框架(四)

簡介

  • Spring WebFlux是Spring Framework 5.0中引入的新的響應式Web框架。與Spring MVC不同,它不需要Servlet API,完全異步且無阻塞,並 通過Reactor項目實現Reactive Streams規範。基於jdk8的管道流(先學習Lamda表達式)完成異步無阻塞。

爲什麼使用

  • 部分答案是需要非阻塞Web堆棧來處理少量線程的併發性,並使用較少的硬件資源進行擴展。Servlet 3.1確實爲非阻塞I / O提供了API。但是,使用它會遠離Servlet API的其餘部分,其中契約是同步(FilterServlet)或阻塞(getParametergetPart)。這是新的通用API作爲跨任何非阻塞運行時的基礎的動機。這很重要,因爲在異步,非阻塞空間中已經建立了良好的服務器(如Netty)。
  • 答案的另一部分是函數式編程。就像在Java 5中添加註釋一樣創造了機會(例如帶註釋的REST控制器或單元測試),Java 8中添加lambda表達式爲Java中的功能API創造了機會。這是非阻塞應用程序和延續風格的API(如由普及的福音CompletableFutureReactiveX),其允許異步邏輯的聲明性組合物。在編程模型級別,Java 8使Spring WebFlux能夠提供功能性Web端點以及帶註釋的控制器。

可以替換MVC嗎?不可以

  • 因爲大部分框架設置都是阻塞式的,如果使用spring reactive版本都可以使用例如:spring-boot-starter-data-redis-reactive.
  • 如果你的Spring MVC應用程序運行正常,則無需更改。命令式編程是編寫,理解和調試代碼的最簡單方法。你有最多的庫選擇,因爲從歷史上看,大多數庫都是阻塞的。

  • Spring WebFlux提供與此空間中的其他人相同的執行模型優勢,並且還提供服務器選擇(Netty,Tomcat,Jetty,Undertow和Servlet 3.1+容器),選擇編程模型(帶註釋的控制器和功能Web端點),以及選擇反應庫(Reactor,RxJava或其他)。

  • 如果你對與Java 8 lambdas或Kotlin一起使用的輕量級,功能性Web框架感興趣,則可以使用Spring WebFlux功能Web端點。對於較小的應用程序或具有較低複雜要求的微服務而言,這也是一個不錯的選擇,可以從更高的透明度和控制中受益。

  • 在微服務架構中,你可以將應用程序與Spring MVC或Spring WebFlux控制器或Spring WebFlux功能端點混合使用。在兩個框架中支持相同的基於註釋的編程模型,可以更輕鬆地重用知識,同時爲正確的工作選擇合適的工具。

  • 評估應用程序的一種簡單方法是檢查其依賴性。如果你要使用阻塞持久性API(JPA,JDBC)或網絡API,則Spring MVC至少是常見體系結構的最佳選擇。從技術上講,Reactor和RxJava都可以在單獨的線程上執行阻塞調用,但是你不會充分利用非阻塞的Web堆棧。

  • 如果你有一個Spring MVC應用程序調用遠程服務,請嘗試反應WebClient。你可以直接從Spring MVC控制器方法返回反應類型(Reactor,RxJava )。每次呼叫的延遲或呼叫之間的相互依賴性越大,其益處就越大。Spring MVC控制器也可以調用其他無功組件。

  • 如果你有一個龐大的團隊,請記住轉向非阻塞,功能和聲明性編程時的陡峭學習曲線。在沒有完全切換的情況下啓動的實用方法是使用反應WebClient。除此之外,從小處着手衡量效益。我們希望,對於廣泛的應用,這種轉變是不必要的。如果你不確定要查找哪些好處,請首先了解非阻塞I / O的工作原理(例如,單線程Node.js上的併發)及其影響。

  • 我認爲第三方接口調用最佳,spring Flux 替換 spring mvc 就不做介紹了(只使用WebClientWebSockets)

maven

        <!--spring webflux-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>

WebClient

  • Spring WebFlux包含WebClient對HTTP請求的被動,非阻塞。客戶端具有功能齊全,流暢的API以及用於聲明性組合的反應類型,請參閱 Reactive Libraries。WebFlux客戶端和服務器依賴相同的非阻塞編解碼器來編碼和解碼請求和響應內容。 內部WebClient委託給HTTP客戶端庫。默認情況下,它使用 Reactor Netty,內置支持Jetty 反應式HttpClient,其他可以通過一個插件來插入ClientHttpConnector。簡單理解就是沒有併發問題。

使用

  • 最簡單方法WebClient是通過一個靜態工廠方法
  • WebClient.create()

  • WebClient.create(String baseUrl)

  • 上述方法使用Reactor Netty HttpClient和默認設置,並期望 io.projectreactor.netty:reactor-netty在類路徑上。

    還可以使用WebClient.builder()其他選項:

  • uriBuilderFactory:自定義UriBuilderFactory以用作基本URL。

  • defaultHeader:每個請求的標頭。

  • defaultCookie:每個請求的Cookie。

  • defaultRequestConsumer自定義每個請求。

  • filter:每個請求的客戶端過濾器。

  • exchangeStrategies:HTTP消息讀取器/寫入器自定義。

  • clientConnector:HTTP客戶端庫設置。

  ExchangeStrategies strategies = ExchangeStrategies.builder()
            .codecs(configurer -> {
                // ...
            })
            .build();

    WebClient client = WebClient.builder()
            .exchangeStrategies(strategies)
            .build();

mutate()克隆

構建後,WebClient實例是不可變的。但是,你可以克隆它並構建修改後的副本,而不會影響原始實例,如以下示例所示

  WebClient client1 = WebClient.builder()
            .filter(filterA).filter(filterB).build();//Lamda表達式filter獲取攔截的對象

    WebClient client2 = client1.mutate()
            .filter(filterC).filter(filterD).build();

    // client1 has filterA, filterB

    // client2 has filterA, filterB, filterC, filterD

Reactor Netty

要自定義Reactor Netty設置,只需提供預配置HttpClient

 HttpClient httpClient = HttpClient.create().secure(sslSpec -> ...);

    WebClient webClient = WebClient.builder()
            .clientConnector(new ReactorClientHttpConnector(httpClient))
            .build();
  • 默認情況下,HttpClient參與保存的全局Reactor Netty資源 reactor.netty.http.HttpResources,包括事件循環線程和連接池。這是推薦的模式,因爲固定的共享資源是事件循環併發的首選。在此模式下,全局資源將保持活動狀態,直到進程退出。
  • 如果服務器與進程同步,則通常不需要顯式關閉。但是,如果服務器可以在進程中啓動或停止(例如,部署爲WAR的Spring MVC應用程序),則可以ReactorResourceFactory使用globalResources=true(缺省值)聲明Spring管理的bean類型 ,以確保Reactor Netty全局資源是在Spring關閉時ApplicationContext關閉,如下例所示:
@Bean
    public ReactorResourceFactory reactorResourceFactory() {
        return new ReactorResourceFactory();
    }

你還可以選擇不參與全局Reactor Netty資源。但是,在此模式下,你需要確保所有Reactor Netty客戶端和服務器實例都使用共享資源,如以下示例所示:

@Configuration(proxyBeanMethods = false)
public class WebFluxConfig {

    @Bean
    public ReactorResourceFactory resourceFactory() {
        ReactorResourceFactory factory = new ReactorResourceFactory();
        //創建獨立於全球資源的資源
        factory.setUseGlobalResources(false);
        return factory;
    }


    /**
     * TcpClient 配置(HTTP)
     *
     * @return WebClient
     */
    @Bean
    public WebClient webClient() {
       Function<HttpClient, HttpClient> mapper = client -> {
            // 進一步的自定義...
        };
        //將ReactorClientHttpConnector構造函數與資源工廠一起使用
        ClientHttpConnector connector =
                new ReactorClientHttpConnector(resourceFactory(), mapper);
        //將連接器插入WebClient.Builder
        return WebClient.builder().clientConnector(connector).build();
    }
}

 

retrieve()

  • retrieve()方法是獲取響應主體並對其進行解碼的最簡單方法。以下示例顯示瞭如何執行此操作:
 WebClient client = WebClient.create("https://www.test.com");

    Mono<Person> result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .bodyToMono(Person.class);
  • 獲取從響應中解碼的對象流,如以下示例所示:
 Flux<Quote> result = client.get()
            .uri("/quotes").accept(MediaType.TEXT_EVENT_STREAM)
            .retrieve()
            .bodyToFlux(Quote.class);
  • 默認情況下,4XX或5xx狀態代碼的應答導致 WebClientResponseException或它的HTTP狀態的具體子類之一,比如WebClientResponseException.BadRequestWebClientResponseException.NotFound和其他人。你還可以使用該onStatus方法自定義生成的異常,如以下示例所示: 
   Mono<Person> result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .retrieve()
            .onStatus(HttpStatus::is4xxClientError, response -> ...)
            .onStatus(HttpStatus::is5xxServerError, response -> ...)
            .bodyToMono(Person.class);

exchange()

  • exchange()方法提供了比該retrieve方法更多的控制。以下示例等同於retrieve()但也提供對以下內容的訪問ClientResponse
  Mono<Person> result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .exchange()
            .flatMap(response -> response.bodyToMono(Person.class));
  • 在此級別,你還可以創建一個完整的ResponseEntity
 Mono<ResponseEntity<Person>> result = client.get()
            .uri("/persons/{id}", id).accept(MediaType.APPLICATION_JSON)
            .exchange()
            .flatMap(response -> response.toEntity(Person.class));
  • 請注意(與retrieve())不同,exchange()4xx和5xx響應沒有自動錯誤信號。必須檢查狀態代碼並決定如何繼續。

使用時exchange(),必須始終使用任何bodytoEntity方法 ClientResponse來確保釋放資源並避免HTTP連接池的潛在問題。bodyToMono(Void.class)如果沒有預期的響應內容,可以使用。但是,如果響應確實包含內容,則連接將關閉,並且不會放回池中。

Request Body

  • 請求正文可以從一個編碼Object,如下例所示:
Mono<Person> personMono = ... ;

    Mono<Void> result = client.post()
            .uri("/persons/{id}", id)
            .contentType(MediaType.APPLICATION_JSON)
            .body(personMono, Person.class)
            .retrieve()
            .bodyToMono(Void.class);
  • 你還可以編碼對象流,如以下示例所示:
 Flux<Person> personFlux = ... ;

    Mono<Void> result = client.post()
            .uri("/persons/{id}", id)
            .contentType(MediaType.APPLICATION_STREAM_JSON)
            .body(personFlux, Person.class)
            .retrieve()
            .bodyToMono(Void.class);
  • 或者,如果你有實際值,則可以使用syncBody快捷方法,如以下示例所示:
 Person person = ... ;

    Mono<Void> result = client.post()
            .uri("/persons/{id}", id)
            .contentType(MediaType.APPLICATION_JSON)
            .syncBody(person)
            .retrieve()
            .bodyToMono(Void.class);

表單數據

  • 要發送表單數據,你可以提供一個MultiValueMap<String, String>正文。請注意,內容被自動設置爲application/x-www-form-urlencoded通過 FormHttpMessageWriter。以下示例顯示如何使用MultiValueMap<String, String>
MultiValueMap<String, String> formData = ... ;

    Mono<Void> result = client.post()
            .uri("/path", id)
            .syncBody(formData)
            .retrieve()
            .bodyToMono(Void.class);
  • 還可以使用BodyInserters以下方式提供內聯表單數據,如以下示例所示
  import static org.springframework.web.reactive.function.BodyInserters.*;

    Mono<Void> result = client.post()
            .uri("/path", id)
            .body(fromFormData("k1", "v1").with("k2", "v2"))
            .retrieve()
            .bodyToMono(Void.class);
  • 多部分數據
  • 要發送多部分數據,你需要提供MultiValueMap<String, ?>其值爲Object表示部件內容的HttpEntity實例或表示部件的內容和標頭的實例。MultipartBodyBuilder提供了一個方便的API來準備多部分請求。以下示例顯示如何創建MultiValueMap<String, ?>
MultipartBodyBuilder builder = new MultipartBodyBuilder();
    builder.part("fieldPart", "fieldValue");
    builder.part("filePart1", new FileSystemResource("...logo.png"));
    builder.part("jsonPart", new Person("Jason"));
    builder.part("myPart", part); // Part from a server request

    MultiValueMap<String, HttpEntity<?>> parts = builder.build();
  • 在大多數情況下,你不必Content-Type爲每個部件指定。內容類型是根據HttpMessageWriter所選內容類型自動確定的,以便Resource根據文件擴展名自動確定。如有必要,你可以MediaType通過其中一個重載的構建器part方法顯式提供每個部分的使用。
  • 一旦MultiValueMap準備好了,WebClient通過該syncBody方法傳遞給它的最簡單方法是,如下例所示

 

 MultipartBodyBuilder builder = ...;

    Mono<Void> result = client.post()
            .uri("/path", id)
            .syncBody(builder.build())
            .retrieve()
            .bodyToMono(Void.class);
  • 如果MultiValueMap包含至少一個非String值,也可以表示常規表單數據(即application/x-www-form-urlencoded),則無需將其設置Content-Typemultipart/form-data。使用時始終如此 MultipartBodyBuilder,這確保了HttpEntity包裝。
  • 作爲替代方案MultipartBodyBuilder,你還可以通過內置提供內聯樣式的多部分內容BodyInserters,如以下示例所示:
  import static org.springframework.web.reactive.function.BodyInserters.*;

    Mono<Void> result = client.post()
            .uri("/path", id)
            .body(fromMultipartData("fieldPart", "value").with("filePart", resource))
            .retrieve()
            .bodyToMono(Void.class);

客戶過濾器

  • 通過註冊客戶端過濾器(ExchangeFilterFunctionWebClient.Builder 來攔截和修改請求,如以下示例所示:
WebClient client = WebClient.builder()
        .filter((request, next) -> {

            ClientRequest filtered = ClientRequest.from(request)
                    .header("foo", "bar")
                    .build();

            return next.exchange(filtered);
        })
        .build();
  • 這可用於橫切關注點,例如身份驗證。以下示例使用過濾器通過靜態工廠方法進行基本身份驗證:
// static import of ExchangeFilterFunctions.basicAuthentication

WebClient client = WebClient.builder()
        .filter(basicAuthentication("user", "password"))
        .build();
  • 過濾器全局應用於每個請求。要更改特定請求的過濾器行爲,你可以將請求屬性添加到ClientRequest鏈中的所有過濾器,然後如下例所示:
WebClient client = WebClient.builder()
        .filter((request, next) -> {
            Optional<Object> usr = request.attribute("myAttribute");
            // ...
        })
        .build();

client.get().uri("https://example.org/")
        .attribute("myAttribute", "...")
        .retrieve()
        .bodyToMono(Void.class);

    }
  • 還可以複製現有WebClient,插入新過濾器或刪除已註冊的過濾器。以下示例在索引0處插入基本身份驗證篩選器:
// static import of ExchangeFilterFunctions.basicAuthentication

WebClient client = webClient.mutate()
        .filters(filterList -> {
            filterList.add(0, basicAuthentication("user", "password"));
        })
        .build();

 

同步使用

  • WebClient 可以通過在結尾處阻塞結果來使用同步樣式:
//block獲取結果 bodyToMono單個對象使用,bodyToFlux爲集合
Person person = client.get().uri("/person/{id}", i).retrieve()
    .bodyToMono(Person.class)
    .block();

List<Person> persons = client.get().uri("/persons").retrieve()
    .bodyToFlux(Person.class)
    .collectList()
    .block();
  • 但是,如果需要進行多次調用,則可以更有效地避免單獨阻止每個響應,而是等待組合結果
Mono<Person> personMono = client.get().uri("/person/{id}", personId)
        .retrieve().bodyToMono(Person.class);

Mono<List<Hobby>> hobbiesMono = client.get().uri("/person/{id}/hobbies", personId)
        .retrieve().bodyToFlux(Hobby.class).collectList();

Map<String, Object> data = Mono.zip(personMono, hobbiesMono, (person, hobbies) -> {
            Map<String, String> map = new LinkedHashMap<>();
            map.put("person", personName);
            map.put("hobbies", hobbies);
            return map;
        })
        .block();
  • 以上僅是一個例子。有很多其他的模式和運算符可以組合一個反應式管道,它可以進行許多遠程調用,可能是一些嵌套的,相互依賴的,直到最後都沒有阻塞。

WebSocket

WebSocket簡介(以後的篇章會單獨講解websocket)

  • WebSocket協議RFC 6455提供了一種標準化方法,通過單個TCP連接在客戶端和服務器之間建立全雙工雙向通信通道。它是來自HTTP的不同TCP協議,但設計爲使用端口80和443通過HTTP工作,並允許重用現有的防火牆規則。 WebSocket交互以HTTP請求開始,該HTTP請求使用HTTP Upgrade標頭進行升級,或者在這種情況下,切換到WebSocket協議。以下示例顯示了這樣的交互:
GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket 
Connection: Upgrade 
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080
  • 具有WebSocket支持的服務器返回類似於以下內容的輸出,而不是通常的200狀態代碼
HTTP/1.1 101 Switching Protocols   #協議切換
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp
  • HTTP升級請求下的TCP套接字將保持打開狀態,以便客戶端和服務器繼續發送和接收消息。
  • 如果WebSocket服務器在Web服務器(例如nginx)後面運行,
  • 可能需要將其配置爲將WebSocket升級請求傳遞到WebSocket服務器。

HTTP與WebSocket

  • 儘管WebSocket被設計爲與HTTP兼容並且以HTTP請求開始,但重要的是要理解這兩種協議會導致非常不同的體系結構和應用程序編程模型。
  • 在HTTP和REST中,應用程序被建模爲多個URL。要與應用程序交互,客戶端訪問這些URL,請求 - 響應樣式。服務器根據HTTP URL,方法和標頭將請求路由到適當的處理程序。
  • 相比之下,在WebSockets中,初始連接通常只有一個URL。隨後,所有應用程序消息都在同一TCP連接上流動。這指向完全不同的異步,事件驅動的消息傳遞體系結構。
  • WebSocket也是一種低級傳輸協議,與HTTP不同,它不對消息內容規定任何語義。這意味着除非客戶端和服務器就消息語義達成一致,否則無法路由或處理消息。
  • WebSocket客戶端和服務器可以通過Sec-WebSocket-ProtocolHTTP握手請求中的標頭協商使用更高級別的消息傳遞協議(例如,STOMP)。如果沒有,他們需要提出自己的慣例。

何時使用WebSocket

  • WebSockets可以使網頁變得動態和交互。但是,在許多情況下,Ajax和HTTP流式傳輸或長輪詢的組合可以提供簡單有效的解決方案。
  • 例如,新聞,郵件和社交訂閱源需要動態更新,但每隔幾分鐘就可以完全正常更新。另一方面,協作,遊戲和財務應用程序需要更接近實時。
  • 僅延遲不是決定因素。如果消息量相對較低(例如,監視網絡故障),HTTP流式傳輸或輪詢可以提供有效的解決方案。它是低延遲,高頻率和高容量的組合,是使用WebSocket的最佳選擇。
  • 還要記住,在Internet上,受控制之外的限制性代理可能會妨礙WebSocket交互,因爲它們未配置爲傳遞 Upgrade標頭,或者因爲它們關閉看似空閒的長期連接。這意味着將WebSocket用於防火牆內的內部應用程序是一個比面向公衆的應用程序更直接的決策。

WebSocket API

  • Spring Framework提供了一個WebSocket API,可以使用它來編寫處理WebSocket消息的客戶端和服務器端應用程序。
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketSession;

public class MyWebSocketHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        // ...
    }
}
  • 可以將其映射到URL並添加一個WebSocketHandlerAdapter,如下例所示:
@Configuration
static class WebConfig {

    @Bean
    public HandlerMapping handlerMapping() {
        Map<String, WebSocketHandler> map = new HashMap<>();
        map.put("/path", new MyWebSocketHandler());

        SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
        mapping.setUrlMap(map);
        mapping.setOrder(-1); // before annotated controllers
        return mapping;
    }

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter();
    }
}
  • WebSocketHandler
  • 獲取和返回的handle方法 指示會話的應用程序處理何時完成。會話通過兩個流處理,一個用於入站消息,一個用於出站消息。下表描述了處理流的兩種方法:WebSocketHandlerWebSocketSessionMono<Void>
WebSocketSession 方法 描述

Flux<WebSocketMessage> receive()

提供對入站消息流的訪問,並在關閉連接時完成。

Mono<Void> send(Publisher<WebSocketMessage>)

獲取傳出消息的源,寫入消息,並Mono<Void>在源完成並完成寫入時返回完成的消息。

  • 一個WebSocketHandler必須組成入站和出站流爲一個統一的流程,並返回一個Mono<Void>反映該流的完成。根據應用程序要求,統一流程在以下情況下完成:
  • 入站或出站消息流完成。

  • 入站流完成(即連接已關閉),而出站流是無限的。

  • 在選定的點上,通過close方法WebSocketSession

  • 當入站和出站消息流組合在一起時,無需檢查連接是否打開,因爲Reactive Streams信號終止活動。入站流接收完成或錯誤信號,並且出站流接收取消信號。
  • 處理程序的最基本實現是處理入站流的實現。以下示例顯示了這樣的實現:
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {
        return session.receive()    //1         
                .doOnNext(message -> {
                    // ...             //2     
                })
                .concatMap(message -> {
                    // ...          //3        
                })
                .then();     //4               
    }
}
1 訪問入站流。
2 對每條消息做點什麼。
3 執行使用消息內容的嵌套異步操作。
4 Mono<Void>收到完成後返回完成。
對於嵌套的異步操作,您可能需要調用message.retain()使用池化數據緩衝區的底層服務器(例如,Netty)。否則,可能在您有機會讀取數據之前釋放數據緩衝區。
  • 以下實現組合了入站和出站流:
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Flux<WebSocketMessage> output = session.receive()    //處理入站消息流           
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .map(value -> session.textMessage("Echo " + value));    //創建出站消息,生成組合流

        return session.send(output);     //返回我們繼續收到時未完成的Mono <Void>                              
    }
}
  • 入站和出站流可以是獨立的,只有完成才能連接,如下例所示:
class ExampleHandler implements WebSocketHandler {

    @Override
    public Mono<Void> handle(WebSocketSession session) {

        Mono<Void> input = session.receive()    //處理入站消息流                            
                .doOnNext(message -> {
                    // ...
                })
                .concatMap(message -> {
                    // ...
                })
                .then();

        Flux<String> source = ... ;
        Mono<Void> output = session.send(source.map(session::textMessage)); //放送傳出的消息

        return Mono.zip(input, output).then();    //加入流並返回Mono <Void>,當任一流結束時完成                          
    }
}
  • DataBuffer

  • DataBuffer是WebFlux中字節緩衝區的表示。要理解的關鍵點是在某些服務器(如Netty)上,字節緩衝區被池化並引用計數,並且必須在使用時釋放以避免內存泄漏。在Netty上運行時,DataBufferUtils.retain(dataBuffer)如果應用程序希望保留輸入數據緩衝區以確保它們不被釋放,並且隨後在使用DataBufferUtils.release(dataBuffer)緩衝區時使用,則必須使用它們。

服務器配置

  • RequestUpgradeStrategy每個服務器公開可用於底層的WebSocket發動機的WebSocket相關的配置選項。以下示例在Tomcat上運行時設置WebSocket選項:
@Configuration
static class WebConfig {

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter(webSocketService());
    }

    @Bean
    public WebSocketService webSocketService() {
        TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
        strategy.setMaxSessionIdleTimeout(0L);
        return new HandshakeWebSocketService(strategy);
    }
}
  • 檢查服務器的升級策略以查看可用的選項。目前,只有Tomcat和Jetty公開了這樣的選項。

客戶端

  • Spring WebFlux提供了一個WebSocketClient抽象,包括Reactor Netty,Tomcat,Jetty,Undertow和標準Java(即JSR-356)的實現。

      Tomcat客戶端實際上是標準Java的擴展,在WebSocketSession處理過程中具有一些額外的功能,以利用特定於Tomcat的API來暫停接收消息以獲得反壓。

    要啓動WebSocket會話,您可以創建客戶端的實例並使用其execute 方法:

  • WebSocketClient client = new ReactorNettyWebSocketClient();
    
    URI url = new URI("ws://localhost:8080/path");
    client.execute(url, session ->
            session.receive()
                    .doOnNext(System.out::println)
                    .then());

    某些客戶端(如Jetty)實現Lifecycle並需要在使用它們之前停止並啓動。所有客戶端都具有與底層WebSocket客戶端配置相關的構造函數選項。

整合

spring boot 整合flux webClient  (tcp協議[即常用的HTTP基於TCP協議])

  • 無需配置採用默認
@Getter
@Setter
public class City {
    private String status;
    private String province;
}
public interface CityService {
    City getCity();
}


@Service
public class CityServiceImpl implements CityService {

    public City getCity() {
        //高德測試
        WebClient client = WebClient.create();
        Mono<City> result = client.get()
                .uri("https://restapi.amap.com/v3/ip").accept(MediaType.APPLICATION_JSON)
                .headers(httpHeaders -> {
                    httpHeaders.add("output", "JSON");
                    httpHeaders.add("key", "高德key");
                    httpHeaders.add("ip", "百度一個IP地址");
                })
                .exchange()
                .flatMap(response -> response.bodyToMono(City.class));

        return result.block();
    }

}
@RunWith(SpringRunner.class)
@SpringBootTest(classes= WebApplication.class)
public class Try {

    @Autowired
    private CityService cityService;


    @Test
    public void Test() {
        City city = cityService.getCity();
        System.out.println(city.getProvince());
        System.out.println(city.getStatus());
    }
}

 spring boot 整合flux WebSocketClient (一般很少使用)

@Configuration(proxyBeanMethods = false)
public class WebSocketClientConfig {

    @Bean
    public WebSocketHandlerAdapter handlerAdapter() {
        return new WebSocketHandlerAdapter(webSocketService());
    }

    @Bean
    public WebSocketService webSocketService() {
        TomcatRequestUpgradeStrategy strategy = new TomcatRequestUpgradeStrategy();
        strategy.setMaxSessionIdleTimeout(0L);
        return new HandshakeWebSocketService(strategy);
    }
}
WebSocketClient client = new ReactorNettyWebSocketClient();

URI url = new URI("ws://localhost:8080/path");
client.execute(url, session ->
        session.receive()
                .doOnNext(System.out::println)
                .then());

下一篇: RSocket 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章