locked
Web API OWIN Host - Limit Request Size RRS feed

All replies

  • User1066278571 posted

    Don't know if anything comes out of the box, but this person made a NuGet package using MessageHandlers which does what you want.

    https://www.nuget.org/packages/WebApiThrottle/

    greetings Damien

    Friday, February 7, 2014 5:15 PM
  • User1594254982 posted

    Following is one way of doing it.

    Some notes:

    Here I am handling 2 scenarios. One where the Content-Length header value is present. In this case the check is easy to see if the request body content exceeds the max value.   The other scenario handles chunked requests where Content-Length header is NOT present. Here I am registering this middleware before the Web API middleware.

    NOTE: There is one flaw in this technique that if a request is not intended for Web API and to some other middleware after Web API, then these limits would apply to them too. But if yours is not this kind of case (which is for majority of the cases), then you need not worry about it.

        public void Configuration(IAppBuilder appBuilder)
        {
            appBuilder.Use(typeof(RequestSizeLimitingMiddleware), (long)100); //Example: limiting the request size to 100 bytes here. You can change it later.
    
            var config = new HttpConfiguration();
            config.MapHttpAttributeRoutes();
            config.EnsureInitialized();
    
            appBuilder.UseWebApi(config);
        }
    
        public class RequestSizeLimitingMiddleware : OwinMiddleware
        {
            public RequestSizeLimitingMiddleware(OwinMiddleware next, long maxRequestSizeInBytes)
                : base(next) 
            {
                this.MaxRequestSizeInBytes = maxRequestSizeInBytes;
            }
    
            public long MaxRequestSizeInBytes { get; private set; }
    
            public override async Task Invoke(IOwinContext context)
            {
                IOwinRequest request = context.Request;
    
                if (request != null)
                {
                    string[] values = null;
                    if (request.Headers.TryGetValue("Content-Length", out values))
                    {
                        if (Convert.ToInt64(values[0]) > MaxRequestSizeInBytes)
                        {
                            throw new InvalidOperationException(string.Format("Request size exceeds the allowed maximum size of {0} bytes", MaxRequestSizeInBytes));
                        }
                    }
                    if (request.Headers.TryGetValue("Transfer-Encoding", out values)
                        && values[0] == "chunked")
                    {
                        RequestSizeLimitingStream wrappingStream = new RequestSizeLimitingStream(request.Body, MaxRequestSizeInBytes);
    
                        request.Body = wrappingStream;
                    }
                }
    
                await Next.Invoke(context);
            }
        }
            
        public class RequestSizeLimitingStream : Stream
        {
            private Stream _innerStream;
            private long totalBytesReadCount = 0;
            private long maxRequestSizeInBytes = 0;
    
            public RequestSizeLimitingStream(Stream innerStream, long maxReceivedMessageSize)
            {
                _innerStream = innerStream;
                this.maxRequestSizeInBytes = maxReceivedMessageSize;
            }
    
            protected Stream InnerStream
            {
                get { return _innerStream; }
            }
    
            public override bool CanRead
            {
                get { return _innerStream.CanRead; }
            }
    
            public override bool CanSeek
            {
                get { return _innerStream.CanSeek; }
            }
    
            public override bool CanWrite
            {
                get { return _innerStream.CanWrite; }
            }
    
            public override long Length
            {
                get { return _innerStream.Length; }
            }
    
            public override long Position
            {
                get { return _innerStream.Position; }
                set { _innerStream.Position = value; }
            }
    
            public override int ReadTimeout
            {
                get { return _innerStream.ReadTimeout; }
                set { _innerStream.ReadTimeout = value; }
            }
    
            public override bool CanTimeout
            {
                get { return _innerStream.CanTimeout; }
            }
    
            public override int WriteTimeout
            {
                get { return _innerStream.WriteTimeout; }
                set { _innerStream.WriteTimeout = value; }
            }
    
            protected override void Dispose(bool disposing)
            {
                if (disposing)
                {
                    _innerStream.Dispose();
                }
                base.Dispose(disposing);
            }
    
            public override long Seek(long offset, SeekOrigin origin)
            {
                return _innerStream.Seek(offset, origin);
            }
    
            public override int Read(byte[] buffer, int offset, int count)
            {
                int currentNumberOfBytesRead = _innerStream.Read(buffer, offset, count);
    
                ValidateRequestSize(currentNumberOfBytesRead);
    
                return currentNumberOfBytesRead;
            }
    
            public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
            {
                int currentNumberOfBytesRead = await _innerStream.ReadAsync(buffer, offset, count, cancellationToken);
    
                ValidateRequestSize(currentNumberOfBytesRead);
    
                return currentNumberOfBytesRead;
            }
    
            public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
            {
                return _innerStream.BeginRead(buffer, offset, count, callback, state);
            }
    
            public override int EndRead(IAsyncResult asyncResult)
            {
                int currentNumberOfBytesRead = _innerStream.EndRead(asyncResult);
    
                ValidateRequestSize(currentNumberOfBytesRead);
    
                return currentNumberOfBytesRead;
            }
    
            public override int ReadByte()
            {
                int currentNumberOfBytesRead = _innerStream.ReadByte();
    
                ValidateRequestSize(currentNumberOfBytesRead);
    
                return currentNumberOfBytesRead;
            }
    
            public override void Flush()
            {
                _innerStream.Flush();
            }
    
            public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
            {
                return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken);
            }
    
            public override Task FlushAsync(CancellationToken cancellationToken)
            {
                return _innerStream.FlushAsync(cancellationToken);
            }
    
            public override void SetLength(long value)
            {
                _innerStream.SetLength(value);
            }
    
            public override void Write(byte[] buffer, int offset, int count)
            {
                _innerStream.Write(buffer, offset, count);
            }
    
            public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
            {
                return _innerStream.WriteAsync(buffer, offset, count, cancellationToken);
            }
    
            public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
            {
                return _innerStream.BeginWrite(buffer, offset, count, callback, state);
            }
    
            public override void EndWrite(IAsyncResult asyncResult)
            {
                _innerStream.EndWrite(asyncResult);
            }
    
            public override void WriteByte(byte value)
            {
                _innerStream.WriteByte(value);
            }
    
            private void ValidateRequestSize(int currentNumberOfBytesRead)
            {
                totalBytesReadCount += currentNumberOfBytesRead;
    
                if (totalBytesReadCount > maxRequestSizeInBytes)
                {
                    throw new InvalidOperationException(string.Format("Request size exceeds the allowed maximum size of {0} bytes", maxRequestSizeInBytes));
                }
            }
        }

    Friday, February 7, 2014 6:32 PM
  • User-1656591598 posted

    Hi Kiran,

    Thank you for the reply.  This seems to work as I wanted.  However, in the case which handles the chunked requests, in the RequestSizeLimitingStream class, you throw an exception in the ValidateRequestSize function.  This ultimately returns a HTTP 500 error as the HTTP response.  I would like to send back a custom error.  How can I do this?

    I tried experimenting with an ExceptionFilter on the HttpConfiguration object in my start up class, but, this does not seem to catch the exception.

    Any ideas on how to do this?

    Thanks

    Tuesday, February 11, 2014 2:55 PM
  • User1594254982 posted

     I would like to send back a custom error.  How can I do this?

    You could instaed throw an HttpResponseException having the custom error message.

    I tried experimenting with an ExceptionFilter on the HttpConfiguration object in my start up class, but, this does not seem to catch the exception.

    This would depend on where you are trying to read the request stream. For example, if you are reading the request stream inside an action or action filter, then I would expect the exception filter to have been invoked...but if you are reading at a message handler, then exception filters do not come into picture as they sit in higher layers of the stack...

    Tuesday, February 11, 2014 5:38 PM
  • User-1656591598 posted

    Kiran,

    I tried to throw the HttpResponseException and still received a 500 error.

    For example, in the RequestSizeLimitingMiddleware class, in the Invoke method, I wanted to quickly test this, so I hardcoded it to throw the exception:

    throw new HttpResponseException(new HttpResponseMessage(System.Net.HttpStatusCode.BadRequest));

    I issued a request via Fiddler, and I get back a 500 error still.

    Basically, in your sample code, wherever you throw the InvalidOperationException, I'd like to return a different HTTP status code than 500.

    Any other ideas?

    Tuesday, February 11, 2014 8:02 PM
  • User601250725 posted

    Be warned ye who enters here. First, thanks for the sample code. There doesn't seem to be much of anything else around for this.

    There's a bug in the implementation though that I thought I should point out.

    The issue is with the way the ReadByte method works. It does not return a byte count, it returns the value of the read byte or -1 (if end of stream).

    public override int ReadByte()
    {
      int value = _innerStream.ReadByte();
      ThowIfMaxRequestMessageSize(value != -1 ? 1 : 0);
      return value;
    }

    The above code is correct.

    Here we could actually run into the request message size limit simply by reading a lot of large byte values successively. It's very unlikely that the stream will be used like this but if that happens you'll have these issues. Moreover, there are more subtle issues with the way a forward reading stream can operate that can cause issues here. So I humbly suggest to anyone using this code to test it thoroughly in the situations you will be using it.

    Wednesday, December 9, 2015 3:49 PM