400 Bad Request on large requests

Topics: Web Api
Jul 22, 2011 at 1:03 PM

I have some requests that send a lot of data and I am getting 400 Bad Request responses.

How do I increase the request limit for WCF Web API?

Jul 22, 2011 at 3:28 PM

Here is a post on it on stackoverflow http://stackoverflow.com/questions/6462571/c-wcf-web-api-4-maxreceivedmessagesize/6473839#6473839  My answers shows how to configure it if you are self-hosting.

Jul 22, 2011 at 9:40 PM

Thanks for replying but I'm hosting in IIS7.

How do you increase MaxReceivedMessageSize in IIS7?

Jul 26, 2011 at 6:46 AM

Here is an example of how you do it - http://pastebin.com/CNjyMuhL

Thanks to Jeremy Boyd.

Jul 26, 2011 at 7:50 PM

I'm having the exact same issue but if I do as Jeremy suggested I get a "HttpException, Error while copying content to a stream" in my C# client. The client is using the Web Api HttpClient for posting data to a Web Api-based service. 

The data I'm trying to post i about 400 KB and I've set the MaxReceivedMessageSize to 1024KB. Could this be an issue with the HttpClient? 

 

Best regards, 
Benny Olsson 

Coordinator
Jul 26, 2011 at 9:50 PM

Benny, can you show the stack trace or send a repro illustrating the problem.

Thanks

Glenn

Jul 26, 2011 at 9:52 PM

I debugged this a bit further by stepping thru HttpClient.Post() using the Web Api Preview 4 source code. The error I get is "Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host." and it appears in SerializeToStream() of the StreamContent-class. If I try the exact same call, but with less data, it works as expected. The payload when it fails is about 400KB. 

Regards, 
Benny Olsson 

Jul 26, 2011 at 9:57 PM

Stack trace is: 

> Microsoft.Net.Http.dll!System.Net.Http.HttpContent.GetStreamCopyException(System.Exception originalException) Line 490 C# 
Microsoft.Net.Http.dll!System.Net.Http.HttpContent.CopyTo(System.IO.Stream stream, System.Net.TransportContext context) Line 198 + 0x8 bytes C# 
Microsoft.Net.Http.dll!System.Net.Http.HttpClientChannel.Send(System.Net.Http.HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) Line 615 + 0x23 bytes C# 
Microsoft.Net.Http.dll!System.Net.Http.HttpClient.Send(System.Net.Http.HttpRequestMessage request, System.Net.Http.HttpCompletionOption completionOption, System.Threading.CancellationToken cancellationToken) Line 174 + 0x31 bytes C#
Microsoft.Net.Http.dll!System.Net.Http.HttpClient.Send(System.Net.Http.HttpRequestMessage request) Line 141 + 0x2c bytes C# 
Microsoft.Net.Http.dll!System.Net.Http.HttpClient.Post(System.Uri requestUri, System.Net.Http.HttpContent content) Line 337 + 0xb bytes C# 
icrosoft.Net.Http.dll!System.Net.Http.HttpClient.Post(string requestUri, System.Net.Http.HttpContent content) Line 344 + 0xd bytes C# 
Unicorn Client Synchronization Service.exe!Unicorn_Client_Synchronization_Service.Form1.Send(System.Uri uri, System.IO.MemoryStream content) Line 264 + 0x13 bytes C# 
Unicorn Client Synchronization Service.exe!Unicorn_Client_Synchronization_Service.Form1.SendCustomersToServer() Line 255 + 0x32 bytes C# 
Unicorn Client Synchronization Service.exe!Unicorn_Client_Synchronization_Service.Form1.btnControl_Click(object sender, System.EventArgs e) Line 34 + 0x8 bytes C#
...

 

The GetStreamCopyException is thrown after catching an IOException() in HttpContent.CopyTo(). The Exception was generated after a call to StreamContent.SerializeToStream().

Jul 26, 2011 at 10:01 PM

Regarding repro;

Create a memoryStream containing ~400KB of data. initialize a HttpContent of type StreamContent() and feed it with the memorystream. Call HttpClient.Post() and post it to a Web Api based web service that exposes a simple

[WebInvoke(Method="POST", UriTemplate="")]
public HttpResponseMessage Post(HttpRequestMessage req){}

Jul 27, 2011 at 10:01 PM

Has anyone found a solution to this?

Coordinator
Jul 27, 2011 at 10:13 PM

I am looking into this with our folks that own the HttpClient, I don't have one yet.

Jul 27, 2011 at 10:23 PM

You know..maybe I have a different problem. I get the same error on the server side. when calling: request.Content.ReadAsString()

At first I was getting a 400 when posting a large message through fiddler (75k for example).  Then I did something like:

((HttpBinding)host.Description.Endpoints[0].Binding).MaxReceivedMessageSize = int.MaxValue;

And the request "goes through", but when I try the Content.ReadAsString() I get the message:

Cannot write more bytes to the buffer than the configured maximum buffer size: 65536

Jul 27, 2011 at 11:51 PM

I tried the following...but there were no results. 

public class HttpsServiceHostFactory : HttpConfigurableServiceHostFactory {

    public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses) {
        
        var host = base.CreateServiceHost(constructorString, baseAddresses);
                    
        foreach (var httpBinding in from serviceEndpoint in host.Description.Endpoints
                                    where serviceEndpoint.ListenUri.Scheme == "https"
                                    select (HttpBinding)serviceEndpoint.Binding) {

            httpBinding.Security.Mode = HttpBindingSecurityMode.Transport;
            httpBinding.MaxReceivedMessageSize = int.MaxValue;
        }

        foreach (var httpBinding in host.Description.Endpoints) {
            ((HttpBinding)httpBinding.Binding).MaxReceivedMessageSize = int.MaxValue;
        }
        
        return host;

    }
    
}
Does anyone know if I'm on the right track?  I see people with a similar problem in WCF, but the solution is always to add a config element to a specific binding name.  Would this accomplish the same thing?

Jul 28, 2011 at 4:24 PM

It looks like I'm getting this error because of the System.Net.Http.HttpContent has:

internal const int DefaultMaxBufferSize = 65536; // 64KB

Is there a way for me to override this at all?  Are there any options for excepting larger requests?

Jul 28, 2011 at 5:49 PM

So the problem ended up being in the request.Content.ReadAsString() method I was using for some logging.

I ended up reading straight from the stream and now everything is functioning properly.

Coordinator
Jul 28, 2011 at 5:59 PM

Glad you solved it! Would be a good blog post.

Glenn

Jul 28, 2011 at 6:10 PM
Edited Jul 28, 2011 at 7:04 PM

If I had a blog maybe...but here is what I did to get the contents of an HttpContent object.  Hopefully this helps someone else:

private static string getHttpContent(HttpContent content) {

            string contentMeat = string.Empty;

            if (content == null)
                return contentMeat;

            try {

                if (content.ContentReadStream != null && content.ContentReadStream.CanRead) {

                    var currentPosition = content.ContentReadStream.Position;
                    
                    // Set it to the beginning so that we can read the entire stream.
                    content.ContentReadStream.Position = 0;

                    using (var wrapper = new NonClosingStreamWrapper(content.ContentReadStream)) {
                        using (StreamReader reader = new StreamReader(wrapper))
                            contentMeat = reader.ReadToEnd();
                    }

                    content.ContentReadStream.Position = currentPosition;

                }

            } catch (Exception ex) {
                contentMeat = "Error Reading Stream: " + ex.Message;
            }

            return contentMeat;

        }

Please let me know if there is a better way of doing this.

 

Jul 28, 2011 at 8:17 PM

I'm happy to say that the solution posted by JamesNK fixed my problem. I was on the right track by changing MaxReceivedMessageSize but there were a few implementation details that I had done wrong. Namely, the double null-arguments when calling Routes.MapServiceRoute with a custom ServiceHostFactory. 

Safe to say, there were no error in the HttpClient that related to this issue.

 

All in all, problem solved. Here is how I did it. 

RouteTable.Routes.MapServiceRoute<CacheResource, CacheServiceHostFactory>("cache", null, null);

 

public class CacheServiceHostFactory : HttpConfigurableServiceHostFactory

    {

        public override System.ServiceModel.ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)

        {

            var host = base.CreateServiceHost(constructorString, baseAddresses);
            foreach (HttpEndpoint endpoint in host.Description.Endpoints)

            {

                endpoint.MaxReceivedMessageSize = 1024*1024;

            }

            return host;

        }

    }

Jul 28, 2011 at 8:26 PM

I actually did the same thing also. I used the code above in place of request.Content.ReadAsString() when the request message is large.

Coordinator
Jul 28, 2011 at 9:28 PM

As for the bug with using a custom factory for routing, I am going to fix this. However in the next version that hits codeplex I am going to add a callback for configuring the endpoint right in the config class.

Glenn