文章目录
前言
开局两张图,内容全靠编。本文主要分析tomcat的请求解析流程,包括request和response对象的生成,continer和pipeline的关系,filterChain的处理一直到最后找到servlet,其实应该首先分析Tomcat的线程模型,无奈别人写的太好,推荐这篇深度解读Tomcat中的NIO模型。
filterChain
一、Request对象的生成
示Tomcat中request对象主要是通过parseRequestLine()和parseHeaders()方法进行解析的,具体代码参考源码,太长了就不展示了。在Http11Processor中的service方法中完成了对请求行和请求头的分析,inputbuffer只能顺序读取一次,剩余部分就是请求体,依旧设置在request的inputbuffer中,所以获取请求体的内容可以通过inputbuffer。
//解析请求行
if (!inputBuffer.parseRequestLine(keptAlive)) {
if (inputBuffer.getParsingRequestLinePhase() == -1) {
return SocketState.UPGRADING;
} else if (handleIncompleteRequestLineRead()) {
break;
}
}
//解析请求头
if (!http09 && !inputBuffer.parseHeaders()) {
// We've read part of the request, don't recycle it
// instead associate it with the socket
openSocket = true;
readComplete = false;
break;
}
二、cookie和session
1.cookie
tomcat通过在请求头中获取cookie,入口方法为parseCookieHeader()
public void parseCookieHeader(MimeHeaders headers,
ServerCookies serverCookies) {
if (headers == null) {
// nothing to process
return;
}
// process each "cookie" header
int pos = headers.findHeader("Cookie", 0);
while (pos >= 0) {
MessageBytes cookieValue = headers.getValue(pos);
if (cookieValue != null && !cookieValue.isNull() ) {
if (cookieValue.getType() != MessageBytes.T_BYTES ) {
if (log.isDebugEnabled()) {
Exception e = new Exception();
// TODO: Review this in light of HTTP/2
log.debug("Cookies: Parsing cookie as String. Expected bytes.", e);
}
cookieValue.toBytes();
}
if (log.isDebugEnabled()) {
log.debug("Cookies: Parsing b[]: " + cookieValue.toString());
}
ByteChunk bc = cookieValue.getByteChunk();
Cookie.parseCookie(bc.getBytes(), bc.getOffset(), bc.getLength(),
serverCookies);
}
// search from the next position
pos = headers.findHeader("Cookie", ++pos);
}
}
2.session
服务器通过SessionId区分区分不同的会话,使用SessionId获取对应Session中的内容。通过request.getSession()方法触发,当无法获取到session时,会创建一个新的session。此时我们考虑一下web开发中Servlet三大域对象的应用,分别是request,session和context,request就是一次请求,session中数据受两个因素影响:(1)客户端cookie中存储的sessionid,客户端关闭,sessionid丢失,数据丢失;(2)服务器端session超时,导致数据丢失,context的生命周期伴随着整个web应用。session的存储使用的是ConcurrentHashMap,保证并发下的数据安全。
protected Session doGetSession(boolean create) {
// There cannot be a session if no context has been assigned yet
Context context = getContext();
if (context == null) {
return null;
}
// Return the current session if it exists and is valid
if ((session != null) && !session.isValid()) {
session = null;
}
if (session != null) {
return session;
}
// Return the requested session if it exists and is valid
Manager manager = context.getManager();
if (manager == null) {
return null; // Sessions are not supported
}
if (requestedSessionId != null) {
try {
//查找session是否存在
session = manager.findSession(requestedSessionId);
。。。。。。。
}
// Create a new session if requested and the response is not committed
if (!create) {
return null;
}
boolean trackModesIncludesCookie =
context.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.COOKIE);
if (trackModesIncludesCookie && response.getResponse().isCommitted()) {
throw new IllegalStateException(sm.getString("coyoteRequest.sessionCreateCommitted"));
}
// Re-use session IDs provided by the client in very limited
// circumstances.
String sessionId = getRequestedSessionId();
。。。。。。省略
//创建新的session
session = manager.createSession(sessionId);
// Creating a new session cookie based on that session //cookie中添加sessionid
if (session != null && trackModesIncludesCookie) {
Cookie cookie = ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure());
response.addSessionCookieInternal(cookie);
}
if (session == null) {
return null;
}
session.access();
return session;
}
三.pipeline和value
Tomcat容器中内容的执行是通过一个管道来控制的,如图所示的四种容器都持有一个Pipeline用以执行预定义好的任务,任务装载在Value中,被称为阀。通过CoyoteAdapter开始调用,下面给出调用过程中的重点代码。基于责任链模式进行了多次调用,最终调用到filterChain的doFilter方法。
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
throws Exception {
try {
// Parse and set Catalina and configuration specific
// request parameters 完成了对session,cookie等参数的解析
postParseSuccess = postParseRequest(req, request, res, response);
if (postParseSuccess) {
//check valves if we support async
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
}
host.getPipeline().getFirst().invoke(request, response);
getNext().invoke(request, response);
。。。。。
filterChain.doFilter
(request.getRequest(), response.getResponse());
四.filterChain
filterChain的doFilter也是基于责任链模式,不断重复调用,最终通过servlet的service方法,重点代码如下。
private void internalDoFilter(ServletRequest request,
ServletResponse response)
throws IOException, ServletException {
// Call the next filter if there is one
if (pos < n) {
ApplicationFilterConfig filterConfig = filters[pos++];
try {
Filter filter = filterConfig.getFilter();
if (request.isAsyncSupported() && "false".equalsIgnoreCase(
filterConfig.getFilterDef().getAsyncSupported())) {
request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
}
if( Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res, this};
SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
} else {
//导通filter的处理方法
filter.doFilter(request, response, this);
}
} catch (IOException | ServletException | RuntimeException e) {
throw e;
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("filterChain.filter"), e);
}
return;
}
// We fell off the end of the chain -- call the servlet instance
try {
if ((request instanceof HttpServletRequest) &&
(response instanceof HttpServletResponse) &&
Globals.IS_SECURITY_ENABLED ) {
final ServletRequest req = request;
final ServletResponse res = response;
Principal principal =
((HttpServletRequest) req).getUserPrincipal();
Object[] args = new Object[]{req, res};
SecurityUtil.doAsPrivilege("service",
servlet,
classTypeUsedInService,
args,
principal);
} else {
//调用servlet的处理方法
servlet.service(request, response);
}
}
}
五.servlet的匹配过程
tomcat通过http的url地址匹配对应的servlet,中间经过了host,context和wrapper三级匹配过程,重点源码如下。
while (mapRequired) {
// This will map the the latest version by default
//匹配过程开始
connector.getService().getMapper().map(serverName, decodedURI,
version, request.getMappingData());
}
public void map(MessageBytes host, MessageBytes uri, String version,
MappingData mappingData) throws IOException {
//匹配host
if (host.isNull()) {
String defaultHostName = this.defaultHostName;
if (defaultHostName == null) {
return;
}
host.getCharChunk().append(defaultHostName);
}
host.toChars();
uri.toChars();
//匹配context
internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData);
}
private final void internalMap(CharChunk host, CharChunk uri,
String version, MappingData mappingData) throws IOException {
// Wrapper mapping
if (!contextVersion.isPaused()) {
//匹配wrapper
internalMapWrapper(contextVersion, uri, mappingData);
}
}
。本文springmvc中的dispatcherServlet就相当于一个普通的servlet,在springboot中对dispcherServlet进行了默认配置,主要在DispatcherServletRegistrationConfiguration中,重点代码如下
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
//路径没有配置时默认是跟路径
default String getServletUrlMapping() {
if (getPath().equals("") || getPath().equals("/")) {
return "/";
}
if (getPath().contains("*")) {
return getPath();
}
if (getPath().endsWith("/")) {
return getPath() + "*";
}
return getPath() + "/*";
}
pringmvc中因为springboot的默认context路径也是“/”,所以可以直接通过http://ip:port/访问,修改context路径可以修改server.servlet.context-path的值,如下所示。
server.servlet.context-path= /springboot
总结
本文主要分析了tomcat对http请求的解析流程,并对过程中的重点对象进行了源码解析,下一期将主要分析springmvc的流程,一起食用效果更佳