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,会处理不过来)
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
注:
上面的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类 开始,整个过程中,自己设置了几十个断点(有些不会用到);
1、Eclipse中 以 调试模式 启动项目(调试时 可以使用 快捷键);
2、使用 Postman 访问准备的接口;
@GetMapping("hello") public String hello(@RequestParam(value="name", defaultValue = "World") String name) { return String.format("Hello, %s", name); }
注:写文章前最后一次调试 耗时近 2小时!(太费精时了!)
3、重复上面的步骤,直到 找到 一个请求被 Spring Web 处理的完整路径。
打开Postman,发送GET请求,此时 Postman、Eclipse展示如下:
- 请求在调试结束前,一直处于 发送状态
- 发出请求,停止了第一个断点位置——AbstractProtocol 类 的 process 函数,左边可以看到 对应线程的调用栈(stack),在右边(下图未展示),还可以看到当前的变量(鼠标放到变量上 也可以看到)。
当前方法变量表——可以看变量的值:
接下来,开始 各种执行各种调试命令,出发!
……若干小时后……
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 是 抽象类,其下的很多子类 会在 本地分析中被用到:
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 分支,其它的 应该类似的。
其后,有陆续调用了 DispatchServlet#doService、DispatchServlet#doDispatch等,不再具体分析,可以看前面的 调查结果 以及 自行调试。
最终,调用 HandlerMethodReturnValueHandlerComposite#handleReturnValue 想要客户端,得到响应。
注:调查过程中,发现很多 对象是在 Spring容器中存在的,比如,webEndpointServletHandlerMapping、managementServletContext。
---本文完---
疑问:
1、请求数据 怎么 定制?Servlet中的 过滤器、拦截器、AOP 等怎么使用?
2、本文的HTTP 是 1.1,其它还有 HTTP 2、WebSocket、ReactiveWeb,可以进行类似的分析;
3、响应数据 怎么 定制?
4、Web应用的初始化是怎么做的呢?还需 调试 入口类的 run方法——又是几多精时啊。
还要继续 探究 才是。