Tomcat源码解析(七):底层如何获取请求url、请求头、json数据?

Tomcat源码系列文章

Tomcat源码解析(一):Tomcat整体架构

Tomcat源码解析(二):Bootstrap和Catalina

Tomcat源码解析(三):LifeCycle生命周期管理

Tomcat源码解析(四):StandardServer和StandardService

Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper

Tomcat源码解析(六):Connector、ProtocolHandler、Endpoint

Tomcat源码解析(七):底层如何获取请求url、请求头、json数据?


文章目录

  • 前言
  • 一、SocketProcessor
    • 1、SocketProcessor结构
    • 2、ConnectionHandler连接处理器
      • 2.1、Http11Processor的创建(包括连接器Req和Res的实例化)
      • 2.2、Http11Processor的process方法
      • 2.2、Http11Processor的service方法
  • 二、解析请求行数据
    • 1、解析请求行六个阶段
    • 2、nio读取数据
  • 三、解析请求头数据
    • 1、解析并校验每个请求头
  • 四、适配器转化Request和Response
    • 1、创建容器Req和Res
    • 2、解析请求后的处理
  • 五、获取get和post请求数据
    • 1、GET请求
    • 2、POST请求
      • 2.1、获取json请求体源码
  • 总结


前言

  前文中我们介绍了连接器的初始化和启动,实际就是EndPoint的初始化启动,EndPoint主要负责接收socket请求,然后将socket请求包装为SocketProcessor对象(实现Runnable接口)扔给线程池Executor处理。接下来介绍NIO如何解析请求数据,网络字节流与Request和Response对象的转化。

在这里插入图片描述

一、SocketProcessor

1、SocketProcessor结构

在这里插入图片描述

  • SocketProcessor的父类SocketProcessorBase实现Runnable接口,run方法调用子类的doRun()方法,典型的模板方法
public abstract class SocketProcessorBase<S> implements Runnable {protected SocketWrapperBase<S> socketWrapper;...@Overridepublic final void run() {synchronized (socketWrapper) {// 可能会同时触发读取和写入的处理。上面的同步确保处理不会并行进行// 下面的测试确保,如果要处理的第一个事件导致套接字被关闭,则不处理后续事件if (socketWrapper.isClosed()) {return;}doRun();}}protected abstract void doRun();
}
  • 查看子类SocketProcessor的doRun()即为线程执行的核心方法
// SocketProcessor类方法
@Override
protected void doRun() {// 该方法将会执行于 tomcat 的 worker 线程中,比如 : http-nio-8080-exec-1// 获取待处理的客户端请求NioChannel socket = socketWrapper.getSocket();SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());try {// 这里的 handshake 是用来处理 https 的握手过程的,// 如果是 http 不需要该握手阶段,下面会将该标志设置为 0, 表示握手已经完成int handshake = -1;try {if (key != null) {if (socket.isHandshakeComplete()) {// 无需 TLS 握手。让处理程序处理此套接字事件组合handshake = 0;} else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||event == SocketEvent.ERROR) {// 无法完成 TLS 握手。将其视为握手失败handshake = -1;} else {handshake = socket.handshake(key.isReadable(), key.isWritable());// 握手过程从套接字读取写入。因此,握手完成后,状态可能会OPEN_WRITE。// 但是,握手发生在打开套接字时,因此在完成后必须始终OPEN_READ状态// 始终设置此选项是可以的,因为它仅在握手完成时使用。event = SocketEvent.OPEN_READ;}}} catch (IOException x) {handshake = -1;...} catch (CancelledKeyException ckx) {handshake = -1;}if (handshake == 0) {// 处理握手完成或者不需要握手的情况SocketState state = SocketState.OPEN;// 处理来自此套接字的请求if (event == null) {state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);} else {// 核心内容,调用process方法处理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();}} catch (CancelledKeyException cx) {// 出现异常,取消掉此事件socket.getPoller().cancelledKey(key);}...
}

2、ConnectionHandler连接处理器

  上一节中核心方法getHandler().process(socketWrapper, event),getHandler()即为获取连接处理器,在上一章节Tomcat源码解析(六):Connector、ProtocolHandler、Endpoint中创建Http11NioProtocol的父类AbstractHttp11Protocol构造中创建的连接处理器ConnectionHandler。

// AbstractProtocol的内部类ConnectionHandler的方法
private final Map<S,Processor> connections = new ConcurrentHashMap<>();@Override
public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {// 删除了很多代码,只保留主要内容...// NioChannelS socket = wrapper.getSocket();// 调用Http11NioProtocol的createProcessor()创建Http11Processorprocessor = getProtocol().createProcessor();// 核心方法:调用Http11Processor的process方法state = processor.process(wrapper, status);...
}

2.1、Http11Processor的创建(包括连接器Req和Res的实例化)

  连接处理器ConnectionHandler调用process实际就是调用Processor的process方法。(Processor是接口,实现类有Http11Processor和AjpProcessor,这里为了屏蔽不同模型的差异。我们这里通过Http11NioProtocol类创建的是Http11Processor

// AbstractHttp11Protocol类方法
@Override
protected Processor createProcessor() {Http11Processor processor = new Http11Processor(getMaxHttpHeaderSize(),getAllowHostHeaderMismatch(), getRejectIllegalHeaderName(), getEndpoint(),getMaxTrailerSize(), allowedTrailerHeaders, getMaxExtensionSize(),getMaxSwallowSize(), httpUpgradeProtocols, getSendReasonPhrase(),relaxedPathChars, relaxedQueryChars);// CoyoteAdapter在Connector初始化时候创建// 作用是将连接器中的request和response转化为容器中的request和response,然后调用Servelt方法processor.setAdapter(getAdapter());processor.setMaxKeepAliveRequests(getMaxKeepAliveRequests());processor.setConnectionUploadTimeout(getConnectionUploadTimeout());processor.setDisableUploadTimeout(getDisableUploadTimeout());processor.setCompressionMinSize(getCompressionMinSize());processor.setCompression(getCompression());processor.setNoCompressionUserAgents(getNoCompressionUserAgents());processor.setCompressibleMimeTypes(getCompressibleMimeTypes());processor.setRestrictedUserAgents(getRestrictedUserAgents());processor.setMaxSavePostSize(getMaxSavePostSize());processor.setServer(getServer());processor.setServerRemoveAppProvidedValues(getServerRemoveAppProvidedValues());return processor;
}
  • 创建Http11NioProtocol时候实例化了RequestResponse对象
    • org.apache.coyote.Request
    • org.apache.coyote.Response
  • 这两个对象是连接器的Req和Res,后续会通过Adapter转化为容器Req和Res(即Servelt中的Request和Response)
// AbstractHttp11Protocol类方法
@Override
protected Processor createProcessor() {Http11Processor processor = new Http11Processor(getMaxHttpHeaderSize(),getAllowHostHeaderMismatch(), getRejectIllegalHeaderName(), getEndpoint(),getMaxTrailerSize(), allowedTrailerHeaders, getMaxExtensionSize(),getMaxSwallowSize(), httpUpgradeProtocols, getSendReasonPhrase(),relaxedPathChars, relaxedQueryChars);processor.setAdapter(getAdapter());processor.setMaxKeepAliveRequests(getMaxKeepAliveRequests());processor.setConnectionUploadTimeout(getConnectionUploadTimeout());processor.setDisableUploadTimeout(getDisableUploadTimeout());processor.setCompressionMinSize(getCompressionMinSize());processor.setCompression(getCompression());processor.setNoCompressionUserAgents(getNoCompressionUserAgents());processor.setCompressibleMimeTypes(getCompressibleMimeTypes());processor.setRestrictedUserAgents(getRestrictedUserAgents());processor.setMaxSavePostSize(getMaxSavePostSize());processor.setServer(getServer());processor.setServerRemoveAppProvidedValues(getServerRemoveAppProvidedValues());return processor;
}
  • Http11Processor构造方法
  • Http11InputBuffer这个类中的一个属性byteBuffer,会从NioChannel中读取到所有的请求数据,设置到连接器req中,那么req也能拿到所有的请求数据(后面会讲到,讲到后面就呼应上了)
public Http11Processor(int maxHttpHeaderSize, boolean allowHostHeaderMismatch,boolean rejectIllegalHeaderName, AbstractEndpoint<?> endpoint, int maxTrailerSize,Set<String> allowedTrailerHeaders, int maxExtensionSize, int maxSwallowSize,Map<String,UpgradeProtocol> httpUpgradeProtocols, boolean sendReasonPhrase,String relaxedPathChars, String relaxedQueryChars) {super(endpoint);httpParser = new HttpParser(relaxedPathChars, relaxedQueryChars);// Http11InputBuffer这个类中的一个属性byteBuffer// 会从NioChannel中读取到所有的请求数据(后面会讲到)inputBuffer = new Http11InputBuffer(request, maxHttpHeaderSize, rejectIllegalHeaderName, httpParser);// 设置到连接器req中,那么req也能拿到所有的请求数据request.setInputBuffer(inputBuffer);outputBuffer = new Http11OutputBuffer(response, maxHttpHeaderSize, sendReasonPhrase);response.setOutputBuffer(outputBuffer);// Create and add the identity filters.inputBuffer.addFilter(new IdentityInputFilter(maxSwallowSize));outputBuffer.addFilter(new IdentityOutputFilter());...
}
  • Http11Processor父类AbstractProcessor的构造方法,实例化连接器Req和Res
public AbstractProcessor(AbstractEndpoint<?> endpoint) {this(endpoint, new Request(), new Response());
}
package org.apache.coyote;public final class Request {...
}
package org.apache.coyote;public final class Response {...
}

2.2、Http11Processor的process方法

  • 实际调用Http11Processor父类AbstractProcessor的父类AbstractProcessorLight的process方法

在这里插入图片描述

2.2、Http11Processor的service方法

  • 初始化nio操作的16k大小的直接内存ByteBuff缓存区,请求数据都是从这里读取
  • 解析请求行数据,请求类型、请求url、get请求参数
  • 解析请求头数据
  • 使用Adapter适配器将连接器Req和Res转化为容器Req和Res调用Servelt方法
@Override
public SocketState service(SocketWrapperBase<?> socketWrapper)throws IOException {...// 将NioChannel设置到当前对象中(Http11Processor的父类AbstractProcessor)setSocketWrapper(socketWrapper);// 初始化直接内存16k的ByteBuffer缓存区inputBuffer.init(socketWrapper);outputBuffer.init(socketWrapper);// FlagskeepAlive = true;openSocket = false;readComplete = true;boolean keptAlive = false;SendfileState sendfileState = SendfileState.DONE;while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null && sendfileState == SendfileState.DONE && !endpoint.isPaused()) {// Parsing the request headertry {// 解析请求行,请求类型、请求url、get请求参数if (!inputBuffer.parseRequestLine(keptAlive)) {if (inputBuffer.getParsingRequestLinePhase() == -1) {return SocketState.UPGRADING;} else if (handleIncompleteRequestLineRead()) {break;}}if (endpoint.isPaused()) {// 503 - Service unavailableresponse.setStatus(503);} else {keptAlive = true;request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());// 解析请求头数据if (!inputBuffer.parseHeaders()) {openSocket = true;readComplete = false;break;}// 设置读取超时时间if (!disableUploadTimeout) {socketWrapper.setReadTimeout(connectionUploadTimeout);}}} catch (Throwable t) {// ... 抛异常打印日志// 400 - Bad Requestresponse.setStatus(400);}...if (getErrorState().isIoAllowed()) {rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);try {// 请求前的准备,校验主机名和提取端口等内容,就不展开说了prepareRequest();} catch (Throwable t) {// ... 抛异常打印日志// 500 - Internal Server Errorresponse.setStatus(500);}}...// Process the request in the adapterif (getErrorState().isIoAllowed()) {try {rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);// 将请求和响应对象传递给适配器,转化为容器的Req和Res对象调用ServeltgetAdapter().service(request, response);...} catch (Throwable t) {// ... 抛异常打印日志// 500 - Internal Server Errorresponse.setStatus(500);}}...// 文件处理,以后有机会单独将sendfileState = processSendfile(socketWrapper);}
}
  • 初始化byteBuffer缓存区
// Http11InputBuffer类方法
void init(SocketWrapperBase<?> socketWrapper) {wrapper = socketWrapper;wrapper.setAppReadBufHandler(this);int bufLength = headerBufferSize +wrapper.getSocketBufferHandler().getReadBuffer().capacity();if (byteBuffer == null || byteBuffer.capacity() < bufLength) {// 分配16ke直接内存缓冲区byteBuffer = ByteBuffer.allocate(bufLength);byteBuffer.position(0).limit(0);}
}

二、解析请求行数据

在这里插入图片描述

1、解析请求行六个阶段

  • 一阶段:fill方法会从NioChannel通道中读取数据到ByteBuff缓冲区;跳过空行,即解析到\r(回车)或\n(换行)直接跳过
  • 二阶段:解析请求方式,如GET或POST
  • 三阶段:跳过" "(空格)或\t(tab)
  • 四阶段:解析请求url,包括请求url和?后面的参数
  • 五阶段:跳过" "(空格)或\t(tab)
  • 六阶段:解析请求协议,如果HTTP/1.1
boolean parseRequestLine(boolean keptAlive) throws IOException {...// 跳过空行if (parsingRequestLinePhase < 2) {byte chr = 0;do {// Read new bytes if neededif (byteBuffer.position() >= byteBuffer.limit()) {...// fill会从NioChannel通道中读取数据到ByteBuff缓冲区if (!fill(false)) {parsingRequestLinePhase = 1;return false;}...}...chr = byteBuffer.get();char my = (char) chr;System.out.println("解析请求行阶段1(跳过\r或\n): " + my);// 如果解析出\r或\n(回车换行),即一直循环读取} while ((chr == Constants.CR) || (chr == Constants.LF));/**如果解析出不是回车换行,如get请求则上面会打印G,post请求会打印P此时position读取位置想右走了一位,此时将它减1这样下个阶段读取请求方式就能读到GET了,否则只能读到ET*/byteBuffer.position(byteBuffer.position() - 1);parsingRequestLineStart = byteBuffer.position();// 设置为2,进入以下第二个阶段,解析请求方式parsingRequestLinePhase = 2;}if (parsingRequestLinePhase == 2) {boolean space = false;while (!space) {...int pos = byteBuffer.position();byte chr = byteBuffer.get();char my = (char) chr;System.out.println("解析请求行阶段2(请求方式): " + my);if (chr == Constants.SP || chr == Constants.HT) {space = true;// 请求阶段2其实就是解析请求方式,get还是post// 设置请求方式到req中request.method().setBytes(byteBuffer.array(), parsingRequestLineStart,pos - parsingRequestLineStart);}// token内容,暂时不分析 else if (!HttpParser.isToken(chr)) {byteBuffer.position(byteBuffer.position() - 1);request.protocol().setString(Constants.HTTP_11);throw new IllegalArgumentException(sm.getString("iib.invalidmethod"));}}// 设置为3,进入以下第三个阶段,解析空格或/tparsingRequestLinePhase = 3;}if (parsingRequestLinePhase == 3) {boolean space = true;while (space) {...			byte chr = byteBuffer.get();System.out.println("解析请求行阶段3(跳过''或\t): " + (char)chr);if (!(chr == Constants.SP || chr == Constants.HT)) {space = false;byteBuffer.position(byteBuffer.position() - 1);}}parsingRequestLineStart = byteBuffer.position();// 设置为4,进入以下第四个阶段,解析请求urlparsingRequestLinePhase = 4;}if (parsingRequestLinePhase == 4) {int end = 0;// Reading the URIboolean space = false;while (!space) {...			int pos = byteBuffer.position();byte chr = byteBuffer.get();System.out.println("解析请求行阶段4(请求url): " + (char)chr);// 解析到空格和\t结束第四阶段解析if (chr == Constants.SP || chr == Constants.HT) {space = true;end = pos;// 解析到\r和\n结束第四阶段解析} else if (chr == Constants.CR || chr == Constants.LF) {// HTTP/0.9 style requestparsingRequestLineEol = true;space = true;end = pos;// 解析到?结束第四阶段解析} else if (chr == Constants.QUESTION && parsingRequestLineQPos == -1) {parsingRequestLineQPos = pos;} ...}if (parsingRequestLineQPos >= 0) {// 设置请求url?后面的参数到req中request.queryString().setBytes(byteBuffer.array(), parsingRequestLineQPos + 1,end - parsingRequestLineQPos - 1);// 设置请求url到req中request.requestURI().setBytes(byteBuffer.array(), parsingRequestLineStart,parsingRequestLineQPos - parsingRequestLineStart);} else {request.requestURI().setBytes(byteBuffer.array(), parsingRequestLineStart,end - parsingRequestLineStart);}parsingRequestLinePhase = 5;}if (parsingRequestLinePhase == 5) {boolean space = true;while (space) {...			byte chr = byteBuffer.get();System.out.println("解析请求行阶段5(跳过''或\t): " + (char)chr);if (!(chr == Constants.SP || chr == Constants.HT)) {space = false;byteBuffer.position(byteBuffer.position() - 1);}}parsingRequestLineStart = byteBuffer.position();parsingRequestLinePhase = 6;end = 0;}if (parsingRequestLinePhase == 6) {// Reading the protocol// Protocol is always "HTTP/" DIGIT "." DIGITwhile (!parsingRequestLineEol) {...int pos = byteBuffer.position();byte chr = byteBuffer.get();System.out.println("解析请求行阶段6(请求协议): " + (char)chr);if (chr == Constants.CR) {end = pos;} else if (chr == Constants.LF) {if (end == 0) {end = pos;}parsingRequestLineEol = true;} else if (!HttpParser.isHttpProtocol(chr)) {throw new IllegalArgumentException(sm.getString("iib.invalidHttpProtocol"));}}if ((end - parsingRequestLineStart) > 0) {// 设置请求协议到req中request.protocol().setBytes(byteBuffer.array(), parsingRequestLineStart,end - parsingRequestLineStart);} else {request.protocol().setString("");}parsingRequestLine = false;parsingRequestLinePhase = 0;parsingRequestLineEol = false;parsingRequestLineStart = 0;return true;}throw new IllegalStateException("Invalid request line parse phase:" + parsingRequestLinePhase);
}

2、nio读取数据

  • fill方法从NioChannel通道中读取数据到ByteBuff缓冲区
  • 读取了请求所有数据,包括请求方式、请求url及参数、请求头、post方式的json请求体(下面讲如何获取)
// Http11InputBuffer类方法
private boolean fill(boolean block) throws IOException {...// 对缓冲区设置标记byteBuffer.mark();if (byteBuffer.position() < byteBuffer.limit()) {// 设置缓冲区的当前位置byteBuffer.position(byteBuffer.limit());}// 设置缓冲区界限byteBuffer.limit(byteBuffer.capacity());// 通过NioChannel通道读取数据到ByteBuffer中int nRead = wrapper.read(block, byteBuffer);// 将位置 position 转到以前设置的mark 所在的位置byteBuffer.limit(byteBuffer.position()).reset();...
}

三、解析请求头数据

在这里插入图片描述

1、解析并校验每个请求头

// Http11InputBuffer类方法
boolean parseHeaders() throws IOException {...do {// 解析没个请求头name和valuestatus = parseHeader();// 校验每个请求头大小等if (byteBuffer.position() > headerBufferSize || byteBuffer.capacity() - byteBuffer.position() < socketReadBufferSize) {throw new IllegalArgumentException(sm.getString("iib.requestheadertoolarge.error"));}} while (status == HeaderParseStatus.HAVE_MORE_HEADERS);
}
  • 主要内容就是解析请求头的name和value,然后设置到req中
private HeaderParseStatus parseHeader() throws IOException {// 跳过空行byte chr = 0;while (headerParsePos == HeaderParsePosition.HEADER_START) {...		chr = byteBuffer.get();System.out.println("解析请求头(跳过/r(回车)): "+ (char)chr);if (chr == Constants.CR) {// Skip} else if (chr == Constants.LF) {return HeaderParseStatus.DONE;} else {byteBuffer.position(byteBuffer.position() - 1);break;}}...// 解析请求头namewhile (headerParsePos == HeaderParsePosition.HEADER_NAME) {...int pos = byteBuffer.position();chr = byteBuffer.get();System.out.println("解析请求头name: "+ (char)chr);if (chr == Constants.COLON) {headerParsePos = HeaderParsePosition.HEADER_VALUE_START;// 将请求头name添加到headerValue对象中headerData.headerValue = headers.addValue(byteBuffer.array(), headerData.start,pos - headerData.start);pos = byteBuffer.position();// Mark the current buffer positionheaderData.start = pos;headerData.realPos = pos;headerData.lastSignificantChar = pos;break;} else if (!HttpParser.isToken(chr)) {// token内容略过}// 字母A~Z转化为小写if ((chr >= Constants.A) && (chr <= Constants.Z)) {byteBuffer.put(pos, (byte) (chr - Constants.LC_OFFSET));}}...// 解析请求头valuewhile (headerParsePos == HeaderParsePosition.HEADER_VALUE_START ||headerParsePos == HeaderParsePosition.HEADER_VALUE ||headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) {if (headerParsePos == HeaderParsePosition.HEADER_VALUE_START) {// Skipping spaceswhile (true) {...chr = byteBuffer.get();System.out.println("解析请求头跳过' '(空格)和/t(tab): "+ (char)chr);if (!(chr == Constants.SP || chr == Constants.HT)) {headerParsePos = HeaderParsePosition.HEADER_VALUE;byteBuffer.position(byteBuffer.position() - 1);break;}}}if (headerParsePos == HeaderParsePosition.HEADER_VALUE) {// Reading bytes until the end of the lineboolean eol = false;while (!eol) {...chr = byteBuffer.get();System.out.println("解析请求头value: "+ (char)chr);if (chr == Constants.CR) {// Skip} else if (chr == Constants.LF) {eol = true;} else if (chr == Constants.SP || chr == Constants.HT) {byteBuffer.put(headerData.realPos, chr);headerData.realPos++;} else {byteBuffer.put(headerData.realPos, chr);headerData.realPos++;headerData.lastSignificantChar = headerData.realPos;}}...}...}// 设置请求头的值,上面已经给headerValue设置过nameheaderData.headerValue.setBytes(byteBuffer.array(), headerData.start,headerData.lastSignificantChar - headerData.start);headerData.recycle();return HeaderParseStatus.HAVE_MORE_HEADERS;
}

  以上解析请求行和请求头,都将解析出的数据连接器的Request中。
  Http11Processor构造方法中创建了Http11InputBuffer,而从NioChannel通道中读取数据到都放到ByteBuff缓冲区byteBuffer,创建Http11Processor中有提到,Http11Processor和连接器Req都能获取到它,这里包含了所有的请求数据目前请求行和请求头数据已经解析出来放到连接器的Request中,byteBuffer剩下的内容就是post请求体内容,这里Tomcat没有解析出放到某个属性下,而是需要我们自己去解析,后面会如何获取。

四、适配器转化Request和Response

在这里插入图片描述

// CoyoteAdapter类方法
@Override
public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)throws Exception {Request request = (Request) req.getNote(ADAPTER_NOTES);Response response = (Response) res.getNote(ADAPTER_NOTES);if (request == null) {// 创建容器Requestrequest = connector.createRequest();request.setCoyoteRequest(req);// 创建容器Responseresponse = connector.createResponse();response.setCoyoteResponse(res);// 容器Req和Res互相设置,你总有我,我中有你request.setResponse(response);response.setRequest(request);// 将容器Req和Res添加到连接器req和res的Object notes[]中// 下次请求直接获取,不需要创建容器Req和Resreq.setNote(ADAPTER_NOTES, request);res.setNote(ADAPTER_NOTES, response);// 设置请求参数编码req.getParameters().setQueryStringCharset(connector.getURICharset());}if (connector.getXpoweredBy()) {response.addHeader("X-Powered-By", POWERED_BY);}boolean async = false;boolean postParseSuccess = false;// 设置工作线程名称:http-nio-8080-exec-1req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());try {// 解析请求后的处理postParseSuccess = postParseRequest(req, request, res, response);if (postParseSuccess) {//check valves if we support asyncrequest.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());// Calling the container(调用容器)connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);}...} catch (IOException e) {// Ignore} finally {...}
}

1、创建容器Req和Res

  • 容器Request
// Connector类方法
public Request createRequest() {Request request = new Request();request.setConnector(this);return (request);
}
package org.apache.catalina.connector;public class Request implements org.apache.catalina.servlet4preview.http.HttpServletRequest {.../*** 连接器Request*/protected org.apache.coyote.Request coyoteRequest;...
}
  • 容器Response
// Connector类方法
public Response createResponse() {Response response = new Response();response.setConnector(this);return (response);
}
package org.apache.catalina.connector;public class Response implements HttpServletResponse {.../*** 连接器Response*/protected org.apache.coyote.Response coyoteResponse;...
}

2、解析请求后的处理

在这里插入图片描述

  • 如果没有设置端口,https端口为443http为80
  • 获取sessionId,即jsessionid为key的参数,设置到Request中
// CoyoteAdapter类方法
protected boolean postParseRequest(org.apache.coyote.Request req, Request request,org.apache.coyote.Response res, Response response) throws IOException, ServletException {...String proxyName = connector.getProxyName();int proxyPort = connector.getProxyPort();if (proxyPort != 0) {req.setServerPort(proxyPort);} else if (req.getServerPort() == -1) {// 如果没有设置端口,https端口为443,http为80if (req.scheme().equals("https")) {req.setServerPort(443);} else {req.setServerPort(80);}}if (proxyName != null) {req.serverName().setString(proxyName);}MessageBytes undecodedURI = req.requestURI();// Check for ping OPTIONS * request// 对于跨越的预检请求,设置响应头if (undecodedURI.equals("*")) {if (req.method().equalsIgnoreCase("OPTIONS")) {StringBuilder allow = new StringBuilder();allow.append("GET, HEAD, POST, PUT, DELETE");// Trace if allowedif (connector.getAllowTrace()) {allow.append(", TRACE");}// Always allow optionsallow.append(", OPTIONS");res.setHeader("Allow", allow.toString());// Access log entry as processing won't reach AccessLogValveconnector.getService().getContainer().logAccess(request, response, 0, true);return false;} else {response.sendError(400, "Invalid URI");}}// 解析初始化参数,略过boolean mapRequired = true;while (mapRequired) {...String sessionID;if (request.getServletContext().getEffectiveSessionTrackingModes().contains(SessionTrackingMode.URL)) {// 获取sessionId,即jsessionid为key的参数sessionID = request.getPathParameter(SessionConfig.getSessionUriParamName(request.getContext()));if (sessionID != null) {// 如果存在添加到request中request.setRequestedSessionId(sessionID);request.setRequestedSessionURL(true);}}// 解析cookie中的sessionIdparseSessionCookiesId(request);parseSessionSslId(request);...}...return true;
}

五、获取get和post请求数据

  在解析请求行数据和请求头数据的源码中,我都添加了字节读取的日志,下面分别对get和post请求做下测试。

1、GET请求

get请求示例

在这里插入图片描述

请求行打印日志

  • 请求方式:GET
  • 请求url:/springmvc/servletTomcat?a=1&b=2
  • 请求协议:HTTP/1.1
解析请求行阶段1(跳过\r或\n): G
解析请求行阶段2(请求方式): G
解析请求行阶段2(请求方式): E
解析请求行阶段2(请求方式): T
解析请求行阶段2(请求方式):  
解析请求行阶段3(跳过''或\t): /
解析请求行阶段4(请求url): /
解析请求行阶段4(请求url): s
解析请求行阶段4(请求url): p
解析请求行阶段4(请求url): r
解析请求行阶段4(请求url): i
解析请求行阶段4(请求url): n
解析请求行阶段4(请求url): g
解析请求行阶段4(请求url): m
解析请求行阶段4(请求url): v
解析请求行阶段4(请求url): c
解析请求行阶段4(请求url): /
解析请求行阶段4(请求url): s
解析请求行阶段4(请求url): e
解析请求行阶段4(请求url): r
解析请求行阶段4(请求url): v
解析请求行阶段4(请求url): l
解析请求行阶段4(请求url): e
解析请求行阶段4(请求url): t
解析请求行阶段4(请求url): T
解析请求行阶段4(请求url): o
解析请求行阶段4(请求url): m
解析请求行阶段4(请求url): c
解析请求行阶段4(请求url): a
解析请求行阶段4(请求url): t
解析请求行阶段4(请求url): ?
解析请求行阶段4(请求url): a
解析请求行阶段4(请求url): =
解析请求行阶段4(请求url): 1
解析请求行阶段4(请求url): &
解析请求行阶段4(请求url): b
解析请求行阶段4(请求url): =
解析请求行阶段4(请求url): 2
解析请求行阶段4(请求url):  
解析请求行阶段5(''或	): H
解析请求行阶段6(请求协议): H
解析请求行阶段6(请求协议): T
解析请求行阶段6(请求协议): T
解析请求行阶段6(请求协议): P
解析请求行阶段6(请求协议): /
解析请求行阶段6(请求协议): 1
解析请求行阶段6(请求协议): .
解析请求行阶段6(请求协议): 1
解析请求行阶段6(请求协议): 
解析请求行阶段6(请求协议): 

请求头打印日志

  • Accept-Charset:utf-8
  • Date:2024-10-10
解析请求头(跳过/r(回车)): A
解析请求头key: A
解析请求头key: c
解析请求头key: c
解析请求头key: e
解析请求头key: p
解析请求头key: t
解析请求头key: -
解析请求头key: C
解析请求头key: h
解析请求头key: a
解析请求头key: r
解析请求头key: s
解析请求头key: e
解析请求头key: t
解析请求头key: :
解析请求头跳过' '(空格)/t(tab):  
解析请求头跳过' '(空格)/t(tab): u
解析请求头value: u
解析请求头value: t
解析请求头value: f
解析请求头value: -
解析请求头value: 8
解析请求头value: 
解析请求头value: 解析请求头(跳过/r(回车)): D
解析请求头key: D
解析请求头key: a
解析请求头key: t
解析请求头key: e
解析请求头key: :
解析请求头跳过' '(空格)/t(tab):  
解析请求头跳过' '(空格)/t(tab): 2
解析请求头value: 2
解析请求头value: 0
解析请求头value: 2
解析请求头value: 4
解析请求头value: -
解析请求头value: 1
解析请求头value: 0
解析请求头value: -
解析请求头value: 1
解析请求头value: 0
解析请求头value: 
解析请求头value: 

2、POST请求

post请求示例

在这里插入图片描述

// post请求获取请求体方式
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String json = IOUtils.toString(req.getInputStream());System.out.println("servletTomcat==>doPost:" + json);
}

请求行打印日志

  • 请求方式:POST
  • 请求url:/springmvc/servletTomcat
  • 请求协议:HTTP/1.1
解析请求行阶段1(跳过\r或\n): P
解析请求行阶段2(请求方式): P
解析请求行阶段2(请求方式): O
解析请求行阶段2(请求方式): S
解析请求行阶段2(请求方式): T
解析请求行阶段2(请求方式):  
解析请求行阶段3(跳过''或\t): /
解析请求行阶段4(请求url): /
解析请求行阶段4(请求url): s
解析请求行阶段4(请求url): p
解析请求行阶段4(请求url): r
解析请求行阶段4(请求url): i
解析请求行阶段4(请求url): n
解析请求行阶段4(请求url): g
解析请求行阶段4(请求url): m
解析请求行阶段4(请求url): v
解析请求行阶段4(请求url): c
解析请求行阶段4(请求url): /
解析请求行阶段4(请求url): s
解析请求行阶段4(请求url): e
解析请求行阶段4(请求url): r
解析请求行阶段4(请求url): v
解析请求行阶段4(请求url): l
解析请求行阶段4(请求url): e
解析请求行阶段4(请求url): t
解析请求行阶段4(请求url): T
解析请求行阶段4(请求url): o
解析请求行阶段4(请求url): m
解析请求行阶段4(请求url): c
解析请求行阶段4(请求url): a
解析请求行阶段4(请求url): t
解析请求行阶段4(请求url):  
解析请求行阶段5(''或	): H
解析请求行阶段6(请求协议): H
解析请求行阶段6(请求协议): T
解析请求行阶段6(请求协议): T
解析请求行阶段6(请求协议): P
解析请求行阶段6(请求协议): /
解析请求行阶段6(请求协议): 1
解析请求行阶段6(请求协议): .
解析请求行阶段6(请求协议): 1
解析请求行阶段6(请求协议): 
解析请求行阶段6(请求协议): 

请求头打印日志

  • 自动添加的请求头有很多,我只挑两个展示出
  • Content-Type:application/json
  • Accept:*/*
解析请求头(跳过/r(回车)): C
解析请求头key: C
解析请求头key: o
解析请求头key: n
解析请求头key: t
解析请求头key: e
解析请求头key: n
解析请求头key: t
解析请求头key: -
解析请求头key: T
解析请求头key: y
解析请求头key: p
解析请求头key: e
解析请求头key: :
解析请求头跳过' '(空格)/t(tab):  
解析请求头跳过' '(空格)/t(tab): a
解析请求头value: a
解析请求头value: p
解析请求头value: p
解析请求头value: l
解析请求头value: i
解析请求头value: c
解析请求头value: a
解析请求头value: t
解析请求头value: i
解析请求头value: o
解析请求头value: n
解析请求头value: /
解析请求头value: j
解析请求头value: s
解析请求头value: o
解析请求头value: n
解析请求头value: 
解析请求头value: 解析请求头(跳过/r(回车)): A
解析请求头key: A
解析请求头key: c
解析请求头key: c
解析请求头key: e
解析请求头key: p
解析请求头key: t
解析请求头key: :
解析请求头跳过' '(空格)/t(tab):  
解析请求头跳过' '(空格)/t(tab): *
解析请求头value: *
解析请求头value: /
解析请求头value: *
解析请求头value: 
解析请求头value: 

2.1、获取json请求体源码

  • 核心代码:req.getInputStream().read()
// CoyoteInputStream类方法
@Override
public int read() throws IOException {checkNonBlockingRead();if (SecurityUtil.isPackageProtectionEnabled()) {...} else {return ib.readByte();}
}
  • 进入readByte方法,每次请求,都会将连接器Req下的byteBuffer赋值给bb
// InputBuffer类方法private ByteBuffer bb;public int readByte() throws IOException {if (closed) {throw new IOException(sm.getString("inputBuffer.streamClosed"));}// 每次请求,都会将连接器Req下的byteBuffer赋值给bb// 连接器Req下的byteBuffer是读取NioChannel通道的所有请求数据// 请求行,请求头数据已经获取完,游标的下个位置就是请求体了if (checkByteBufferEof()) {return -1;}return bb.get() & 0xFF;
}

总结

  • Nio通过NioChannel将请求数据读取到ByteBuffer缓冲区中
    • 先解析请求行,包括请求方式、请求url、请求协议
    • 再解析请求头的name和value
    • 解析都是通过byte chr = byteBuffer.get();每个字节逐一解析的
  • org.apache.coyote.Requestorg.apache.catalina.connector.Request区别
    • org.apache.coyote.Request 是Tomcat连接器(Connector)组件中使用的请求对象,它位于Tomcat的底层,是处理网络协议的底层对象,例如HTTP
    • org.apache.catalina.connector.Request 是Tomcat容器(Container)组件中使用的请求对象,它是针对Web应用的,封装了HTTP请求的详细信息,如请求行、请求头、请求体等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/16672.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

最大连续1的个数(滑动窗口)

算法原理&#xff1a; 这道题大眼一看是关于翻转多少个0的问题&#xff0c;但是&#xff0c;如果你按照这种思维去做题&#xff0c;肯定不容易。所以我们要换一种思维去做&#xff0c;这种思维不是一下就能想到的&#xff0c;所以想不到也情有可原。 题目是&#xff1a;给定一…

Vue3:动态路由+子页面(新增、详情页)动态路由配置(代码全注释)

文章目录 实现思路调用后端接口获取用户权限获取页面权限动态绑定到路由对象中动态添加子页面路由 实现思路 emm&#xff0c;项目中使用动态路由实现根据后端返回的用户详情信息&#xff0c;动态将该用户能够访问的页面信息&#xff0c;动态生成并且绑定到路由对象中。但是后…

如何从清空的回收站中恢复已删除的Excel文件?

“嗨&#xff0c;几天前我删除了很多没有备份的Excel文件。回收站已清空。当我意识到我犯了一个大错误时&#xff0c;所有的Excel文件都消失了&#xff0c;回收站里什么都没有。清空回收站后是否可以恢复已删除的 Excel 文件&#xff1f; 回收站是一种工具&#xff0c;可让您在…

【openlayers系统学习】4.2Mapbox 样式渲染图层

二、Mapbox 样式渲染图层 显然我们目前的地图需要一些样式。 VectorTile​ 图层的样式与 Vector​ 图层的样式工作方式完全相同。那里描述的样式在这里也适用。 对于这样的地图&#xff0c;创建数据驱动的样式&#xff08;对矢量图层操作&#xff09;非常简单。但矢量切片也用…

单兵组网设备+指挥中心:集群系统技术详解

一、单兵设备功能特点 单兵组网设备是现代通信技术的重要成果&#xff0c;旨在为单个作战或工作单元提供高效的通信和数据传输能力。其主要功能特点包括&#xff1a; 1. 便携性&#xff1a;设备轻巧&#xff0c;便于单兵携带和使用&#xff0c;适应各种复杂环境。 2. 通信能…

PHP之fastadmin系统配置分组增加配置和使用

目录 一、实现功能&#xff1a;fasttadmin实现添加系统配置分组和添加参数、使用 二、添加分组 三、配置分组参数 四、最终存储位置 五、获取配置参数 一、实现功能&#xff1a;fasttadmin实现添加系统配置分组和添加参数、使用 二、添加分组 在字典配置中找到分组对应键值…

linux系统——top资源管理器

在linux系统中&#xff0c;有类似于windows系统中的资源管理器&#xff0c;top用于实时的监控系统的任务执行状态以及硬件配置信息 在linux中&#xff0c;输入top命令&#xff0c;可以进入相应界面&#xff0c;在此界面可以使用一些指令进行操作 如&#xff0c;输入z 可以改变…

终端安全管理系统、天锐DLP(数据泄露防护系统)| 数据透明加密保护,防止外泄!

终端作为企业员工日常办公、数据处理和信息交流的关键工具&#xff0c;承载着企业运营的核心信息资产。一旦终端安全受到威胁&#xff0c;企业的敏感数据将面临泄露风险&#xff0c;业务流程可能遭受中断&#xff0c;甚至整个企业的运营稳定性都会受到严重影响。 因此&#xff…

【EVI】Hume AI 初探

写在前面的话 Hume AI宣布已在B轮融资中筹集5000万美元&#xff0c;由前Google DeepMind研究员Alan Cowen创立并担任CEO。该AI模型专注于理解人类情感&#xff0c;并发布了「共情语音界面」演示&#xff0c;通过语音对话实现互动。从 Hume AI 官网展示的信息&#xff0c;EVI 能…

力扣刷题--747. 至少是其他数字两倍的最大数【简单】

题目描述 给你一个整数数组 nums &#xff0c;其中总是存在 唯一的 一个最大整数 。 请你找出数组中的最大元素并检查它是否 至少是数组中每个其他数字的两倍 。如果是&#xff0c;则返回 最大元素的下标 &#xff0c;否则返回 -1 。 示例 1&#xff1a; 输入&#xff1a;n…

Python-opencv通过距离变换提取图像骨骼

文章目录 距离变换distanceTransform函数 距离变换 如果把二值图像理解成地形&#xff0c;黑色表示海洋&#xff0c;白色表示陆地&#xff0c;那么陆地上任意一点&#xff0c;到海洋都有一个最近的距离&#xff0c;如下图所示&#xff0c;对于左侧二值图像来说&#xff0c;【d…

漂流瓶挂机项目,聊天脚本赚钱新玩法,号称单机30-50+ (教程+软件)

一、项目简介&#xff1a; 漂流瓶挂机项目主要是通过使用探遇漂流瓶、音麦漂流瓶等聊天软件&#xff0c;为用户提供一个聊天赚钱的平台。男性用户需要充值后才能发送消息&#xff0c;而女性用户则可以通过接收消息赚取分红。男性用户发送给女性用户的消息费用大约在.1-.2元之间…

VScode中对git的学习笔记

1.git是什么&#xff1f; Git是一个功能强大的分布式版本控制系统&#xff0c;由Linux内核的创始人Linus Torvalds在2005年创建。它以其速度、数据完整性和支持大型项目的能力而闻名&#xff0c;被广泛应用于软件开发中。Git允许开发者在本地机器上拥有完整的代码库副本&#x…

SashulinMessageBroker:在消息流中调用C++ DLL

一、背景 在现实应用中&#xff0c;算法、核心逻辑为了追求快速高效的运行速度&#xff0c;很多人都采用C来编写&#xff0c;并打包成动态库供外部使用。SMB针对这种应用场景&#xff0c;提供了DLL组件&#xff0c;实现在消息流中对DLL的动态调用。下实例讲解如何实现DLL as S…

多旋翼+发电机:国债应急系留照明无人机技术详解

多旋翼发电机技术的应急系留照明无人机是一种集成了先进飞行技术、发电技术和照明技术的无人机系统。这种无人机具有高度的灵活性、移动性和适应性&#xff0c;能够在各种复杂环境下迅速部署&#xff0c;为夜间搜救、救援等应急任务提供高效、可靠的照明支持。 无人机参数&…

融汇11款AI工具构建完美应用

本文将为您介绍25个开源项目&#xff0c;分为上下两篇以便您融汇它们来制作自己的AI应用。人工智能&#xff08;AI&#xff09;应用在近年来得到了长足的发展。从语音助手到软件开发&#xff0c;人工智能已在我们的生活中无处不在&#xff0c;并得到了广泛应用。 如您所见&…

构建智慧城市公共服务系统的功能架构设计

随着城市化进程的加速&#xff0c;城市公共服务系统在保障居民生活品质、提升城市管理水平方面扮演着愈发重要的角色。构建智慧城市公共服务系统的功能架构设计至关重要&#xff0c;它不仅需要充分考虑居民需求与城市管理的实际情况&#xff0c;还需要整合先进的科技手段&#…

LINGO:存贮问题

存贮模型中的基本概念 模型&#xff1a; 基本要素&#xff1a; &#xff08;1&#xff09;需求率&#xff1a;单位时间内对某种物品的需求量&#xff0c;用D表示。 &#xff08;2&#xff09;订货批量&#xff1a;一次订货中&#xff0c;包含某种货物的数量&#xff0c;用 Q表…

【C语言】实现贪吃蛇--项目实践(超详细)

前言&#xff1a; 贪吃蛇游戏大家都玩过吧&#xff1f;这次我们要用C语言来亲手制作一个&#xff01;这个项目不仅能让我们复习C语言的知识&#xff0c;还能了解游戏是怎么一步步做出来的。我们会一起完成蛇的移动、食物的生成&#xff0c;还有碰撞检测等有趣的部分。准备好了…

新计划,不断变更!做自己,接受不美好!猪肝移植——早读(逆天打工人爬取热门微信文章解读)

时间不等人 引言Python 代码第一篇 做自己&#xff0c;没有很好也没关系第二篇结尾 引言 新计划&#xff1a; 早上一次性发几个视频不现实 所以更改一下 待后面有比较稳定的框架再优化 每天早上更新 早到8点 晚到10点 你刚刚好上班或者上课 然后偷瞄的看两眼 学习一下 补充知…