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)));
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容