Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WCF REST, streamed upload of files and httpRuntime maxRequestLength property

I have created a simple WCF service to prototype file uploading. The service:

[ServiceContract]
public class Service1
{
    [OperationContract]
    [WebInvoke(Method = "POST", UriTemplate = "/Upload")]
    public void Upload(Stream stream)
    {
        using (FileStream targetStream = new FileStream(@"C:\Test\output.txt", FileMode.Create, FileAccess.Write))
        {
            stream.CopyTo(targetStream);
        }
    }
}

It uses webHttpBinding with transferMode set to "Streamed" and maxReceivedMessageSize, maxBufferPoolSize and maxBufferSize all set to 2GB. httpRuntime has maxRequestLength set to 10MB.

The client issues HTTP requests in the following way:

HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(@"http://.../Service1.svc/Upload");

request.Method = "POST";
request.SendChunked = true;
request.AllowWriteStreamBuffering = false;
request.ContentType = MediaTypeNames.Application.Octet;

using (FileStream inputStream = new FileStream(@"C:\input.txt", FileMode.Open, FileAccess.Read))
{
    using (Stream outputStream = request.GetRequestStream())
    {
        inputStream.CopyTo(outputStream);
    }
}

Now, finally, what's wrong:

When uploading the file 100MB big, the server returns HTTP 400 (Bad request). I've tried to enable WCF tracing, but it shows no error. When I increase httpRuntime.maxRequestLength to 1GB, the file gets uploaded without problems. The MSDN says that maxRequestLength "specifies the limit for the input stream buffering threshold, in KB".

This leads me to believe that the whole file (all 100MB of it) is first stored in "input stream buffer" and only then it is available to my Upload method on server. I can actually see that the size of file on server does not gradually increase (as I would expect), instead, in the moment it is created it is already 100MB big.

The question: How can I get this to work so that the "input stream buffer" is reasonably small (say, 1MB) and when it overflows, my Upload method gets called? In other words, I want the upload to be truly streamed without having to buffer the whole file anywhere.

EDIT: I now discovered the httpRuntime contains another setting that is relevant here - requestLengthDiskThreshold. It seems that when the input buffer grows beyond this threshold, it is no longer stored in memory, but instead, on filesystem. So at least the whole 100MB big file is not kept in memory (this is what I was most afraid of), however, I still would like to know whether there is some way to avoid this buffer altogether.

like image 680
Nikola Anusev Avatar asked Jan 15 '13 13:01

Nikola Anusev


People also ask

What is the maxrequestlength property of the input stream?

The MaxRequestLength property specifies the limit for the buffering threshold of the input stream. For example, this limit can be used to prevent denial of service attacks that are caused by users who post large files to the server.

How to upload files using WCF in IIS?

The first step is to create a WCF REST service that will be used for downloading and uploading the file. I prefer to keep all my services in a separate WCF project. This project is then hosted in the IIS. Add a new Blank solution by the name of “FileHandling”. Add a new WCF Service Application and give it the name FileHandling.WCFHost.

How does the service downloadfilefromremotelocation work in WCF?

The service on being called receives the file and then saves it at the destinationFilePath on the server. In this example, both WCFHost and the client are located on the same server but the WCFHost can be located on any other server as well. The method DownloadFileFromRemoteLocation takes the following 2 parameters:

What is the maximum size of a HTTP request?

Http Runtime Section. Max Request Length Property System. Web. Configuration Gets or sets the maximum request size. The maximum request size in kilobytes. The default size is 4096 KB (4 MB). Configuration Property Attribute Integer Validator Attribute The selected value is less than RequestLengthDiskThreshold.


2 Answers

If you are using .NET 4 and hosting your service in IIS7+, you may be affected an ASP.NET bug which is described in the following blog post:

http://blogs.microsoft.co.il/blogs/idof/archive/2012/01/17/what-s-new-in-wcf-4-5-improved-streaming-in-iis-hosting.aspx

Basically, for streamed requests, the ASP.NET handler in IIS will buffer the whole request before handing over control to WCF. And this handler obeys the maxRequestLength limit.

As far as I know, there is no workaround for the bug and you have the following options:

  • upgrade to .NET 4.5
  • self-host your service instead of using IIS
  • use a binding that is not based on HTTP, so that the ASP.NET handler is not involved
like image 66
cvlad Avatar answered Nov 15 '22 12:11

cvlad


This may be a bug in the streaming implementation. I found a MSDN article that suggests doing exactly what you are describing at http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/fb9efac5-8b57-417e-9f71-35d48d421eb4/. Unfortunately the Microsoft employee suggesting the fix found a bug in the implementation and didn't follow up with details on a fix.

That said it looks like the implementation is broken which you could test by profiling your code with a memory profiler and verifying whether or not the entire file is being stored in memory. If the entire file is being stored in memory, you won't be able to fix this issue, unless somebody finds a configuration issue with your code.

That said, while using requestLengthDiskThreshold could technically work, it will dramatically increase your write times as each file will have to be written first as temp data, read from temp data, written again as final, and finally the temp data deleted. As you have already said you are dealing with extremely large files so I doubt such a solution is acceptable.

Your best bet is to use a chunking framework and manually reconstruct the file. I found instructions on how to write such logic at http://aspilham.blogspot.com/2011/03/file-uploading-in-chunks-using.html but have not had the time to check it for accuracy.

I'm sorry I can't tell you why your code isn't working as documented, but something similar to the 2nd example should be able to work without ballooning your memory footprint.

like image 41
Chris Walter Avatar answered Nov 15 '22 13:11

Chris Walter