HttpObjectDecoder源码浅析

文章目录

HttpObjectDecoder

固定常量定义

java 复制代码
// 请求行/响应行 最大允许的长度
public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;

// 每个请求头/响应头的最大允许的长度
public static final int DEFAULT_MAX_HEADER_SIZE = 8192;

// 默认支持分块传输, 否则不支持transfer-encoding: chunked 请求头/响应头
public static final boolean DEFAULT_CHUNKED_SUPPORTED = true;

// 是否到达分块大小后, 才封装为1个消息体传给后面的入站处理器
public static final boolean DEFAULT_ALLOW_PARTIAL_CHUNKS = true;

// 最大允许读取的分块大小
public static final int DEFAULT_MAX_CHUNK_SIZE = 8192;

// 是否校验头名字的合法性
public static final boolean DEFAULT_VALIDATE_HEADERS = true;

// 初始化AppendableCharSequence的底层字符数组的长度
public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;

// 允许重复的Content-Length头对应的值,但值必须一样
public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;

成员属性定义

java 复制代码
// 最大允许读取的分块大小
private final int maxChunkSize;

// 是否支持分块传输, 即是否支持transfer-encoding: chunked 请求头/响应头
private final boolean chunkedSupported;

// 是否到达分块大小后, 才封装为1个消息体传给后面的入站处理器
private final boolean allowPartialChunks;

// 是否校验头名字的合法性
protected final boolean validateHeaders;

// 允许重复的Content-Length头对应的值,但值必须一样
private final boolean allowDuplicateContentLengths;

// 头解析器
private final HeaderParser headerParser;

// 行解析器
private final LineParser lineParser;

// 请求行+请求头 / 响应行 + 响应头
private HttpMessage message;

// 当前处理过程中读取到应该要接收到的块大小值
private long chunkSize;

// content-length头的值, 如果存在的话 >= 0
private long contentLength = Long.MIN_VALUE;

// 标记是否需要重置请求
private volatile boolean resetRequested;

// 当前处理过程中正在处理到的请求头
private CharSequence name;

// 当前处理过程中正在处理到的请求头对应的值
private CharSequence value;

// 最后1块http消息体
private LastHttpContent trailer;

// 初始状态为 <跳过控制字符>
private State currentState = State.SKIP_CONTROL_CHARS;

State

java 复制代码
private enum State {
    
    // 跳过控制字符
    SKIP_CONTROL_CHARS,
    
    // 开始读取
    READ_INITIAL,
    
    // 读取头
    READ_HEADER,
    
    // 读取可变长度内容
    READ_VARIABLE_LENGTH_CONTENT,
    
    // 读取固定长度内容
    READ_FIXED_LENGTH_CONTENT,
    
    // 读取块大小
    READ_CHUNK_SIZE,
    
    // 读取块内容
    READ_CHUNKED_CONTENT,
    
    // 读取块分隔符
    READ_CHUNK_DELIMITER,
    
    // 读取块尾部
    READ_CHUNK_FOOTER,
    
    // 错误消息
    BAD_MESSAGE,

    // 协议升级
    UPGRADED
}

HeaderParser

java 复制代码
private static class HeaderParser implements ByteProcessor {
    
    // 可追加字符的字符串实现(就是由1个字符串数据,然后扩容就是加大数组容量后,拷贝原字符数组)
    private final AppendableCharSequence seq;
    
    // 最多允许处理的字符长度
    private final int maxLength;
    
    // 表示当前已经处理了多少个字节
    int size;

    HeaderParser(AppendableCharSequence seq, int maxLength) {
        this.seq = seq;
        this.maxLength = maxLength;
    }

    public AppendableCharSequence parse(ByteBuf buffer) {
        
        // 保存旧的size
        final int oldSize = size;
        
        // 重置seq的position置为0
        seq.reset();
        
        // 逐个字节遍历buffer, 找到目标字符, 就停止遍历, 返回目标字符的索引
        int i = buffer.forEachByte(this);
        
        // 如果没有找到目标字符,恢复旧size,返回null
        if (i == -1) {
            size = oldSize;
            return null;
        }
        
        // 即找到了目标字符
        // buffer的readerIndex向前移动1位
        buffer.readerIndex(i + 1);
        
        // 返回处理后的seq
        return seq;
    }

    public void reset() {
        size = 0;
    }

    @Override
    public boolean process(byte value) throws Exception {
        
        // 0xFF 即 1111 1111
        char nextByte = (char) (value & 0xFF);
        
        // 如果遇到了换行符
        if (nextByte == HttpConstants.LF) {
            
            // seq目前的长度
            int len = seq.length();
            
            // 如果seq的最后面是个回车符
            if (len >= 1 && seq.charAtUnsafe(len - 1) == HttpConstants.CR) {
                // size减1
                -- size;
                // seq长度减1
                seq.setLength(len - 1);
            }
            
            // 返回false, 表示不要再继续遍历了,已经找到了
            return false;
        }

        // 如果未遇到换行符, size+1,但不能超过maxLength
        increaseCount();

        // seq加上这个字节
        seq.append(nextByte);
        
        // 表示需要继续遍历
        return true;
    }

    protected final void increaseCount() {
        if (++ size > maxLength) {
            throw newException(maxLength);
        }
    }

    protected TooLongFrameException newException(int maxLength) {
        return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");
    }
}

LineParser

java 复制代码
private final class LineParser extends HeaderParser {

    LineParser(AppendableCharSequence seq, int maxLength) {
        super(seq, maxLength);
    }

    @Override
    public AppendableCharSequence parse(ByteBuf buffer) {
        // 解析前, 先将size置为0
        reset(); 
        // 沿用父类HeaderParser的逻辑
        return super.parse(buffer);
    }

    @Override
    public boolean process(byte value) throws Exception {
        
        // 如果当前状态是 <跳过控制字符>
        if (currentState == State.SKIP_CONTROL_CHARS) {
            
            char c = (char) (value & 0xFF);
            
            // [\u0000-\u001F] 和 [\u007F-\u009F] 是控制字符
            // \t、\n、\u000B、\f、\r、\u001C、\u001D、\u001E、\u001F 是空白字符
            if (Character.isISOControl(c) || Character.isWhitespace(c)) {
                // size加1
                increaseCount();
                // 跳过 控制字符 和 空白字符
                return true;
            }
            
            // 跳过所有的连续的控制字符 和 空白字符后,或 没有控制字符和空白字符后,
            // 直接切换到下1个状态 <开始读取>
            currentState = State.READ_INITIAL;
        }
        
        // 沿用父类HeaderParser的逻辑, 也是去找换行符
        return super.process(value);
    }

    @Override
    protected TooLongFrameException newException(int maxLength) {
        return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");
    }
}

构造方法

java 复制代码
protected HttpObjectDecoder(
        int maxInitialLineLength, 
    	int maxHeaderSize, 
    	int maxChunkSize,
        boolean chunkedSupported, 
    	boolean validateHeaders, 
    	int initialBufferSize,
        boolean allowDuplicateContentLengths, 
    	boolean allowPartialChunks) {
    
    checkPositive(maxInitialLineLength, "maxInitialLineLength");
    
    checkPositive(maxHeaderSize, "maxHeaderSize");
    
    checkPositive(maxChunkSize, "maxChunkSize");

    // 可追加字符的字符串实现(就是由1个字符串数据,然后扩容就是加大数组容量后,拷贝原字符数组)
    AppendableCharSequence seq = new AppendableCharSequence(initialBufferSize);
    
    // 行解析器(继承自头解析器,区别在于会跳过控制字符和空白字符)
    lineParser = new LineParser(seq, maxInitialLineLength);
    
    // 头解析器(找到LF换行符就获取1个头)
    headerParser = new HeaderParser(seq, maxHeaderSize);
    
    this.maxChunkSize = maxChunkSize;
    
    this.chunkedSupported = chunkedSupported
        
    this.validateHeaders = validateHeaders;
    
    this.allowDuplicateContentLengths = allowDuplicateContentLengths;
    
    this.allowPartialChunks = allowPartialChunks;
}

decode

java 复制代码
@Override
protected void decode(ChannelHandlerContext ctx, 
                      ByteBuf buffer, 
                      List<Object> out) throws Exception {
    
    // 如果需要重置请求
    if (resetRequested) {
        
        // 重置各个属性
        resetNow();
    }

    switch (currentState) {
    case SKIP_CONTROL_CHARS: // 初始状态就是 <跳过控制字符>, 直接进入下一个case(不判断条件)
        // Fall-through
    case READ_INITIAL: try {
        
        // 遍历buffer中的每个字节, 
        // 如果能找到LF换行符, 那么line将不为null, 则可以解析该请求行/响应头
        // 如果未找到LF换行符, 那么line为null, 说明后面的LF还未收到, 则继续接收
        AppendableCharSequence line = lineParser.parse(buffer);
        if (line == null) { // 未找到LF换行符的情况
            // 继续接收, 等待下次处理
            return;
        }
        
        // 拆分http请求行
        String[] initialLine = splitInitialLine(line);
        // 请求行 拆分结果若不足3个,说明有问题,继续恢复当前状态为初始状态,结束当前处理
        if (initialLine.length < 3) { 
            currentState = State.SKIP_CONTROL_CHARS;
            return;
        }

        // 将请求行/响应行封装为HttpMessage, 
        // 请求会被封装为 DefaultHttpRequest
        // 响应会被封装为 DefaultHttpResponse
        message = createMessage(initialLine);
        
        // 读取完请求行/响应行 之后, 就要读取 头信息了,切换当前状态为 读取头信息
        currentState = State.READ_HEADER;
        
        // 直接进入下一个case(不判断条件)

    } catch (Exception e) {
        // 如果读取行信息发生异常,则封装出1个 GET /bad-request http/1.1 请求体为空的DefaultFullHttpRequest添加到out中,给后面的入站处理器处理
        out.add(invalidMessage(buffer, e));
        // 结束当前的处理
        return;
    }
    case READ_HEADER: try { // 读取完行信息后,就开始读取头信息
        
        // 读取请求头信息,并且确定请求体内容的读取方式
        State nextState = readHeaders(buffer);
        
        // 如果头信息还未完全接收完,则等待接收完再处理
        if (nextState == null) {
            return;
        }
        
        // 此时,头信息已接收完毕,根据头信息 切换 当前状态
        currentState = nextState;
        
        switch (nextState) {
                
        // 说明当前请求已经没有内容了, 
        case SKIP_CONTROL_CHARS:
            // 直接将http消息添加到out中,
            out.add(message);
            // 并补上一个LastHttpContent.EMPTY_LAST_CONTENT(这个里面没有trailerHeader)
            out.add(LastHttpContent.EMPTY_LAST_CONTENT);
            // 重置各个属性,因为已经解码完了
            resetNow();
            // 结束当前方法
            return;
                
        // 说明请求方希望通过chunked的方式传输内容
        case READ_CHUNK_SIZE:
             
            // 如果当前不支持chunked的方式传输,则抛出异常
            if (!chunkedSupported) {
                throw new IllegalArgumentException("Chunked messages not supported");
            }
			
            // 基于chunked的方式传输内容,需要先将http消息传给后面的入站处理器,后面再解析分块内容
            // 下次解码,会跑到下面的 case READ_CHUNK_SIZE: 的分支上去哦
            out.add(message);
                
            // 结束当前方法
            return;
        default:
            
            // 那就只剩下 可变长度内容 和 固定长度内容 咯
                
            // 获取content-length头的值
            long contentLength = contentLength();
                
            // 如果content-length是0 或者 content-length是-1并且是请求解码器
            if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
                // 先添加http消息到out中
                out.add(message);
                // 并补上一个LastHttpContent.EMPTY_LAST_CONTENT(这个里面没有trailerHeader)
                out.add(LastHttpContent.EMPTY_LAST_CONTENT);
                // 重置各个属性,因为已经解码完了
                resetNow();
                // 结束当前方法
                return;
            }

            // 因为只剩下这2种情况了,并且消息体是有内容的情况
            assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
                    nextState == State.READ_VARIABLE_LENGTH_CONTENT;

            // 也是先添加http消息到out中
            out.add(message);

            // 如果是 读取 固定长度内容,则将chunkSize 赋值为 contentLength
            if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
                chunkSize = contentLength;
            }
            // 针对剩下的2种情况,都是由chunkSize去决定读取多长的内容
            // 读取内容交给 下次解码
            return;
        }
    } catch (Exception e) {
        // 发生了异常
        // 则封装出1个 GET /bad-request http/1.1 请求体为空的DefaultFullHttpRequest添加到out中,给后面的入站处理器处理out.add(invalidMessage(buffer, e));
        return;
    }
            
    // 
    case READ_VARIABLE_LENGTH_CONTENT: {
        // Keep reading data as a chunk until the end of connection is reached.
        int toRead = Math.min(buffer.readableBytes(), maxChunkSize);
        if (toRead > 0) {
            ByteBuf content = buffer.readRetainedSlice(toRead);
            out.add(new DefaultHttpContent(content));
        }
        return;
    }
    case READ_FIXED_LENGTH_CONTENT: {
        int readLimit = buffer.readableBytes();

        // Check if the buffer is readable first as we use the readable byte count
        // to create the HttpChunk. This is needed as otherwise we may end up with
        // create an HttpChunk instance that contains an empty buffer and so is
        // handled like it is the last HttpChunk.
        //
        // See https://github.com/netty/netty/issues/433
        if (readLimit == 0) {
            return;
        }

        int toRead = Math.min(readLimit, maxChunkSize);
        if (toRead > chunkSize) {
            toRead = (int) chunkSize;
        }
        ByteBuf content = buffer.readRetainedSlice(toRead);
        chunkSize -= toRead;

        if (chunkSize == 0) {
            // Read all content.
            out.add(new DefaultLastHttpContent(content, validateHeaders));
            resetNow();
        } else {
            out.add(new DefaultHttpContent(content));
        }
        return;
    }
    /**
     * everything else after this point takes care of reading chunked content. basically, read chunk size,
     * read chunk, read and ignore the CRLF and repeat until 0
     */
    case READ_CHUNK_SIZE: try {
        AppendableCharSequence line = lineParser.parse(buffer);
        if (line == null) {
            return;
        }
        int chunkSize = getChunkSize(line.toString());
        this.chunkSize = chunkSize;
        if (chunkSize == 0) {
            currentState = State.READ_CHUNK_FOOTER;
            return;
        }
        currentState = State.READ_CHUNKED_CONTENT;
        // fall-through
    } catch (Exception e) {
        out.add(invalidChunk(buffer, e));
        return;
    }
    case READ_CHUNKED_CONTENT: {
        assert chunkSize <= Integer.MAX_VALUE;
        int toRead = Math.min((int) chunkSize, maxChunkSize);
        if (!allowPartialChunks && buffer.readableBytes() < toRead) {
            return;
        }
        toRead = Math.min(toRead, buffer.readableBytes());
        if (toRead == 0) {
            return;
        }
        HttpContent chunk = new DefaultHttpContent(buffer.readRetainedSlice(toRead));
        chunkSize -= toRead;

        out.add(chunk);

        if (chunkSize != 0) {
            return;
        }
        currentState = State.READ_CHUNK_DELIMITER;
        // fall-through
    }
    case READ_CHUNK_DELIMITER: {
        final int wIdx = buffer.writerIndex();
        int rIdx = buffer.readerIndex();
        while (wIdx > rIdx) {
            byte next = buffer.getByte(rIdx++);
            if (next == HttpConstants.LF) {
                currentState = State.READ_CHUNK_SIZE;
                break;
            }
        }
        buffer.readerIndex(rIdx);
        return;
    }
    case READ_CHUNK_FOOTER: try {
        LastHttpContent trailer = readTrailingHeaders(buffer);
        if (trailer == null) {
            return;
        }
        out.add(trailer);
        resetNow();
        return;
    } catch (Exception e) {
        out.add(invalidChunk(buffer, e));
        return;
    }
    case BAD_MESSAGE: {
        // Keep discarding until disconnection.
        buffer.skipBytes(buffer.readableBytes());
        break;
    }
    case UPGRADED: {
        int readableBytes = buffer.readableBytes();
        if (readableBytes > 0) {
            // Keep on consuming as otherwise we may trigger an DecoderException,
            // other handler will replace this codec with the upgraded protocol codec to
            // take the traffic over at some point then.
            // See https://github.com/netty/netty/issues/2173
            out.add(buffer.readBytes(readableBytes));
        }
        break;
    }
    default:
        break;
    }
}

resetNow

java 复制代码
private void resetNow() {
    
    // 获取到当前解码的http消息(请求行/响应头 + 请求头/响应头)
    HttpMessage message = this.message;
    
    // 重置 http消息为null
    this.message = null;
    
    // 重置请求头名为null
    name = null;
    
    // 重置请求头值为null
    value = null;
    
    // 重置content-length为 Long的最小值
    contentLength = Long.MIN_VALUE;
    
    // 将size属性置为0,即处理过的字符数量为0
    lineParser.reset();
    // 将size属性置为0,即处理过的字符数量为0
    headerParser.reset();
    
    // 重置最后一块http消息体为null
    trailer = null;
    
    // 如果当前解码器不是请求解码器,即是响应解码器
    if (!isDecodingRequest()) {
        // 拿到解码到的http响应消息
        HttpResponse res = (HttpResponse) message;
        // 如果res的响应状态码不是101,则返回false
        // 如果res的响应状态码是101,此时,如果upgrade头是null,则返回true,如果upgrade既不是http/1.0,也不是http/1.1,则返回true,其它情况返回false
        if (res != null && isSwitchingToNonHttp1Protocol(res)) {
            // 将当前状态置为升级协议状态
            currentState = State.UPGRADED;
            // 结束方法
            return;
        }
    }

    // 将重置请求标记位置为false
    resetRequested = false;
    
    // 重置当前状态为 跳过控制字符
    currentState = State.SKIP_CONTROL_CHARS;
}

splitInitialLine

java 复制代码
private static String[] splitInitialLine(AppendableCharSequence sb) {
    int aStart;
    int aEnd;
    int bStart;
    int bEnd;
    int cStart;
    int cEnd;
    
    // 请求行 如: GET /uri http/1.1
    // 响应行 如: http/1.1 200 OK

    // 找第1部分
    // 找到不为空格符的字符
    aStart = findNonSPLenient(sb, 0);
    // 找到空格符
    aEnd = findSPLenient(sb, aStart);

    // 找第2部分
    bStart = findNonSPLenient(sb, aEnd);
    bEnd = findSPLenient(sb, bStart);

    // 找第3部分
    cStart = findNonSPLenient(sb, bEnd);
    cEnd = findEndOfString(sb);

    // 返回的数组只会是3个元素
    return new String[] {
            sb.subStringUnsafe(aStart, aEnd),
            sb.subStringUnsafe(bStart, bEnd),
            cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
}

readHeaders

java 复制代码
private State readHeaders(ByteBuf buffer) {
    
    // HttpMessage是包含头信息的, 因为刚刚只读取到了请求行,现在需要补充头信息了
    final HttpMessage message = this.message;
    final HttpHeaders headers = message.headers();

    // 先去尝试找到1个头信息, 如果遇到了LF, 则说明找到了, 如果没遇到LF, 则需要 继续等一直等到LF为止或超过headerParser允许的最大长度为止
    // 如果找到LF, 则至少返回的是空字符串"", 即表示没有任何头信息
    AppendableCharSequence line = headerParser.parse(buffer);
    
    if (line == null) { // 没遇到LF, 则需要 继续等一直等到LF为止
        return null;
    }
    
    // 上面至少找到了1个头信息
    if (line.length() > 0) {
        do {
            
            // 获取当前解析行的第一个字符
            char firstChar = line.charAtUnsafe(0);
            
            if (name != null && (firstChar == ' ' || firstChar == '\t')) {
                // 此处接着上一轮的未接收完的头接着处理,如果第一个字符是 ' ' 或 \t
                // 则将上次的值 拼接上1个' ' 加上当前的这 一行
                String trimmedLine = line.toString().trim();
                String valueStr = String.valueOf(value);
                value = valueStr + ' ' + trimmedLine;
            } else {
                
                if (name != null) {
                    // 此处接着上一轮的未接收完的头接着处理, 直接添加上一轮的头到headers中
                    headers.add(name, value);
                }
                
                // 通过冒号":"分割 头的名字 和 头的值
                splitHeader(line);
            }

            // 又去尝试找下一行的头信息, 如果下一行的头信息 的 LF还没接收到的话, 则继续等接收到后,再继续处理
            line = headerParser.parse(buffer);
            
            // LF还没接收到的话, 则继续等接收到后,再继续处理
            // 或者是请求头的下一行直接就是\r\n,表示请求头结束了, 此时的line就是空字符串,line的长度就为0了,直接跳出循环,由下面的步骤添加最后解析的头
            if (line == null) { 
                return null;
            }
            
        } while (line.length() > 0);
    }

	// 添加上最后解析的头
    if (name != null) {
        headers.add(name, value);
    }

	// 头已经解析完了,将name和value重置为null
    name = null;
    value = null;

	// 行 和 头 信息已经解析过了,创建1个HttpMessageDecoderResult,它继承了DecoderResult
    // 表示解码成功 SIGNAL_SUCCESS
    HttpMessageDecoderResult decoderResult = new HttpMessageDecoderResult(lineParser.size, headerParser.size);
    // 将 解码成功的结果 设置给 HttpMessage
    message.setDecoderResult(decoderResult);

    // 获取到Content-Length头对应的值
    List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
    
    // 如果Content-Length头的值存在
    if (!contentLengthFields.isEmpty()) {
        
        // http协议版本 是否为 http/1.0及之前的版本
        HttpVersion version = message.protocolVersion();
        boolean isHttp10OrEarlier = version.majorVersion() < 1 
            || (version.majorVersion() == 1 && version.minorVersion() == 0);
        
        // 获取content-length头对应的值
        contentLength = HttpUtil.normalizeAndGetContentLength(contentLengthFields,
                isHttp10OrEarlier, allowDuplicateContentLengths);
        
        // 设置content-length头对应的值
        if (contentLength != -1) {
            headers.set(HttpHeaderNames.CONTENT_LENGTH, contentLength);
        }
    }

    // 判断内容是不是一定是空的
    if (isContentAlwaysEmpty(message)) {
        
        // 如果 内容是空的,移除掉transfer-encoding中的chunked值
        HttpUtil.setTransferEncodingChunked(message, false);
        
        // 当前状态修改为 <跳过控制字符>
        return State.SKIP_CONTROL_CHARS;
        
    } 
    // 当前Http消息是否包含值为chunked 的transfer-encoding头
    else if (HttpUtil.isTransferEncodingChunked(message)) {
        
        // 如果同时也存在content-length头,并且协议版本是http/1.1 ,
        // 那么就需要移除content-length头,并且会将contentLength属性的值改为Long.MIN_VALUE
        // 这说明http/1.1 不支持同时含有 transfer-encoding: chunked 和 content-length: 128
        // 并且transfer-encoding的优先级更高哦
        if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) {
            handleTransferEncodingChunkedWithContentLength(message);
        }
        
        // 当前状态切换为 <读取块大小>
        return State.READ_CHUNK_SIZE;
        
    } else if (contentLength() >= 0) {
        
        // 走到这里说明 transfer-encoding头不包含chunked 或者 不包含transfer-encoding头
        // 并且此时content-length的值 大于0
        // 当前状态切换为 <读取固定长度内容>
        return State.READ_FIXED_LENGTH_CONTENT;
        
    } else {
        
        // 走到这里, 说明既没有 transfer-encoding头不包含chunked 或者 不包含transfer-encoding头,content-length也未指定
        // 那就将当前状体切换为 <读取可变长度内容>
        return State.READ_VARIABLE_LENGTH_CONTENT;
    }
}
splitHeader
java 复制代码
private void splitHeader(AppendableCharSequence sb) {
    final int length = sb.length();
    int nameStart;
    int nameEnd;
    int colonEnd;
    int valueStart;
    int valueEnd;

    // 从偏移量为0开始,找到给定字符串中 非空白字符 的 起始索引
    nameStart = findNonWhitespace(sb, 0);
    
    // 从 起始索引 开始查找
    for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
        
        // 拿到nameEnd位置的字符
        char ch = sb.charAtUnsafe(nameEnd);
        
        // 如果字符是冒号":",或者 (不是请求解码器 并且是 ' ' 或 (char) 0x09 )
        if (ch == ':' || (!isDecodingRequest() && isOWS(ch))) {
            // 那就结束查找
            break;
        }
    }

    // nameEnd找到了结束都还没有找到,都还没找到冒号,就抛出异常了
    if (nameEnd == length) {
        throw new IllegalArgumentException("No colon found");
    }

    // 从nameEnd开始查找冒号
    for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
        if (sb.charAtUnsafe(colonEnd) == ':') {
            // 冒号所在索引 + 1
            colonEnd ++;
            break;
        }
    }

    // 拿到 请求头的名字(name是成员属性)
    name = sb.subStringUnsafe(nameStart, nameEnd);
    
    // 找到 请求头 值的开始位置
    valueStart = findNonWhitespace(sb, colonEnd);
    
    // 如果全是空白字符,那就是空字符串
    if (valueStart == length) {
        value = EMPTY_VALUE; // "" (value是成员属性)
    } else {
        // 从后面开始查找值结束的位置
        valueEnd = findEndOfString(sb);
        // 截取开始位置和结束位置的部分作为值
        value = sb.subStringUnsafe(valueStart, valueEnd); //(value是成员属性)
    }
}
findNonWhitespace
java 复制代码
private static int findNonWhitespace(AppendableCharSequence sb, int offset) {
    for (int result = offset; result < sb.length(); ++result) {
        
        char c = sb.charAtUnsafe(result);
        
        /*
        java认为是空白字符(不包括补充字符)的有: 
        It is a Unicode space character (SPACE_SEPARATOR, LINE_SEPARATOR, or PARAGRAPH_SEPARATOR) but is not also a non-breaking space ('\u00A0', '\u2007', '\u202F').
        It is '\t', U+0009 HORIZONTAL TABULATION.
        It is '\n', U+000A LINE FEED.
        It is '\u000B', U+000B VERTICAL TABULATION.
        It is '\f', U+000C FORM FEED.
        It is '\r', U+000D CARRIAGE RETURN.
        It is '\u001C', U+001C FILE SEPARATOR.
        It is '\u001D', U+001D GROUP SEPARATOR.
        It is '\u001E', U+001E RECORD SEPARATOR.
        It is '\u001F', U+001F UNIT SEPARATOR.
        */
        // 不是空白字符, 就返回该非空白字符的索引
        if (!Character.isWhitespace(c)) {
            return result;
        } else if (!isOWS(c)) { // 是空白字符但只允许是: ' ' 或 (char) 0x09 , 其它均抛出异常
            // Only OWS is supported for whitespace
            throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
                    " but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");
        }
    }
    
    // 若全部都是 ' ' 或 (char) 0x09 ,则返回整个字符串的长度
    return sb.length();
}
normalizeAndGetContentLength
java 复制代码
public static long normalizeAndGetContentLength(
        List<? extends CharSequence> contentLengthFields, boolean isHttp10OrEarlier,
        boolean allowDuplicateContentLengths) {
    
    // 如果content-length的值是空的,那就返回-1
    if (contentLengthFields.isEmpty()) {
        return -1;
    }

    // 先获取第一个值
    String firstField = contentLengthFields.get(0).toString();
    
    // 如果content-length的值不止1个 或者 第一个值包含"," 那就意味着content-length头有指定多个值
    boolean multipleContentLengths =
            contentLengthFields.size() > 1 || firstField.indexOf(COMMA) >= 0;

    // 如果 content-length头有指定多个值 并且 http协议版本是 http/1.0之后的版本
    if (multipleContentLengths && !isHttp10OrEarlier) {
        
        // 如果允许content-length头可以有指定多个值,那这些值都必须要相同才可以,不然会抛出异常哦
        if (allowDuplicateContentLengths) {
            
            // 初始值为null
            String firstValue = null;
            
            for (CharSequence field : contentLengthFields) {
                
                // 使用","分割
                String[] tokens = field.toString().split(COMMA_STRING, -1);
                
                // 遍历分割后的数组
                for (String token : tokens) {
                    
                    // 去掉前后的空白字符
                    String trimmed = token.trim();
                    
                    if (firstValue == null) {
                        // 赋初始值
                        firstValue = trimmed;
                    } else if (!trimmed.equals(firstValue)) { // 如果跟之前的值不一致,则抛异常
                        throw new IllegalArgumentException(
                                "Multiple Content-Length values found:");
                    }
                }
            }
            // 将值赋给firstField
            firstField = firstValue;
        } else {
			// 如果不允许指定多个值,则直接抛出异常
            throw new IllegalArgumentException(
                    "Multiple Content-Length values found: " + contentLengthFields);
        }
    }

	// firstField值不能为空,并且第一个字符必须是数字,否则抛出异常
    if (firstField.isEmpty() || !Character.isDigit(firstField.charAt(0))) {
        // Reject the message as invalid
        throw new IllegalArgumentException("Content-Length value is not a number: " + firstField);
    }
    
    try {
        // 转为Long
        final long value = Long.parseLong(firstField);
        // content-length的值不能是负数
        return checkPositiveOrZero(value, "Content-Length value");
    } catch (NumberFormatException e) {
        throw new IllegalArgumentException("Content-Length value is not a number");
    }
}
isContentAlwaysEmpty
java 复制代码
protected boolean isContentAlwaysEmpty(HttpMessage msg) {
    
    // 如果 HttpMessage是响应消息
    if (msg instanceof HttpResponse) {
        
        HttpResponse res = (HttpResponse) msg;
        int code = res.status().code();
        
        // 如果响应状态码是 100-200, 
        if (code >= 100 && code < 200) {
            
            // 如果code不是101,则返回true,表示content是空的
            // 如果code是101,如果响应头包括sec-websocket-accept头,则返回true,表示content是空的
            //               如果响应头不包括sec-websocket-accept头,并且 不包含 值为websocket的Upgrade头,则返回true,表示content是空的
            // 其它情况返回false
            return !(code == 101 && !res.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT)
                     && res.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true));
        }

        switch (code) {
        // 204(No Content)、304(Redirection)响应状态码表示响应体内容一定为空
        case 204: case 304:
            return true;
        default:
            // 其它响应状态码,需要根据实际情况判断
            return false;
        }
    }
    
    // 对于请求,则需要根据实际情况判断
    return false;
}
setTransferEncodingChunked

HttpUtil工具类中的方法:若chunked为true,表示要给transfer-encoding头,添加chunked值,并移除掉content-length头;若chunked为false,则移除掉transfer-encoding头中的chunked值,但允许保留transfer-encoding头的其它值

java 复制代码
public static void setTransferEncodingChunked(HttpMessage m, boolean chunked) {
    
    // 若chunked为true,表示要给transfer-encoding头,添加chunked值,并移除掉content-length头;
    if (chunked) {
        m.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
        m.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
    } else {
        // 若chunked为false,则移除掉transfer-encoding头中的chunked值,但允许保留transfer-encoding头的其它值
        List<String> encodings = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING);
        // 如果transfer-encoding头没有值就算了
        if (encodings.isEmpty()) {
            return;
        }
        List<CharSequence> values = new ArrayList<CharSequence>(encodings);
        Iterator<CharSequence> valuesIt = values.iterator();
        while (valuesIt.hasNext()) {
            CharSequence value = valuesIt.next();
            if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(value)) {
                valuesIt.remove();
            }
        }
        if (values.isEmpty()) {
            m.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
        } else {
            m.headers().set(HttpHeaderNames.TRANSFER_ENCODING, values);
        }
    }
}
相关推荐
是苏浙2 小时前
零基础入门Java之认识String类
java·开发语言
悟空码字2 小时前
从零到一搭建SpringCloud微服务,一场代码世界的“分家”大戏
java·后端·spring cloud
于樱花森上飞舞2 小时前
【多线程】常见的锁策略与锁
java·开发语言·算法·java-ee
吃喝不愁霸王餐APP开发者2 小时前
使用Mockito与WireMock对美团霸王餐接口进行契约测试与集成验证
java·json
明洞日记2 小时前
【设计模式手册023】外观模式 - 如何简化复杂系统
java·设计模式·外观模式
独自归家的兔2 小时前
面试实录:三大核心问题深度拆解(三级缓存 + 工程规范 + 逻辑思维)
java·后端·面试·职场和发展
毕设源码-郭学长2 小时前
【开题答辩全过程】以 共享单车后台管理系统为例,包含答辩的问题和答案
java·开发语言·tomcat
北城以北88882 小时前
SpringBoot--SpringBoot集成RabbitMQ
java·spring boot·rabbitmq·java-rabbitmq
Zsh-cs2 小时前
SpringMVC
java·springmvc