什么响应代码适合这种情况?

时间:2013-02-19 03:47:00

标签: http http-response-codes

我正在开发一个网络游戏。作为游戏的一部分,您可以从一组有限的功能开始,并在游戏时解锁更多功能。

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

我正在尝试找出最适合回复的状态代码。

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

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

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

我需要一个好的,坚实的理由,为什么应该使用一个选项而不是其他选项。

3 个答案:

答案 0 :(得分:3)

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

长解释

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

  

10.4.10 409冲突

     

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

     

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

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

后来我对这个案子进行了一点调查,我发现魔鬼在细节上。我们来看一下403 Forbidden404 Not Found的规范。

  

10.4.4 403禁止

     

服务器理解请求,但拒绝履行请求。授权无效,请求不应重复。如果请求方法不是HEAD并且服务器希望公开为什么请求没有得到满足,那么它应该描述实体中拒绝的原因。当服务器不希望明确拒绝请求的原因,或者没有其他响应适用时,通常会使用此状态代码。

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

  

10.4.5 404 Not Found

     

服务器未找到与Request-URI匹配的任何内容。没有说明该病症是暂时的还是永久性的。

     

[省略]

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

也许在您的设置中,您的403和404页面的缓存配置不正确。如果是这样,请咨询this answer on StackOverflow。它给出了有关缓存4xx页面的详细答案。

如果你不想搞乱缓存标题,请使用所谓的cache-buster并像这样传递系统时间(假设PHP是你的web语言):

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

这会生成/fields?1361948122之类的网址,每秒都在增加。它是Markus A提出的解决方案的变体。

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

答案 1 :(得分:2)

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

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

  1. 授权无效...:这只涉及HTTP协议中的授权机制,旨在将403与401区分开来。此声明不适用于任何形式的自定义授权或会话状态管理。
  2. ...请求不应重复...:必须始终在会话上下文中看到请求,因此如果用户的会话上下文发生更改(他解锁了某个功能),然后他重试访问相同的资源,这是一个不同的请求,即没有违反这个建议。
  3. 当然,您也可以定义自己的错误代码,但由于它可能不会以任何官方方式保留,因此无法保证某些浏览器制造商不会故意或意外地使用该代码触发特定(调试)动作。这不太可能,但不是不允许的。

    不过,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); 
    }
    

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

    希望这会有所帮助。 :)

答案 2 :(得分:0)

我觉得没有必要抛出错误代码,尽管你只是显示了像

这样的消息
  

您必须是XX级才能访问此页面或类似成长时回来

使用代码200-OK本身,因此不存在缓存问题,并且还实现了目标。