[netty5: HttpObjectEncoder HttpObjectDecoder]-源碼解析

在閱讀該篇文章之前,推薦先閱讀以下內容:

  1. [netty5: HttpObject]-源碼解析
  2. [netty5: MessageToMessageCodec & MessageToMessageEncoder & MessageToMessageDecoder]-源碼分析
  3. [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 Content304 Not Modified)決定是否移除 Content-LengthTransfer-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 消息時的不同步驟。

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/87956.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/87956.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/87956.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

uniapp的navigator跳轉功能

接下來&#xff0c;我將圍繞一個常見的電商小程序來構建一系列連貫的使用場景。在這個過程中&#xff0c;我們將把 <navigator> 組件的所有關鍵屬性和方法都串聯起來&#xff0c;并詳細解釋它們在每個環節所扮演的角色和作用。 核心場景&#xff1a;構建一個電商小程序的…

v-for的用法及案例

目錄 一.v-for的用法 1.舉例1 2.舉例2 二.購物車案例 1.代碼 2.存在的問題&#xff1a;復選框錯位 3.解決方案&#xff1a; 賦值給key屬性一個唯一的值 一.v-for的用法 1.舉例1 <template><view><view v-for"(item,index) in 10" :key"…

BigQuery對象引用(ObjectRef)全面指南:一站式整合結構化與非結構化多模態數據分析

引言 企業需要同時管理有組織表格中的結構化數據&#xff0c;以及日益增長的非結構化數據&#xff08;如圖片、音頻和文檔&#xff09;。傳統上&#xff0c;聯合分析這些多樣化數據類型非常復雜&#xff0c;通常需要使用不同的工具。非結構化媒體通常需要導出到專門的服務進行…

【開源品鑒】FRP源碼閱讀

frp 是一款高性能的反向代理應用&#xff0c;專注于內網穿透&#xff0c;支持多種協議和 P2P 通信功能&#xff0c;目前在 GitHub 上已有 80k 的 star。本文將深入探討其源碼&#xff0c;揭示其背后的實現原理。1. 前言 frp 是一款高性能的反向代理應用&#xff0c;專注于內網…

day048-系統負載高排查流程與前后端分離項目

文章目錄 0. 老男孩思想1. 系統負載高排查流程1.1 進程/線程相關命令1.1.1 jps1.1.2 jstack1.1.3 jmap1.1.4 top -Hp pid 1.2 排查流程圖 2. 前后端分離項目2.1 項目說明2.2 負載均衡2.3 數據庫配置2.3.1 安裝數據庫服務2.3.2 配置數據庫環境 2.4 后端配置2.5 四層負載均衡配置…

Spring Boot 牽手EasyExcel:解鎖高效數據處理姿勢

引言 在日常的 Java 開發中&#xff0c;處理 Excel 文件是一個極為常見的需求。無論是數據的導入導出&#xff0c;還是報表的生成&#xff0c;Excel 都扮演著重要的角色。例如&#xff0c;在企業的財務管理系統中&#xff0c;需要將每月的財務數據導出為 Excel 報表&#xff0…

【ARM AMBA AXI 入門 21 -- AXI partial 訪問和 narrow 訪問的區別】

文章目錄 Overview一、定義區別二、AXI 信號層面對比三、舉例說明示例一:Partial Access示例二:Narrow Access四、硬件/系統處理角度五、AXI 總線接口信號舉例對比Partial Write 事務:Narrow Write 事務(32-bit Master on 64-bit Bus):六、總結對比表七,軟件判斷判斷 Pa…

使用Ideal創建一個spring boot的helloWorld項目

說明&#xff1a;本篇將介紹如何使用Ideal2024.2.1去創建一個spring boot的helloWorld項目&#xff0c;本篇將包含創建的詳細步驟以及spring boot項目的目錄結構說明&#xff0c;創建過程中的選項說明等。詳細步驟如下&#xff1a;第一步&#xff1a;點擊文件——新建——項目&…

國內Ubuntu訪問不了github等外網

各位小伙伴們&#xff0c;大家好呀。 大家是不是經常遇到訪問不了外網的情況呀。 在Ubuntu中可以這樣做。 訪問這個網站網站測速-Ping檢測-Trace查詢-Dig查詢-路由跟蹤查詢-tools.ipip.net&#xff0c; 對于github.com&#xff0c;在這個網站輸入github.com&#xff0c;會返…

PDF轉換工具,即開即用

在辦公室里&#xff0c;這句話被反復驗證。每天面對成堆的Word和Excel文件&#xff0c;將它們轉換成PDF格式是常有的事。可之前用過的工具&#xff0c;不是一次只能轉一個&#xff0c;就是操作繁瑣得讓人頭疼。記得有次趕項目&#xff0c;需要把二十多個文檔轉成PDF&#xff0c…

2. 你可以說一下 http 版本的發展過程嗎

你可以說一下 http 版本的發展過程嗎 總結&#xff1a;0.9&#xff1a;只能發送 get&#xff0c;無狀態。1.0&#xff1a;新增 post&#xff0c;請求頭&#xff0c;狀態碼&#xff0c;cookie。1.1&#xff1a;新增 put/delete/options/patch&#xff0c;keep-alive&#xff0c…

04-Linux驅動模塊的自動加載

概述 上一節&#xff0c;我們講述了Linux驅動開發的基本的模塊代碼編寫和手動執行模塊加載的操作&#xff0c; 這一節&#xff0c;我們講述嵌入式設備上使用Sysvint引導方式下如何開機自動加載模塊的步驟。感興趣的同學看下使用systemd引導方式的開啟自動加載模塊的步驟 操作…

【牛客算法】游游的整數切割

文章目錄 一、題目介紹1.1 題目鏈接1.2 題目描述1.3 輸入描述1.4 輸出描述1.5 示例二、解題思路2.1 核心算法設計2.2 性能優化關鍵2.3 算法流程圖三、解法實現3.1 解法一:基礎遍歷法3.1.1 初級版本分析3.2 解法二:奇偶預統計法(推薦)3.2.1 優化版本分析四、總結與拓展4.1 關…

筆記本電腦忽亮忽暗問題

關于筆記本電腦忽亮忽暗的問題這個問題困擾了我大半年&#xff0c;最后忽然找到解決方法了---主要的話有三種可能性1.關閉顯示器自動調亮的功能2.關閉節能模式自動調亮功能3.調整顯卡的功率&#xff0c;關閉自動調亮功能一開始一直都是嘗試的第一種方法&#xff0c;沒解決。。。…

Qt的頂部工具欄在多個界面使用

Qt的工具欄在多個界面使用1、前言2、創建一個工具欄類2.1 新建一個工具欄類3、提升工具欄類3.1登錄界面添加工具欄3.2 創建工具欄對象4、總結1、前言 今天遇到了個問題&#xff0c;頂部的工具欄&#xff0c;像軟鍵盤&#xff0c;時間顯示和退出按鈕那些&#xff0c;想在多個界…

C#和SQL Server連接常用通訊方式

C#和SQL Server連接通訊 在 C# 中與 SQL Server 建立數據庫連接&#xff0c;主要通過 ADO.NET 技術實現。以下是幾種常見的連接方式及相關實踐&#xff1a; ADO.NET 全面指南&#xff1a;C# 數據庫訪問核心技術 ADO.NET 是 .NET Framework 中用于數據訪問的核心組件&#xf…

安卓10.0系統修改定制化____實現自動開啟 USB 調試?的步驟解析 列舉常用的幾種修改方法

對于安卓開發者、測試人員,甚至是喜歡折騰手機的數碼愛好者來說,USB 調試是一個非常重要的功能。它能讓手機與電腦相連,實現應用安裝、系統調試、數據傳輸等操作。但每次連接手機都要手動去設置里開啟 USB 調試,實在麻煩。其實,通過修改安卓 10.0 的 ROM,就能讓手機自動開…

Redisson詳細教程 - 從入門到精通

目錄 1. 什么是Redisson 2. 為什么要用Redisson 3. 環境準備和配置 4. 基礎使用方法 5. 分布式數據結構 6. 分布式鎖詳解 7. 分布式服務 8. 實際應用場景 9. 最佳實踐 10. 常見問題解答 總結 1. 什么是Redisson 簡單理解 想象一下,Redis就像一個超級強大的"內…

動態規劃VS記憶化搜索(2)

luoguP1434滑雪 題目描述 Michael 喜歡滑雪。這并不奇怪&#xff0c;因為滑雪的確很刺激。可是為了獲得速度&#xff0c;滑的區域必須向下傾斜&#xff0c;而且當你滑到坡底&#xff0c;你不得不再次走上坡或者等待升降機來載你。Michael 想知道在一個區域中最長的滑坡。區域由…

如何將服務守護進程化

進程組 什么是進程組 之前我們提到了進程的概念&#xff0c; 其實每一個進程除了有一個進程 ID(PID)之外 還屬于一個進程組。進程組是一個或者多個進程的集合&#xff0c; 一個進程組可以包含多個進程。 每一個進程組也有一個唯一的進程組 ID(PGID)&#xff0c; 并且這個 PGID …