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 getData(){

        DeferredResult 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 最终由 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 interceptors = new ArrayList();
		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

你可能感兴趣的:(spring,Web入门及源码学习)