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

news2024/12/23 20:23:12

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;
	...
    @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时候实例化了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);

    // 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端口为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为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.Requestorg.apache.catalina.connector.Request区别
    • org.apache.coyote.Request 是Tomcat连接器(Connector)组件中使用的请求对象,它位于Tomcat的底层,是处理网络协议的底层对象,例如HTTP
    • org.apache.catalina.connector.Request 是Tomcat容器(Container)组件中使用的请求对象,它是针对Web应用的,封装了HTTP请求的详细信息,如请求行、请求头、请求体等

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

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

相关文章

Python使用virtualenv创建虚拟环境

目录 第一步&#xff1a;安装virtualenv 第二步&#xff1a;选择一个文件夹用来放所创建的虚拟环境 第三步&#xff1a;创建虚拟环境 第四步&#xff1a;激活虚拟环境 第五步&#xff1a;退出虚拟环境 第六步&#xff1a;测试安装django 前提&#xff1a;你得有个python环…

价格预言机领导者 Pyth 与 Eclipse 平台集成,为高频 DeFi 应用提供支持

本篇文章将对这一战略合作伙伴关系&#xff0c;以及 Pyth 网络在 Eclipse 生态系统中扮演的关键角色进行深入探讨。 目前&#xff0c;Pyth 价格数据已正式上线于 Eclipse 测试网。Eclipse 是首个结合了以太坊安全性、Solana 性能和 Celestia DA 的 Solana虚拟机(SVM) Layer2 方…

喜讯!宝兰德斩获2024数字中国创新大赛·信创赛道全国总决赛三等奖

5月24日&#xff0c;由国家发展和改革委员会、国家数据局、国家互联网信息办公室、科学技术部、国务院国有资产监督管理委员会和福建省人民政府共同主办的2024数字中国创新大赛信创赛道全国总决赛颁奖典礼暨闭幕式大会在福州海峡国际会展中心圆满落幕。依托专业技术研发能力及丰…

【网络安全】勒索软件ShrinkLocker使用 windows系统安全工具BitLocker实施攻击

文章目录 威胁无不不在BitLocker 概述如何利用BitLocker进行攻击如何降低影响Win11 24H2 装机默认开启 BitLocker推荐阅读 威胁无不不在 网络攻击的形式不断发展&#xff0c;即便是合法的 Windows 安全功能也会成为黑客的攻击工具。 卡巴斯基实验室专家 发现 使用BitLocker的…

路径规划 | 图解粒子群(PSO)算法(附ROS C++仿真)

目录 0 专栏介绍1 从鸟群迁徙说起2 粒子群算法基本概念3 粒子群算法流程4 粒子群算法ROS实现 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细介绍全局规划(图搜索、采样法、智能算法等)&#xff1b;局部规…

使用 Ollama框架 下载和使用 Llama3 AI大模型的完整指南

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;AI大模型部署与应用专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年5月24日20点59分 &#x1f004;️文章质量&#xff1a;96分 目录 &#x1f4a5;Ollama介绍 主要特点 主要优点 应…

Android14之Binder调试(二百一十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【论文笔记】advPattern

【论文题目】 advPattern: Physical-World Attacks on Deep Person Re-Identification via Adversarially Transformable Patterns Abstract 本文首次尝试对深度reID实施鲁棒的物理世界攻击。提出了一种新颖的攻击算法&#xff0c;称为advPattern&#xff0c;用于在衣服上生成…

java “错误:编码GBK 的不可映射字符”

环境&#xff1a;JDK-17 本机编码&#xff1a;utf-8 代码编码&#xff1a;GBK 错误&#xff1a;java “错误&#xff1a;编码GBK 的不可映射字符” 解决1&#xff1a;记事本打开java源文件&#xff0c;另存为选择ANSI编码 解决2&#xff1a;复制代码再将编码格式改为utf-8,…

STM32无源蜂鸣器播放音乐

开发板&#xff1a;野火霸天虎V2 单片机&#xff1a;STM32F407ZGT6 开发软件&#xff1a;MDKSTM32CubeMX 文章目录 前言一、找一篇音乐的简谱二、确定音调三、确定节拍四、使用STM32CubeMX生成初始化代码五、代码分析 前言 本实验使用的是低电平触发的无源蜂鸣器 无源蜂鸣器是…

DevExpress WinForms中文教程 - HTML CSS支持的实战应用(二)

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…

VScode中对git的学习笔记

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

基于UDP的TFTP文件传输-实现网盘上传下载功能

数据传输模式&#xff1a;octet(二进制模式) #include<head.h> char* down_up_request(char* buf,char* filename,int rw,int sockfd,struct sockaddr_in in); int download(struct sockaddr_in in,char* filename,char* buf,int sockfd); int upload(struct sockaddr_in…

【深度学习】多层感知器MLP模型对 MNIST 数据集中的手写数字进行分类

本模型是解决一个十分类的问题&#xff1a; 在这之前&#xff0c;可以先看看一个Pytorch的简单的示例&#xff1a; 它展示了如何定义一个简单的神经网络、准备数据、训练模型以及评估模型&#xff1a; import torch import torch.nn as nn import torch.optim as optim …

私有云和多云管理平台 | Cloudpods v3.10.15 正式发布

功能优化 【主机】裸金属详情页增加部分属性信息【监控】优化告警策略&#xff0c;支持同时设置多监控指标【主机】支持透传设备自动探测【主机】LVM 块存储支持快照【监控】简化 Telegraf 容器的挂载点【主机】新建 VMware 支持同时填写备注信息【存储】KVM 支持对接 LVM 存储…

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

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

深入解析内置模块OS:让你的Python代码更懂操作系统

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、OS模块简介与基础应用 二、文件与目录操作详解 三、OS模块的高级应用&#xff1a;双色…

【NumPy】关于numpy.mean()函数,看这一篇文章就够了

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

【竞技宝】西甲:锋霸想和尤文再续前缘,三进宫能否实现?

马竞本赛季的比赛任务已经全部完成,整体来说床单军团的发挥无法让球迷们满意,休赛期球队肯定会有很多补强和调整。根据西班牙媒体报道,球队锋线大将莫拉塔不满自己在球队的出场时间,希望今年夏天离开球队。而西班牙锋霸想要去的球队也已经浮出水面,那就是他曾经效力过的尤文图斯…

【字典树(前缀树) 字符串】2416. 字符串的前缀分数和

本文涉及知识点 字典树&#xff08;前缀树) 字符串 LeetCode 2416. 字符串的前缀分数和 给你一个长度为 n 的数组 words &#xff0c;该数组由 非空 字符串组成。 定义字符串 word 的 分数 等于以 word 作为 前缀 的 words[i] 的数目。 例如&#xff0c;如果 words [“a”,…