【问题标题】:HttpWebRequest with caching enabled throws exceptions启用缓存的 HttpWebRequest 引发异常
【发布时间】:2015-05-06 18:25:32
【问题描述】:

我正在开发一个小型 C#/WPF 应用程序,该应用程序使用手工制作的 HttpWebRequest 调用和 JSON 序列化与在 Ruby on Rails 中实现的 Web 服务接口。如果没有缓存,一切都可以正常工作,而且我还可以进行 HTTP 身份验证和压缩。

一旦我通过设置request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.CacheIfAvailable); 启用缓存,事情就会出错 - 在生产环境中。当连接到一个简单的 WEBrick 实例时,一切正常,我得到了HTTP/1.1 304 Not Modified,正如预期的那样,HttpWebRequest 提供了缓存的内容。

当我对生产服务器尝试相同的操作时,运行 nginx/0.8.53 + Phusion Passenger 3.0.0,应用程序中断。第一个请求(未缓存)得到了正确处理,但是在导致 304 响应的第二个请求中,我收到了一个 WebException 声明“请求被中止:请求被取消。”当我调用request.GetResponse()。

我已经通过fiddler 建立了连接,但这并没有太大帮助; WEBrick 和 nginx 都返回一个空的实体主体,尽管响应头不同。拦截请求并更改 nginx 的响应标头以匹配 WEBrick 并没有改变任何东西,这让我认为这可能是一个保持活动的问题;但是,设置request.KeepAlive = false; 不会改变任何东西 - 它在连接到 WEBrick 时不会破坏东西,并且在连接到 nginx 时不会修复东西。

对于它的价值,WebException.InnerException 是一个NullReferenceException 与以下StackTrace:

at System.Net.HttpWebRequest.CheckCacheUpdateOnResponse()
at System.Net.HttpWebRequest.CheckResubmitForCache(Exception& e)
at System.Net.HttpWebRequest.DoSubmitRequestProcessing(Exception& exception)
at System.Net.HttpWebRequest.ProcessResponse()
at System.Net.HttpWebRequest.SetResponse(CoreResponseData coreResponseData)

(工作的)WEBrick 连接的标头:

########## request
GET /users/current.json HTTP/1.1
Authorization: Basic *REDACTED*
Content-Type: application/json
Accept: application/json
Accept-Charset: utf-8
Host: testbox.local:3030
If-None-Match: "84a49062768e4ca619b1c081736da20f"
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
########## response
HTTP/1.1 304 Not Modified
X-Ua-Compatible: IE=Edge
Etag: "84a49062768e4ca619b1c081736da20f"
Date: Wed, 01 Dec 2010 18:18:59 GMT
Server: WEBrick/1.3.1 (Ruby/1.8.7/2010-08-16)
X-Runtime: 0.177545
Cache-Control: max-age=0, private, must-revalidate
Set-Cookie: *REDACTED*

(抛出异常的)nginx 连接的标头:

########## request
GET /users/current.json HTTP/1.1
Authorization: Basic *REDACTED*
Content-Type: application/json
Accept: application/json
Accept-Charset: utf-8
Host: testsystem.local:8080
If-None-Match: "a64560553465e0270cc0a23cc4c33f9f"
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
########## response
HTTP/1.1 304 Not Modified
Connection: keep-alive
Status: 304
X-Powered-By: Phusion Passenger (mod_rails/mod_rack) 3.0.0
ETag: "a64560553465e0270cc0a23cc4c33f9f"
X-UA-Compatible: IE=Edge,chrome=1
X-Runtime: 0.240160
Set-Cookie: *REDACTED*
Cache-Control: max-age=0, private, must-revalidate
Server: nginx/0.8.53 + Phusion Passenger 3.0.0 (mod_rails/mod_rack)

更新:

我尝试做一个快速而肮脏的手动 ETag 缓存,但结果证明这是不行的:我在调用 request.GetResponce() 时得到一个 WebException,告诉我 “远程服务器返回一个错误:(304)未修改。” - 是的,.NET,我有点知道,我想(尝试)自己处理它,grr。

更新 2:

更接近问题的根源。 showstopper 似乎是 initial 请求的响应标头中的差异。 WEBrick 包含一个 Date: Wed, 01 Dec 2010 21:30:01 GMT 标头,该标头不存在于 nginx 回复中。还有其他区别,但是用 fiddler 拦截初始 nginx 回复并添加Date 标头,随后的HttpWebRequests 能够处理(未修改的)nginx 304 回复。

打算尝试寻找解决方法,以及让 nginx 添加 Date 标头。

更新 3:

似乎服务器端问题与 Phusion Passenger 相关,他们有一个 open issue 关于缺少 Date 标头。我仍然会说HttpWebRequest 的行为是......次优。

更新 4:

为该错误添加了Microsoft Connect 票证。

【问题讨论】:

  • 嗨!我认为您在 HttpWebRequest 中发现了一个错误。你能创建一个重现日志(参见ferozedaud.blogspot.com/2009/08/tracing-with-systemnet.html)并在connect 上创建一个错误请求吗?这样,微软就可以解决这个问题。
  • @feroze:我昨天没时间了,但我现在已经在 Connect 上添加了 bug 票。

标签: c# httpwebrequest


【解决方案1】:

我认为设计人员认为当“预期行为”(即获取响应主体)无法完成时抛出异常是合理的。您可以按如下方式巧妙地处理此问题:

catch (WebException ex)
{
    if (ex.Status == WebExceptionStatus.ProtocolError)
    {
        var statusCode = ((HttpWebResponse)ex.Response).StatusCode;
        // Test against HttpStatusCode enumeration.
    }
    else
    {
        // Do something else, e.g. throw;
    }
}

【讨论】:

  • 可能值得一试,但似乎是一个特别讨厌的黑客 - 特别是因为(尝试手动进行缓存时),在事件尝试获取响应之前,我已经在 GetResponse() 收到异常溪流。另外,如果我能让自动缓存在 nginx 上工作,那将是最好的 - 但无论如何谢谢:)
【解决方案2】:

所以,结果是 Phusion Passenger(或 nginx,取决于您如何看待它 - 以及 Thin)没有添加 Date HTTP 响应标头,并结合我认为的错误在 .NET HttpWebRequest 中(在我的情况下没有 If-Modified-Since,因此不需要 Date)导致问题。

这个特殊情况的解决方法是编辑我们的 Rails ApplicationController:

class ApplicationController < ActionController::Base
    # ...other stuff here

    before_filter :add_date_header
    # bugfix for .NET HttpWebRequst 304-handling bug and various
    # webservers' lazyness in not adding the Date: response header.
    def add_date_header
        response.headers['Date'] = Time.now.to_s
    end
end

更新:

事实证明它比“just”设置HttpRequestCachePolicy 更复杂一些——为了重现,我还需要手动构建 HTTP 基本身份验证。所以涉及到的组件如下:

  1. 不包含 HTTP“日期:”响应标头的 HTTP 服务器。
  2. 手动构造 HTTP 授权请求头。
  3. 使用 HttpRequestCachePolicy。

我能想到的最小的复制品:

namespace Repro
{
    using System;
    using System.IO;
    using System.Net;
    using System.Net.Cache;
    using System.Text;

    class ReproProg
    {
        const string requestUrl = "http://drivelog.miracle.local:3030/users/current.json";

        // Manual construction of HTTP basic auth so we don't get an unnecessary server
        // roundtrip telling us to auth, which is what we get if we simply use
        // HttpWebRequest.Credentials.
        private static void SetAuthorization(HttpWebRequest request, string _username, string _password)
        {
            string userAndPass = string.Format("{0}:{1}", _username, _password);
            byte[] authBytes = Encoding.UTF8.GetBytes(userAndPass.ToCharArray());
            request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(authBytes);
        }

        static public void DoRequest()
        {
            var request = (HttpWebRequest) WebRequest.Create(requestUrl);

            request.Method = "GET";
            request.CachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.CacheIfAvailable);
            SetAuthorization(request, "user@domain.com", "12345678");

            using(var response = request.GetResponse())
            using(var stream = response.GetResponseStream())
            using(var reader = new StreamReader(stream))
            {
                string reply = reader.ReadToEnd();
                Console.WriteLine("########## Server reply: {0}", reply);
            }
        }

        static public void Main(string[] args)
        {
            DoRequest();    // works
            DoRequest();    // explodes
        }
    }
}

【讨论】:

    猜你喜欢
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 2021-12-12
    • 1970-01-01
    • 2013-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多