2010-12-01 67 views
5

我正在使用手工調製的HttpWebRequest調用和JSON序列化來處理與Ruby on Rails中實現的Web服務接口的小型C#/ WPF應用程序。如果沒有緩存,所有事情都按照預期運行,並且我也有HTTP認證和壓縮工作。啓用緩存的HttpWebRequest引發異常

一旦啓用緩存,通過設置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.InnerExceptionNullReferenceException具有以下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的緩存,但事實證明這是一個不走的:我得到一個WebException調用request.GetResponce()的時候,告訴我,「遠程服務器返回錯誤:( 304)未修改「。 - 是的,.NET,我有點知道,我想(嘗試)自己處理它,grr。

UPDATE 2:

更接近問題的根源。 showstopper似乎是初始請求的響應標題中的差異。 WEBrick包含一個Date: Wed, 01 Dec 2010 21:30:01 GMT標題,它在nginx答覆中不存在。還有其他的不同點,但是用提琴手攔截最初的nginx回覆並添加一個Date標題,後續的HttpWebRequest能夠處理(未修改的)nginx 304回覆。

想要尋找解決方法,以及讓nginx添加Date標頭。

更新3:

看來,服務器端的問題是與乘客的Phusion,他們有一個open issue關於缺乏Date頭的。我仍然認爲HttpWebRequest的行爲是......不理想。

UPDATE 4:

增加了Microsoft Connect票的bug。

+0

嗨!我認爲你在HttpWebRequest中發現了一個錯誤。您可以創建repro日誌(請參閱http://ferozedaud.blogspot.com/2009/08/tracing-with-systemnet.html)並在http:// connect?上創建一個bug請求。這樣,微軟可以解決這個問題。 – feroze 2010-12-02 00:37:16

+0

@feroze:昨天我用完了時間,但我現在在Connect上添加了錯誤票。 – snemarch 2010-12-02 09:37:53

回答

1

所以,原來是乘客的Phusion(或nginx的,這取決於你如何看待它 - 和薄爲好),不添加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 

UPDATE:

原來這是一個有點比 「只是」 設置HttpRequestCachePolicy更復雜 - 要重播,我還需要手動構建HTTP基本身份驗證。因此涉及的組件如下:

  1. HTTP服務器不包含HTTP「Date:」響應標頭。
  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, "[email protected]", "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 
     } 
    } 
} 
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; 
    } 
}