protected override void DecodeLast(IChannelHandlerContext context, IByteBuffer input, List <object> output) { base.DecodeLast(context, input, output); if (SharedConstants.False < (uint)Volatile.Read(ref _resetRequested)) { // If a reset was requested by decodeLast() we need to do it now otherwise we may produce a // LastHttpContent while there was already one. ResetNow(); } // Handle the last unfinished message. if (_message is object) { bool chunked = HttpUtil.IsTransferEncodingChunked(_message); if (_currentState == State.ReadVariableLengthContent && !input.IsReadable() && !chunked) { // End of connection. output.Add(EmptyLastHttpContent.Default); ResetNow(); return; } if (_currentState == State.ReadHeader) { // If we are still in the state of reading headers we need to create a new invalid message that // signals that the connection was closed before we received the headers. output.Add(InvalidMessage(Unpooled.Empty, new PrematureChannelClosureException("Connection closed before received headers"))); ResetNow(); return; } // Check if the closure of the connection signifies the end of the content. bool prematureClosure; if (IsDecodingRequest() || chunked) { // The last request did not wait for a response. prematureClosure = true; } else { // Compare the length of the received content and the 'Content-Length' header. // If the 'Content-Length' header is absent, the length of the content is determined by the end of the // connection, so it is perfectly fine. prematureClosure = ContentLength() > 0; } if (!prematureClosure) { output.Add(EmptyLastHttpContent.Default); } ResetNow(); } }
State?ReadHeaders(IByteBuffer buffer) { IHttpMessage httpMessage = _message; HttpHeaders headers = httpMessage.Headers; AppendableCharSequence line = _headerParser.Parse(buffer); if (line is null) { return(null); } // ReSharper disable once ConvertIfDoToWhile if ((uint)line.Count > 0u) { do { byte firstChar = line.Bytes[0]; if (_name is object && (firstChar == c_space || firstChar == c_tab)) { //please do not make one line from below code //as it breaks +XX:OptimizeStringConcat optimization ICharSequence trimmedLine = CharUtil.Trim(line); _value = new AsciiString($"{_value} {trimmedLine}"); } else { if (_name is object) { _ = headers.Add(_name, _value); } SplitHeader(line); } line = _headerParser.Parse(buffer); if (line is null) { return(null); } } while ((uint)line.Count > 0u); } // Add the last header. if (_name is object) { _ = headers.Add(_name, _value); } // reset name and value fields _name = null; _value = null; var values = headers.GetAll(HttpHeaderNames.ContentLength); uint contentLengthValuesCount = (uint)values.Count; if (contentLengthValuesCount > 0u) { // Guard against multiple Content-Length headers as stated in // https://tools.ietf.org/html/rfc7230#section-3.3.2: // // If a message is received that has multiple Content-Length header // fields with field-values consisting of the same decimal value, or a // single Content-Length header field with a field value containing a // list of identical decimal values (e.g., "Content-Length: 42, 42"), // indicating that duplicate Content-Length header fields have been // generated or combined by an upstream message processor, then the // recipient MUST either reject the message as invalid or replace the // duplicated field-values with a single valid Content-Length field // containing that decimal value prior to determining the message body // length or forwarding the message. if (contentLengthValuesCount > 1u && httpMessage.ProtocolVersion == HttpVersion.Http11) { ThrowHelper.ThrowArgumentException_Multiple_Content_Length_Headers_Found(); } if (!long.TryParse(values[0].ToString(), out _contentLength)) { ThrowHelper.ThrowArgumentException_Invalid_Content_Length(); } } if (IsContentAlwaysEmpty(httpMessage)) { HttpUtil.SetTransferEncodingChunked(httpMessage, false); return(State.SkipControlChars); } else if (HttpUtil.IsTransferEncodingChunked(httpMessage)) { if (contentLengthValuesCount > 0u && httpMessage.ProtocolVersion == HttpVersion.Http11) { HandleTransferEncodingChunkedWithContentLength(httpMessage); } return(State.ReadChunkSize); } else if (ContentLength() >= 0L) { return(State.ReadFixedLengthContent); } else { return(State.ReadVariableLengthContent); } }
protected override void Encode(IChannelHandlerContext context, object message, List <object> output) { IByteBuffer buf = null; if (message is IHttpMessage) { if (this.state != StInit) { throw new InvalidOperationException($"unexpected message type: {StringUtil.SimpleClassName(message)}"); } var m = (T)message; buf = context.Allocator.Buffer((int)this.headersEncodedSizeAccumulator); // Encode the message. this.EncodeInitialLine(buf, m); this.state = this.IsContentAlwaysEmpty(m) ? StContentAlwaysEmpty : HttpUtil.IsTransferEncodingChunked(m) ? StContentChunk : StContentNonChunk; this.SanitizeHeadersBeforeEncode(m, this.state == StContentAlwaysEmpty); this.EncodeHeaders(m.Headers, buf); buf.WriteShort(HttpConstants.CrlfShort); this.headersEncodedSizeAccumulator = HeadersWeightNew * PadSizeForAccumulation(buf.ReadableBytes) + HeadersWeightHistorical * this.headersEncodedSizeAccumulator; } // 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 (message is IByteBuffer potentialEmptyBuf) { if (!potentialEmptyBuf.IsReadable()) { output.Add(potentialEmptyBuf.Retain()); return; } } if (message is IHttpContent || message is IByteBuffer || message is IFileRegion) { switch (this.state) { case StInit: throw new InvalidOperationException($"unexpected message type: {StringUtil.SimpleClassName(message)}"); case StContentNonChunk: long contentLength = ContentLength(message); if (contentLength > 0) { if (buf != null && buf.WritableBytes >= contentLength && message is IHttpContent) { // merge into other buffer for performance reasons buf.WriteBytes(((IHttpContent)message).Content); output.Add(buf); } else { if (buf != null) { output.Add(buf); } output.Add(EncodeAndRetain(message)); } if (message is ILastHttpContent) { this.state = StInit; } break; } goto case StContentAlwaysEmpty; // fall-through! case StContentAlwaysEmpty: // ReSharper disable once ConvertIfStatementToNullCoalescingExpression if (buf != null) { // We allocated a buffer so add it now. output.Add(buf); } else { // Need to produce some output otherwise an // IllegalStateException will be thrown as we did not write anything // Its ok to just write an EMPTY_BUFFER as if there are reference count issues these will be // propagated as the caller of the encode(...) method will release the original // buffer. // Writing an empty buffer will not actually write anything on the wire, so if there is a user // error with msg it will not be visible externally output.Add(Unpooled.Empty); } break; case StContentChunk: if (buf != null) { // We allocated a buffer so add it now. output.Add(buf); } this.EncodeChunkedContent(context, message, ContentLength(message), output); break; default: throw new EncoderException($"unexpected state {this.state}: {StringUtil.SimpleClassName(message)}"); } if (message is ILastHttpContent) { this.state = StInit; } } else if (buf != null) { output.Add(buf); } }
State?ReadHeaders(IByteBuffer buffer) { IHttpMessage httpMessage = this.message; HttpHeaders headers = httpMessage.Headers; AppendableCharSequence line = this.headerParser.Parse(buffer); if (line == null) { return(null); } // ReSharper disable once ConvertIfDoToWhile if (line.Count > 0) { do { byte firstChar = line.Bytes[0]; if (this.name != null && (firstChar == ' ' || firstChar == '\t')) { ICharSequence trimmedLine = CharUtil.Trim(line); this.value = new AsciiString($"{this.value} {trimmedLine}"); } else { if (this.name != null) { headers.Add(this.name, this.value); } this.SplitHeader(line); } line = this.headerParser.Parse(buffer); if (line == null) { return(null); } } while (line.Count > 0); } // Add the last header. if (this.name != null) { headers.Add(this.name, this.value); } // reset name and value fields this.name = null; this.value = null; State nextState; if (this.IsContentAlwaysEmpty(httpMessage)) { HttpUtil.SetTransferEncodingChunked(httpMessage, false); nextState = State.SkipControlChars; } else if (HttpUtil.IsTransferEncodingChunked(httpMessage)) { nextState = State.ReadChunkSize; } else if (this.ContentLength() >= 0) { nextState = State.ReadFixedLengthContent; } else { nextState = State.ReadVariableLengthContent; } return(nextState); }