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.pswp.cn/bicheng/16672.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/16672.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/16672.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

golang實現mediasoup的tcp服務及channel通道

tcp模塊 定義相關類 Client&#xff1a;表示客戶端連接&#xff0c;包含網絡連接conn、指向服務器的指針Server和Channel指針c。server&#xff1a;表示TCP服務器&#xff0c;包含服務器地址address、TLS配置config以及三個回調函數&#xff1a; onNewClientCallback&#xf…

最大連續1的個數(滑動窗口)

算法原理&#xff1a; 這道題大眼一看是關于翻轉多少個0的問題&#xff0c;但是&#xff0c;如果你按照這種思維去做題&#xff0c;肯定不容易。所以我們要換一種思維去做&#xff0c;這種思維不是一下就能想到的&#xff0c;所以想不到也情有可原。 題目是&#xff1a;給定一…

Vue3:動態路由+子頁面(新增、詳情頁)動態路由配置(代碼全注釋)

文章目錄 實現思路調用后端接口獲取用戶權限獲取頁面權限動態綁定到路由對象中動態添加子頁面路由 實現思路 emm&#xff0c;項目中使用動態路由實現根據后端返回的用戶詳情信息&#xff0c;動態將該用戶能夠訪問的頁面信息&#xff0c;動態生成并且綁定到路由對象中。但是后…

如何從清空的回收站中恢復已刪除的Excel文件?

“嗨&#xff0c;幾天前我刪除了很多沒有備份的Excel文件。回收站已清空。當我意識到我犯了一個大錯誤時&#xff0c;所有的Excel文件都消失了&#xff0c;回收站里什么都沒有。清空回收站后是否可以恢復已刪除的 Excel 文件&#xff1f; 回收站是一種工具&#xff0c;可讓您在…

LeetCode 343. 整數拆分 (dp動態規劃)

343. 整數拆分 力扣題目鏈接(opens new window) 給定一個正整數 n&#xff0c;將其拆分為至少兩個正整數的和&#xff0c;并使這些整數的乘積最大化。 返回你可以獲得的最大乘積。 示例 1: 輸入: 2輸出: 1解釋: 2 1 1, 1 1 1。 示例 2: 輸入: 10輸出: 36解釋: 10 3 …

【openlayers系統學習】4.2Mapbox 樣式渲染圖層

二、Mapbox 樣式渲染圖層 顯然我們目前的地圖需要一些樣式。 VectorTile? 圖層的樣式與 Vector? 圖層的樣式工作方式完全相同。那里描述的樣式在這里也適用。 對于這樣的地圖&#xff0c;創建數據驅動的樣式&#xff08;對矢量圖層操作&#xff09;非常簡單。但矢量切片也用…

單兵組網設備+指揮中心:集群系統技術詳解

一、單兵設備功能特點 單兵組網設備是現代通信技術的重要成果&#xff0c;旨在為單個作戰或工作單元提供高效的通信和數據傳輸能力。其主要功能特點包括&#xff1a; 1. 便攜性&#xff1a;設備輕巧&#xff0c;便于單兵攜帶和使用&#xff0c;適應各種復雜環境。 2. 通信能…

簡述vue-router 組件復用導致路由參數失效怎么辦

當使用Vue Router時&#xff0c;組件復用可能會導致路由參數失效的問題。為了解決這個問題&#xff0c;我們可以采取以下策略&#xff1a; 1. 監聽路由變化 在Vue組件中&#xff0c;我們可以使用watch屬性來監聽$route對象的變化。當路由發生變化時&#xff0c;如果目標組件是…

第 8 章 機器人實體導航實現_路徑規劃(自學二刷筆記)

重要參考&#xff1a; 課程鏈接:https://www.bilibili.com/video/BV1Ci4y1L7ZZ 講義鏈接:Introduction Autolabor-ROS機器人入門課程《ROS理論與實踐》零基礎教程 9.3.5 導航實現05_路徑規劃 路徑規劃仍然使用 navigation 功能包集中的 move_base 功能包。 5.1編寫launch文…

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 能…

計算機視覺與深度學習實戰:以Python為工具,基于深度學習的汽車目標檢測

隨著人工智能技術的飛速發展,計算機視覺與深度學習已經成為當今科技領域的熱點。其中,汽車目標檢測作為自動駕駛、智能交通等系統的核心技術,受到了廣泛關注。本文將以Python為工具,探討基于深度學習的汽車目標檢測方法及其實戰應用。 一、計算機視覺與深度學習基礎 計算機…

力扣刷題--747. 至少是其他數字兩倍的最大數【簡單】

題目描述 給你一個整數數組 nums &#xff0c;其中總是存在 唯一的 一個最大整數 。 請你找出數組中的最大元素并檢查它是否 至少是數組中每個其他數字的兩倍 。如果是&#xff0c;則返回 最大元素的下標 &#xff0c;否則返回 -1 。 示例 1&#xff1a; 輸入&#xff1a;n…

Python-opencv通過距離變換提取圖像骨骼

文章目錄 距離變換distanceTransform函數 距離變換 如果把二值圖像理解成地形&#xff0c;黑色表示海洋&#xff0c;白色表示陸地&#xff0c;那么陸地上任意一點&#xff0c;到海洋都有一個最近的距離&#xff0c;如下圖所示&#xff0c;對于左側二值圖像來說&#xff0c;【d…

Gitee的原理及應用詳解(三)

本系列文章簡介&#xff1a; Gitee是一款開源的代碼托管平臺&#xff0c;是國內最大的代碼托管平臺之一。它基于Git版本控制系統&#xff0c;提供了代碼托管、項目管理、協作開發、代碼審查等功能&#xff0c;方便團隊協作和項目管理。Gitee的出現&#xff0c;在國內的開發者社…

漂流瓶掛機項目,聊天腳本賺錢新玩法,號稱單機30-50+ (教程+軟件)

一、項目簡介&#xff1a; 漂流瓶掛機項目主要是通過使用探遇漂流瓶、音麥漂流瓶等聊天軟件&#xff0c;為用戶提供一個聊天賺錢的平臺。男性用戶需要充值后才能發送消息&#xff0c;而女性用戶則可以通過接收消息賺取分紅。男性用戶發送給女性用戶的消息費用大約在.1-.2元之間…

VScode中對git的學習筆記

1.git是什么&#xff1f; Git是一個功能強大的分布式版本控制系統&#xff0c;由Linux內核的創始人Linus Torvalds在2005年創建。它以其速度、數據完整性和支持大型項目的能力而聞名&#xff0c;被廣泛應用于軟件開發中。Git允許開發者在本地機器上擁有完整的代碼庫副本&#x…

讀書筆記分享

1.蘇格拉底只在需要的時候才索取&#xff0c;那樣便能以最少的物質滿足自身的要求。他認為每個人都天生體質脆弱&#xff0c;只有在貧乏的環境中才會鍛煉地強壯起來。生活中的大多數人認為&#xff0c;奢華才是幸福的生活。無休止的物質積聚&#xff0c;讓人們每天生活在一個內…