Posting a large file can fail if you enable Client Certificates
Overview
If you require client certificates and POST or PUT a large amount of data, your request may fail. This has been an issue that has existed with IIS for at least 10 years (this now applies to Azure App Services on the Windows platform as well since it uses IIS). There is not much information on this so this blog should help clarify this issue. The solution is incredibly simple!
Issue
The underlying protocol for HTTPS (TCP) will break up large data packets into more than one frame. Normally this is not an issue for applications and transparent to both client and server. Some applications that require client certificates can run into a problem because of this… when the initial data is being pushed in multiple frames and the IIS server demands a client certificate before continuing. You can actually see this in a network trace when the data starts flowing from the client to the server (after the initially SSL handshake of the Server Hello) and the first packet of data is sent. The server reply is the start of the request for the client certificate and then the next packet coming from client contains more data. At this point the server throws an error because it expected the next data on the wire to be the client certificate. This does not happen with smaller packets of data because the entire request to POST or PUT has finished and the next thing the server gets IS the client cert handshake and not additional data from the PUSH or PUT
Resolution
To solve this issue, you simply need to utilize one of these two techniques:
- Establish the connection first with a HEAD request
- Set the Expect: 100-continue header for the request
Option: Establish the connection first with a HEAD request
I don’t like this one as much as the next option, but it has worked successfully. The concept is the HEAD request has no data at all and establishes the SSL connection without any issues. Then you rely on the underlying implementation of your HTTP library to re-use this connection (most do), there is no further SSL handshake and you avoid the problem
Option: Set the Expect: 100-continue header with for the request
This is the best option. According to the RFC this is why this actually exists! In simple terms, once the server is ready for the request (after the SSL negotiations in this case) you get a 100 status back and can continue to POST or PUT your data. All HTTP client libraries implement this (since it is part of the RFC) so this is simple and guaranteed to work.
Below is a sample of setting this in .NET when using HttpWebRequest (or any of those derivatives). You do this once in your code before you make any HTTP Request in code (like in global.asax or Startup.cs
ServicePointManager.Expect100Continue = true; See: ServicePointManager.Expect100Continue Property
If you are using the newer HttpClient class adds this automatically (for obvious reasons).
Other
Summary
Although this has been an issue for over a decade, this never found its way to any public facing documentation (most likely because the Expect header is best practice for us old-school types and the newer classes and libraries all include this header by default).
If you find this blog useful please drop us a note!
Comments
- Anonymous
July 19, 2017
We haven't been able to get this to work. The Expect header is clearly in the request object during debug, but we still get a 413 error. The only way we have been able to get this to work is by setting uploadReadAheadSize to the same value as requestLimits maxAllowedContentLength which is not ideal since such a large about of data would be buffered at once.