我有一个使用WebForms的ASP.NET 3.5应用程序,目前它正在IIS6上托管。一切都很棒。
但是,在切换到安装了IIS8的Windows 2012服务器后,我们会间歇性地获取截断请求。大部分时间在事件日志中的视图状态异常中显示,但是,对于没有ViewState的表单,我们会得到不完整的帖子(最后几个字段缺失/部分截断)。
这成了问题,我们升级到微软的支持,经过数周的调试,他们说这是II7及以上的“正确”行为。他们的解释是IIS管道从6改为7。
IIS6及以下版本会在将整个请求传递给Asp.net之前缓冲整个请求,截断的请求将被忽略 IIS7及更高版本会在发送初始标头后将请求发送到Asp.net,由应用程序来处理截断的请求。
如果存在连接问题(用户在传输期间拔出电缆)或用户在帖子中按下停止/重新加载页面时,这就会出现问题。
在我们的HTTP日志中,我们看到与截断的请求相关的“connection_dropped”消息。
我很难相信这种行为是有意的,但我们已经在几个不同的服务器上进行了测试,并在IIS7及更高版本(Windows 2008,2008 R2和2012)上获得了相同的结果。
我的问题是:
1)这种行为是否有意义?
2)如果这是“正确”行为,您如何保护您的应用免受可能处理不完整数据的影响?
3)为什么应用程序开发人员有责任检测不完整的请求?假设,为什么app开发人员会处理不完整的请求,而不是忽略它?
我写了一个小的asp.net应用程序和网站来演示这个问题。
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; }
}
}
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);
}
答案 0 :(得分:2)
1)如果您正在为集成模式下分配了3.5应用程序的应用程序池运行管道,则可能无法处理请求的处理方式due to ISAPI behavior。您可能正在生成它无法正确理解的请求,然后将它们截断为默认值。您是否尝试在经典模式下运行应用程序池?
2)功能测试。大量的功能测试。创建测试工具并进行应用程序可以进行的所有调用,以确保其正常工作。这不是100%的解决方案,但实际上并非如此。有许多计算机科学论文解释了为什么无法测试应用程序运行的每一种可能情况based on the Halting Problem。
3)因为你写了代码。您不应该有不完整的请求,因为请求可能是一个重要的数据,您需要发回一个错误,说明处理请求时出现问题,否则发布方只是看到请求已神秘消失。
答案 1 :(得分:2)
IIS改变其行为的原因是因为我们(开发人员)需要更多地控制请求处理。在请求中断的情况下,我们有调查隐形请求原因的问题。我们需要在应用程序级别记录请求以进行调查&保持记录中。例如,如果请求涉及信用卡交易等金融交易,我们需要更多控制权,我们需要记录每一步的合规性。
IIS是一个Web服务器框架,应用程序级数据验证不是他们的责任。如果请求被破坏,那意味着输入不完整&应用程序级逻辑将决定做什么。应用程序必须响应正确的错误代码和失败。这就是ASP.NET mvc具有模型验证的原因,它允许您在应用程序级别验证完整输入。
您可以使用IsClientConnected检查底层套接字是否仍然连接。
由于网络已经更多的AJAX,更多的移动,我们有时使用ping来检查远程服务的健康状况,我们不一定得出结论,破坏的请求是一个错误,必须删除。我们可能仍希望接受破碎的请求。它是应用程序级开发人员可以选择的选择,而不是IIS。