美文网首页IT干货
Netty之HttpRequest和HttpResponse

Netty之HttpRequest和HttpResponse

作者: bingoc | 来源:发表于2016-04-23 19:23 被阅读9799次

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

    相关文章

      网友评论

        本文标题:Netty之HttpRequest和HttpResponse

        本文链接:https://www.haomeiwen.com/subject/nozxrttx.html