Tomcat源码系列文章
Tomcat源码解析(二):Bootstrap和Catalina
Tomcat源码解析(四):StandardServer和StandardService
Tomcat源码解析(五):StandardEngine、StandardHost、StandardContext、StandardWrapper
Tomcat源码解析(六):Connector、ProtocolHandler、Endpoint
Tomcat源码解析(七):底层如何获取请求url、请求头、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;
...
@Override
public 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) {
// 删除了很多代码,只保留主要内容
...
// NioChannel
S socket = wrapper.getSocket();
// 调用Http11NioProtocol的createProcessor()创建Http11Processor
processor = 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时候实例化了
Request
和Response
对象- 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);
// Flags
keepAlive = 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 header
try {
// 解析请求行,请求类型、请求url、get请求参数
if (!inputBuffer.parseRequestLine(keptAlive)) {
if (inputBuffer.getParsingRequestLinePhase() == -1) {
return SocketState.UPGRADING;
} else if (handleIncompleteRequestLineRead()) {
break;
}
}
if (endpoint.isPaused()) {
// 503 - Service unavailable
response.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 Request
response.setStatus(400);
}
...
if (getErrorState().isIoAllowed()) {
rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
try {
// 请求前的准备,校验主机名和提取端口等内容,就不展开说了
prepareRequest();
} catch (Throwable t) {
// ... 抛异常打印日志
// 500 - Internal Server Error
response.setStatus(500);
}
}
...
// Process the request in the adapter
if (getErrorState().isIoAllowed()) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
// 将请求和响应对象传递给适配器,转化为容器的Req和Res对象调用Servelt
getAdapter().service(request, response);
...
} catch (Throwable t) {
// ... 抛异常打印日志
// 500 - Internal Server Error
response.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 needed
if (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,进入以下第三个阶段,解析空格或/t
parsingRequestLinePhase = 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,进入以下第四个阶段,解析请求url
parsingRequestLinePhase = 4;
}
if (parsingRequestLinePhase == 4) {
int end = 0;
// Reading the URI
boolean 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 request
parsingRequestLineEol = 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 "." DIGIT
while (!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和value
status = 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;
}
}
...
// 解析请求头name
while (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 position
headerData.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));
}
}
...
// 解析请求头value
while (headerParsePos == HeaderParsePosition.HEADER_VALUE_START ||
headerParsePos == HeaderParsePosition.HEADER_VALUE ||
headerParsePos == HeaderParsePosition.HEADER_MULTI_LINE) {
if (headerParsePos == HeaderParsePosition.HEADER_VALUE_START) {
// Skipping spaces
while (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 line
boolean 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设置过name
headerData.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) {
// 创建容器Request
request = connector.createRequest();
request.setCoyoteRequest(req);
// 创建容器Response
response = connector.createResponse();
response.setCoyoteResponse(res);
// 容器Req和Res互相设置,你总有我,我中有你
request.setResponse(response);
response.setRequest(request);
// 将容器Req和Res添加到连接器req和res的Object notes[]中
// 下次请求直接获取,不需要创建容器Req和Res
req.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-1
req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
try {
// 解析请求后的处理
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);
}
...
} 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端口为443
,http为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为80
if (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 allowed
if (connector.getAllowTrace()) {
allow.append(", TRACE");
}
// Always allow options
allow.append(", OPTIONS");
res.setHeader("Allow", allow.toString());
// Access log entry as processing won't reach AccessLogValve
connector.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中的sessionId
parseSessionCookiesId(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.Request
和org.apache.catalina.connector.Request
区别- org.apache.coyote.Request 是Tomcat连接器(Connector)组件中使用的请求对象,它位于Tomcat的底层,是处理网络协议的底层对象,例如HTTP
- org.apache.catalina.connector.Request 是Tomcat容器(Container)组件中使用的请求对象,它是针对Web应用的,封装了HTTP请求的详细信息,如请求行、请求头、请求体等