在閱讀該篇文章之前,推薦先閱讀以下內容:
- [netty5: HttpObject]-源碼解析
- [netty5: MessageToMessageCodec & MessageToMessageEncoder & MessageToMessageDecoder]-源碼分析
- [netty5: ByteToMessageCodec & MessageToByteEncoder & ByteToMessageDecoder]-源碼分析
HttpObjectEncoder
HttpObjectEncoder
類用于編碼 HTTP 消息對象(如請求和響應),并根據不同的消息類型(如普通內容、分塊內容或無內容)來處理編碼過程。它支持對 HTTP 頭、初始行、內容和尾部進行編碼,并根據消息類型和內容長度動態調整內存分配,以提高編碼效率。此外,還提供了一個方法來處理分塊傳輸編碼的內容,并在消息為空時輸出合適的空緩沖區。
public abstract class HttpObjectEncoder<H extends HttpMessage> extends MessageToMessageEncoder<Object> {static final short CRLF_SHORT = (CR << 8) | LF;private static final int ZERO_CRLF_MEDIUM = ('0' << 16) | CRLF_SHORT;// \r\nprivate static final byte[] CRLF = {CR, LF};// 0\r\nprivate static final byte[] ZERO_CRLF_CRLF = { '0', CR, LF, CR, LF };// 新的頭部數據權重 0.2private static final float HEADERS_WEIGHT_NEW = 1 / 5f;// 歷史頭部數據權重 0.8private static final float HEADERS_WEIGHT_HISTORICAL = 1 - HEADERS_WEIGHT_NEW;// 新的尾部數據權重 0.2private static final float TRAILERS_WEIGHT_NEW = HEADERS_WEIGHT_NEW;// 歷史尾部數據權重 0.8private static final float TRAILERS_WEIGHT_HISTORICAL = HEADERS_WEIGHT_HISTORICAL;// 初始假設的頭部大小private float headersEncodedSizeAccumulator = 256;// 初始假設的尾部大小private float trailersEncodedSizeAccumulator = 256;private static final int ST_INIT = 0;private static final int ST_CONTENT_NON_CHUNK = 1;private static final int ST_CONTENT_CHUNK = 2;private static final int ST_CONTENT_ALWAYS_EMPTY = 3;private Supplier<Buffer> crlfBufferSupplier;private Supplier<Buffer> zeroCrlfCrlfBufferSupplier;@SuppressWarnings("RedundantFieldInitialization")private int state = ST_INIT;@Overrideprotected void encodeAndClose(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {Buffer buf = null;if (msg instanceof HttpMessage) {if (state != ST_INIT) {throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg) + ", state: " + state);}@SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })H m = (H) msg;buf = ctx.bufferAllocator().allocate((int) headersEncodedSizeAccumulator);encodeInitialLine(buf, m);state = isContentAlwaysEmpty(m) ? ST_CONTENT_ALWAYS_EMPTY :HttpUtil.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;sanitizeHeadersBeforeEncode(m, state == ST_CONTENT_ALWAYS_EMPTY);encodeHeaders(m.headers(), buf);buf.writeShort(CRLF_SHORT);headersEncodedSizeAccumulator = HEADERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +HEADERS_WEIGHT_HISTORICAL * headersEncodedSizeAccumulator;}// 跳過空的消息,優化數據流的處理if (msg instanceof Buffer && ((Buffer) msg).readableBytes() == 0) {out.add(msg);return;}if (msg instanceof HttpContent || msg instanceof Buffer || msg instanceof FileRegion) {switch (state) {case ST_INIT:// 如果當前狀態是 ST_INIT,但消息類型是 HttpContent 或 Buffer 或 FileRegion,則拋出 IllegalStateException,說明這個消息類型在 ST_INIT 狀態下是不允許出現的。// 在拋出異常之前,釋放消息 msg 占用的資源Resource.dispose(msg);throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg) + ", state: " + state);case ST_CONTENT_NON_CHUNK:// 處理非分塊內容:對于 ST_CONTENT_NON_CHUNK 狀態,首先獲取內容的長度 contentLength(msg)final long contentLength = contentLength(msg);if (contentLength > 0) {// 如果內容長度大于零且 buf 緩沖區有足夠的可寫空間,則將內容合并到 buf 中(提高性能,避免頻繁分配新的緩沖區)if (buf != null && buf.writableBytes() >= contentLength && msg instanceof HttpContent) {buf.writeBytes(((HttpContent<?>) msg).payload());Resource.dispose(msg);out.add(buf);} else {if (buf != null) {out.add(buf);}out.add(encode(msg));}// 如果 msg 是 LastHttpContent(即最后一部分 HTTP 內容),則將 state 重置為 ST_INIT,表示處理完成if (msg instanceof LastHttpContent) {state = ST_INIT;}break;} else {// do not break, let's fall-through}// fall-through!case ST_CONTENT_ALWAYS_EMPTY:Resource.dispose(msg);if (buf != null) {out.add(buf);} else {out.add(ctx.bufferAllocator().allocate(0));}break;case ST_CONTENT_CHUNK:// 先將現有的緩沖區 buf 添加到 out 中if (buf != null) {// We allocated a buffer so add it now.out.add(buf);}// 處理分塊傳輸編碼(chunked transfer encoding)內容encodeChunkedContent(ctx, msg, contentLength(msg), out);break;default:throw new Error();}if (msg instanceof LastHttpContent) {state = ST_INIT;}} else if (buf != null) {out.add(buf);}}// 將傳入的 HttpHeaders 對象中的所有頭部字段逐個編碼,并將編碼后的字節數據寫入 buf 中protected void encodeHeaders(HttpHeaders headers, Buffer buf) {for (Entry<CharSequence, CharSequence> header : headers) {HttpHeadersEncoder.encoderHeader(header.getKey(), header.getValue(), buf);}}// 負責將數據分塊并添加到 out 列表中。具體處理流程包括:// 1. 編碼分塊大小并將其添加到輸出緩沖區。// 2. 處理 LastHttpContent,如果有尾部頭部(trailers),則處理并編碼它們。// 3. 處理內容長度為 0 的情況,不進行分塊傳輸,直接將消息添加到輸出列表。private void encodeChunkedContent(ChannelHandlerContext ctx, Object msg, long contentLength, List<Object> out) {if (contentLength > 0) {String lengthHex = Long.toHexString(contentLength);Buffer buf = ctx.bufferAllocator().allocate(lengthHex.length() + 2);buf.writeCharSequence(lengthHex, StandardCharsets.US_ASCII);buf.writeShort(CRLF_SHORT);out.add(buf);out.add(encode(msg));out.add(crlfBuffer(ctx.bufferAllocator()));}if (msg instanceof LastHttpContent) {HttpHeaders headers = ((LastHttpContent<?>) msg).trailingHeaders();if (headers.isEmpty()) {out.add(zeroCrlfCrlfBuffer(ctx.bufferAllocator()));} else {Buffer buf = ctx.bufferAllocator().allocate((int) trailersEncodedSizeAccumulator);buf.writeMedium(ZERO_CRLF_MEDIUM);encodeHeaders(headers, buf);buf.writeShort(CRLF_SHORT);trailersEncodedSizeAccumulator = TRAILERS_WEIGHT_NEW * padSizeForAccumulation(buf.readableBytes()) +TRAILERS_WEIGHT_HISTORICAL * trailersEncodedSizeAccumulator;out.add(buf);}if (contentLength == 0) {// EmptyLastHttpContent or LastHttpContent with empty payload((LastHttpContent<?>) msg).close();}} else if (contentLength == 0) {out.add(encode(msg));}}// 在編碼消息之前清理其頭部,默認實現為空操作(noop),但可以根據需要進行擴展。protected void sanitizeHeadersBeforeEncode(@SuppressWarnings("unused") H msg, boolean isAlwaysEmpty) {// noop}// 判斷某些特殊消息(如 `HEAD` 或 `CONNECT` 請求)是否始終沒有消息體,以便跳過內容處理。protected boolean isContentAlwaysEmpty(@SuppressWarnings("unused") H msg) {return false;}// 判斷是否接受傳出的消息。// 檢查消息是否為 HttpObject、Buffer 或 FileRegion 類型,只有這些類型的消息才會被接受并繼續處理@Overridepublic boolean acceptOutboundMessage(Object msg) throws Exception {return msg instanceof HttpObject || msg instanceof Buffer || msg instanceof FileRegion;}private static Object encode(Object msg) {if (msg instanceof Buffer) {return msg;}if (msg instanceof HttpContent) {return ((HttpContent<?>) msg).payload();}if (msg instanceof FileRegion) {return msg;}Resource.dispose(msg);throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));}private static long contentLength(Object msg) {if (msg instanceof HttpContent) {return ((HttpContent<?>) msg).payload().readableBytes();}if (msg instanceof Buffer) {return ((Buffer) msg).readableBytes();}if (msg instanceof FileRegion) {return ((FileRegion) msg).count();}Resource.dispose(msg);throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));}// 為緩沖區增加一些額外的空間。其目的是避免內存過度分配和防止在需要時發生緩沖區擴展或復制private static int padSizeForAccumulation(int readableBytes) {return (readableBytes << 2) / 3;}// GET /index.html HTTP/1.1// HTTP/1.1 200 OKprotected abstract void encodeInitialLine(Buffer buf, H message) throws Exception;// \r\nprotected Buffer crlfBuffer(BufferAllocator allocator) {if (crlfBufferSupplier == null) {crlfBufferSupplier = allocator.constBufferSupplier(CRLF);}return crlfBufferSupplier.get();}// 0\r\n\r\nprotected Buffer zeroCrlfCrlfBuffer(BufferAllocator allocator) {if (zeroCrlfCrlfBufferSupplier == null) {zeroCrlfCrlfBufferSupplier = allocator.constBufferSupplier(ZERO_CRLF_CRLF);}return zeroCrlfCrlfBufferSupplier.get();}
}
HttpRequestEncoder
HttpRequestEncoder 類用于將 HTTP 請求消息(HttpRequest)編碼為符合 HTTP 協議規范的字節數據。它的主要任務是將請求的初始行(包括請求方法、URI 和協議版本)正確地編碼到 Buffer 中,并處理 URI 中可能存在的一些細節(如缺失的斜杠或查詢參數)。
/*** Encodes an {@link HttpRequest} or an {@link HttpContent} into a {@link Buffer}.*/
public class HttpRequestEncoder extends HttpObjectEncoder<HttpRequest> {private static final char SLASH = '/';private static final char QUESTION_MARK = '?';private static final short SLASH_AND_SPACE_SHORT = SLASH << 8 | SP;private static final int SPACE_SLASH_AND_SPACE_MEDIUM = SP << 16 | SLASH_AND_SPACE_SHORT;@Overridepublic boolean acceptOutboundMessage(Object msg) throws Exception {return super.acceptOutboundMessage(msg) && !(msg instanceof HttpResponse);}// GET /index.html HTTP/1.1@Overrideprotected void encodeInitialLine(Buffer buf, HttpRequest request) throws Exception {buf.writeCharSequence(request.method().asciiName(), StandardCharsets.US_ASCII);String uri = request.uri();if (uri.isEmpty()) {// Add " / " as absolute path if uri is not present.// See https://tools.ietf.org/html/rfc2616#section-5.1.2buf.writeMedium(SPACE_SLASH_AND_SPACE_MEDIUM);} else {CharSequence uriCharSequence = uri;boolean needSlash = false;int start = uri.indexOf("://");if (start != -1 && uri.charAt(0) != SLASH) {start += 3;// Correctly handle query params.// See https://github.com/netty/netty/issues/2732int index = uri.indexOf(QUESTION_MARK, start);if (index == -1) {if (uri.lastIndexOf(SLASH) < start) {needSlash = true;}} else {if (uri.lastIndexOf(SLASH, index) < start) {uriCharSequence = new StringBuilder(uri).insert(index, SLASH);}}}buf.writeByte(SP).writeCharSequence(uriCharSequence, StandardCharsets.UTF_8);if (needSlash) {// write "/ " after uribuf.writeShort(SLASH_AND_SPACE_SHORT);} else {buf.writeByte(SP);}}request.protocolVersion().encode(buf);buf.writeShort(CRLF_SHORT);}
}
HttpResponseEncoder
HttpResponseEncoder
類用于將 HTTP 響應消息(HttpResponse
)編碼為符合 HTTP 協議規范的字節數據。它的主要任務是將響應的狀態行(包括狀態碼、狀態描述和協議版本)編碼到 Buffer
中,并根據響應的狀態(如 204 No Content
或 304 Not Modified
)決定是否移除 Content-Length
和 Transfer-Encoding
等頭部信息,以確保響應符合 HTTP 規范。
/*** Encodes an {@link HttpResponse} or an {@link HttpContent} into a {@link Buffer}.*/
public class HttpResponseEncoder extends HttpObjectEncoder<HttpResponse> {@Overridepublic boolean acceptOutboundMessage(Object msg) throws Exception {return super.acceptOutboundMessage(msg) && !(msg instanceof HttpRequest);}// HTTP/1.1 200 OK@Overrideprotected void encodeInitialLine(Buffer buf, HttpResponse response) throws Exception {response.protocolVersion().encode(buf);buf.writeByte(SP);response.status().encode(buf);buf.writeShort(CRLF_SHORT);}// 在響應頭編碼前,對響應頭進行清理,去除不必要的 Content-Length 和 Transfer-Encoding 頭,// 特別是對于無內容的響應(如 204 No Content、304 Not Modified)或 205 Reset Content 響應。@Overrideprotected void sanitizeHeadersBeforeEncode(HttpResponse msg, boolean isAlwaysEmpty) {if (isAlwaysEmpty) {HttpResponseStatus status = msg.status();if (status.codeClass() == HttpStatusClass.INFORMATIONAL ||status.code() == HttpResponseStatus.NO_CONTENT.code()) {msg.headers().remove(HttpHeaderNames.CONTENT_LENGTH);msg.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);} else if (status.code() == HttpResponseStatus.RESET_CONTENT.code()) {msg.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);msg.headers().set(HttpHeaderNames.CONTENT_LENGTH, HttpHeaderValues.ZERO);}}}// 根據響應的狀態碼判斷該響應是否包含內容。// 如果是信息性狀態、204 No Content、304 Not Modified 或 205 Reset Content 等,返回 true 表示響應沒有內容;// 其他情況則返回 false,表示響應包含內容@Overrideprotected boolean isContentAlwaysEmpty(HttpResponse msg) {HttpResponseStatus status = msg.status();if (status.codeClass() == HttpStatusClass.INFORMATIONAL) {if (status.code() == HttpResponseStatus.SWITCHING_PROTOCOLS.code()) {return msg.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_VERSION);}return true;}return status.code() == HttpResponseStatus.NO_CONTENT.code() ||status.code() == HttpResponseStatus.NOT_MODIFIED.code() ||status.code() == HttpResponseStatus.RESET_CONTENT.code();}
}
HttpObjectDecoder
public abstract class HttpObjectDecoder extends ByteToMessageDecoder {public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;public static final int DEFAULT_MAX_HEADER_SIZE = 8192;public static final boolean DEFAULT_CHUNKED_SUPPORTED = true;public static final boolean DEFAULT_VALIDATE_HEADERS = true;public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;private final boolean chunkedSupported;protected final HttpHeadersFactory headersFactory;protected final HttpHeadersFactory trailersFactory;private final boolean allowDuplicateContentLengths;private final Buffer parserScratchBuffer;private final HeaderParser headerParser;private final LineParser lineParser;private HttpMessage message;private long chunkSize;private long contentLength = Long.MIN_VALUE;private boolean chunked;private boolean isSwitchingToNonHttp1Protocol;private final AtomicBoolean resetRequested = new AtomicBoolean();// These will be updated by splitHeader(...)private AsciiString name;private String value;private LastHttpContent<?> trailer;private State currentState = State.SKIP_CONTROL_CHARS;protected HttpObjectDecoder() {this(new HttpDecoderConfig());}protected HttpObjectDecoder(HttpDecoderConfig config) {headersFactory = config.getHeadersFactory();trailersFactory = config.getTrailersFactory();parserScratchBuffer = MemoryManager.unpooledHeap(config.getInitialBufferSize());lineParser = new LineParser(parserScratchBuffer, config.getMaxInitialLineLength());headerParser = new HeaderParser(parserScratchBuffer, config.getMaxHeaderSize());chunkedSupported = config.isChunkedSupported();allowDuplicateContentLengths = config.isAllowDuplicateContentLengths();}@Overrideprotected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception {try (parserScratchBuffer) {super.handlerRemoved0(ctx);}}@Overrideprotected void decode(ChannelHandlerContext ctx, Buffer buffer) throws Exception {if (resetRequested.get()) {resetNow();}switch (currentState) {case SKIP_CONTROL_CHARS:// Fall-throughcase READ_INITIAL: try {// 跳過前面錯誤的ASCII碼小于32的內容,解析第一行內容Buffer line = lineParser.parse(buffer);if (line == null) {return;}// 用于將初始行拆分成多個部分(如 HTTP 方法、URL 和協議版本)final String[] initialLine = splitInitialLine(line);assert initialLine.length == 3 : "initialLine::length must be 3";// 根據解析出的初始行創建消息對象message = createMessage(initialLine);currentState = State.READ_HEADER;// fall-through} catch (Exception e) {ctx.fireChannelRead(invalidMessage(ctx, message, buffer, e));return;}case READ_HEADER: try {// 解析 HTTP 頭部信息,根據頭部中的字段(如 Content-Length, Transfer-Encoding)決定解碼后續的內容// 如果沒有消息體(Content-Length == 0 或 -1 且是請求消息),則直接跳到下一步。// 如果是分塊傳輸編碼,跳轉到 READ_CHUNK_SIZE。// 否則,繼續讀取固定長度或可變長度的消息體。State nextState = readHeaders(buffer);if (nextState == null) {return;}currentState = nextState;switch (nextState) {case SKIP_CONTROL_CHARS:addCurrentMessage(ctx);ctx.fireChannelRead(new EmptyLastHttpContent(ctx.bufferAllocator()));resetNow();return;case READ_CHUNK_SIZE:if (!chunkedSupported) {throw new IllegalArgumentException("Chunked messages not supported");}// Chunked encoding - generate HttpMessage first. HttpChunks will follow.addCurrentMessage(ctx);return;default:if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {ctx.fireChannelRead(message);ctx.fireChannelRead(new EmptyLastHttpContent(ctx.bufferAllocator()));resetNow();return;}assert nextState == State.READ_FIXED_LENGTH_CONTENT ||nextState == State.READ_VARIABLE_LENGTH_CONTENT;addCurrentMessage(ctx);if (nextState == State.READ_FIXED_LENGTH_CONTENT) {chunkSize = contentLength;}return;}} catch (Exception e) {ctx.fireChannelRead(invalidMessage(ctx, message, buffer, e));return;}case READ_VARIABLE_LENGTH_CONTENT: {// 繼續讀取消息體,直到連接關閉。一般用于請求/響應沒有明確的 Content-Length 或使用了 Transfer-Encoding: chunked。int toRead = buffer.readableBytes();if (toRead > 0) {Buffer content = buffer.split();ctx.fireChannelRead(new DefaultHttpContent(content));}return;}case READ_FIXED_LENGTH_CONTENT: {// 讀取固定長度的消息體。如果讀取到的字節數與剩余的 chunkSize 相等,則解碼完成,跳轉到下一個階段。int toRead = buffer.readableBytes();if (toRead == 0) {return;}if (toRead > chunkSize) {toRead = (int) chunkSize;}Buffer content = buffer.readSplit(toRead);chunkSize -= toRead;if (chunkSize == 0) {// Read all content.ctx.fireChannelRead(new DefaultLastHttpContent(content, trailersFactory));resetNow();} else {ctx.fireChannelRead(new DefaultHttpContent(content));}return;}// 解析分塊傳輸編碼的塊大小。每個塊有自己的大小,解析出大小后進入 READ_CHUNKED_CONTENT 狀態case READ_CHUNK_SIZE: try {Buffer line = lineParser.parse(buffer);if (line == null) {return;}assert line.countComponents() == 1: "line should have exactly one component";try (var componentIterator = line.forEachComponent()) {var component = componentIterator.first();int chunkSize = getChunkSize(component.readableArray(),component.readableArrayOffset() + line.readerOffset(),line.readableBytes());this.chunkSize = chunkSize;if (chunkSize == 0) {currentState = State.READ_CHUNK_FOOTER;return;}currentState = State.READ_CHUNKED_CONTENT;}// fall-through} catch (Exception e) {ctx.fireChannelRead(invalidChunk(ctx.bufferAllocator(), buffer, e));return;}// 讀取分塊內容,根據解析出的塊大小讀取數據。如果讀取完成,跳到 READ_CHUNK_DELIMITER 狀態。case READ_CHUNKED_CONTENT: {assert chunkSize <= Integer.MAX_VALUE;int toRead = (int) chunkSize;toRead = Math.min(toRead, buffer.readableBytes());if (toRead == 0) {return;}HttpContent<?> chunk = new DefaultHttpContent(buffer.readSplit(toRead));chunkSize -= toRead;ctx.fireChannelRead(chunk);if (chunkSize != 0) {return;}currentState = State.READ_CHUNK_DELIMITER;// fall-through}// 處理分塊傳輸編碼中的塊分隔符(即 CRLF)。根據是否有剩余數據,決定是否繼續讀取下一個數據塊。case READ_CHUNK_DELIMITER: {// include LF in the bytes to skipint bytesToSkip = buffer.bytesBefore(HttpConstants.LF) + 1;if (bytesToSkip > 0) {currentState = State.READ_CHUNK_SIZE;buffer.skipReadableBytes(bytesToSkip);} else {buffer.skipReadableBytes(buffer.readableBytes());}return;}// 解析分塊傳輸編碼的尾部,通常是 HTTP 頭部中的 Trailer 信息。解析完成后重置狀態。case READ_CHUNK_FOOTER: try {LastHttpContent<?> trailer = readTrailingHeaders(ctx.bufferAllocator(), buffer);if (trailer == null) {return;}ctx.fireChannelRead(trailer);resetNow();return;} catch (Exception e) {ctx.fireChannelRead(invalidChunk(ctx.bufferAllocator(), buffer, e));return;}// 處理錯誤的消息。如果解碼過程中發生異常,會進入該狀態并丟棄數據。case BAD_MESSAGE: {// Keep discarding until disconnection.buffer.skipReadableBytes(buffer.readableBytes());break;}// 處理協議升級。在這種情況下,解碼器會將剩余的數據交給新的協議解碼器繼續處理。case UPGRADED: {int readableBytes = buffer.readableBytes();if (readableBytes > 0) {ctx.fireChannelRead(buffer.split());}break;}default:break;}}// ...
}
State
State
枚舉定義了 HTTP 消息解碼過程中各個階段的狀態。每個狀態代表了解碼器在解析 HTTP 消息時的不同步驟。
State | Description |
---|---|
SKIP_CONTROL_CHARS | 跳過控制字符(如回車、換行符等)。 |
READ_INITIAL | 讀取初始行(如請求行或響應行)。 |
READ_HEADER | 讀取頭部信息。 |
READ_VARIABLE_LENGTH_CONTENT | 讀取可變長度的消息體。 |
READ_FIXED_LENGTH_CONTENT | 讀取固定長度的消息體。 |
READ_CHUNK_SIZE | 讀取分塊傳輸編碼中的塊大小。 |
READ_CHUNKED_CONTENT | 讀取分塊傳輸編碼中的數據內容。 |
READ_CHUNK_DELIMITER | 讀取分塊傳輸編碼中的塊分隔符(CRLF)。 |
READ_CHUNK_FOOTER | 讀取分塊傳輸編碼的尾部(如結尾的 CRLF)。 |
BAD_MESSAGE | 解析過程中發生錯誤,無法繼續解碼。 |
UPGRADED | 協議已升級,用于處理協議升級。 |
這些狀態幫助控制解碼器在不同解析階段的行為,確保按正確的順序和格式解析 HTTP 消息。
private enum State {SKIP_CONTROL_CHARS,READ_INITIAL,READ_HEADER,READ_VARIABLE_LENGTH_CONTENT,READ_FIXED_LENGTH_CONTENT,READ_CHUNK_SIZE,READ_CHUNKED_CONTENT,READ_CHUNK_DELIMITER,READ_CHUNK_FOOTER,BAD_MESSAGE,UPGRADED
}
HeaderParser
HeaderParser
類用于解析 HTTP 請求或響應頭部的內容。它接收一個緩沖區 Buffer
,逐步解析其中的數據,并將解析結果存儲在 seq
中。它會根據最大長度 maxLength
限制頭部的解析大小,如果超過限制則拋出異常。該類還處理 CRLF(回車換行符)分隔符,確保解析到有效的頭部數據,并且能夠在頭部數據解析完畢時更新緩沖區的讀取位置。
private static class HeaderParser {protected final Buffer seq;protected final int maxLength;int size;HeaderParser(Buffer seq, int maxLength) {this.seq = seq;this.maxLength = maxLength;}public Buffer parse(Buffer buffer) {final int readableBytes = buffer.readableBytes();final int readerIndex = buffer.readerOffset();final int maxBodySize = maxLength - size;assert maxBodySize >= 0;// adding 2 to account for both CR (if present) and LF// don't remove 2L: it's key to cover maxLength = Integer.MAX_VALUEfinal long maxBodySizeWithCRLF = maxBodySize + 2L;final int toProcess = (int) Math.min(maxBodySizeWithCRLF, readableBytes);final int toIndexExclusive = readerIndex + toProcess;assert toIndexExclusive >= readerIndex;int toLf = buffer.bytesBefore(HttpConstants.LF);final int indexOfLf = readerIndex + toLf;if (toLf == -1) {if (readableBytes > maxBodySize) {// TODO: Respond with Bad Request and discard the traffic// or close the connection.// No need to notify the upstream handlers - just log.// If decoding a response, just throw an exception.throw newException(maxLength);}return null;}final int endOfSeqIncluded;if (indexOfLf > readerIndex && buffer.getByte(indexOfLf - 1) == HttpConstants.CR) {// Drop CR if we had a CRLF pairendOfSeqIncluded = indexOfLf - 1;} else {endOfSeqIncluded = indexOfLf;}final int newSize = endOfSeqIncluded - readerIndex;if (newSize == 0) {seq.resetOffsets();buffer.readerOffset(indexOfLf + 1);return seq;}int size = this.size + newSize;if (size > maxLength) {throw newException(maxLength);}this.size = size;seq.resetOffsets();seq.ensureWritable(newSize, newSize, false);buffer.copyInto(readerIndex, seq, 0, newSize);seq.writerOffset(newSize);buffer.readerOffset(indexOfLf + 1);return seq;}public void reset() {size = 0;}protected TooLongFrameException newException(int maxLength) {return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");}
}
LineParser
LineParser
類繼承自 HeaderParser
,用于解析 HTTP 請求或響應的單行(如請求行、狀態行或頭部行)。它首先跳過控制字符(如回車、換行等),然后調用父類 HeaderParser
解析有效的 HTTP 行。類中的 skipControlChars
方法負責跳過這些不需要的字符,并在必要時拋出異常。如果超出了最大長度 maxLength
,會拋出 TooLongHttpLineException
異常。
private final class LineParser extends HeaderParser {LineParser(Buffer seq, int maxLength) {super(seq, maxLength);}@Overridepublic Buffer parse(Buffer buffer) {// Suppress a warning because HeaderParser.reset() is supposed to be calledreset();final int readableBytes = buffer.readableBytes();if (readableBytes == 0) {return null;}final int readerIndex = buffer.readerOffset();if (currentState == State.SKIP_CONTROL_CHARS && skipControlChars(buffer, readableBytes, readerIndex)) {return null;}return super.parse(buffer);}private boolean skipControlChars(Buffer buffer, int readableBytes, int readerIndex) {assert currentState == State.SKIP_CONTROL_CHARS;final int maxToSkip = Math.min(maxLength, readableBytes);final int firstNonControlIndex = buffer.openCursor(readerIndex, maxToSkip).process(SKIP_CONTROL_CHARS_BYTES);if (firstNonControlIndex == -1) {buffer.skipReadableBytes(maxToSkip);if (readableBytes > maxLength) {throw newException(maxLength);}return true;}// from now on we don't care about control charsbuffer.readerOffset(readerIndex + firstNonControlIndex);currentState = State.READ_INITIAL;return false;}@Overrideprotected TooLongFrameException newException(int maxLength) {return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");}
}
HttpRequestDecoder
HttpRequestDecoder 類用于解碼 HTTP 請求消息(HttpRequest)并將其轉換為 HttpMessage 對象。它繼承自 HttpObjectDecoder,并通過解析請求行(包括請求方法、URI 和協議版本)以及請求頭(如 Host、Content-Type、Content-Length)來完成解碼過程。該類支持不同的 HTTP 方法(如 GET 和 POST)和協議版本(如 HTTP/1.0 和 HTTP/1.1)。它還能夠根據特定的頭部標識符對請求頭進行拆分,以便正確識別請求中的各個字段。通過 HttpDecoderConfig,它允許自定義解碼器的配置,如最大初始行長度和最大頭部大小。
public class HttpRequestDecoder extends HttpObjectDecoder {private static final AsciiString Accept = AsciiString.cached("Accept");private static final AsciiString Host = AsciiString.cached("Host");private static final AsciiString Connection = AsciiString.cached("Connection");private static final AsciiString ContentType = AsciiString.cached("Content-Type");private static final AsciiString ContentLength = AsciiString.cached("Content-Length");private static final int GET_AS_INT = (int) charsToLong("GET");private static final int POST_AS_INT = (int) charsToLong("POST");private static final long HTTP_1_1_AS_LONG = charsToLong("HTTP/1.1");private static final long HTTP_1_0_AS_LONG = charsToLong("HTTP/1.0");;private static final int HOST_AS_INT = (int) charsToLong("Host");;private static final long CONNECTION_AS_LONG_0 = charsToLong("Connecti");private static final short CONNECTION_AS_SHORT_1 = (short) charsToLong("on");private static final long CONTENT_AS_LONG = charsToLong("Content-");;private static final int TYPE_AS_INT = (int) charsToLong("Type");private static final long LENGTH_AS_LONG = charsToLong("Length");private static final long ACCEPT_AS_LONG = charsToLong("Accept");private static long charsToLong(String cs) {long result = cs.charAt(0);int shift = 0;for (int i = 1; i < cs.length(); i++) {result |= (long) cs.charAt(i) << (shift += 8);}return result;}public HttpRequestDecoder() {}public HttpRequestDecoder(int maxInitialLineLength, int maxHeaderSize) {super(new HttpDecoderConfig().setMaxInitialLineLength(maxInitialLineLength).setMaxHeaderSize(maxHeaderSize));}public HttpRequestDecoder(HttpDecoderConfig config) {super(config);}// [GET, /index.html, HTTP/1.1]@Overrideprotected HttpMessage createMessage(String[] initialLine) throws Exception {return new DefaultHttpRequest(// Do strict version checkingHttpVersion.valueOf(initialLine[2], true),HttpMethod.valueOf(initialLine[0]), initialLine[1], headersFactory);}// 解析 HTTP 請求中的頭部字段名(Header Name)@Overrideprotected AsciiString splitHeaderName(final byte[] sb, final int start, final int length) {// 獲取頭部字段的第一個字符final byte firstChar = sb[start];// 如果第一個字符是 'H',檢查是否是 "Host" 頭部字段if (firstChar == 'H') {if (length == 4 && isHost(sb, start)) {return Host;}} // 如果第一個字符是 'A',檢查是否是 "Accept" 頭部字段else if (firstChar == 'A') {if (length == 6 && isAccept(sb, start)) {return Accept;}} // 如果第一個字符是 'C',檢查是否是 "Connection"、"Content-Type" 或 "Content-Length" 頭部字段else if (firstChar == 'C') {if (length == 10) {if (isConnection(sb, start)) {return Connection;}} else if (length == 12) {if (isContentType(sb, start)) {return ContentType;}} else if (length == 14) {if (isContentLength(sb, start)) {return ContentLength;}}}// 如果沒有匹配的情況,調用父類方法來處理return super.splitHeaderName(sb, start, length);}// 解析 HTTP 請求的初始行,并識別出 HTTP 方法@Overrideprotected String splitFirstWordInitialLine(final byte[] sb, final int start, final int length) {if (length == 3) {if (isGetMethod(sb, start)) {return HttpMethod.GET.name();}} else if (length == 4) {if (isPostMethod(sb, start)) {return HttpMethod.POST.name();}}return super.splitFirstWordInitialLine(sb, start, length);}// 解析 HTTP 請求的初始行,并識別出 HTTP 版本@Overrideprotected String splitThirdWordInitialLine(final byte[] sb, final int start, final int length) {if (length == 8) {final long maybeHttp1_x = sb[start] |sb[start + 1] << 8 |sb[start + 2] << 16 |sb[start + 3] << 24 |(long) sb[start + 4] << 32 |(long) sb[start + 5] << 40 |(long) sb[start + 6] << 48 |(long) sb[start + 7] << 56;if (maybeHttp1_x == HTTP_1_1_AS_LONG) {return HttpVersion.HTTP_1_1_STRING;} else if (maybeHttp1_x == HTTP_1_0_AS_LONG) {return HttpVersion.HTTP_1_0_STRING;}}return super.splitThirdWordInitialLine(sb, start, length);}@Overrideprotected HttpMessage createInvalidMessage(ChannelHandlerContext ctx) {return new DefaultFullHttpRequest(HttpVersion.HTTP_1_0, HttpMethod.GET, "/bad-request",ctx.bufferAllocator().allocate(0), headersFactory, trailersFactory);}// 指示當前正在解析的是一個 HTTP 請求而不是響應@Overrideprotected boolean isDecodingRequest() {return true;}// 判斷消息類型來優化內容解析,對于 `DefaultHttpRequest` 返回 `false` 表示請求可能包含內容,而其他類型的消息則交由父類處理。@Overrideprotected boolean isContentAlwaysEmpty(final HttpMessage msg) {if (msg.getClass() == DefaultHttpRequest.class) {return false;}return super.isContentAlwaysEmpty(msg);}
}
HttpResponseDecoder
HttpResponseDecoder
類用于解析 HTTP 響應消息的初始行(版本、狀態碼和狀態描述)和頭部,生成對應的 HttpResponse
對象,并在解析失敗時返回一個無效的響應。
public class HttpResponseDecoder extends HttpObjectDecoder {private static final HttpResponseStatus UNKNOWN_STATUS = new HttpResponseStatus(999, "Unknown");public HttpResponseDecoder() {}public HttpResponseDecoder(int maxInitialLineLength, int maxHeaderSize) {super(new HttpDecoderConfig().setMaxInitialLineLength(maxInitialLineLength).setMaxHeaderSize(maxHeaderSize));}public HttpResponseDecoder(HttpDecoderConfig config) {super(config);}@Overrideprotected HttpMessage createMessage(String[] initialLine) {return new DefaultHttpResponse(// Do strict version checkingHttpVersion.valueOf(initialLine[0], true),HttpResponseStatus.valueOf(Integer.parseInt(initialLine[1]), initialLine[2]), headersFactory);}@Overrideprotected HttpMessage createInvalidMessage(ChannelHandlerContext ctx) {return new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, UNKNOWN_STATUS, ctx.bufferAllocator().allocate(0),headersFactory, trailersFactory);}@Overrideprotected boolean isDecodingRequest() {return false;}
}