Tomcat源码解析:请求处理过程

2023-12-22 15:18

本文主要是介绍Tomcat源码解析:请求处理过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 建立Socket连接
    • 发布PollerEvent事件
    • 处理PollerEvent事件
  • 读取Socket数据
  • 解析Http数据
    • 匹配servlet
    • 调用servlet
      • 创建filter调用链
      • 调用Filter
      • 调用servlet
  • 总结

我们都知道,一个http请求过程一般为 浏览器发送请求->建立socket连接->通过socket读取数据->解析http数据->调用后台服务完成响应,而对于Tomcat来说,着重点在 建立socket连接->通过socket读取数据->解析http数据->调用filter->调用servlet处理并返回响应,本文将详细介绍Tomcat请求过程。


前言

当Tomcat启动时,会启动一组线程用于不同阶段的请求处理过程。

在这里插入图片描述

从图中可以看出,根据不同协议分别启动了Acceptor和Poller线程,先介绍一下这两个线程的作用,这里只对http协议的请求过程进行分析。

  • Acceptor:用于接收新连接,并将新连接添加到Poller事件队列中。
  • Poller:用于监听Socket事件,当Socket可读或者可写时,调用线程池处理Socket请求。

建立Socket连接

Acceptor#run

public void run() {int errorDelay = 0;// Loop until we receive a shutdown commandwhile (endpoint.isRunning()) {try {//...U socket = null;// 监听socket负责接收新连接socket = endpoint.serverSocketAccept();// Successful accept, reset the error delayerrorDelay = 0;// Configure the socketif (endpoint.isRunning() && !endpoint.isPaused()) {// setSocketOptions() will hand the socket off to// an appropriate processor if successful//处理接受到的socket对象并发布事件if (!endpoint.setSocketOptions(socket)) {endpoint.closeSocket(socket);}} else {endpoint.destroySocket(socket);}} catch (Throwable t) {//...}}state = AcceptorState.ENDED;
}
  1. Acceptor 在启动后会阻塞在 endpoint.serverSocketAccept(); 方法处,当有新连接到达时,该方法返回一个 SocketChannel。这里的endpointNioEndpoint
protected SocketChannel serverSocketAccept() throws Exception {return serverSock.accept();
}
  1. endpoint.setSocketOptions(socket)会将socket封装到NioChannel中,并注册到Poller。
protected boolean setSocketOptions(SocketChannel socket) {// Process the connection// Disable blocking, polling will be usedsocket.configureBlocking(false);Socket sock = socket.socket();socketProperties.setProperties(sock);NioChannel channel = null;if (nioChannels != null) {channel = nioChannels.pop();}if (channel == null) {SocketBufferHandler bufhandler = new SocketBufferHandler(socketProperties.getAppReadBufSize(),socketProperties.getAppWriteBufSize(),socketProperties.getDirectBuffer());if (isSSLEnabled()) {channel = new SecureNioChannel(socket, bufhandler, selectorPool, this);} else {channel = new NioChannel(socket, bufhandler);}} else {channel.setIOChannel(socket);channel.reset();}//获取Poller并将socket注册到Poller当中getPoller0().register(channel);//...return true;
}

发布PollerEvent事件

NioEndpoint 维护了一个 Poller 数组,使用时,从中取出一个。如果当前Poller线程只有一个时,默认返回pollers[0],否则当一个连接分配给 pollers[index] 时,下一个连接就会分配给 pollers[(index+1)%pollers.length]

public Poller getPoller0() {if (pollerThreadCount == 1) {return pollers[0];} else {int idx = Math.abs(pollerRotater.incrementAndGet()) % pollers.length;return pollers[idx];}
}

然后将 Socket 添加到该PollerPollerEvent队列中。

public void register(final NioChannel socket) {socket.setPoller(this);NioSocketWrapper socketWrapper = new NioSocketWrapper(socket, NioEndpoint.this);socket.setSocketWrapper(socketWrapper);socketWrapper.setPoller(this);socketWrapper.setReadTimeout(getConnectionTimeout());socketWrapper.setWriteTimeout(getConnectionTimeout());socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());socketWrapper.setSecure(isSSLEnabled());socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.PollerEvent r = null;if (eventCache != null) {r = eventCache.pop();}if (r == null) {r = new PollerEvent(socket, socketWrapper, OP_REGISTER);} else {r.reset(socket, socketWrapper, OP_REGISTER);}// 添加到PollerEvent队列当中addEvent(r);
}private void addEvent(PollerEvent event) {// 添加到PollerEvent队列当中events.offer(event);if (wakeupCounter.incrementAndGet() == 0) {selector.wakeup();}
}

处理PollerEvent事件

Poller线程会消费PollerEvent队列

Poller#run

public void run() {//..if (!close) {//这里会处理PollerEvent事件hasEvents = events();}//...Iterator<SelectionKey> iterator =keyCount > 0 ? selector.selectedKeys().iterator() : null;while (iterator != null && iterator.hasNext()) {SelectionKey sk = iterator.next();NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();if (socketWrapper == null) {iterator.remove();} else {iterator.remove();//处理socket请求processKey(sk, socketWrapper);}}
}
  1. events()处理PollerEvent事件并将socket注册到selector当中,省略了部分代码,Poller和PollerEvent都是NioEndpoint的内部类。
public class Poller implements Runnable {public boolean events() {boolean result = false;PollerEvent pe = null;for (int i = 0, size = events.size(); i < size && (pe = events.poll()) != null; i++ ) {result = true;//调用PollerEvent的runpe.run();pe.reset();if (running && !paused && eventCache != null) {eventCache.push(pe);}}return result;}
}    public static class PollerEvent implements Runnable {public void run() {if (interestOps == OP_REGISTER) {//将Socket注册到channel中socket.getIOChannel().register(socket.getPoller().getSelector(), SelectionKey.OP_READ, socketWrapper);} }
}
  1. processKey(sk, socketWrapper)处理socket数据。

Poller#processKey

protected void processKey(SelectionKey sk, NioSocketWrapper attachment) {if (sk.isReadable()) {if (!processSocket(attachment, SocketEvent.OPEN_READ, true)) {closeSocket = true;}}//...if (!closeSocket && sk.isWritable()) {if (!processSocket(attachment, SocketEvent.OPEN_WRITE, true)) {closeSocket = true;}}
}

当Socket可读或者可写时,处理Socket数据。

读取Socket数据

AbstractEndpoint#processSocket

public boolean processSocket(SocketWrapperBase<S> socketWrapper,SocketEvent event, boolean dispatch) {if (socketWrapper == null) {return false;}SocketProcessorBase<S> sc = null;if (processorCache != null) {sc = processorCache.pop();}// 1.创建socket数据处理器if (sc == null) {sc = createSocketProcessor(socketWrapper, event);} else {sc.reset(socketWrapper, event);}//2.启动线程处理socket数据,Executor executor = getExecutor();if (dispatch && executor != null) {executor.execute(sc);} else {sc.run();}return true;
}
  1. 创建socket数据处理器

**NioEndpoint#createSocketProcessor **

protected SocketProcessorBase<NioChannel> createSocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {return new SocketProcessor(socketWrapper, event);
}
  1. 启动线程处理socket数据。如果配置了线程池会启动线程池来执行。
protected class SocketProcessor extends SocketProcessorBase<NioChannel> {protected void doRun() {NioChannel socket = socketWrapper.getSocket();SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());int handshake = -1;if (handshake == 0) {SocketState state = SocketState.OPEN;// Process the request from this socket// 处理socket数据if (event == null) {state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);} else {state = getHandler().process(socketWrapper, event);}if (state == SocketState.CLOSED) {close(socket, key);}} else if (handshake == -1 ) {close(socket, key);} else if (handshake == SelectionKey.OP_READ){socketWrapper.registerReadInterest();} else if (handshake == SelectionKey.OP_WRITE){socketWrapper.registerWriteInterest();}}
}

这里的handler为ConnectionHandler,来源于Http11NioProtocol构造方法。

public class Http11NioProtocol extends AbstractHttp11JsseProtocol<NioChannel> {public Http11NioProtocol() {super(new NioEndpoint());}
}public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {public AbstractHttp11Protocol(AbstractEndpoint<S,?> endpoint) {super(endpoint);setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);setHandler(cHandler);getEndpoint().setHandler(cHandler);} 
}

那么接下来聚焦于它的process方法。

ConnectionHandler#process

public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {//...S socket = wrapper.getSocket();Processor processor = connections.get(socket);//....if (processor == null) {//创建processorprocessor = getProtocol().createProcessor();register(processor);}SocketState state = SocketState.CLOSED;do {//处理socketstate = processor.process(wrapper, status);//...省略了处理完成之后清理socket的操作} while ( state == SocketState.UPGRADING);// Make sure socket/processor is removed from the list of current// connectionsconnections.remove(socket);release(processor);return SocketState.CLOSED;
}

默认一个新连接的情况下,会调用processor.process(wrapper, status)代码,而processor的实例一般是在getProtocol().createProcessor()创建的,而protocol则:

public ConnectionHandler(AbstractProtocol<S> proto) {this.proto = proto;
}protected AbstractProtocol<S> getProtocol() {return proto;
}

AbstractProtocol有两个实现类,AbstractHttp11ProtocolAbstractAjpProtocol,这里是Http请求,所以获取到的protocol为AbstractHttp11Protocol。而AbstractHttp11Protocol创建了Http11Processor实例。

protected Processor createProcessor() {Http11Processor processor = new Http11Processor(this, adapter);return processor;
}

由于Http11Processor没有实现process方法,所以实际调用了其父类的父类AbstractProcessorLight的process方法。

public abstract class AbstractProcessorLight implements Processor {@Overridepublic SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)throws IOException {SocketState state = SocketState.CLOSED;Iterator<DispatchType> dispatches = null;do {//...if (status == SocketEvent.OPEN_READ){//state = service(socketWrapper);} else {state = SocketState.CLOSED;}} while (state == SocketState.ASYNC_END ||dispatches != null && state != SocketState.CLOSED);return state;}
}

根据不同的SocketEvent做不同操作,这里的事件是OPEN_READ,所以调用service方法解析http数据。

解析Http数据

Http11Processor#service

public SocketState service(SocketWrapperBase<?> socketWrapper)throws IOException {RequestInfo rp = request.getRequestProcessor();rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);// Setting up the I/OsetSocketWrapper(socketWrapper);inputBuffer.init(socketWrapper);outputBuffer.init(socketWrapper);// Flagswhile (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&sendfileState == SendfileState.DONE && !protocol.isPaused()) {// Parsing the request header// 解析requestLineif (!inputBuffer.parseRequestLine(keptAlive, protocol.getConnectionTimeout(),protocol.getKeepAliveTimeout())) {if (inputBuffer.getParsingRequestLinePhase() == -1) {return SocketState.UPGRADING;} else if (handleIncompleteRequestLineRead()) {break;}}if (protocol.isPaused()) {response.setStatus(503);setErrorState(ErrorState.CLOSE_CLEAN, null);} else {keptAlive = true;request.getMimeHeaders().setLimit(protocol.getMaxHeaderCount());//解析headerif (!inputBuffer.parseHeaders()) {openSocket = true;readComplete = false;break;}if (!protocol.getDisableUploadTimeout()) {socketWrapper.setReadTimeout(protocol.getConnectionUploadTimeout());}}//...//指定request body的读取filterprepareRequest();//...// Process the request in the adapterif (getErrorState().isIoAllowed()) {rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);//处理requestgetAdapter().service(request, response);if(keepAlive && !getErrorState().isError() && !isAsync() &&statusDropsConnection(response.getStatus())) {setErrorState(ErrorState.CLOSE_CLEAN, null);}}// Finish the handling of the request//...}
}
  1. 解析请求header。
  2. 指定request body的读取filter。
  3. 匹配servlet。

这里主要针对匹配servlet作详细解释。

CoyoteAdapter#service

public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)throws Exception {//...// 解析request并匹配servletpostParseSuccess = postParseRequest(req, request, res, response);if (postParseSuccess) {request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());// 调用匹配成功的servletconnector.getService().getContainer().getPipeline().getFirst().invoke(request, response);}//...
}
  1. 解析request并匹配servlet
  2. 调用匹配成功的servlet

匹配servlet

protected boolean postParseRequest(org.apache.coyote.Request req, Request request,org.apache.coyote.Response res, Response response) throws IOException, ServletException {//...connector.getService().getMapper().map(serverName, decodedURI,version, request.getMappingData());//..
}

这里只关注如何匹配,省略了一些有关seesion和cookie的处理。

Mapper在StandardService启动时MapperListener 的start方法初始化。MapperListener启动这里放一张截图方便理解。

在这里插入图片描述

Mapper#map

public void map(MessageBytes host, MessageBytes uri, String version,MappingData mappingData) throws IOException {if (host.isNull()) {String defaultHostName = this.defaultHostName;if (defaultHostName == null) {return;}host.getCharChunk().append(defaultHostName);}host.toChars();uri.toChars();//匹配context(即war包)名称,设置到mappingData中,匹配成功后匹配context的child wrapper名称,设置到mappingData中。internalMap(host.getCharChunk(), uri.getCharChunk(), version, mappingData); 
}private final void internalMap(CharChunk host, CharChunk uri,String version, MappingData mappingData) throws IOException {if (mappingData.host != null) {throw new AssertionError();}//...ContextList contextList = mappedHost.contextList;MappedContext[] contexts = contextList.contexts;int pos = find(contexts, uri);if (pos == -1) {return;}int lastSlash = -1;int uriEnd = uri.getEnd();int length = -1;boolean found = false;MappedContext context = null;while (pos >= 0) {context = contexts[pos];//匹配Contextif (uri.startsWith(context.name)) {length = context.name.length();if (uri.getLength() == length) {found = true;break;} else if (uri.startsWithIgnoreCase("/", length)) {found = true;break;}}if (lastSlash == -1) {lastSlash = nthSlash(uri, contextList.nesting + 1);} else {lastSlash = lastSlash(uri);}uri.setEnd(lastSlash);pos = find(contexts, uri);}uri.setEnd(uriEnd);if (!found) {if (contexts[0].name.equals("")) {context = contexts[0];} else {context = null;}}if (context == null) {return;}mappingData.contextPath.setString(context.name);//匹配contextversionContextVersion contextVersion = null;ContextVersion[] contextVersions = context.versions;final int versionCount = contextVersions.length;if (versionCount > 1) {Context[] contextObjects = new Context[contextVersions.length];for (int i = 0; i < contextObjects.length; i++) {contextObjects[i] = contextVersions[i].object;}mappingData.contexts = contextObjects;if (version != null) {contextVersion = exactFind(contextVersions, version);}}if (contextVersion == null) {// Return the latest version// The versions array is known to contain at least one elementcontextVersion = contextVersions[versionCount - 1];}mappingData.context = contextVersion.object;mappingData.contextSlashCount = contextVersion.slashCount;// Wrapper mapping 匹配wrapperif (!contextVersion.isPaused()) {internalMapWrapper(contextVersion, uri, mappingData);}}

这里主要分为两步进行匹配。

  1. 首先匹配context.name,匹配成功后设置到mappingData.contextPath
  2. 匹配wrapper.name,匹配成功后设置到mappingData.wrapperPath
private final void internalMapWrapper(ContextVersion contextVersion,CharChunk path,MappingData mappingData) throws IOException {//...// Rule 1 -- Exact Match 匹配真实的wrapper,即项目里自己写的MappedWrapper[] exactWrappers = contextVersion.exactWrappers;internalMapExactWrapper(exactWrappers, path, mappingData);// Rule 2 -- Prefix Match 通配匹配boolean checkJspWelcomeFiles = false;MappedWrapper[] wildcardWrappers = contextVersion.wildcardWrappers;if (mappingData.wrapper == null) {internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,path, mappingData);if (mappingData.wrapper != null && mappingData.jspWildCard) {char[] buf = path.getBuffer();if (buf[pathEnd - 1] == '/') {mappingData.wrapper = null;checkJspWelcomeFiles = true;} else {mappingData.wrapperPath.setChars(buf, path.getStart(),path.getLength());mappingData.pathInfo.recycle();}}}//...// Rule 3 -- Extension Match 扩展匹配MappedWrapper[] extensionWrappers = contextVersion.extensionWrappers;if (mappingData.wrapper == null && !checkJspWelcomeFiles) {internalMapExtensionWrapper(extensionWrappers, path, mappingData,true);}// Rule 4 -- Welcome resources processing for servletsif (mappingData.wrapper == null) {boolean checkWelcomeFiles = checkJspWelcomeFiles;if (!checkWelcomeFiles) {char[] buf = path.getBuffer();checkWelcomeFiles = (buf[pathEnd - 1] == '/');}if (checkWelcomeFiles) {for (int i = 0; (i < contextVersion.welcomeResources.length)&& (mappingData.wrapper == null); i++) {path.setOffset(pathOffset);path.setEnd(pathEnd);path.append(contextVersion.welcomeResources[i], 0,contextVersion.welcomeResources[i].length());path.setOffset(servletPath);// Rule 4a -- Welcome resources processing for exact macthinternalMapExactWrapper(exactWrappers, path, mappingData);// Rule 4b -- Welcome resources processing for prefix matchif (mappingData.wrapper == null) {internalMapWildcardWrapper(wildcardWrappers, contextVersion.nesting,path, mappingData);}// Rule 4c -- Welcome resources processing//            for physical folderif (mappingData.wrapper == null&& contextVersion.resources != null) {String pathStr = path.toString();WebResource file =contextVersion.resources.getResource(pathStr);if (file != null && file.isFile()) {internalMapExtensionWrapper(extensionWrappers, path,mappingData, true);if (mappingData.wrapper == null&& contextVersion.defaultWrapper != null) {mappingData.wrapper =contextVersion.defaultWrapper.object;mappingData.requestPath.setChars(path.getBuffer(), path.getStart(),path.getLength());mappingData.wrapperPath.setChars(path.getBuffer(), path.getStart(),path.getLength());mappingData.requestPath.setString(pathStr);mappingData.wrapperPath.setString(pathStr);}}}}path.setOffset(servletPath);path.setEnd(pathEnd);}}//...// Rule 7 -- Default servletif (mappingData.wrapper == null && !checkJspWelcomeFiles) {if (contextVersion.defaultWrapper != null) {mappingData.wrapper = contextVersion.defaultWrapper.object;mappingData.requestPath.setChars(path.getBuffer(), path.getStart(), path.getLength());mappingData.wrapperPath.setChars(path.getBuffer(), path.getStart(), path.getLength());mappingData.matchType = MappingMatch.DEFAULT;}// Redirection to a folderchar[] buf = path.getBuffer();if (contextVersion.resources != null && buf[pathEnd -1 ] != '/') {String pathStr = path.toString();// Note: Check redirect first to save unnecessary getResource()//       call. See BZ 62968.if (contextVersion.object.getMapperDirectoryRedirectEnabled()) {WebResource file;// Handle context rootif (pathStr.length() == 0) {file = contextVersion.resources.getResource("/");} else {file = contextVersion.resources.getResource(pathStr);}if (file != null && file.isDirectory()) {// Note: this mutates the path: do not do any processing// after this (since we set the redirectPath, there// shouldn't be any)path.setOffset(pathOffset);path.append('/');mappingData.redirectPath.setChars(path.getBuffer(), path.getStart(), path.getLength());} else {mappingData.requestPath.setString(pathStr);mappingData.wrapperPath.setString(pathStr);}} else {mappingData.requestPath.setString(pathStr);mappingData.wrapperPath.setString(pathStr);}}}path.setOffset(pathOffset);path.setEnd(pathEnd);
}

匹配规则:

  1. 匹配exact servlet,即项目里真实的servlet。
  2. 通用匹配,
  3. 扩展匹配,tomcat容器web.xml默认的servlet
  4. reources匹配,即欢迎页面等
  5. 匹配未配置的扩展名映射,如index.jsf,index.do。
  6. default servlet的匹配。

这里重点看一下匹配exact servlet

private final void internalMapExactWrapper(MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) {//根据path寻找wrapperMappedWrapper wrapper = exactFind(wrappers, path);if (wrapper != null) {mappingData.requestPath.setString(wrapper.name);mappingData.wrapper = wrapper.object;if (path.equals("/")) {//根目录// Special handling for Context Root mapped servletmappingData.pathInfo.setString("/");mappingData.wrapperPath.setString("");// This seems wrong but it is what the spec says...mappingData.contextPath.setString("");mappingData.matchType = MappingMatch.CONTEXT_ROOT;} else {mappingData.wrapperPath.setString(wrapper.name);mappingData.matchType = MappingMatch.EXACT;}}
}

匹配成功后,将wrapper.namematchType设置到mappingData中以备调用servlet时使用。这里放一张截图方便理解。

在这里插入图片描述

到这里postParseRequest就完成了。

调用servlet

CoyoteAdapter#service

connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

这里通过调用每个容器的pipeline的Valve对象,实现了对Servlet的调用,每个容器都有一个或者多个Valve,这些Valve来源于两个地方,一个是conf/server.xml中配置的Valve,另外一个就是每一个容器的构造其中自己初始化的Valve

这里调用了StandardEngineValve.invoke,然后继续往下调用。

public final void invoke(Request request, Response response)throws IOException, ServletException {// Select the Host to be used for this RequestHost host = request.getHost();if (host == null) {return;}if (request.isAsyncSupported()) {request.setAsyncSupported(host.getPipeline().isAsyncSupported());}// Ask this Host to process this requesthost.getPipeline().getFirst().invoke(request, response);
}

通过debug代码,可以得到一个完整的调用链。

->org.apache.catalina.core.StandardEngineValve#invoke
-->org.apache.catalina.valves.AbstractAccessLogValve#invoke
--->org.apache.catalina.valves.ErrorReportValve#invoke
---->org.apache.catalina.core.StandardHostValve#invoke
----->org.apache.catalina.authenticator.AuthenticatorBase#invoke
------>org.apache.catalina.core.StandardContextValve#invoke
------->org.apache.catalina.core.StandardWrapperValve#invoke

因为StandardWrapperValve中最终调用了servlet,所以重点看下。

final class StandardWrapperValve extends ValveBase {public final void invoke(Request request, Response response) throws IOException, ServletException {//...if (!unavailable) {//实例化servletservlet = wrapper.allocate();}//...//create filter调用链ApplicationFilterChain filterChain =ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);//...//调用filterfilterChain.doFilter(request.getRequest(),response.getResponse());}
}
  1. 实例化servlet。
  2. 创建filter调用链。
  3. 调用filter

创建filter调用链

ApplicationFilterFactory#createFilterChain

public static ApplicationFilterChain createFilterChain(ServletRequest request,Wrapper wrapper, Servlet servlet) {// If there is no servlet to execute, return nullif (servlet == null)return null;// Create and initialize a filter chain objectApplicationFilterChain filterChain = null;if (request instanceof Request) {Request req = (Request) request;if (Globals.IS_SECURITY_ENABLED) {filterChain = new ApplicationFilterChain();} else {filterChain = (ApplicationFilterChain) req.getFilterChain();if (filterChain == null) {filterChain = new ApplicationFilterChain();req.setFilterChain(filterChain);}}} else {// Request dispatcher in usefilterChain = new ApplicationFilterChain();}filterChain.setServlet(servlet);filterChain.setServletSupportsAsync(wrapper.isAsyncSupported());// 获取启动时初始化的filterStandardContext context = (StandardContext) wrapper.getParent();FilterMap filterMaps[] = context.findFilterMaps();// If there are no filter mappings, we are done// 如果没有filter 直接返回if ((filterMaps == null) || (filterMaps.length == 0))return filterChain;// Acquire the information we will need to match filter mappingsDispatcherType dispatcher =(DispatcherType) request.getAttribute(Globals.DISPATCHER_TYPE_ATTR);String requestPath = null;Object attribute = request.getAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR);if (attribute != null){requestPath = attribute.toString();}String servletName = wrapper.getName();//校验requestPath是否匹配filter的urlPatterns// Add the relevant path-mapped filters to this filter chainfor (int i = 0; i < filterMaps.length; i++) {if (!matchDispatcher(filterMaps[i] ,dispatcher)) {continue;}if (!matchFiltersURL(filterMaps[i], requestPath))continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {// FIXME - log configuration problemcontinue;}filterChain.addFilter(filterConfig);}//校验servlet name是否匹配filter的servletNames// Add filters that match on servlet name secondfor (int i = 0; i < filterMaps.length; i++) {if (!matchDispatcher(filterMaps[i] ,dispatcher)) {continue;}if (!matchFiltersServlet(filterMaps[i], servletName))continue;ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)context.findFilterConfig(filterMaps[i].getFilterName());if (filterConfig == null) {// FIXME - log configuration problemcontinue;}filterChain.addFilter(filterConfig);}// Return the completed filter chainreturn filterChain;
}
  • 首先创建并初始化一个filterChain对象。
  • 循环调用matchFiltersURL(filterMaps[i], requestPath)匹配requestPath与filter的urlPatterns,requestPath即servlet路径,匹配成功后将当前filter添加到filterChain
private static boolean matchFiltersURL(FilterMap filterMap, String requestPath) {// Check the specific "*" special URL pattern, which also matches// named dispatchesif (filterMap.getMatchAllUrlPatterns())return true;if (requestPath == null)return false;// Match on context relative request pathString[] testPaths = filterMap.getURLPatterns();for (int i = 0; i < testPaths.length; i++) {if (matchFiltersURL(testPaths[i], requestPath)) {return true;}}// No matchreturn false;}private static boolean matchFiltersURL(String testPath, String requestPath) {if (testPath == null)return false;// Case 1 - Exact Matchif (testPath.equals(requestPath))return true;// Case 2 - Path Match ("/.../*")if (testPath.equals("/*"))return true;if (testPath.endsWith("/*")) {if (testPath.regionMatches(0, requestPath, 0,testPath.length() - 2)) {if (requestPath.length() == (testPath.length() - 2)) {return true;} else if ('/' == requestPath.charAt(testPath.length() - 2)) {return true;}}return false;}// Case 3 - Extension Matchif (testPath.startsWith("*.")) {int slash = requestPath.lastIndexOf('/');int period = requestPath.lastIndexOf('.');if ((slash >= 0) && (period > slash)&& (period != requestPath.length() - 1)&& ((requestPath.length() - period)== (testPath.length() - 1))) {return testPath.regionMatches(2, requestPath, period + 1,testPath.length() - 2);}}// Case 4 - "Default" Matchreturn false; // NOTE - Not relevant for selecting filters}
  • 循环调用matchFiltersServlet(filterMaps[i], servletName)匹配servletName与filter的servletNames,匹配成功后将当前filter添加到filterChain
  private static boolean matchFiltersServlet(FilterMap filterMap,String servletName) {if (servletName == null) {return false;}// Check the specific "*" special servlet nameelse if (filterMap.getMatchAllServletNames()) {return true;} else {String[] servletNames = filterMap.getServletNames();for (int i = 0; i < servletNames.length; i++) {if (servletName.equals(servletNames[i])) {return true;}}return false;}}

到这里创建filter调用链就结束了,开始调用filter。

调用Filter

ApplicationFilterChain#doFilter

public void doFilter(ServletRequest request, ServletResponse response)throws IOException, ServletException {//...internalDoFilter(request,response);
}private void internalDoFilter(ServletRequest request,ServletResponse response)throws IOException, ServletException {// n=filter.length ,addFilter时初始化。if (pos < n) {ApplicationFilterConfig filterConfig = filters[pos++];Filter filter = filterConfig.getFilter();//调用filterfilter.doFilter(request, response, this);return;}//...//filter调用完成之后调用servlet的service方法。servlet.service(request, response);}

调用servlet

最后会调用HttpServlet的service方法来区分调用GET/POST等方法。

protected void service(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {String method = req.getMethod();if (method.equals(METHOD_GET)) {long lastModified = getLastModified(req);if (lastModified == -1) {doGet(req, resp);} else {long ifModifiedSince;try {ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);} catch (IllegalArgumentException iae) {ifModifiedSince = -1;}if (ifModifiedSince < (lastModified / 1000 * 1000)) {// If the servlet mod time is later, call doGet()// Round down to the nearest second for a proper compare// A ifModifiedSince of -1 will always be lessmaybeSetLastModified(resp, lastModified);doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) {long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) {doPost(req, resp);} else if (method.equals(METHOD_PUT)) {doPut(req, resp);} else if (method.equals(METHOD_DELETE)) {doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) {doOptions(req,resp);} else if (method.equals(METHOD_TRACE)) {doTrace(req,resp);} else {//// Note that this means NO servlet supports whatever// method was requested, anywhere on this server.//String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}
}

以上。

总结

通过上面的分析,其实我们已经清楚了当一个请求过来以后,Tomcat是如何处理的。我们再来做一个总体的总结:

  1. 用户浏览器发送请求,请求会发送到对应的Connector监听的Socket端口。
  2. Connector从Socket流中获取数据,然后根据Http协议将其解析为Request和Reponse对象。
  3. 找到Request对象对应的Host,Context,Wrapper。
  4. 调用filter链。
  5. 调用最终的Servelt的service进行处理。

最后放一张整体的调用链。

在这里插入图片描述

参考:

https://github.com/c-rainstorm/blog/blob/master/tomcat/%E8%B0%88%E8%B0%88Tomcat%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B.md

https://blog.csdn.net/jiaomingliang/article/details/47414657

https://www.jianshu.com/p/d8a2bc7d3c21

这篇关于Tomcat源码解析:请求处理过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/524448

相关文章

最新版IDEA配置 Tomcat的详细过程

《最新版IDEA配置Tomcat的详细过程》本文介绍如何在IDEA中配置Tomcat服务器,并创建Web项目,首先检查Tomcat是否安装完成,然后在IDEA中创建Web项目并添加Web结构,接着,... 目录配置tomcat第一步,先给项目添加Web结构查看端口号配置tomcat    先检查自己的to

Apache Tomcat服务器版本号隐藏的几种方法

《ApacheTomcat服务器版本号隐藏的几种方法》本文主要介绍了ApacheTomcat服务器版本号隐藏的几种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需... 目录1. 隐藏HTTP响应头中的Server信息编辑 server.XML 文件2. 修China编程改错误

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

在C#中合并和解析相对路径方式

《在C#中合并和解析相对路径方式》Path类提供了几个用于操作文件路径的静态方法,其中包括Combine方法和GetFullPath方法,Combine方法将两个路径合并在一起,但不会解析包含相对元素... 目录C#合并和解析相对路径System.IO.Path类幸运的是总结C#合并和解析相对路径对于 C

Java解析JSON的六种方案

《Java解析JSON的六种方案》这篇文章介绍了6种JSON解析方案,包括Jackson、Gson、FastJSON、JsonPath、、手动解析,分别阐述了它们的功能特点、代码示例、高级功能、优缺点... 目录前言1. 使用 Jackson:业界标配功能特点代码示例高级功能优缺点2. 使用 Gson:轻量

Java如何接收并解析HL7协议数据

《Java如何接收并解析HL7协议数据》文章主要介绍了HL7协议及其在医疗行业中的应用,详细描述了如何配置环境、接收和解析数据,以及与前端进行交互的实现方法,文章还分享了使用7Edit工具进行调试的经... 目录一、前言二、正文1、环境配置2、数据接收:HL7Monitor3、数据解析:HL7Busines

SpringBoot中Get请求和POST请求接收参数示例详解

《SpringBoot中Get请求和POST请求接收参数示例详解》文章详细介绍了SpringBoot中Get请求和POST请求的参数接收方式,包括方法形参接收参数、实体类接收参数、HttpServle... 目录1、Get请求1.1 方法形参接收参数 这种方式一般适用参数比较少的情况,并且前后端参数名称必须

python解析HTML并提取span标签中的文本

《python解析HTML并提取span标签中的文本》在网页开发和数据抓取过程中,我们经常需要从HTML页面中提取信息,尤其是span元素中的文本,span标签是一个行内元素,通常用于包装一小段文本或... 目录一、安装相关依赖二、html 页面结构三、使用 BeautifulSoup javascript

若依部署Nginx和Tomcat全过程

《若依部署Nginx和Tomcat全过程》文章总结了两种部署方法:Nginx部署和Tomcat部署,Nginx部署包括打包、将dist文件拉到指定目录、配置nginx.conf等步骤,Tomcat部署... 目录Nginx部署后端部署Tomcat部署出现问题:点击刷新404总结Nginx部署第一步:打包

Nginx、Tomcat等项目部署问题以及解决流程

《Nginx、Tomcat等项目部署问题以及解决流程》本文总结了项目部署中常见的four类问题及其解决方法:Nginx未按预期显示结果、端口未开启、日志分析的重要性以及开发环境与生产环境运行结果不一致... 目录前言1. Nginx部署后未按预期显示结果1.1 查看Nginx的启动情况1.2 解决启动失败的