9

我正在开发一个网页游戏。作为游戏的一部分,您从一组有限的功能开始,然后在玩游戏时解锁更多功能。

例如,您/fields在教程中的第 3 步中解锁。但是,如果您只是/fields在地址栏中导航到怎么办?

我正在尝试找出响应的最佳状态代码。

403 似乎很理想,因为用户在解锁之前被禁止访问该页面。
404 也很有意义,因为在解锁之前页面在技术上“不存在”,并且还会阻止用户区分不存在的页面和他们尚未解锁的页面之间的区别。

但是在这两种情况下,我都有一些用户报告了浏览器缓存 403/404 结果的问题,并且即使在解锁后也不让他们访问页面,除非他们完全清除缓存。

我想知道我是否应该继续使用 403 或 404,或者我应该使用未使用的 4XX 代码(例如 442 和自定义 statusText),或者甚至开玩笑地发送HTTP/1.1 418 I'm A Teapot以响应用户在他们不应该在的地方闲逛。

我需要一个好的、可靠的理由说明为什么应该使用一个选项而不是其他选项。

4

3 回答 3

5

tl; dr 409 Conflict是一个想法,但也许你在缓存方面有问题。在这种情况下,强制重新加载的缓存破坏器将起作用。

长解释

也许409 Conflict状态码会有意义:

10.4.10 409 冲突

由于与资源的当前状态冲突,无法完成请求。仅在预期用户可能能够解决冲突并重新提交请求的情况下才允许使用此代码。响应正文应该包含足够的信息让用户识别冲突的来源。理想情况下,响应实体将包含足够的信息供用户或用户代理解决问题;但是,这可能是不可能的,也不是必需的。

响应 PUT 请求时最有可能发生冲突。例如,如果正在使用版本控制并且被 PUT 的实体包括对资源的更改,这些更改与早期(第三方)请求所做的更改相冲突,则服务器可能会使用 409 响应来指示它无法完成请求. 在这种情况下,响应实体可能会以响应 Content-Type 定义的格式包含两个版本之间差异的列表。

这是有道理的,因为资源只有在用户完成教程后才可用。在此之前,资源处于“无效”状态。用户可以通过完成教程来解决这个冲突。

后来我对这个案子进行了更多调查,我发现魔鬼在细节中。403 Forbidden让我们阅读和的规范404 Not Found

10.4.4 403 禁止

服务器理解请求,但拒绝执行。授权将无济于事,并且不应重复请求。如果请求方法不是 HEAD 并且服务器希望公开请求未完成的原因,它应该在实体中描述拒绝的原因。当服务器不希望确切地揭示请求被拒绝的原因或没有其他响应适用时,通常使用此状态代码。

重要的是“请求不应重复”的规范。从不重新请求 403 页面的浏览器可能会做正确的事情。但是,让我们继续 404:

10.4.5 404 未找到

服务器未找到任何与请求 URI 匹配的内容。没有说明这种情况是暂时的还是永久性的。

[省略]

现在我们有问题了!如果规范允许它们是临时的,为什么你的 404 页面会被缓存?

也许在您的设置中,您的 403 和 404 页面的缓存配置不正确。如果是这样,请在 StackOverflow 上查阅此答案。它给出了关于缓存 4xx 页面的详细答案。

如果您不想弄乱缓存标头,请使用所谓的缓存破坏器并像这样传递系统时间(假设 PHP 作为您的 Web 语言):

<a href="/fields?<?php echo time(); ?>">

这会产生类似的 URL /fields?1361948122,每秒都在增加。这是 Markus A 提出的解决方案的变体。

我假设1361948122您的资源忽略了查询字符串。如果不是,请在查询字符串参数中传递 cache-buster,例如t=1361948122并确保t您的资源不评估该参数。

于 2013-02-22T21:23:26.987 回答
2

就 HTTP 错误代码的预期目的而言,我肯定会选择403 Forbidden,因为该页面确实存在(404 已退出),但用户暂时被禁止访问它(并且此限制不是由于资源冲突造成的) ,就像并发修改一样,但是由于用户的帐户状态,我认为 409 也是如此)。基于其预期目的的另一个明智的选择可能是 401,但正如 nalply 在他的评论中已经指出的那样,此代码会触发一些(如果不是全部)浏览器显示登录对话框,因为这意味着使用标准的 web 身份验证机制可以解决问题。所以,这里绝对不是你的选择。

在 403 的描述中,有两件事似乎有点“不合适”,所以让我来解决它们:

  1. 授权无济于事……:这里只讲HTTP协议内部的授权机制,是为了区分403和401。此声明不适用于任何形式的自定义授权或会话状态管理。
  2. ...不应该重复请求...:必须始终在会话上下文中看到请求,因此如果用户的会话上下文更改(他解锁了一项功能)然后他重试访问相同的资源,即一个不同的请求,即没有违反这个建议。

当然,您也可以定义自己的错误代码,但由于它可能不会以任何官方方式保留,因此无法保证某些浏览器制造商不会有意或无意地使用该代码来触发特定的(调试)动作。这不太可能,但不是不允许的。

不过,418 也可以。:)

当然,如果您想特别掩盖功能的潜在可用性,您也可以决定使用 404,因为这是不给爱管闲事的用户任何提示的唯一方法。

现在,对于您的缓存问题:

这些状态代码(403、404、409、418)中的任何一个都不应触发浏览器违反您的意愿缓存页面。问题是许多浏览器只是简单地尝试缓存所有内容以变得更加活泼。在我看来,Opera 是这里最差的。我一直在为这些事情拉头发很多次。应该可以使用正确的标头设置来解决所有问题,但是我遇到过浏览器或服务器或某些中间代理决定忽略它们并无论如何都会破坏我的页面的情况。

到目前为止,我发现的唯一绝对肯定地保证重新加载的可靠方法是添加一个虚拟请求参数,如 /fields?t=29873,其中 29873 是一个数字,对于您在任何可能相关的范围内发出的每个请求都是唯一的时间尺度。当然,在服务器上,您可以简单地忽略此参数。请注意,当您的用户第一次打开您的页面时,仅仅从 1 开始然后计算后续请求是不够的,因为浏览器可能会在页面重新加载时保留缓存。

我在 Java 中进行 Web 开发(服务器和客户端都使用 GWT),并使用此代码生成虚拟“数字”:

private static final char[] base64chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_.".toCharArray();
private static int tagIndex = 0;

/**
 * Generates a unique 6-character tag string that is guaranteed to not repeat
 * for about 400 days, if this function is, on average, not called more often
 * than twice every millisecond.
 * 
 * @return the tag string
 */
public static String nowTag() {
    int tag = (int) ((System.currentTimeMillis() >>> 5)); // adjust
    char[] result = new char[6];
    result[5] = base64chars[(tagIndex++) & 63];
    result[4] = base64chars[tag & 63];
    tag >>>= 6;
    result[3] = base64chars[tag & 63];
    tag >>>= 6;
    result[2] = base64chars[tag & 63];
    tag >>>= 6;
    result[1] = base64chars[tag & 63];
    tag >>>= 6;
    result[0] = base64chars[tag & 63];
    return new String(result); 
}

它使用系统时钟与计数器相结合,每毫秒最多可以提供两个有保证的唯一值。您可能不需要此速度,因此您可以随意更改>>> 5我用“调整”标记的速度以满足您的需要。如果您将其增加 1,则您的费率会下降两倍,并且您的唯一性时间跨度会加倍。所以,例如,如果你把>>> 8相反,您可以每 4 毫秒生成大约 1 个值,并且这些值不应在 3200 天内重复。当然,如果用户弄乱了系统时钟,这种保证值不会重复的保证将消失。但由于这些值不是按顺序生成的,因此您仍然不太可能两次点击相同的数字。该代码生成一个 6 字符的文本字符串 (base64) 而不是十进制数字,以使 URL 尽可能短。

希望这可以帮助。:)

于 2013-02-26T03:28:26.940 回答
0

我觉得没有必要抛出错误代码,尽管你只是显示一条消息

您必须达到 XX 级才能访问此页面或有趣的东西,例如长大后回来

使用代码200-OK本身,所以不会有缓存问题,也达到了目的。

于 2013-02-27T07:30:23.867 回答