使用HttpWebRequest时,为什么在某些链接上会出现“(304)未修改”错误?

54
有没有想法为什么我使用HttpWebRequest访问某些链接时,代码中会出现"The remote server returned an error: (304) Not Modified."?
我使用的代码来自Jeff在这里的帖子(该页面似乎已经消失,请参见Wayback Machine的存档副本)。
请注意,代码的概念是一个简单的代理服务器,因此我将浏览器指向本地运行的代码,它获取我的浏览器请求,然后通过创建新的HttpWebRequest进行代理传递,正如您在代码中看到的那样。它对大多数站点/链接都有效,但对于某些站点/链接,会出现此错误。您将在代码中看到一个关键部分,它似乎从浏览器请求中复制http标头设置到其发送到站点的请求中,并且复制了标头属性。不确定问题是否与它模拟请求的这个方面以及结果返回时发生的情况有关?
case "If-Modified-Since":
   request.IfModifiedSince = DateTime.Parse(listenerContext.Request.Headers[key]);
   break;

我从http://en.wikipedia.org/wiki/Main_Page获取了一个示例问题。

提示:此处进行更新

仍然无法解决。基本上我可以确定有一个链接有问题,第一次它似乎工作正常,第二次出现错误,第三次正常,第四次出现错误,第五次正常等等。好像代码中有某些状态没有清除或者其他的东西。我尝试使用“using”类型语句等来清理代码。

下面是代码。如果有人能找出为什么每隔一次浏览到像http://newsimg.bbc.co.uk/css/screen/1_0_16/nol/v4/story.css(从第二次开始,而不是第一次)的链接时,通过这个代理代码会出现错误,我会很高兴听到你的意见。

class Program
{
    static void Main(string[] args)
    {
        Proxy p = new Proxy(8080);

        Thread proxythread = new Thread(new ThreadStart(p.Start));
        proxythread.Start();

        Console.WriteLine("Proxy Started. Press Any Key To Stop...");
        Console.ReadKey();

        p.Stop();
     }
}

public class Proxy
{
    private HttpListener _listener;
    private int _port;

    public Proxy(int port)
    {
        int defaultport = 8080;

        // Setup Thread Pool
        System.Threading.ThreadPool.SetMaxThreads(50, 1000);
        System.Threading.ThreadPool.SetMinThreads(50, 50);

        // Sanitize Port Number
        if (port < 1024 || port > 65535)
            port = defaultport;

        // Create HttpListener Prefix
        string prefix = string.Format("http://*:{0}/", port);
        _port = port;

        // Create HttpListener
        _listener = new HttpListener();
        _listener.Prefixes.Add(prefix);
    }

    public void Start()
    {
        _listener.Start();

        while (true)
        {
            HttpListenerContext request = null;

            try
            {
                request = _listener.GetContext();

                // Statistics (by Greg)
                int availThreads = -1;
                int compPortThreads = -1;
                ThreadPool.GetAvailableThreads(out availThreads, out compPortThreads);
                log("INFO", request.Request.Url.ToString(), "START - [" + availThreads + "]");

                ThreadPool.QueueUserWorkItem(ProcessRequest, request);
            }
            catch (HttpListenerException ex)
            {
                log("ERROR", "NA", "INFO: HttpListenerException - " + ex.Message);
                break;
            }
            catch (InvalidOperationException ex)
            {
                log("ERROR", "NA", "INFO: InvalidOperationException - " + ex.Message);
                break;
            }
        }
    }

    public void Stop()
    {
        _listener.Stop();
    }

    private void log(string sev, string uri, string message)
    {
        Console.Out.WriteLine(Process.GetCurrentProcess().Id + " - " + sev + " (" + uri + "): " + message);
    }

    private void ProcessRequest(object _listenerContext)
    {
        #region local variables
        HttpWebRequest psRequest;                   // Request to send to remote web server
        HttpWebResponse psResponse;                 // Response from remote web server         
        List<byte> requestBody = new List<byte>();  // Byte array to hold the request's body
        List<byte> responseBody = new List<byte>(); // Byte array to hold the response's body
        byte[] buffer;
        string uri = "";
        #endregion

        var listenerContext = (HttpListenerContext)_listenerContext;
        uri = listenerContext.Request.Url.ToString().Replace(string.Format(":{0}", _port), "");

        // Create Interent Request 
        HttpWebRequest internetRequest = (HttpWebRequest)WebRequest.Create(uri);
        #region Build Request Up
        internetRequest.Method = listenerContext.Request.HttpMethod;
        internetRequest.ProtocolVersion = listenerContext.Request.ProtocolVersion;
        internetRequest.UserAgent = listenerContext.Request.UserAgent;
        foreach (string key in listenerContext.Request.Headers.AllKeys)
        {
            try
            {
                switch (key)
                {
                    case "Proxy-Connection":
                    case "Connection":
                        internetRequest.KeepAlive = (listenerContext.Request.Headers[key].ToLower() == "keep-alive") ? true : false;
                        break;

                    case "Content-Length":
                        internetRequest.ContentLength = listenerContext.Request.ContentLength64;
                        break;

                    case "Content-Type":
                        internetRequest.ContentType = listenerContext.Request.ContentType;
                        break;

                    case "Accept":
                        internetRequest.Accept = listenerContext.Request.Headers[key];
                        break;

                    case "Host":
                        break;

                    case "Referer":
                        internetRequest.Referer = listenerContext.Request.Headers[key];
                        break;

                    case "If-Modified-Since":
                        internetRequest.IfModifiedSince = DateTime.Parse(listenerContext.Request.Headers[key]);
                        break;

                    default:
                        internetRequest.Headers.Add(key, listenerContext.Request.Headers[key]);
                        break;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Error settup up psRequest object. Error = " + ex.Message + "\n" + ex.StackTrace);
            }
        }
        #endregion

        #region Copy content into request
        buffer = new byte[1024];
        using (Stream instream = listenerContext.Request.InputStream)
        {
            int incount = instream.Read(buffer, 0, buffer.Length);
            while (incount > 0)
            {
                internetRequest.GetRequestStream().Write(buffer, 0, incount);
                incount = instream.Read(buffer, 0, buffer.Length);
            }
        }
        #endregion

        // Get Internet Response
        HttpWebResponse internetResponse = null;
        try
        {
            using (internetResponse = (HttpWebResponse)internetRequest.GetResponse())
            {
                #region Configure Local Response Header Keys
                foreach (string key in internetResponse.Headers.Keys)
                {
                    try
                    {
                        switch (key)
                        {
                            case "Transfer-Encoding":
                                listenerContext.Response.SendChunked = (internetResponse.Headers[key].ToLower() == "chunked") ? true : false;
                                break;

                            case "Content-Length":
                                listenerContext.Response.ContentLength64 = internetResponse.ContentLength;
                                break;

                            case "Content-Type":
                                listenerContext.Response.ContentType = internetResponse.Headers[key];
                                break;

                            case "Keep-Alive":
                                listenerContext.Response.KeepAlive = true;
                                break;

                            default:
                                listenerContext.Response.Headers.Add(key, internetResponse.Headers[key]);
                                break;
                        }
                    }
                    catch (Exception ex)
                    {
                        log("ERROR", uri, "Error settup up listenerContext.Response objects. Error = " + ex.Message + "\n" + ex.StackTrace);
                    }
                }
                #endregion

                try
                {
                    // Transfer the body data from Internet Response to Internal Response
                    buffer = new byte[1024];
                    using (Stream inputStream = internetResponse.GetResponseStream())
                    {
                        int outcount = inputStream.Read(buffer, 0, buffer.Length);
                        while (outcount > 0)
                        {
                            listenerContext.Response.OutputStream.Write(buffer, 0, outcount);
                            outcount = inputStream.Read(buffer, 0, buffer.Length);
                        }
                    }
                }
                catch (Exception ex)
                {
                    log("ERROR", uri, "Could not obtain response from URI: " + ex.Message);
                }
                finally
                {
                    listenerContext.Response.OutputStream.Close();
                }
            }
        }
        catch (Exception ex)
        {
            //if (ex is InvalidOperationException ||
            //    ex is ProtocolViolationException ||
            //    ex is WebException)
            //{
            //    log(uri, "Could not successfully get response: " + ex.GetType() + " - " + ex.Message);
            //    listenerContext.Response.Close();
            //    return;
            //}
            //else { throw; }

            log("ERROR", uri, "Could not successfully get response: " + ex.GetType() + " - " + ex.Message);
            listenerContext.Response.Close();
        }
    }
}

以下是我看到的一个例子 - 第一个结果很好,第二个出现了错误...

Proxy Started. Press Any Key To Stop...
2080 - INFO (http://newsimg.bbc.co.uk:8080/css/screen/1_0_16/nol/v4/story.css): START - [50]
2080 - INFO (http://newsimg.bbc.co.uk:8080/css/screen/1_0_16/nol/v4/story.css): START - [50]
2080 - ERROR (http://newsimg.bbc.co.uk/css/screen/1_0_16/nol/v4/story.css): Could not successfully get response: System.Net.WebException - The remote server returned an error: (304) Not Modified.

1
304不是错误! - Emad Aghaei
5个回答

107

首先,这不是一个错误。 3xx 表示重定向。真正的错误是 4xx(客户端错误)和 5xx(服务器错误)。

如果客户端收到 304 Not Modified,那么客户端有责任从自己的缓存中显示相关的资源。通常情况下,代理不需要担心这个问题。它只是传递消息。


1
谢谢 - 那么你的意思是,我应该仍然捕获异常(就在我发出请求的时候),但是然后检查异常中的内容,如果是304,那么就继续执行下去? - Greg
2
不 - 如果您发送了if-modified-since或if-none-match,则意味着您有一个本地(缓存)副本 - 服务器正在指示您使用该副本。 - symcbean
1
好的-只需要确保在本地请求中返回相同的状态代码,这似乎可以正常工作。有关代码的更多详细信息,请参见https://dev59.com/-E3Sa4cB1Zd3GeqPsRYI。 - Greg
这意味着与客户端中可用的缓存数据相比,来自API的数据没有更改,因此导致了304状态码,对吗? - Mihae Kheel
1
我可以从哪里学到这个,你知道吗?W3?有什么书可以建议吗? - Kid
@Shad:我认为这个视频非常值得观看 https://www.youtube.com/watch?v=jTo8U2WeQl8,而且这个资源作为快速参考也非常棒 https://restfulapi.net/http-status-codes/。 - BalusC

23

这是预期的行为。

当您发出HTTP请求时,服务器通常返回代码200 OK。如果您设置了If-Modified-Since,则服务器可能会返回304 Not modified(并且响应不会包含内容)。这应该是您提示页面未被修改的信号。

类的作者错误地决定304视为错误并抛出异常。现在,每次您尝试使用If-Modified-Since时都需要捕获异常来清理它们留下的后果。


这个简直是绝对的疯狂。实际的错误代码是否应该引起异常是有争议的。这个异常是对异常行为极度不理解的可怕失败。 - Alexander

20

仅仅按下 F5 并不总是有效。

为什么?

因为您的 ISP 也会为您缓存网络数据。

解决方法:强制刷新。

通过在 Firefox 或 Chrome 中按下 CTRL + F5 强制刷新浏览器,以清除 ISP 缓存,而不仅仅是按下 F5

然后您可以在浏览器 F12 开发人员工具网络选项卡中看到 200 响应,而不是 304。

另一个技巧是在请求页面的 URL 字符串末尾添加问号 ?

http://localhost:52199/Customers/Create?

问号将确保浏览器刷新请求,而不缓存任何先前的请求。

此外,在 Visual Studio 中,您可以将默认浏览器设置为Chrome隐身模式,以避免在开发过程中出现缓存问题,方法如下(自行绘制):

Go to browsers list Select browse with... Click Add... Point to the chrome.exe on your platform, add argument "Incognito" Choose the browser you just added and set as default, then click browse


1

这不是问题,而是因为缓存...

为了克服这个问题,请在您的端点调用中添加一个时间戳,例如axios.get('/api/products').

时间戳后应该是axios.get(/api/products?${Date.now()}.

它会解决您的304状态代码问题。


-5

我认为您还没有安装这些功能。请参考下面的图片。

enter image description here

几天前我也遇到了这个问题。 安装了此功能后,我解决了它。 如果您尚未安装该功能,请安装。

安装过程:

  1. 进入Android Studio
  2. 工具
  3. Android
  4. SDK管理器
  5. 外观和行为
  6. Android SDK

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接