Netty HttpRequestDecoder详细解析过程分析

x33g5p2x  于2021-08-17 转载在 Java  
字(18.9k)|赞(0)|评价(0)|浏览(628)

HttpRequestDecoder

这东西一看就知道是HTTP请求的解码器,就是用来解析HTTP协议格式的。我们用一个简单的例子来说,就下面这个,一个请求解码,一个响应编码,最后一个自定义的处理器,我们来分析下,请求解码做了什么。

pipeline.addLast(new HttpRequestDecoder());
pipeline.addLast(new HttpResponseEncoder());
pipeline.addLast("MyTestHttpServerHandler", new MyHttpServerHandler());

类结构

在这里插入图片描述
看到他是继承了一个HttpObjectDecoder,这个应该是一个通用的HTTP解码器,也是继承以前讲过的ByteToMessageDecoder,我们先来看下HttpRequestDecoder这个类,其实没多少东西,主要的在HttpObjectDecoder都实现了。从下面可以看到其实这个类本身没实现什么解码逻辑,主要的还是在他的父类。

public class HttpRequestDecoder extends HttpObjectDecoder { 

	//构造函数
    public HttpRequestDecoder() { 
    }
c HttpRequestDecoder(
            int maxInitialLineLength, int maxHeaderSize, int maxChunkSize) { 
        super(maxInitialLineLength, maxHeaderSize, maxChunkSize, true);
    }

    public HttpRequestDecoder(
            int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders) { 
        super(maxInitialLineLength, maxHeaderSize, maxChunkSize, true, validateHeaders);
    }

    public HttpRequestDecoder(
            int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean validateHeaders,
            int initialBufferSize) { 
        super(maxInitialLineLength, maxHeaderSize, maxChunkSize, true, validateHeaders, initialBufferSize);
    }
	//根据请求行创建HttpMessage 版本,方法,URI
    @Override
    protected HttpMessage createMessage(String[] initialLine) throws Exception { 
        return new DefaultHttpRequest(
                HttpVersion.valueOf(initialLine[2]),
                HttpMethod.valueOf(initialLine[0]), initialLine[1], validateHeaders);
    }
	//无效请求
    @Override
    protected HttpMessage createInvalidMessage() { 
        return new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request", validateHeaders);
    }
    //是否是请求解码
    @Override
    protected boolean isDecodingRequest() { 
        return true;
    }
}

HttpObjectDecoder

基本属性

我们先了解下一些重要的属性,因为要解析HTTP协议格式,所以需要有换行符解析器,请求头解析器,还要看是否是用content-length传输还是用transfer-encoding块传输。还定义了一些状态,用来执行不同的逻辑。

private static final String EMPTY_VALUE = "";//请求头空值

    private final int maxChunkSize;//块的最大长度
    private final boolean chunkedSupported;//是否支持分块chunk发送
    protected final boolean validateHeaders;//是否验证头名字合法性
    private final HeaderParser headerParser;//请求头解析器
    private final LineParser lineParser;//换行符解析器

    private HttpMessage message;//请求的消息,包括请求行和请求头
    private long chunkSize;//保存下一次要读的消息体长度
    private long contentLength = Long.MIN_VALUE;//消息体长度
    private volatile boolean resetRequested;//重置请求

    // These will be updated by splitHeader(...)
    private CharSequence name;//头名字
    private CharSequence value;//头的值

    private LastHttpContent trailer;//请求体结尾
 ·/** 状态
     * The internal state of { @link HttpObjectDecoder}.
     * <em>Internal use only</em>.
     */
    private enum State { 
        SKIP_CONTROL_CHARS,//检查控制字符
        READ_INITIAL,//开始读取
        READ_HEADER,//读取头
        READ_VARIABLE_LENGTH_CONTENT,//读取可变长内容,用于chunk传输
        READ_FIXED_LENGTH_CONTENT,//读取固定长内容 用于Content-Length
        READ_CHUNK_SIZE,//chunk传输的每个chunk尺寸
        READ_CHUNKED_CONTENT,//每个chunk内容
        READ_CHUNK_DELIMITER,//chunk分割
        READ_CHUNK_FOOTER,//最后一个chunk
        BAD_MESSAGE,//无效消息
        UPGRADED//协议切换
    }
    //状态
    private State currentState = State.SKIP_CONTROL_CHARS;

构造函数

构造函数,参数对应一行最大长度请求头的最大长度请求体或者某个块的最大长度,是否支持chunk块传输。

protected HttpObjectDecoder() { 
        this(4096, 8192, 8192, true);
    }

    /**
     * Creates a new instance with the specified parameters.
     */
    protected HttpObjectDecoder(
            int maxInitialLineLength, int maxHeaderSize, int maxChunkSize, boolean chunkedSupported) { 
        this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, true);
    }

    /**
     * Creates a new instance with the specified parameters.
     */
    protected HttpObjectDecoder(
            int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
            boolean chunkedSupported, boolean validateHeaders) { 
        this(maxInitialLineLength, maxHeaderSize, maxChunkSize, chunkedSupported, validateHeaders, 128);
    }

    protected HttpObjectDecoder(
            int maxInitialLineLength, int maxHeaderSize, int maxChunkSize,
            boolean chunkedSupported, boolean validateHeaders, int initialBufferSize) { 
        checkPositive(maxInitialLineLength, "maxInitialLineLength");
        checkPositive(maxHeaderSize, "maxHeaderSize");
        checkPositive(maxChunkSize, "maxChunkSize");

        AppendableCharSequence seq = new AppendableCharSequence(initialBufferSize);
        lineParser = new LineParser(seq, maxInitialLineLength);
        headerParser = new HeaderParser(seq, maxHeaderSize);
        this.maxChunkSize = maxChunkSize;
        this.chunkedSupported = chunkedSupported;
        this.validateHeaders = validateHeaders;
    }

AppendableCharSequence 可添加的字符序列

这个底层是一个字符数组,可以动态添加到最后,具体的源码可以自己看看,因为下面要用,所以我就提一下。

HeaderParser头解析器

其实就是检查字节缓冲区,获取一行头信息,ByteProcessor 这个就是处理是否遇到某个字节,就是process方法。这边处理的就是如果发现是回车,就不添加任何字符,返回true,继续解析,遇到换行就返回false,不解析了,否则将字符添加到字符序列中,返回true,继续解析。

private static class HeaderParser implements ByteProcessor { 
        private final AppendableCharSequence seq;//可添加的字符序列
        private final int maxLength;//最大长度
        private int size;//索引

        HeaderParser(AppendableCharSequence seq, int maxLength) { 
            this.seq = seq;
            this.maxLength = maxLength;
        }
        //解析缓冲区
        public AppendableCharSequence parse(ByteBuf buffer) { 
            final int oldSize = size;
            seq.reset();
            int i = buffer.forEachByte(this);
            if (i == -1) { //没读到换行,或者报异常了
                size = oldSize;
                return null;
            }
            buffer.readerIndex(i + 1);
            return seq;
        }
        //读到的字符个数清零
        public void reset() { 
            size = 0;
        }
        //处理数据,遇到换行了就结束
        @Override
        public boolean process(byte value) throws Exception { 
            char nextByte = (char) (value & 0xFF);
            if (nextByte == HttpConstants.CR) { //遇到回车符,直接返回true,不添加字符
                return true;
            }
            if (nextByte == HttpConstants.LF) { //遇到换行符,就会结束
                return false;
            }

            if (++ size > maxLength) { //溢出了
                throw newException(maxLength);
            }

            seq.append(nextByte);//添加
            return true;
        }
		//头过大
        protected TooLongFrameException newException(int maxLength) { 
            return new TooLongFrameException("HTTP header is larger than " + maxLength + " bytes.");
        }
    }

process哪里用到呢

其实是在parse方法的buffer.forEachByte(this)里。

forEachByte

这个方法就是传一个字节处理器,然后字节缓冲区挨个处理字节,返回索引。

@Override
    public int forEachByte(ByteProcessor processor) { 
        ensureAccessible();
        try { 
            return forEachByteAsc0(readerIndex, writerIndex, processor);
        } catch (Exception e) { 
            PlatformDependent.throwException(e);
            return -1;
        }
    }

forEachByteAsc0

这个就是具体的方法啦,里面调用了processorprocess方法,从头到位把每个字节传进去处理,如果有遇到换行符,会返回相应索引,否则就是-1

int forEachByteAsc0(int start, int end, ByteProcessor processor) throws Exception { 
        for (; start < end; ++start) { 
            if (!processor.process(_getByte(start))) { 
                return start;
            }
        }

        return -1;//表示没有遇到换行符
    }

LineParser行解析器

继承了头解析器,只是解析的时候要reset一下,就是把读到的个数清0,因为是一行行读,每次读完一行就得清理个数。虽然字符串序列可以不处理,可以复用。

private static final class LineParser extends HeaderParser { 

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

        @Override
        public AppendableCharSequence parse(ByteBuf buffer) { 
            reset();//从头开始,要重置索引
            return super.parse(buffer);
        }

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

decode解码

这个是最核心的方法,包括了解析请求行,请求头,请求体,但是会将请求行和请求头整合起来形成一个请求DefaultHttpRequest传递到后面,把请求体再封装成消息体传递到后面,因为请求体可能很大,所以也可能会有多次封装,那后面处理器就可能收到多次消息体。如果是GET的话是没有消息体的,首先收到一个DefaultHttpRequest,然后是一个LastHttpContent。如果是POST的话,先收到DefaultHttpRequest,然后可能多个内容DefaultHttpContent和一个DefaultLastHttpContent。下面我们来看源码,这个方法源码很长,所以我打算按照状态来讲。

检查并略过控制字符SKIP_CONTROL_CHARS

首先我们要检查下我们的字节缓冲区里面是不是全是控制字符(类似回车换行,空格这种),如果是的话就不处理,返回了,不是的话就略过控制字符,然后返回。如果不全是控制字符,那就状态切换到READ_INITIAL开始读取。

case SKIP_CONTROL_CHARS: { 
            if (!skipControlCharacters(buffer)) { 
                return;//如果全是控制字符就返回了
            }
            currentState = State.READ_INITIAL;
        }

skipControlCharacters略过控制字符

这个方法就是看没有不是控制字符的,如果全是控制字符,就返回false,有不是控制字符的就略过控制字符并返回true。具体方法就是从可读索引开始,直接获取对应的无符号字节,然后判断是不是ISO的控制字符或者空格,如果不是,直接就返回,否则就继续,直到遇到不是的位置,然后要略过控制字符。

private static boolean skipControlCharacters(ByteBuf buffer) { 
        boolean skiped = false;
        final int wIdx = buffer.writerIndex();
        int rIdx = buffer.readerIndex();
        while (wIdx > rIdx) { 
            int c = buffer.getUnsignedByte(rIdx++);//获取无符号字节
            if (!Character.isISOControl(c) && !Character.isWhitespace(c)) { //不是ISO的控制字符也不是空格
                rIdx--;
                skiped = true;//有不是控制字符的,直接返回
                break;
            }
        }
        buffer.readerIndex(rIdx);//略过控制字符
        return skiped;
    }

开始读取READ_INITIAL

会开始读取一行,如果没有读到换行符,可能是因为数据还没收全,那就什么都不做,返回。
否则就开始分割,分割出方法,URI,协议,当然如果请求头无效,就不管了,重新返回到SKIP_CONTROL_CHARS状态。如果是有效的,就封装成请求消息HttpMessage包括请求行和请求头信息,讲状态切换到READ_HEADER读头信息。

case READ_INITIAL: try { //读取请求行
            AppendableCharSequence line = lineParser.parse(buffer);//解析一行数据
            if (line == null) { //没解析到换行符
                return;
            }
            String[] initialLine = splitInitialLine(line);//行分割后的数组
            if (initialLine.length < 3) { //小于3个就说明格式(方法 URI 版本)不对,直接忽略
                // Invalid initial line - ignore.
                currentState = State.SKIP_CONTROL_CHARS;
                return;
            }

            message = createMessage(initialLine);//创建请求消息
            currentState = State.READ_HEADER;
            // fall-through
        } catch (Exception e) { 
            out.add(invalidMessage(buffer, e));
            return;
        }

splitInitialLine分割请求行

可以看到其实执行了3次检测,刚好把请求行给分割出来,最后用字符串切割出来封装成数组返回。

//按空格进行一行的分割
    private static String[] splitInitialLine(AppendableCharSequence sb) { 
        int aStart;
        int aEnd;
        int bStart;
        int bEnd;
        int cStart;
        int cEnd;

        aStart = findNonWhitespace(sb, 0);//找出不是空格的第一个索引
        aEnd = findWhitespace(sb, aStart);//找出空格索引

        bStart = findNonWhitespace(sb, aEnd);
        bEnd = findWhitespace(sb, bStart);

        cStart = findNonWhitespace(sb, bEnd);
        cEnd = findEndOfString(sb);

        return new String[] { 
                sb.subStringUnsafe(aStart, aEnd),
                sb.subStringUnsafe(bStart, bEnd),
                cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
    }

createMessage创建请求消息

创建一个DefaultHttpRequest,就是一个HttpRequest接口的默认实现,封装请求行和请求头信息。

@Override
    protected HttpMessage createMessage(String[] initialLine) throws Exception { 
        return new DefaultHttpRequest(
                HttpVersion.valueOf(initialLine[2]),//协议版本
                HttpMethod.valueOf(initialLine[0]), initialLine[1], validateHeaders);//方法和URI
    }

invalidMessage无效消息

创建一个无效消息,状态直接为BAD_MESSAGE无效,把缓冲区内的数据直接都略过,如果请求消息没创建好,就创建一个,然后设置失败结果并带上异常信息返回。

private HttpMessage invalidMessage(ByteBuf in, Exception cause) { 
        currentState = State.BAD_MESSAGE;//设置无效数据,这样后面同一个消息的数据都会被略过

        in.skipBytes(in.readableBytes());//直接不可读,略过可读数据

        if (message == null) { 
            message = createInvalidMessage();
        }
        message.setDecoderResult(DecoderResult.failure(cause));//设置失败

        HttpMessage ret = message;
        message = null;
        return ret;
    }

createInvalidMessage创建完整的请求

直接返回完整的请求消息,参数设置成有问题的就可以了。

@Override
    protected HttpMessage createInvalidMessage() { 
        return new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request", validateHeaders);
    }

刚好讲完了解析请求行,好像有点多了,下一篇再讲解析头吧。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

READ_HEADER读取头

我们继续上一篇,现在的状态到了读取了。首先会先解析请求头,然后看里面有没有transfer-encoding或者content-length,来进行后续的消息体读取。

case READ_HEADER: try { //读取请求头
   			State nextState = readHeaders(buffer);
            if (nextState == null) { 
                return;
            }
            currentState = nextState;
            switch (nextState) { 
            case SKIP_CONTROL_CHARS://没有内容,直接传递两个消息
                out.add(message);
                out.add(LastHttpContent.EMPTY_LAST_CONTENT);空内容
                resetNow();
                return;
            case READ_CHUNK_SIZE://块协议传递
                if (!chunkedSupported) { 
                    throw new IllegalArgumentException("Chunked messages not supported");
                }
                out.add(message);
                return;
            default:
                //没有transfer-encoding或者content-length头 表示没消息体,比如GET请求
                long contentLength = contentLength();
                if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) { //没消息体,直接就补一个空消息体
                    out.add(message);//消息行和消息头
                    out.add(LastHttpContent.EMPTY_LAST_CONTENT);//空消息体
                    resetNow();//重置属性
                    return;
                }

                assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
                        nextState == State.READ_VARIABLE_LENGTH_CONTENT;
				//有消息体,就先放入行和头信息,下一次解码再进行消息体的读取
                out.add(message);//

                if (nextState == State.READ_FIXED_LENGTH_CONTENT) { 

                    chunkSize = contentLength;//如果是固定长度的消息体,要保存下一次要读的消息体长度
                }

                return;
            }
        } catch (Exception e) { 
            out.add(invalidMessage(buffer, e));//异常了就无效
            return;
        }

readHeaders解析头

主要就是按行解析头消息,然后进行头信息分割,然后放入headers ,最后根据content-length来决定后面的状态,是读取固定长READ_FIXED_LENGTH_CONTENT还是可变长READ_VARIABLE_LENGTH_CONTENT,还是是读取块大小READ_CHUNK_SIZE

private State readHeaders(ByteBuf buffer) { 
        final HttpMessage message = this.message;
        final HttpHeaders headers = message.headers();//获得请求头

        AppendableCharSequence line = headerParser.parse(buffer);//解析请求头
        if (line == null) { 
            return null;
        }
        if (line.length() > 0) { 
            do { 
                char firstChar = line.charAtUnsafe(0);
                if (name != null && (firstChar == ' ' || firstChar == '\t')) { 
                    //please do not make one line from below code
                    //as it breaks +XX:OptimizeStringConcat optimization
                    String trimmedLine = line.toString().trim();
                    String valueStr = String.valueOf(value);
                    value = valueStr + ' ' + trimmedLine;
                } else { 
                    if (name != null) { 
                        headers.add(name, value);//如果名字解析出来表示值也出来了,就添加进去
                    }
                    splitHeader(line);//分割请求头
                }

                line = headerParser.parse(buffer);//继续解析头
                if (line == null) { 
                    return null;
                }
            } while (line.length() > 0);
        }

        // Add the last header.
        if (name != null) { //添加最后一个
            headers.add(name, value);
        }

        // reset name and value fields 重置
        name = null;
        value = null;
        //找content-length头信息
        List<String> values = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
        int contentLengthValuesCount = values.size();//长度头的值的个数

        if (contentLengthValuesCount > 0) { 
            if (contentLengthValuesCount > 1 && message.protocolVersion() == HttpVersion.HTTP_1_1) { //如果是HTTP_1_1找到多个Content-Length是不对的,要抛异常
                throw new IllegalArgumentException("Multiple Content-Length headers found");
            }
            contentLength = Long.parseLong(values.get(0));//获取消息体长
        }

        if (isContentAlwaysEmpty(message)) { //空内容
            HttpUtil.setTransferEncodingChunked(message, false);//不开启块传输
            return State.SKIP_CONTROL_CHARS;
        } else if (HttpUtil.isTransferEncodingChunked(message)) { 
            if (contentLengthValuesCount > 0 && message.protocolVersion() == HttpVersion.HTTP_1_1) { //HTTP_1_1如果开启了快协议,就不能设置Content-Length了
                throw new IllegalArgumentException(
                        "Both 'Content-Length: " + contentLength + "' and 'Transfer-Encoding: chunked' found");
            }

            return State.READ_CHUNK_SIZE;//块传输,要获取大小
        } else if (contentLength() >= 0) { 
            return State.READ_FIXED_LENGTH_CONTENT;//可以固定长度解析消息体
        } else { 
            return State.READ_VARIABLE_LENGTH_CONTENT;//可变长度解析,或者没有Content-Length,http1.0以及之前或者1.1 非keep alive,Content-Length可有可无
        }
    }

这里有两个要注意的,如果是HTTP1.1一个头只能对应一个值,而且Content-LengthTransfer-Encoding不能同时存在。http1.0以及之前或者http1.1没设置keepalive的话Content-Length可有可无。

Header的结构

外部看上去很像是跟MAP一样添加头信息,其实内部还是使用了数组单链表双向循环链表,好比是HashMap的加强版。使用了hash算法定位数组的索引,然后有冲突的时候用单链表头插进去,而且头信息顺序按照双向循环链表连起来了,方便前后定位。具体的细节可以看源码,我就不多说了。
在这里插入图片描述

READ_VARIABLE_LENGTH_CONTENT读取可变长内容

直接读取可读的字节,然后封装成DefaultHttpContent内容传递。

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;
        }

READ_FIXED_LENGTH_CONTENT读取固定长内容

固定长度就是有contentLength,读取长度,如果等于记录的长度chunkSize ,就表示读完了,直接传递最后内容DefaultLastHttpContent。否则说明没读完,就传递内容DefaultHttpContent

case READ_FIXED_LENGTH_CONTENT: { //有固定长消息体
            int readLimit = buffer.readableBytes();
            if (readLimit == 0) { 
                return;
            }

            int toRead = Math.min(readLimit, maxChunkSize);//读取的个数
            if (toRead > chunkSize) { //如果大于块长度chunkSize,就读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;
        }

READ_CHUNK_SIZE读取块大小

如果是chunk块传输,根据块传输协议,就应该是获取块大小。协议格式我画了个图:
在这里插入图片描述
比如要传输aab,使用块协议,第一块长度是2,内容是aa,第二块长度是1,内容是b,第三块长度是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) { //读到块结束标记 0\r\n
                currentState = State.READ_CHUNK_FOOTER;
                return;
            }
            currentState = State.READ_CHUNKED_CONTENT;//继续读内容
            // fall-through
        } catch (Exception e) { 
            out.add(invalidChunk(buffer, e));//无效块
            return;
        }

如果读取的块长度是0了,那说明要到最后一个了,状态就要转到READ_CHUNK_FOOTER,否则就转到读内容READ_CHUNKED_CONTENT

getChunkSize获取块尺寸

这里连;空格控制字符都算截止符了。

private static int getChunkSize(String hex) { 
        hex = hex.trim();
        for (int i = 0; i < hex.length(); i ++) { 
            char c = hex.charAt(i);
            if (c == ';' || Character.isWhitespace(c) || Character.isISOControl(c)) { 
                hex = hex.substring(0, i);
                break;
            }
        }

        return Integer.parseInt(hex, 16);
    }

READ_CHUNKED_CONTENT读取块内容

根据块长度chunkSize读取字节,如果读取长度等于chunkSize,表示读完了,需要读取分隔符,也就是换车换行了,状态转到READ_CHUNK_DELIMITER,否则就将读取的内容,封装成DefaultHttpContent传递下去,然后下一次继续读取内容。

case READ_CHUNKED_CONTENT: { //读取块内容,其实没读取,只是用切片,从切片读,不影响原来的
            assert chunkSize <= Integer.MAX_VALUE;
            int toRead = Math.min((int) chunkSize, maxChunkSize); 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
        }

READ_CHUNK_DELIMITER读取块分隔符

其实就是回车换行符,找到了就转到READ_CHUNK_SIZE继续去取下一个块长度。

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;
        }

READ_CHUNK_FOOTER读最后一个块

如果读取的块长度chunkSize=0的话,就说明是最后一个块了,然后要看下是否还有头信息在后面,有头信息的话会封装成DefaultLastHttpContent,如果没有的话头信息就是LastHttpContent.EMPTY_LAST_CONTENT

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;
        }

readTrailingHeaders读取最后的头信息

会去读取一行,如果没读出来换行,表示可能没收到数据,也就是没读完,那就返回,继续下一次。
如果读出来发现就只有回车换行,那就说明没有头信息,结束了,就返回一个 LastHttpContent.EMPTY_LAST_CONTENT,否则的话就创建一个DefaultLastHttpContent内容,然后进行头信息的解析,解析出来的头信息就放入内容中,并返回内容。

private LastHttpContent readTrailingHeaders(ByteBuf buffer) { 
        AppendableCharSequence line = headerParser.parse(buffer);
        if (line == null) { //没有换行,表示没读完呢
            return null;
        }
        LastHttpContent trailer = this.trailer;
        if (line.length() == 0 && trailer == null) { //直接读到\r\n 即读到空行,表示结束,无头信息,返回空内容
            return LastHttpContent.EMPTY_LAST_CONTENT;
        }

        CharSequence lastHeader = null;
        if (trailer == null) { 
            trailer = this.trailer = new DefaultLastHttpContent(Unpooled.EMPTY_BUFFER, validateHeaders);//空内容
        }
        while (line.length() > 0) { //chunk最后可能还有头信息 key: 1\r\n
            char firstChar = line.charAtUnsafe(0);
            if (lastHeader != null && (firstChar == ' ' || firstChar == '\t')) { 
                List<String> current = trailer.trailingHeaders().getAll(lastHeader);
                if (!current.isEmpty()) { 
                    int lastPos = current.size() - 1;
                    //please do not make one line from below code
                    //as it breaks +XX:OptimizeStringConcat optimization
                    String lineTrimmed = line.toString().trim();
                    String currentLastPos = current.get(lastPos);
                    current.set(lastPos, currentLastPos + lineTrimmed);
                }
            } else { //解析头信息
                splitHeader(line);//
                CharSequence headerName = name;
                if (!HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(headerName) &&
                        !HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(headerName) &&
                        !HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(headerName)) { 
                    trailer.trailingHeaders().add(headerName, value);
                }
                lastHeader = name;
                // reset name and value fields
                name = null;
                value = null;
            }
            line = headerParser.parse(buffer);
            if (line == null) { 
                return null;
            }
        }

        this.trailer = null;
        return trailer;
    }

BAD_MESSAGE无效消息

直接略过后续一起的内容。

case BAD_MESSAGE: { 
            // Keep discarding until disconnection.
            buffer.skipBytes(buffer.readableBytes());//坏消息,直接略过,不读
            break;
        }

UPGRADED协议切换

其实就是协议的转换。

case UPGRADED: { //协议切换
            int readableBytes = buffer.readableBytes();
            if (readableBytes > 0) {  
                out.add(buffer.readBytes(readableBytes));
            }
            break;
        }

resetNow重置属性

每次成功解码操作后都要重新设置属性。

private void resetNow() { 
        HttpMessage message = this.message;
        this.message = null;
        name = null;
        value = null;
        contentLength = Long.MIN_VALUE;
        lineParser.reset();
        headerParser.reset();
        trailer = null;
        if (!isDecodingRequest()) { //不是请求解码,如果要升级协议
            HttpResponse res = (HttpResponse) message;
            if (res != null && isSwitchingToNonHttp1Protocol(res)) { 
                currentState = State.UPGRADED;
                return;
            }
        }

        resetRequested = false;
        currentState = State.SKIP_CONTROL_CHARS;
    }

至此整个基本完成了HttpRequestDecoder就是他的子类,自己看下就懂了,核心方法都被父类实现了。
给一个只用了HttpRequestDecoder的运行结果。

运行结果

GET

先是DefaultHttpRequest
在这里插入图片描述
然后LastHttpContent中的EMPTY_LAST_CONTENT
在这里插入图片描述

POST

先是DefaultHttpRequest
在这里插入图片描述
然后是DefaultLastHttpContent
在这里插入图片描述
在这里插入图片描述

如果是发送比较大的信息,比如:
在这里插入图片描述
那就是可能会出现好几次消息体解析:
在这里插入图片描述
当然也可能一次,看接受缓冲区的情况啦:
在这里插入图片描述

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

相关文章