我有一个使用WebForms的ASP.NET 3.5应用程序,当前正在IIS6上托管。一切都很好。
但是,切换到安装了IIS8的Windows 2012服务器后,我们会间歇性地收到截断的请求。在大多数情况下,这会在事件日志中显示为viewstate异常,但是,在没有ViewState的表单上,我们会收到不完整的帖子(最后几个字段丢失/部分被截断了)。
这变得非常棘手,以至于我们升级到Microsoft支持,经过数周的调试,他们说这是II7及更高版本的“正确”行为。他们的解释是IIS管道从6更改为7。
IIS6及以下版本将在将整个请求传递给Asp.net之前对其进行缓冲,被忽略的请求将被忽略。
IIS7及更高版本将在发送初始 header 后将请求发送到Asp.net,这取决于应用程序来处理被截断的请求。
当存在连接问题(用户在传输过程中拔下电缆)或用户在发布过程中按停止/重新加载页面时,这将成为问题。
在我们的HTTP日志中,我们看到与被截断的请求相关的“connection_dropped”消息。
我很难相信这种行为是预期的,但是我们已经在几台不同的服务器上进行了测试,并在IIS7及更高版本(Windows 2008、2008 R2和2012)上获得了相同的结果。
我的问题是:
1)这种行为甚至有意义吗?
2)如果这是“正确”的行为,如何保护您的应用程序免受潜在的不完整数据的处理?
3)为什么开发人员有责任检测不完整的请求?假设地,为什么应用程序开发人员除了忽略不完整的请求外,还会处理不完整的请求?
更新
我编写了一个小的asp.net应用程序和网站来演示该问题。
服务器
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; }
}
}
客户
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);
}
最佳答案
1)如果您正在为集成模式下分配了3.5个应用程序的应用程序池运行管道,则due to ISAPI behavior可能会处理如何处理您的请求。您可能会生成无法正确理解的请求,然后将其截断为默认值。您是否尝试过以经典模式运行应用程序池?
2)功能测试。大量的功能测试。创建一个测试工具,并进行应用程序可以进行的所有调用以确保其正常运行。这不是100%的解决方案,但实际上没有。有许多计算机科学论文解释了为什么不可能在每种可能的情况下测试您的应用程序可以运行based on the Halting Problem的情况。
3)因为您编写了代码。您不应有不完整的请求,因为该请求可能是重要数据,并且您需要发回一条错误消息,指出处理请求存在问题,否则,发行方只会认为请求已神秘消失。