Netty之HttpRequest和HttpResponse

HttpRequest

Netty中的httprequest類結(jié)構(gòu)如下圖所示


DefaultFullHttpRequest

先來(lái)看DefaultFullHttpRequest,主要參數(shù)包括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;

然后來(lái)看看DefaultHttpHeaders里面包含了一個(gè)headerEntry數(shù)組,headerEntry是一個(gè)鍵值對(duì)

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

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

HttpResponse

HttpResponse的類結(jié)構(gòu)和Request基本一樣,但其沒有HttpMethod類型缓艳,只有一個(gè)HttpResponseStatus扶踊,所以我們一般抓到的包中協(xié)議以http開頭的都是Response,有g(shù)et等開頭的是Request

HttpResponse類結(jié)構(gòu)
    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

這兩個(gè)handler均繼承與HttpObjectEncoder郎任,編碼方式也類似秧耗,代碼如下

    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
        ByteBuf buf = null;
        if (msg instanceof HttpMessage) {//如果是HttpMessage執(zhí)行編譯
            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) {//如果存在內(nèi)容編碼內(nèi)容

            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轉(zhuǎn)成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);//分割協(xié)議頭
                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);//內(nèi)容為空
                        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 >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

這個(gè)handler其實(shí)就是對(duì)Content進(jìn)行了壓縮舶治,包含了壓縮方式和壓縮等級(jí)分井,這里有g(shù)zip和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)));
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市霉猛,隨后出現(xiàn)的幾起案子尺锚,更是在濱河造成了極大的恐慌,老刑警劉巖惜浅,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘫辩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坛悉,警方通過查閱死者的電腦和手機(jī)伐厌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)裸影,“玉大人挣轨,你說(shuō)我怎么就攤上這事⌒桑” “怎么了卷扮?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)均践。 經(jīng)常有香客問我晤锹,道長(zhǎng),這世上最難降的妖魔是什么彤委? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任鞭铆,我火速辦了婚禮,結(jié)果婚禮上葫慎,老公的妹妹穿的比我還像新娘衔彻。我一直安慰自己薇宠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布艰额。 她就那樣靜靜地躺著澄港,像睡著了一般。 火紅的嫁衣襯著肌膚如雪柄沮。 梳的紋絲不亂的頭發(fā)上回梧,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音祖搓,去河邊找鬼狱意。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拯欧,可吹牛的內(nèi)容都是我干的详囤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼镐作,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼藏姐!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起该贾,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤羔杨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后杨蛋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兜材,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年逞力,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曙寡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掏击,死狀恐怖卵皂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砚亭,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布殴玛,位于F島的核電站捅膘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏滚粟。R本人自食惡果不足惜寻仗,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凡壤。 院中可真熱鬧署尤,春花似錦耙替、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至箕别,卻和暖如春铜幽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背串稀。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工除抛, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人母截。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓到忽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親清寇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子绘趋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容