Processing ASP.NET requests when a connection is disconnected after switching to IIS8

I have an ASP.NET 3.5 application using WebForms, it is currently hosted on IIS6. Everything behaves perfectly.

However, after switching to the Windows 2012 server with IIS8 installed, we periodically receive truncated requests. In most cases, this is manifested in the exception of viewstate in our event log, however, in forms that do not have ViewState, we receive incomplete messages (the last few fields are missing / partially truncated).

It became so problematic that we grew up to support Microsoft, and after several weeks of debugging, they said that this was the โ€œcorrectโ€ behavior for II7 and higher. Their explanation was a change in the IIS pipeline from 6 to 7.

IIS6 and below will buffer the entire request before sending it to Asp.net, truncated requests will be ignored.
IIS7 and above would send an Asp.net request after sending the initial headers, this would be prior to the application for processing truncated requests.

This becomes problematic when there are connection problems (the user disconnects his cable during the transaction) or when the user presses the page stop / reload button during the message.

In our HTTP logs, we see "connection_dropped" messages that correlate with truncated requests.

I had problems believing that this behavior was intended, but we tested several servers and got the same results with IIS7 and higher (Windows 2008, 2008 R2 and 2012).

My questions:

1) Does this behavior make sense?

2) If this is the โ€œcorrectโ€ behavior, how to protect the application from potential processing of incomplete data?

3) Why is the developer responsible for detecting incomplete requests? Hypothetically, why does the application developer handle an incomplete request other than ignoring it?

Update

I wrote a small asp.net application and website to demonstrate the problem.

Server

Handler.ashx.cs

public class Handler : IHttpHandler { public void ProcessRequest(HttpContext context) { if (context.Request.HttpMethod == "POST") { var lengthString = context.Request.Form["Length"]; var data = context.Request.Form["Data"]; if (lengthString == null) { throw new Exception("Missing field: Length"); } if (data == null) { throw new Exception("Missing field: Data"); } var expectedLength = int.Parse(lengthString); if (data.Length != expectedLength) { throw new Exception(string.Format("Length expected: {0}, actual: {1}, difference: {2}", expectedLength, data.Length, expectedLength - data.Length)); } } context.Response.ContentType = "text/plain"; context.Response.Write("Hello World, Request.HttpMethod=" + context.Request.HttpMethod); } public bool IsReusable { get { return false; } } } 

Client

Program.cs

 static void Main(string[] args) { var uri = new Uri("http://localhost/TestSite/Handler.ashx"); var data = new string('a', 1024*1024); // 1mb var payload = Encoding.UTF8.GetBytes(string.Format("Length={0}&Data={1}", data.length, data)); // send request truncated by 256 bytes // my assumption here is that the Handler.ashx should not try and handle such a request Post(uri, payload, 256); } private static void Post(Uri uri, byte[] payload, int bytesToTruncate) { var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { // this allows us to disconnect unexpectedly LingerState = new LingerOption(true, 0) }; socket.Connect(uri.Host, uri.Port); SendRequest(socket, uri, payload, bytesToTruncate); socket.Close(); } private static void SendRequest(Socket socket, Uri uri, byte[] payload, int bytesToTruncate) { var headers = CreateHeaders(uri, payload.Length); SendHeaders(socket, headers); SendBody(socket, payload, Math.Max(payload.Length - bytesToTruncate, 0)); } private static string CreateHeaders(Uri uri, int contentLength) { var headers = new StringBuilder(); headers.AppendLine(string.Format("POST {0} HTTP/1.1", uri.PathAndQuery)); headers.AppendLine(string.Format("Host: {0}", uri.Host)); headers.AppendLine("Content-Type: application/x-www-form-urlencoded"); headers.AppendLine("User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:26.0) Gecko/20100101 Firefox/99.0"); headers.AppendLine("Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); headers.AppendLine("Connection: Close"); headers.AppendLine(string.Format("Content-Length: {0}", contentLength)); return headers.ToString(); } private static void SendHeaders(Socket socket, string headers) { socket.Send(Encoding.ASCII.GetBytes(headers)); socket.Send(Encoding.ASCII.GetBytes("\n")); } private static void SendBody(Socket socket, byte[] payload, int numBytesToSend) { socket.Send(payload, 0, numBytesToSend, SocketFlags.None); } 
+7
c # iis webforms
source share
2 answers

1) If you use a pipeline for an application pool that is assigned an application 3.5 in integrated mode, there may be a problem processing your requests due to ISAPI behavior . You can generate queries that he misunderstands, and then truncates them to the default value. Have you tried to run the application pool in classic mode?

2) Functional testing. Lots and lots of functional testing. Create a test harness and make all the calls that your application can make to ensure that it works correctly. This is not a 100% solution, but nothing really happens. There are many informatics articles explaining why it is not possible to check every possible situation in which your application may start based on a shutdown problem .

3) Because you wrote the code. You should not have incomplete requests, because the request may be for an important piece of data, and you need to send an error message stating that there was a problem processing the request, otherwise the issuing party will simply see that the request has mysteriously disappeared.

+2
source share

The reason IIS changed its behavior because we (the developers) needed more control over request processing. In case of violation of the request, we had a problem investigating the cause of invisible requests. We need to register requests at the application level for investigation and record keeping. For example, if the request includes a financial transaction, such as a credit card transaction, we need more control, and we need to record each step for compliance.

IIS is the structure of a web server, and data verification at the application level is not their responsibility. If the request was broken, it means that the input was incomplete, and the application-level logic will decide what to do. The application should respond to the correct error codes and crashes. It is for this reason that ASP.NET mvc has a model validation that allows validation of full input at the application level.

You can use IsClientConnected to check if the underlying socket is connected or not.

As more AJAX and more mobile devices appear on the Internet, and we sometimes use ping to test the functionality of remote services, we do not necessarily conclude that a broken request is an error and should be deleted. We can still live with broken requests. This is a choice that an application level developer can make, not IIS.

+2
source share

All Articles