Netty之HttpRequest和HttpResponse

HttpRequest

Netty中的httprequest类结构如下图所示


DefaultFullHttpRequest

先来看DefaultFullHttpRequest,主要参数包括HttpVersion,HttpMethod,String,即Http版本,使用的Http方法,以及url

    private final ByteBuf content;
    private final HttpHeaders trailingHeader;
    private final boolean validateHeaders;

    public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri) {
        this(httpVersion, method, uri, Unpooled.buffer(0));
    }

    public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, ByteBuf content) {
        this(httpVersion, method, uri, content, true);
    }

    public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri,
                                  ByteBuf content, boolean validateHeaders) {
        super(httpVersion, method, uri, validateHeaders);
        if (content == null) {
            throw new NullPointerException("content");
        }
        this.content = content;
        trailingHeader = new DefaultHttpHeaders(validateHeaders);
        this.validateHeaders = validateHeaders;
    }

再到DefaultHttpMessage,这里封装了http的headers

private HttpVersion version;
private final HttpHeaders headers;

然后来看看DefaultHttpHeaders里面包含了一个headerEntry数组,headerEntry是一个键值对

 private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];

private final class HeaderEntry implements Map.Entry<String, String>

HttpResponse

HttpResponse的类结构和Request基本一样,但其没有HttpMethod类型,只有一个HttpResponseStatus,所以我们一般抓到的包中协议以http开头的都是Response,有get等开头的是Request

HttpResponse类结构
    private final ByteBuf content;
    private final HttpHeaders trailingHeaders;
    private final boolean validateHeaders;

    public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status) {
        this(version, status, Unpooled.buffer(0));
    }

    public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, ByteBuf content) {
        this(version, status, content, true);
    }

    public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status,
                                   ByteBuf content, boolean validateHeaders) {
        super(version, status, validateHeaders);
        if (content == null) {
            throw new NullPointerException("content");
        }
        this.content = content;
        trailingHeaders = new DefaultHttpHeaders(validateHeaders);
        this.validateHeaders = validateHeaders;
    }

HttpResponse包

HttpResponseEncoder和HttpRequestEncoder

这两个handler均继承与HttpObjectEncoder,编码方式也类似,代码如下

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
        ByteBuf buf = null;
        if (msg instanceof HttpMessage) {//如果是HttpMessage执行编译
            if (state != ST_INIT) {
                throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
            }

            @SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })
            H m = (H) msg;

            buf = ctx.alloc().buffer();
            // Encode the message.
            encodeInitialLine(buf, m);//编码命令
            encodeHeaders(m.headers(), buf);//编码头部
            buf.writeBytes(CRLF);
            state = HttpHeaders.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
        }

        // Bypass the encoder in case of an empty buffer, so that the following idiom works:
        //
        //     ch.write(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        //
        // See https://github.com/netty/netty/issues/2983 for more information.

        if (msg instanceof ByteBuf && !((ByteBuf) msg).isReadable()) {
            out.add(EMPTY_BUFFER);
            return;
        }

        if (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) {//如果存在内容编码内容

            if (state == ST_INIT) {
                throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
            }

            final long contentLength = contentLength(msg);
            if (state == ST_CONTENT_NON_CHUNK) {
                if (contentLength > 0) {
                    if (buf != null && buf.writableBytes() >= contentLength && msg instanceof HttpContent) {
                        // merge into other buffer for performance reasons
                        buf.writeBytes(((HttpContent) msg).content());
                        out.add(buf);
                    } else {
                        if (buf != null) {
                            out.add(buf);
                        }
                        out.add(encodeAndRetain(msg));
                    }
                } else {
                    if (buf != null) {
                        out.add(buf);
                    } else {
                        // Need to produce some output otherwise an
                        // IllegalStateException will be thrown
                        out.add(EMPTY_BUFFER);
                    }
                }

                if (msg instanceof LastHttpContent) {
                    state = ST_INIT;
                }
            } else if (state == ST_CONTENT_CHUNK) {
                if (buf != null) {
                    out.add(buf);
                }
                encodeChunkedContent(ctx, msg, contentLength, out);
            } else {
                throw new Error();
            }
        } else {
            if (buf != null) {
                out.add(buf);
            }
        }
    }

头部编码

    @Override
    protected void encodeInitialLine(ByteBuf buf, HttpRequest request) throws Exception {
        request.getMethod().encode(buf);//编码方法
        buf.writeByte(SP);//添加空格

        // Add / as absolute path if no is present.
        // See http://tools.ietf.org/html/rfc2616#section-5.1.2
        String uri = request.getUri();

        if (uri.length() == 0) {
            uri += SLASH;
        } else {//编码URL
            int start = uri.indexOf("://");
            if (start != -1 && uri.charAt(0) != SLASH) {
                int startIndex = start + 3;
                // Correctly handle query params.
                // See https://github.com/netty/netty/issues/2732
                int index = uri.indexOf(QUESTION_MARK, startIndex);
                if (index == -1) {
                    if (uri.lastIndexOf(SLASH) <= startIndex) {
                        uri += SLASH;
                    }
                } else {
                    if (uri.lastIndexOf(SLASH, index) <= startIndex) {
                        int len = uri.length();
                        StringBuilder sb = new StringBuilder(len + 1);
                        sb.append(uri, 0, index)
                          .append(SLASH)
                          .append(uri, index, len);
                        uri = sb.toString();
                    }
                }
            }
        }

        buf.writeBytes(uri.getBytes(CharsetUtil.UTF_8));

        buf.writeByte(SP);
        request.getProtocolVersion().encode(buf);//编码版本
        buf.writeBytes(CRLF);
    }

编码Headers

   static void encode(HttpHeaders headers, ByteBuf buf) {
        if (headers instanceof DefaultHttpHeaders) {
            ((DefaultHttpHeaders) headers).encode(buf);
        } else {
            for (Entry<String, String> header: headers) {
                encode(header.getKey(), header.getValue(), buf);
            }
        }
    }

    @SuppressWarnings("deprecation")
    static void encode(CharSequence key, CharSequence value, ByteBuf buf) {
        if (!encodeAscii(key, buf)) {
            buf.writeBytes(HEADER_SEPERATOR);
        }
        if (!encodeAscii(value, buf)) {
            buf.writeBytes(CRLF);
        }
    }

HttpObjectDecoder

编码是从HttpObject转成bytebuf,而解码就是从bytebuf中读取HttpObject

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
        if (resetRequested) {
            resetNow();
        }

        switch (currentState) {
            case SKIP_CONTROL_CHARS: {
                if (!skipControlCharacters(buffer)) {
                    return;
                }
                currentState = State.READ_INITIAL;
            }
            case READ_INITIAL: try {
                AppendableCharSequence line = lineParser.parse(buffer);
                if (line == null) {
                    return;
                }
                String[] initialLine = splitInitialLine(line);//分割协议头
                if (initialLine.length < 3) {
                    // 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;
            }
            case READ_HEADER: try {
                State nextState = readHeaders(buffer);//读取头部
                if (nextState == null) {
                    return;
                }
                currentState = nextState;
                switch (nextState) {
                    case SKIP_CONTROL_CHARS:
                        // fast-path
                        // No content is expected.
                        out.add(message);
                        out.add(LastHttpContent.EMPTY_LAST_CONTENT);//内容为空
                        resetNow();
                        return;
                    case READ_CHUNK_SIZE://分片大小
                        if (!chunkedSupported) {
                            throw new IllegalArgumentException("Chunked messages not supported");
                        }
                        // Chunked encoding - generate HttpMessage first.  HttpChunks will follow.
                        out.add(message);
                        return;
                    default:
                        /**
                         * <a href="https://tools.ietf.org/html/rfc7230#section-3.3.3">RFC 7230, 3.3.3</a> states that
                         * if a request does not have either a transfer-encoding or a content-length header then the
                         * message body length is 0. However for a response the body length is the number of octets
                         * received prior to the server closing the connection. So we treat this as variable length
                         * chunked encoding.
                         */
                        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 will be decreased as the READ_FIXED_LENGTH_CONTENT state reads data chunk by
                            // chunk.
                            chunkSize = contentLength;
                        }

                        // We return here, this forces decode to be called again where we will decode the content
                        return;
                }
            } catch (Exception e) {
                out.add(invalidMessage(buffer, e));
                return;
            }
            case READ_VARIABLE_LENGTH_CONTENT: {//读取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.readSlice(toRead).retain();
                    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 a 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.readSlice(toRead).retain();
                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);
                toRead = Math.min(toRead, buffer.readableBytes());
                if (toRead == 0) {
                    return;
                }
                HttpContent chunk = new DefaultHttpContent(buffer.readSlice(toRead).retain());
                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 {//读取最后一段Content
                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;
            }
        }
    }

HttpContentCompressor

这个handler其实就是对Content进行了压缩,包含了压缩方式和压缩等级,这里有gzip和zlib两种压缩方式

 @Override
    protected Result beginEncode(HttpResponse headers, String acceptEncoding) throws Exception {
        String contentEncoding = headers.headers().get(HttpHeaders.Names.CONTENT_ENCODING);
        if (contentEncoding != null &&
            !HttpHeaders.Values.IDENTITY.equalsIgnoreCase(contentEncoding)) {
            return null;
        }

        ZlibWrapper wrapper = determineWrapper(acceptEncoding);
        if (wrapper == null) {
            return null;
        }

        String targetContentEncoding;
        switch (wrapper) {
        case GZIP:
            targetContentEncoding = "gzip";
            break;
        case ZLIB:
            targetContentEncoding = "deflate";
            break;
        default:
            throw new Error();
        }

        return new Result(
                targetContentEncoding,
                new EmbeddedChannel(ZlibCodecFactory.newZlibEncoder(
                        wrapper, compressionLevel, windowBits, memLevel)));
    }

推荐阅读更多精彩内容