spring boot项目06:Web请求探秘-整个过程

JDK 8

Spring Boot 2.4.5

Eclipse Version: 2021-03 (4.19.0)

---

今天安排了一个任务:调查Spring Web(Servlet、Tomcat、HTTP 1.1)应用的 HTTP请求是怎么处理的,于是,熬夜(start at 00:41)到现在才终于了解了整个流程。

目标是 了解 GET、POST 两种请求的处理过程,结果,忙活了一天,就把 GET 探究了一遍,POST的,大概差不多吧,不同请求方法小地方有差别。

 

准备:

1、会使用Eclipse进行调试(会其它IDE调试也行,或许效果更好,Step Into/Over/Return)

2、打开Spring Boot项目的调试日志(命令行添加 --debug,够了,,如果使用 --trace,会处理不过来)

spring boot项目06:Web请求探秘-整个过程

3、知识储备:Java NIO相关,比如,selectKey之类的——调试到Poller、Connector时需要。

 

DispatcherServlet,久闻其名,未见其真容,今天算是“见面”了。除了它,还有FrameworkServlet、HttpServlet等。

public abstract class GenericServlet implements Servlet, ServletConfig,
        java.io.Serializable {
}

public abstract class HttpServlet extends GenericServlet {
}

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {
}

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
}

public class DispatcherServlet extends FrameworkServlet {
}

关系大概如下:依次继承extends

spring boot项目06:Web请求探秘-整个过程

注:

上面的DispatcherServlet、FrameworkServlet 属于 package org.springframework.web.servlet,而HttpServlet 属于package javax.servlet.http。

 

思路:

DispatcherServlet中有两个函数比较出名:doDispatch、doService,于是,端点就从这两个函数开始设置;

然后,沿着这两个请求 一直找 函数调用方;

直到,涉及到 org.apache.tomcat.util.net.NioEndpoint#Poller、org.apache.catalina.connector.Connector 的使用时停止——到这里就很难调试下去了。

 

调试步骤:

0、设置各种端点——从DispatchServlet类 开始,整个过程中,自己设置了几十个断点(有些不会用到);

spring boot项目06:Web请求探秘-整个过程

1、Eclipse中 以 调试模式 启动项目(调试时 可以使用 快捷键);

spring boot项目06:Web请求探秘-整个过程

2、使用 Postman 访问准备的接口;

	@GetMapping("hello")
	public String hello(@RequestParam(value="name", defaultValue = "World") String name) {
		return String.format("Hello, %s", name);
	}

spring boot项目06:Web请求探秘-整个过程

注:写文章前最后一次调试 耗时近 2小时!(太费精时了!)

3、重复上面的步骤,直到 找到 一个请求被 Spring Web 处理的完整路径。

 

打开Postman,发送GET请求,此时 Postman、Eclipse展示如下:

- 请求在调试结束前,一直处于 发送状态

spring boot项目06:Web请求探秘-整个过程

- 发出请求,停止了第一个断点位置——AbstractProtocol 类 的 process 函数,左边可以看到 对应线程的调用栈(stack),在右边(下图未展示),还可以看到当前的变量(鼠标放到变量上 也可以看到)。

spring boot项目06:Web请求探秘-整个过程

当前方法变量表——可以看变量的值:

spring boot项目06:Web请求探秘-整个过程

接下来,开始 各种执行各种调试命令,出发!

 

……若干小时后……

 

调查结果
public abstract class AbstractProtocol<S> implements ProtocolHandler, MBeanRegistration {
    protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
        public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
			Processor processor = (Processor) wrapper.getCurrentProcessor();
			processor = recycledProcessors.pop();
			processor = getProtocol().createProcessor(); // Http11NioProtocol, Http11Processor
			wrapper.setCurrentProcessor(processor); // NioEndpoint#NioSocketWrapper
			state = processor.process(wrapper, status);
				AbstractProcessorLight#process
					status == SocketEvent.OPEN_READ
					|
					state = service(socketWrapper); // Http11Processor#service
						setSocketWrapper(socketWrapper);
							super.setSocketWrapper(socketWrapper);
						prepareRequestProtocol(); // Http11Processor
						getAdapter() // CoyoteAdapter
							.service(request, response); // CoyoteAdapter#service
								connector.getService() // StandardService
									.getContainer() // StandardEngine
										.getPipeline() // StandardPipeline
											.getFirst() // StandardEngineValve
												.invoke(request, response); // StandardEngineValve#invoke
													host.getPipeline().getFirst().invoke(request, response); // ErrorReportValve#invoke ?
														getNext().invoke(request, response); // StandardHostValve#invoke
															Context context = request.getContext(); // TomcatEmbeddedContext
															context.getPipeline().getFirst().invoke(request, response); // AuthenticatorBase#invoke
																getNext().invoke(request, response); // StandardContextValve#invoke
																	Wrapper wrapper = request.getWrapper(); // StandardWrapper
																	response.sendAcknowledgement(ContinueResponseTiming.IMMEDIATELY); // Response#action#ACK
																		hook.action(actionCode, param); // hook=Http11Processor
																			ack((ContinueResponseTiming) param);
																	wrapper.getPipeline().getFirst().invoke(request, response); // 最后一句,StandardWrapperValve#invoke
																		StandardWrapper wrapper = (StandardWrapper) getContainer(); // StandardWrapper
																		Context context = (Context) wrapper.getParent(); // TomcatEmbeddedContext
																		servlet = wrapper.allocate();
																		ApplicationFilterChain filterChain = 
																			ApplicationFilterFactory.createFilterChain(request, wrapper, servlet); // 5个 ApplicationFilterConfig
																		Container container = this.container; // StandardWrapper
																		filterChain.doFilter(request.getRequest(), response.getResponse()); // ApplicationFilterChain#doFilter
																			internalDoFilter(request,response);
																				ApplicationFilterConfig filterConfig = filters[pos++];
																				Filter filter = filterConfig.getFilter();
																				filter.doFilter(request, response, this); // OrderedCharacterEncodingFilter#doFilter // 第一次
																					doFilterInternal(httpRequest, httpResponse, filterChain); # CharacterEncodingFilter#doFilterInternal
																						String encoding = getEncoding(); // UTF-8
																						filterChain.doFilter(request, response); // 开始第二轮,Return
																		filterChain.doFilter(request.getRequest(), response.getResponse()); // ApplicationFilterChain#doFilter
																			TomcatEmbeddedContext
																			WebMvcMetricsFilter
																			filter.doFilter(request, response, this); // OncePerRequestFilter#doFilter
																				doFilterInternal(httpRequest, httpResponse, filterChain); // WebMvcMetricsFilter#doFilterInternal
																					filterChain.doFilter(request, response); // // ApplicationFilterChain#doFilter
																		filterChain.doFilter(request.getRequest(), response.getResponse()); // ApplicationFilterChain#doFilter
																			internalDoFilter(request,response);
																				FormContentFilter
																				filter.doFilter(request, response, this); // OncePerRequestFilter#doFilter
																					doFilterInternal(httpRequest, httpResponse, filterChain); // FormContentFilter
																						filterChain.doFilter(request, response);
																		filterChain.doFilter(request.getRequest(), response.getResponse()); // ApplicationFilterChain#doFilter
																			internalDoFilter(request,response);
																				RequestContextFilter
																				filter.doFilter(request, response, this);
																					doFilterInternal(httpRequest, httpResponse, filterChain); // RequestContextFilter
																						filterChain.doFilter(request, response);
																		filterChain.doFilter(request.getRequest(), response.getResponse()); // ApplicationFilterChain#doFilter
																			internalDoFilter(request,response);
																				WsFilter
																				filter.doFilter(request, response, this); // 不同!WsFilter#doFilter
																					chain.doFilter(request, response);
																		filterChain.doFilter(request.getRequest(), response.getResponse()); // ApplicationFilterChain#doFilter
																			internalDoFilter(request,response);
																				// 5个filter用完,继续向下执行
																				servlet.service(request, response); // servlet=DispatchServlet,实际执行HttpServlet#service
																					service(request, response); // 执行FrameworkServlet#service
																						super.service(request, response); // 执行 HttpServlet#service-长
																							doGet(req, resp); // GET请求,执行FrameworkServlet#doGet
																								processRequest(request, response);
																									initContextHolders(request, localeContext, requestAttributes);
																									doService(request, response); // 执行 DispatchServlet#doService
																										logRequest(request); // 打印第一条调试日志!
																										doDispatch(request, response); // 执行 DispatchServlet#doDispatch
																											mappedHandler = getHandler(processedRequest); // 第二条DEBUG日志, HandlerExecutionChain
																											HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // RequestMappingHandlerAdapter, Spring容器中存在此Bean
																											mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); // 执行 AbstractHandlerMethodAdapter#handle
																												return handleInternal(request, response, (HandlerMethod) handler); // 执行 RequestMappingHandlerAdapter#handleInternal
																													mav = invokeHandlerMethod(request, response, handlerMethod);
																														ServletWebRequest webRequest = new ServletWebRequest(request, response);
																														WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); // ServletRequestDataBinderFactory
																														ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);// ModelFactory
																														ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); // com.benzl.prj1.Prj1Application#hello(String)
																														AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); // StandardServletAsyncWebRequest
																														WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); // WebAsyncManager
																														invocableMethod.invokeAndHandle(webRequest, mavContainer); // ServletInvocableHandlerMethod#invokeAndHandle
																															Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // InvocableHandlerMethod#invokeForRequest
																																Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
																																	args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); // HandlerMethodArgumentResolverComposite#resolveArgument
																																		HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
																																		return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); // AbstractNamedValueMethodArgumentResolver#resolveArgument
																																			NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
																																			handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
																																return doInvoke(args);
																																	Method method = getBridgedMethod();  // 执行 HandlerMethod#getBridgedMethod
																																	return method.invoke(getBean(), args); // getBean()=com.benzl.prj1.Prj1Application$$EnhancerBySpringCGLIB$$3f2d7a4c@fb0bff5 // 反射Method#invoke
																															this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest); // HandlerMethodReturnValueHandlerComposite#handleReturnValue
																																handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest); // RequestResponseBodyMethodProcessor 
																																	writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); // AbstractMessageConverterMethodProcessor#writeWithMessageConverters
																																	// 输出第三条DEBUG日志
																																	// 输出第四条DEBUG日志
																																	((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); // AbstractHttpMessageConverter#write
																																	// 这里断片了---------------↓
																																	CoyoteOutputStream#flush
																																		OutputBuffer#flush
																																			OutputBuffer#doFlush
																																				coyoteResponse.sendHeaders();
																																					action(ActionCode.COMMIT, this); // Response#action ? 怎么来这里了?actionCode=COMMIT
																																						hook.action(actionCode, param); // hook=Http11Processor, 执行 AbstractProcessor#action
																																							case COMMIT:
																																							prepareResponse();
																																					setCommitted(true);
																																				coyoteResponse.action(ActionCode.CLIENT_FLUSH, null); // Respone#action, actionCode=CLIENT_FLUSH
																																					hook.action(actionCode, this);
																																						case CLIENT_FLUSH:
																																						action(ActionCode.COMMIT, null); // AbstractProcessor#action
																																						flush(); // 客户端(浏览器、postman收到 响应)!!!
																																	// 这里断片了---------------↑
																																	回
																																回
																															回
																														return getModelAndView(mavContainer, modelFactory, webRequest);
																														finally: webRequest.requestCompleted();
																													return mav;	
																												回				
																											回 DispatchServlet#doDispatch,继续执行...
																											processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
																										回 DispatchServlet#doService
																										...不继续了
		}

 

上面的调查结果请拷贝到 Notepad++ 里面打开,然后,视图->取消(不选)自动换行,再细看。

 

针对调查结果,下面再做一个简要介绍:

本文从 AbstractProtocol 开始:其实,上面停止了 AbstractProtocol的子类ConnectionHandler 的process函数中(起点)。

用到的 协议对象 Http11NioProtocol,对于的处理器 Http11Processor;

NioEndpoint 是 配置 HTTP1.1 使用的,其下包含 Poller内部类,NioSocketWrapper静态内部类,以及其它 内部类;

Http11Processor 源码:继承了 AbstractProcessor,所以,调用时 就会用到 其 父类中的方法(Java基础,同 上面的 *Servlet类)

public interface Processor {
}

public abstract class AbstractProcessorLight implements Processor {
}

public abstract class AbstractProcessor extends AbstractProcessorLight implements ActionHook {
}

public class Http11Processor extends AbstractProcessor {
}

ConnectionHandler中的 process方法 调用 processor.process(wrapper, status),实际调用的是 AbstractProcessorLight#process;

接着,调用 Http11Processor#service:其中重要的 调用 prepareRequestProtocol()、getAdapter().service(request, response);

getAdapter().service(request, response) 中,获取的 org.apache.catalina.connector.CoyoteAdapter 对象,再执行其 service方法;

CoyoteAdapter#service 中最重要的调用:

// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

 

最终的 invoke 实际调用的是 org.apache.catalina.core.StandardEngineValve#invoke。

final class StandardEngineValve extends ValveBase {
}

注意,上面 ValveBase 是 抽象类,其下的很多子类 会在 本地分析中被用到:

spring boot项目06:Web请求探秘-整个过程

StandardEngineValve#invoke 中的重要调用:host.getPipeline().getFirst().invoke(request, response),实际执行的是 ErrorReportValve#invoke;

ErrorReportValve#invoke 中 调用 getNext().invoke(request, response),实际执行 StandardHostValve#invoke;

然后又是 TomcatEmbeddedContext对象、AuthenticatorBase#invoke、StandardContextValve#invoke,

最后,调用了 StandardWrapperValve#invoke。

 

StandardWrapperValve#invoke 调用中,完成了 请求处理、请求响应 的全过程

1、servlet = wrapper.allocate(); 分配 servlet;

2、ApplicationFilterFactory.createFilterChain(request, wrapper, servlet)

创建过滤器链,包含五个过滤器(调试工具中 可以看到):

[
	FilterMap[filterName=characterEncodingFilter, urlPattern=/*], 
	FilterMap[filterName=webMvcMetricsFilter, urlPattern=/*], 
	FilterMap[filterName=formContentFilter, urlPattern=/*], 
	FilterMap[filterName=requestContextFilter, urlPattern=/*], 
	FilterMap[filterName=Tomcat WebSocket (JSR356) Filter, urlPattern=/*]
]

3、filterChain.doFilter(request.getRequest(), response.getResponse())

过滤器链建好后,多次“触发”上面的调用——千万注意,是一个扣一个的链式,而不是 循环调用!

疑问:做什么用呢?

4、ApplicationFilterChain#internalDoFilter 中 调用 servlet.service(request, response)

这里的 servlet 实际是 DispatchServlet对象, 实际执行的是 HttpServlet#service,进一步调用 FrameworkServlet#service。

注意,HttpServlet 中有两个 service函数,都是两个参数,但一个 签名长,一个短(下面的签名没有抛出异常的信息,不是完整的哦!):

// 短 会 调用 长的
public void service(ServletRequest req, ServletResponse res)
// 长
protected void service(HttpServletRequest req, HttpServletResponse resp)

注意了!注意了!注意了!

关键点来了

HttpServlet 中 签名长 的 service函数中包含 各种 处理请求的方法调用——doGet、doHead、doPost、doPut、doDelete、doOptions、doTrace。

本文近分析了 doGet 分支,其它的 应该类似的。

spring boot项目06:Web请求探秘-整个过程

其后,有陆续调用了 DispatchServlet#doService、DispatchServlet#doDispatch等,不再具体分析,可以看前面的 调查结果 以及 自行调试。

最终,调用 HandlerMethodReturnValueHandlerComposite#handleReturnValue 想要客户端,得到响应。

 

注:调查过程中,发现很多 对象是在 Spring容器中存在的,比如,webEndpointServletHandlerMapping、managementServletContext。

 

---本文完---

 

疑问:

1、请求数据 怎么 定制?Servlet中的 过滤器、拦截器、AOP 等怎么使用?

2、本文的HTTP 是 1.1,其它还有 HTTP 2、WebSocket、ReactiveWeb,可以进行类似的分析;

3、响应数据 怎么 定制?

4、Web应用的初始化是怎么做的呢?还需 调试 入口类的 run方法——又是几多精时啊。

还要继续 探究 才是。

 

上一篇:Web开发——使用Filter


下一篇:Kalman实际应用总结