예제 #1
0
        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();
            }
        }
예제 #2
0
        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);
            }
        }
예제 #3
0
        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);
            }
        }
예제 #4
0
        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);
        }