🔗

404 错误处理:重定向还是接 404?

AJAXHTMLHTTPJavaScript搜索引擎

常见的 HTTP 服务器(比如 Spring、Express、Django,甚至 nginx) 一般都会提供自定义错误页面的方式。 也就是说当 4xx、5xx 错误发生时,服务器直接返回配置好的错误页面,隐藏原始的错误信息。 不恰当地配置这些页面有时会产生非常坏的影响,本文以 404 为例对比不同的处理方式:

  • 返回包含 404 内容的页面,同时给出 404 状态码。比如 Google、Github、Facebook、Amazon、Linkedin。
  • 重定向(302/303)至统一的 URL,该 URL 给出具有 404 信息的页面。比如百度、淘宝、腾讯。

状态码及其语义

为了后续的讨论内容更加具体,我们先明确的相关概念。 现在广泛应用的 HTTP/1.1 发布于 1999 年,符合标准的 HTTP 消息拥有透明的、自描述的语义, 其中也规定了不同的HTTP状态码及其对应的语义。在 2000 年 R. T. Fielding 的博士论文 “Architectural Styles and the Design of Network-based Software Architectures” 中再次重申了如何正确使用 HTTP 语义以及 REST 的概念。

通信方遵循一致的标准是为了整个互联网能够高效地运行,状态码更是其中的重要部分。 虽然它对用户不可见,而是经由用户代理(User Agent,比如浏览器、爬虫等)处理。 用户代理会用它决定很多事情,比如是否缓存,是否提示错误给用户。 这些是本文中涉及的状态码及其定义:

200

请求成功。对于GET,应当返回被请求资源的实体;对于POST,应当返回操作的结果。

RFC 2616: 10.2.1

The request has succeeded. The information returned with the response
is dependent on the method used in the request, for example:

GET   an entity corresponding to the requested resource is sent in
          the response;
POST  an entity describing or containing the result of the action;

302

被请求的资源暂时位于另一个URI处,并且对于非HEAD/GET请求, 用户代理在重定向前必须询问用户确认。 RFC 1945 和 RFC 2068 规定客户端不允许更改请求的方法。但很多浏览器会将302当做303来处理。

RFC 2616: 10.3.3

The requested resource resides temporarily under a different URI.
Since the redirection might be altered on occasion, the client SHOULD
continue to use the Request-URI for future requests. This response
is only cacheable if indicated by a Cache-Control or Expires header
field.

303

被请求的资源暂时位于另一个URI处,并且应当以GET方法去请求那个资源。

RFC 2616: 10.3.4

The response to the request can be found under a different URI and
SHOULD be retrieved using a GET method on that resource. This method
exists primarily to allow the output of a POST-activated script to
redirect the user agent to a selected resource. The new URI is not a
substitute reference for the originally requested resource. The 303
response MUST NOT be cached, but the response to the second
(redirected) request might be cacheable.

404

服务器未能找到URI所标识的资源。也常被用于服务器希望隐藏请求被拒绝的具体原因。 例如403、401可能会被统一处理为404。

RFC 2616: 10.4.5

The server has not found anything matching the Request-URI. No
indication is given of whether the condition is temporary or
permanent. 

This status code is commonly used when the server does not wish to
reveal exactly why the request has been refused, or when no other
response is applicable.

处理方式:直接404

直接404是常见Web框架的通用做法:对于每一个用户请求遍历所有可能的路由, 如果任何一个控制器都不处理该请求,则Web服务器返回一个具有404状态码的HTTP响应。

404状态码告诉用户代理(User Agent)请求中的URI所标识的资源不存在, 用户代理将该HTTP响应的主体(往往是HTML)显示给用户, 该HTML页面是终端用户可读的,通常也会包含Not Found信息,以及一些有用的链接。

爬虫是一种特殊的用户代理,通常用于搜索引擎。当搜索引擎的爬虫发现某URI返回404状态码时, 会认为该URI已经失效而不对它进行索引,或者将该URI标识的已索引资源移除。 所以是网站迁移时,如果一个旧的URI会位于一个新的URI处,应当使用301重定向来告知搜索引擎: 该资源并未失效只是位置发生变化。

当我们无法控制服务器返回301时,也可在返回的HTML中使用特殊标记 来告知用户代理这是一个301,这在迁移静态站点时非常有用。

404 的一个问题在于不支持CDN。如果为了提升性能使用CDN服务, 将404.html文件托管到CDN提供商,访问该文件显然会返回200状态码。 因为CDN服务器认为你所访问的文件存在。

处理方式:重定向至错误页面

在国内网站中更常见的方式是将所有错误重定向至错误页面,比如error.html。 当用户代理访问error.html时服务器返回状态码为200,这便是神奇的200 Not Found。 200 Not Found显然不符合HTTP语义标准,下面从搜索引擎和CDN两个方面评价该方法的优劣:

搜索引擎非常不友好。当一个页面Not Found时爬虫并不知情,因为它收到的状态码是303。 于是跟随重定向并索引了错误页面error.html。这意味着该网站会有大量的URL都拥有同样的内容(404页面), 网站会因此受到搜索引擎的惩罚而排名下降。

优点

国内不少公司选择重定向的错误处理方式,笔者总结该方式的优点:

  • 固定的错误页面可以直接托管于 CDN 或静态服务器,成本较低。
  • 固定的URL可以支持更加松耦合的架构,只需约定重定向 URL 即可构建前后端通用的错误处理。
  • 最重要的是简单稳定,不会对外暴露错误详情。

缺点

请求资源不存在时,返回 302 与 404 最大的区别在于客户端是否认为发生了错误。 因此,302 到错误页面会产生意外的后果,尤其是非 HTML 内容被重定向时

  • AJAX 执行结果异常。由于客户端不知有错误发生,会把重定向后的页面当做合法的数据进行处理。
  • JavaScript 执行异常。同样地 302 后的内容会被当做脚本解析执行,发生类似 unexpected token < 之类的错误。

重定向错误页面对用户体验和缓存也有影响:

  • 被 302 后浏览器地址栏 URL 会发生变化,用户需要再次输入 URL 才能继续访问原页面。这意味着如果用户访问时网站崩溃(4xx, 5xx)就会被导向到错误页面,即使多次刷新也会永远停留在错误状态。
  • 被 302 后的内容可以缓存(404 状态的页面是禁止缓存的)。这意味着发生错误的资源,比如脚本、样式、图片等,即使后续恢复浏览器仍然会使用缓存。

此外,本来很清晰的 404 错误 会产生各种出乎意料的错误信息,对开发/调试造成麻烦。例如:

  • AJAX/iconfont 等资源被重定向到错误页面(通常是线上 URL)时,线下调试(线下 origin)会发生跨域错误 Access-Control-Allow-Origin
  • 脚本资源被重定向到错误页面时,会发生类似 unexpected token < 的解析错误。因为 HTML 文件第一个非空字符是 <html> 中的 <,它不是合法的 JavaScript。
  • 样式资源被重定向到错误页面时,会发生 Resource interpreted as Stylesheet but transferred with MIME type text/html 报警。

发生这些错误时,需要去 Chrome DevTools 的 Network 中搜索 status-code:302,找到所有错误页面并查看其 initiator(重定向之前要访问的资源)。

参考

转载请注明来源: https://harttle.land/2016/08/03/error-page-404-or-200.html 欢迎对文中引用进行考证,欢迎指出任何不准确和模糊之处。可以在下面评论区评论,也可以邮件至 harttle@harttle.com

🔝