Spring Web 學習 -- DeferredResult 長連接異步返回

最近在學習有個配置中心(nacos 和 apollo),配置中心在配置變更時通過 http 長連接的方式進行通知。

(1)配置客戶端定時向配置中心發送請求獲取最新配置(apollo客戶端會像服務端發送長輪訓http請求,超時時間60秒,當超時後返回客戶端一個304 httpstatus,表明配置沒有變更,客戶端繼續這個步驟重複發起請求,當有發佈配置的時候,服務端會調用DeferredResult.setResult返回200狀態碼,然後輪訓請求會立即返回(不會超時),客戶端收到響應結果後,會發起請求獲取變更後的配置信息。

(2)當服務器配置變更時會通過與客戶端建立的長連接立即通知客戶端。

HTTP 長連接

HTTP是無狀態的 ,也就是說,瀏覽器和服務器每進行一次HTTP操作就要建立一次連接,當任務結束就中斷連接。如果客戶端瀏覽器訪問的某個HTML或其他類型的Web頁中包含其他Web資源,如JavaScript文件、圖像文件、CSS文件等,此時就要建立一個新的HTTP會話。

HTTP1.1和HTTP1.0相比較而言,最大的區別就是增加了持久連接支持,但還是無狀態的或者說是不可靠的。如果瀏覽器或服務器在其頭信息中加入了這行代碼:Connection:keep-alive,TCP連接在發送後將仍保持打開狀態,於是瀏覽器可以繼續通過相同的連接發送請求。保持連接節省了爲每個請求建立連接的時間,還節約了帶寬。

實現長連接要求客戶端和服務端都支持長連接。

如果web服務器端看到這裏的值爲“Keep-Alive”,或者看到請求使用的是HTTP 1.1(HTTP 1.1默認進行持久連接),它就可以利用持久連接的優點,當頁面包含多個元素時(例如Applet,圖片),顯著地減少下載所需要的時間。要實現這一點, web服務器需要在返回給客戶端HTTP頭信息中發送一個Content-Length(返回信息正文的長度)頭,最簡單的實現方法是:先把內容寫入ByteArrayOutputStream,然 後在正式寫出內容之前計算它的大小

無論客戶端瀏覽器 (Internet Explorer) 還是 Web 服務器具有較低的 KeepAlive 值,它都將是限制因素。例如,如果客戶端的超時值是兩分鐘,而 Web 服務器的超時值是一分鐘,則最大超時值是一分鐘。客戶端或服務器都可以是限制因素

在header中加入 --Connection:keep-alive 
在HTTp協議請求和響應中加入這條就能維持長連接。 
再封裝HTTP消息數據體的消息應用就顯的非常簡單易用

 

DeferredResult 異步請求處理

支持:

在Servlet 3.0中,我們可以從HttpServletRequest對象中獲得一個AsyncContext對象,該對象構成了異步處理的上下文,Request和Response對象都可從中獲取。AsyncContext可以從當前線程傳給另外的線程,並在新的線程中完成對請求的處理並返回結果給客戶端,初始線程便可以還回給容器線程池以處理更多的請求。如此,通過將請求從一個線程傳給另一個線程處理的過程便構成了Servlet 3.0中的異步處理。

示例:

瀏覽器請求地址:http://localhost:8080/data 不會立即返回結果,客戶端與服務端建立長連接,5秒服務端處理結束後返回結果。

@SpringBootApplication
@RestController
public class ConfigClientApplication {

    @RequestMapping("/data")
    public DeferredResult<String> getData(){

        DeferredResult<String> result = new DeferredResult<>();

        Thread thread = new Thread(() ->{

            try {
                //與客戶端建立長連接之後 5秒之後返回結果
                Thread.sleep(5000);

                result.setResult("hello world");
            }catch (Exception e){

            }
        });

        thread.start();

        return result;

    }

    public static void main(String[] args) {
        SpringApplication.run(ConfigClientApplication.class, args);
    }
}

實現原理:

 Controller 返回結果 DeferredResult<String> 最終由 DeferredResultMethodReturnValueHandler 進行處理

public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
        if (returnValue == null) {
            mavContainer.setRequestHandled(true);
        } else {
            DeferredResultAdapter adapter = this.getAdapterFor(returnValue.getClass());
            if (adapter == null) {
                throw new IllegalStateException("Could not find DeferredResultAdapter for return value type: " + returnValue.getClass());
            } else {
                DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue);
                WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, new Object[]{mavContainer});
            }
        }
    }

在 WebAsyncManager 中 會進行各種攔截器處理運行,並返回結果

public void startDeferredResultProcessing(
			final DeferredResult<?> deferredResult, Object... processingContext) throws Exception {

		Assert.notNull(deferredResult, "DeferredResult must not be null");
		Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");

		Long timeout = deferredResult.getTimeoutValue();
		if (timeout != null) {
			this.asyncWebRequest.setTimeout(timeout);
		}

		List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<DeferredResultProcessingInterceptor>();
		interceptors.add(deferredResult.getInterceptor());
		interceptors.addAll(this.deferredResultInterceptors.values());
		interceptors.add(timeoutDeferredResultInterceptor);

		final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);

		this.asyncWebRequest.addTimeoutHandler(new Runnable() {
			@Override
			public void run() {
				try {
					interceptorChain.triggerAfterTimeout(asyncWebRequest, deferredResult);
				}
				catch (Throwable ex) {
					setConcurrentResultAndDispatch(ex);
				}
			}
		});

		if (this.asyncWebRequest instanceof StandardServletAsyncWebRequest) {
			((StandardServletAsyncWebRequest) this.asyncWebRequest).setErrorHandler(
					new StandardServletAsyncWebRequest.ErrorHandler() {
						@Override
						public void handle(Throwable ex) {
							deferredResult.setErrorResult(ex);
						}
					});
		}

		this.asyncWebRequest.addCompletionHandler(new Runnable() {
			@Override
			public void run() {
				interceptorChain.triggerAfterCompletion(asyncWebRequest, deferredResult);
			}
		});

		interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, deferredResult);
		startAsyncProcessing(processingContext);

		try {
			interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult);
			deferredResult.setResultHandler(new DeferredResultHandler() {
				@Override
				public void handleResult(Object result) {
					result = interceptorChain.applyPostProcess(asyncWebRequest, deferredResult, result);
					setConcurrentResultAndDispatch(result);
				}
			});
		}
		catch (Throwable ex) {
			setConcurrentResultAndDispatch(ex);
		}
	}

簡單理解爲最終通過異步返回結果,客戶端與服務端之間通過長連接來保持通信。

 

參考文章:

https://www.cnblogs.com/code-sayhi/articles/10191526.html

https://blog.csdn.net/qq_42164670/article/details/83214016

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