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,会处理不过来)
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方法——又是几多精时啊。
还要继续 探究 才是。