理解REST:动词、错误代码和身份验证

我正在寻找一种方法来包装api的默认功能在我的基于php的web应用程序,数据库和cms。

我环顾四周,发现了几个“骨架”框架。除了我的问题的答案,还有主音,我喜欢它,因为它是一个非常轻量级的REST框架。

我最喜欢REST,因为它简单,并希望基于它创建一个API体系结构。我正在努力理解基本原理,但还没有完全理解。因此,有一些问题。

1. 我理解得对吗?

假设我有一个资源“用户”。我可以像这样设置一些uri:

/api/users     when called with GET, lists users
/api/users     when called with POST, creates user record
/api/users/1   when called with GET, shows user record
when called with PUT, updates user record
when called with DELETE, deletes user record

到目前为止,这是RESTful体系结构的正确表示吗?

2. 我需要更多的动词

创建、更新和删除在理论上可能已经足够了,但实际上我需要更多的动词。我知道这些东西可以被嵌入到更新请求中,但它们是特定的操作,可以有特定的返回代码,我不想把它们都扔到一个操作中。

在用户示例中想到的一些是:

activate_login
deactivate_login
change_password
add_credit

我该如何表达像RESTful URL体系结构那样的动作呢?

我的直觉是对URL进行GET调用

/api/users/1/activate_login

并等待状态码返回。

但是,这偏离了使用HTTP谓词的想法。你怎么看?

3.如何返回错误消息和代码

REST的美妙之处很大一部分来自于它对标准HTTP方法的使用。在出现错误时,我发出一个带有3xx、4xx或5xx错误状态码的报头。对于详细的错误描述,我可以使用正文(对吗?)到目前为止一切顺利。但是,如何传输更详细地描述错误的专有错误码呢?“连接数据库失败”或“数据库登录错误”)?如果我将它与消息一起放入正文中,我必须在之后解析它。这种事情有标准的标题吗?

4. 如何进行身份验证

  • 遵循REST原则的基于API密钥的身份验证是什么样子的?
  • 除了公然违反REST原则之外,在验证REST客户机时使用会话是否有强烈的反对之处?:)(这里只是半开玩笑,基于会话的身份验证在我现有的基础设施中可以很好地发挥作用。)
141286 次浏览

我建议(作为第一次传递)PUT应该只用于更新现有实体。POST应该用来创建新的。即。

/api/users     when called with PUT, creates user record

我觉得不对劲。然而,第一部分的其余部分(重新使用动词)看起来是合乎逻辑的。

  1. 使用post当你不知道如何新的资源URI看起来像(你创建新用户,应用程序将分配新用户它的id), PUT更新或创建资源,你知道他们将如何表示(示例:PUT /myfiles/thisismynewfile.txt)
  2. 在消息正文中返回错误描述
  3. 你可以使用HTTP认证(如果它足够的话) Web服务的状态应该是

对于你说的例子,我将使用以下:

activate_login

# EYZ0

deactivate_login

# EYZ0

change_password

PUT /passwords(假设用户已通过身份验证)

add_credit

POST /credits(假设用户已通过身份验证)

对于错误,你会以你得到请求的格式在正文中返回错误,所以如果你收到:

# EYZ0

你可以用XML发送回响应,JSON等也是如此……

对于身份验证,应该使用http身份验证。

详细,但复制自HTTP 1.1方法规范http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

9.3获得

GET方法意味着检索由Request-URI标识的任何信息(以实体的形式)。如果Request-URI指的是一个产生数据的流程,则应将产生的数据作为响应中的实体返回,而不是流程的源文本,除非该文本恰好是流程的输出。

如果请求消息包含if - modified- since, if - unmodified - since, if - match, if - none - match或if - range报头字段,GET方法的语义将更改为“条件GET”。条件GET方法只要求在条件报头字段所描述的情况下传输实体。条件GET方法旨在减少不必要的网络使用,它允许刷新缓存的实体,而不需要多个请求或传输客户端已经持有的数据。

如果请求消息包含一个Range报头字段,GET方法的语义将更改为“部分GET”。部分GET请求只传输实体的一部分,如第14.35节所述。partial GET方法旨在通过允许完成部分检索的实体而不传输已经由客户端持有的数据来减少不必要的网络使用。

当且仅当GET请求的响应满足第13节中描述的HTTP缓存要求时,该响应是可缓存的。

关于表单使用时的安全注意事项,请参见15.1.3节。

9.5发布

POST方法用于请求源服务器接受请求中包含的实体,作为request - line中由request - uri标识的资源的新附属。POST被设计为允许一个统一的方法覆盖以下函数:

  - Annotation of existing resources;
- Posting a message to a bulletin board, newsgroup, mailing list,
or similar group of articles;
- Providing a block of data, such as the result of submitting a
form, to a data-handling process;
- Extending a database through an append operation.

POST方法执行的实际功能由服务器决定,通常依赖于Request-URI。发布的实体隶属于该URI,就像文件隶属于包含它的目录,新闻文章隶属于发布它的新闻组,或者记录隶属于数据库一样。

POST方法执行的操作可能不会产生可以由URI标识的资源。在本例中,200 (OK)或204 (No Content)是适当的响应状态,这取决于响应是否包含描述结果的实体。

如果源服务器上已经创建了资源,响应应该是201(已创建),并包含一个描述请求状态的实体和指向新资源的实体,以及一个Location头(参见14.30节)。

此方法的响应是不可缓存的,除非响应包含适当的Cache-Control或Expires报头字段。然而,303 (See Other)响应可用于指示用户代理检索可缓存资源。

POST请求必须遵守8.2节中规定的消息传输要求。

安全注意事项请参见15.1.3节。

9.6把

PUT方法要求将所包含的实体存储在所提供的Request-URI下。如果Request-URI引用了一个已经存在的资源,那么所包含的实体应该被认为是原始服务器上的实体的修改版本。如果Request-URI不指向现有资源,并且请求用户代理能够将该URI定义为新资源,则源服务器可以使用该URI创建资源。如果创建了新资源,源服务器必须通过201(已创建)响应通知用户代理。如果修改了现有资源,则应该发送200 (OK)或204 (No Content)响应代码以表示请求成功完成。如果不能使用Request-URI创建或修改资源,则应该给出反映问题性质的适当错误响应。实体的接收者绝对不能忽略任何它不理解或不实现的Content-*(例如Content- range)报头,在这种情况下必须返回501(未实现)响应。

如果请求经过缓存,而request - uri标识了一个或多个当前缓存的实体,那么这些条目应该被视为过期的。此方法的响应不可缓存。

POST和PUT请求之间的根本区别反映在Request-URI的不同含义上。POST请求中的URI标识将处理所包含实体的资源。这个资源可能是一个接受数据的进程,一个通往其他协议的网关,或者一个接受注释的独立实体。相反,PUT请求中的URI标识了请求所包含的实体——用户代理知道要使用什么URI,服务器绝对不能试图将该请求应用于其他资源。如果服务器希望请求应用于不同的URI,

它必须发送一个301(永久移动)响应;用户代理可以自行决定是否重定向请求。

单个资源可以由许多不同的uri标识。例如,一篇文章可能有一个用于标识“当前版本”的URI,它与标识每个特定版本的URI是分开的。在这种情况下,一个通用URI上的PUT请求可能会导致原始服务器定义多个其他URI。

HTTP/1.1没有定义PUT方法如何影响源服务器的状态。

PUT请求必须遵守8.2节中规定的消息传输要求。

除非对特定的实体-头另有指定,否则PUT请求中的实体-头应该应用于PUT创建或修改的资源。

9.7删除

DELETE方法请求源服务器删除由Request-URI标识的资源。源服务器上的人工干预(或其他方式)可以覆盖此方法。即使从源服务器返回的状态代码表明操作已成功完成,客户端也不能保证操作已被执行。但是,服务器不应该表示成功,除非在给出响应时,它打算删除资源或将其移动到一个不可访问的位置。

如果响应包含描述状态的实体,那么成功的响应应该是200 (OK),如果操作尚未实施,则应该是202(已接受),如果操作已经实施,但响应不包括实体,则应该是204 (No Content)。

如果请求经过缓存,而request - uri标识了一个或多个当前缓存的实体,那么这些条目应该被视为过期的。此方法的响应不可缓存。

到目前为止看起来还不错。记住在“Location:”报头中返回新创建用户的URI,作为对POST的响应的一部分,同时返回“201 created”状态码。

是2:通过GET激活是一个坏主意,在URI中包含动词是一种设计气味。您可能想要考虑在GET上返回一个表单。在Web应用程序中,这将是一个带有提交按钮的HTML表单;在API用例中,您可能希望返回一个包含要PUT以激活帐户的URI的表示。当然,您也可以在POST到/users的响应中包含这个URI。使用PUT将确保您的请求是幂等的,即如果客户端不确定是否成功,它可以安全地再次发送。一般来说,考虑一下你可以把动词转换成什么资源(有点像“动词的名词化”)。问问自己,你的具体行动与哪种方法最密切相关。例如change_password -> PUT;禁用>可能删除;add_credit ->可能是POST或PUT。将客户端指向适当的uri,方法是在表示中包含它们。

不要发明新的状态码,除非你认为它们太通用了,值得在全球范围内标准化。尽量使用最合适的状态码(请参阅RFC 2616中的所有状态码)。在响应体中包含附加信息。如果你真的,真的确定你想发明一个新的状态码,再想想;如果你仍然相信,确保至少选择正确的类别(1xx -> OK, 2xx ->信息,3xx ->重定向;4xx->客户端错误,5xx ->服务器错误)。我说过发明新的状态码是个坏主意吗?

在任何可能的情况下,使用HTTP内置的身份验证框架。查看谷歌在GData中进行身份验证的方式。一般来说,不要在uri中放入API键。尝试避免会话以增强可伸缩性并支持缓存——如果对请求的响应由于以前发生的某些事情而不同,那么您通常已经将自己绑定到特定的服务器进程实例。最好是将会话状态转换为客户端状态(例如使其成为后续请求的一部分),或者通过将其转换为(服务器)资源状态来显式地将其转换为(服务器)资源状态,即给它自己的URI。

恕我直言,你对如何设计资源的想法是正确的。我什么都不会改变。

与其试图用更多的动词来扩展HTTP,不如考虑一下根据基本的HTTP方法和资源,您所提议的动词可以简化为什么。例如,不使用activate_login动词,您可以设置如下资源:/api/users/1/login/active,这是一个简单的布尔值。要激活登录,只需PUT一个文档,说“真”或1或其他什么。要禁用PUT文档,该文档为空或显示为0或false。

类似地,要更改或设置密码,只需执行PUTs到/api/users/1/password

每当你需要添加一些东西(比如信用)时,请考虑POSTs。例如,您可以对/api/users/1/credits这样的资源执行POST,其主体包含要添加的学分数量。同一资源上的PUT可以用于覆盖而不是添加值。主体中带有负数的POST将进行减法,等等。

我强烈建议不要扩展基本的HTTP状态码。如果找不到一个与您的情况完全匹配的错误,请选择最接近的一个,并将错误详细信息放入响应体中。另外,请记住HTTP报头是可扩展的;您的应用程序可以定义您喜欢的所有自定义头。例如,我开发的一个应用程序可以在多种情况下返回404 Not Found。我们没有让客户端解析响应体,而是添加了一个新的头,X-Status-Extended,它包含了我们专有的状态代码扩展。所以你可能会看到这样的回答:

HTTP/1.1 404 Not Found
X-Status-Extended: 404.3 More Specific Error Here

这样,像web浏览器这样的HTTP客户端仍然知道如何处理常规的404代码,而更复杂的HTTP客户端可以选择查看X-Status-Extended报头以获得更具体的信息。

对于身份验证,如果可以的话,我建议使用HTTP身份验证。但恕我直言,如果对你来说更容易的话,使用基于cookie的身份验证并没有什么错。

几天后我才注意到这个问题,但我觉得我可以补充一些见解。我希望这能对你的平安创业有所帮助。


第一点:我理解得对吗?

你理解得对。这是RESTful体系结构的正确表示。你可能会发现下面这个来自维基百科的矩阵在定义名词和动词时非常有用:


当处理集合 URI时,例如

  • GET:列出集合的成员,包括它们的成员uri,以便进一步导航。例如,列出所有待售的汽车。

  • PUT:含义定义为“用另一个集合替换整个集合”。

  • POST:在集合中创建一个新条目,其中ID由集合自动分配。创建的ID通常包含在此操作返回的数据中。

  • DELETE:定义为“删除整个集合”。


当处理成员 URI时,例如

  • GET:检索以适当MIME类型表示的集合的寻址成员的表示形式。

  • PUT:更新集合的地址成员或使用指定的ID创建它。

  • POST:将已寻址的成员作为其本身的集合处理,并创建它的新从属。

  • DELETE:删除集合中的寻址成员。


第二点:我需要更多的动词

一般来说,当您认为需要更多动词时,实际上可能意味着需要重新标识资源。记住,在REST中,您总是作用于一个资源或资源的集合。选择什么作为资源对API定义非常重要。

激活/禁用登录:如果您正在创建一个新的会话,那么您可能想要考虑“会话”作为资源。要创建一个新会话,使用POST到http://example.com/sessions/,并在主体中使用凭据。要使它过期,请使用PUT或DELETE(可能取决于您是否打算保留会话历史)到http://example.com/sessions/SESSION_ID

这次的资源是“用户”。您需要一个PUT到http://example.com/users/USER_ID,主体中有旧密码和新密码。您正在对“用户”资源进行操作,更改密码只是一个更新请求。它与关系数据库中的UPDATE语句非常相似。

我的直觉是做一个GET调用 到一个URL # EYZ0 < / p >

这违背了REST的核心原则:正确使用HTTP动词。任何GET请求都不应该留下任何副作用。

例如,GET请求永远不应该在数据库上创建会话,返回带有新会话ID的cookie,或者在服务器上留下任何残留物。GET动词类似于数据库引擎中的SELECT语句。请记住,当使用相同的参数请求时,对带有GET谓词的任何请求的响应都应该是可缓存的,就像请求静态web页面一样。


# EYZ0

将4xx或5xx HTTP状态代码视为错误类别。您可以在正文中详细说明错误。

连接数据库失败: / 数据库登录错误:通常你应该使用500错误来处理这些类型的错误。这是一个服务器端错误。客户没有做错什么。500个错误通常被认为是“可重试的”。也就是说,客户端可以重试相同的请求,并期望它在服务器的问题解决后成功。在正文中指定细节,以便客户端能够为我们人类提供一些上下文。

另一类错误是4xx族,它通常表示客户端做错了什么。特别是,这类错误通常向客户端表明,不需要按原样重试请求,因为它将继续永久地失败。也就是说,客户端在重新尝试这个请求之前需要修改一些东西。例如,“资源未找到”(HTTP 404)或“格式错误请求”(HTTP 400)错误就属于这一类。


要点4:如何进行身份验证

正如第1点所指出的,您可能希望考虑创建一个会话,而不是验证用户。您将返回一个新的“会话ID”,以及相应的HTTP状态代码(200:允许访问或403:拒绝访问)。

然后,您将询问RESTful服务器:“您可以为我获取这个会话ID的资源吗?”

没有身份验证模式——REST是无状态的:你创建一个会话,你要求服务器使用这个会话ID作为参数给你资源,在注销时你删除或过期会话。

简单地说,你完全是在逆向操作。

你不应该从你应该使用的url来接近它。一旦您决定了系统需要哪些资源,以及如何表示这些资源,以及资源和应用程序状态之间的交互,url将有效地“免费”提供。

引用罗伊·菲尔丁

一个REST API应该花费几乎所有的 它在定义 用于表示的媒体类型 资源及驱动应用 状态,或在定义扩展的时候 关系名称和/或 支持超文本的现有标记 标准媒体类型。付出的努力 描述对什么使用什么方法 感兴趣的uri应该完全 的范围内定义 媒体类型的处理规则 (并且,在大多数情况下,已经定义 通过现有的媒体类型)。(失败 这里指的是带外 信息驱动交互

.

.

人们总是从uri开始,并认为这是解决方案,然后他们往往会忽略REST体系结构中的一个关键概念,特别是上面引用的,“这里的失败意味着驱动交互的是带外信息,而不是超文本。”

老实说,很多人看到一堆uri和一些get、put和post,认为REST很简单。REST并不容易。基于HTTP的RPC很容易,通过HTTP有效负载来回移动数据块也很容易。然而,REST不止于此。REST是协议不可知的。HTTP非常流行,适用于REST系统。

REST存在于媒体类型、它们的定义以及应用程序如何通过超文本(实际上是链接)驱动对这些资源可用的操作。

对于REST系统中的媒体类型有不同的看法。一些人喜欢应用程序特定的有效负载,而另一些人喜欢将现有的媒体类型提升到适合应用程序的角色。例如,一方面,您拥有适合应用程序的特定XML模式,而不是使用XHTML之类的东西作为表示(可能是通过微格式或其他机制)。

我认为,这两种方法都有各自的位置,XHTML在人类驱动和机器驱动web重叠的场景中工作得非常好,而前者,更具体的数据类型,我认为更有利于机器对机器的交互。我发现商品格式的提升可能会使内容谈判变得困难。"application/xml+yourresource"作为一种媒体类型要比"application/xhtml+xml"具体得多,因为后者可以应用于许多有效负载,而这些负载可能是机器客户端真正感兴趣的,也可能不是机器客户端不自省就能确定的。

然而,XHTML在web浏览器和呈现非常重要的人类网络中工作得很好(显然)。

您的应用程序将指导您做出此类决定。

REST系统设计过程的一部分是发现系统中的第一类资源,以及派生的支持资源,支持对主要资源的操作所必需的支持资源。一旦发现了资源,那么这些资源的表示以及通过表示中的超文本显示资源流的状态图将成为下一个挑战。

回想一下,在超文本系统中,资源的每种表示都将实际资源表示与资源可用的状态转换结合在一起。将每个资源视为图中的一个节点,链接是将该节点留给其他状态的线。这些链接不仅告诉客户可以做什么,还告诉他们需要做什么(因为一个好的链接结合了URI和所需的媒体类型)。

例如,你可能有:

<link href="http://example.com/users" rel="users" type="application/xml+usercollection"/>
<link href="http://example.com/users?search" rel="search" type="application/xml+usersearchcriteria"/>

您的文档将讨论rel字段名为“users”,媒体类型为“application/xml+youruser”。

这些链接看起来可能是多余的,它们几乎都在与相同的URI通信。但事实并非如此。

这是因为对于“用户”关系,该链接谈论的是用户的集合,您可以使用统一接口来处理集合(GET检索所有用户,DELETE删除所有用户,等等)。

如果您POST到这个URL,您将需要传递一个“application/xml+usercollection”文档,该文档可能只包含一个用户实例,因此您可以添加用户,或者一次添加几个用户。也许您的文档会建议您可以简单地传递单个用户类型,而不是集合。

您可以看到应用程序执行搜索所需的内容(由“search”链接定义)和它的mediatype。搜索媒体类型的文档将告诉您它的行为,以及预期的结果。

不过,这里的要点是uri本身基本上不重要。应用程序控制uri,而不是客户机。除了几个“入口点”,你的客户端应该依赖应用程序提供的uri来工作。

客户端需要知道如何操作和解释媒体类型,但不太需要关心它的去向。

在客户的眼中,这两个链接在语义上是相同的:

<link href="http://example.com/users?search" rel="search" type="application/xml+usersearchcriteria"/>
<link href="http://example.com/AW163FH87SGV" rel="search" type="application/xml+usersearchcriteria"/>

所以,专注于你的资源。关注它们在应用程序中的状态转换,以及如何最好地实现状态转换。

其他基本知识

REST有一个统一的接口约束,它规定REST客户端必须依赖于标准,而不是实际REST服务的特定于应用程序的细节,因此REST客户端不会因微小的更改而中断,而且可能是可重用的。

因此,REST客户机和REST服务之间存在一个契约。如果您使用HTTP作为底层协议,那么以下标准是合同的一部分:

  • HTTP 1.1
  • 方法定义
  • 状态码定义
  • 缓存控制头
  • 接受和内容类型的标头
  • auth头
  • IRI (utf8 URI)
  • 身体(选一个)
  • 注册的特定于应用程序的MIME类型,例如迷宫+ xml
  • 特定于供应商的MIME类型,例如vnd.github + json
  • 通用MIME类型
    • 应用程序特定的RDF词汇表,例如ld + json &# EYZ1 # EYZ2
    • 应用特定的配置文件,例如哈尔+ json &配置文件链接参数(我猜)
  • <李>超链接
    • 什么应该包含它们(选择一个)
      • 发送链接标题
      • 发送一个超媒体响应,例如html, atom+xml, hal+json, ld+json&hydra等…
    • <李>语义
      • 使用IANA链接关系和可能的自定义链接关系
      • 使用特定于应用程序的RDF词汇表

REST有一个无状态约束,它声明REST服务和客户机之间的通信必须是无状态的。这意味着REST服务不能维护客户端状态,因此您不能拥有服务器端会话存储。你必须验证每一个请求。例如,HTTP基本认证(HTTP标准的一部分)是可以的,因为它会在每个请求中发送用户名和密码。

来回答你的问题

  1. 是的,它可以是。

值得一提的是,客户端并不关心IRI结构,他们关心的是语义,因为他们遵循具有链接关系或链接数据(RDF)属性的链接。

关于IRI唯一重要的一点是,单个IRI必须只能标识单个资源。允许单个资源(如用户)拥有许多不同的IRIs。

这是很简单的,为什么我们使用漂亮的iri像/users/123/password;当你通过阅读IRI来理解它时,在服务器上编写路由逻辑就容易得多了。

  1. 你有更多的动词,如PUT, PATCH, OPTIONS,甚至更多,但你不需要更多…而不是添加新的动词,你必须学习如何添加新的资源。
   deactivate_login -> PUT /login/active false
change_password -> PUT /user/xy/password "newpass"
add_credit -> POST /credit/raise {details: {}}

(由于无状态约束,从REST的角度来看,登录没有意义。)

  1. 您的用户并不关心问题存在的原因。他们只想知道是否有成功或错误,可能是一个错误消息,他们可以理解,例如:“对不起,但我们不能保存您的帖子。”等等。

HTTP状态标头是标准标头。其他的都应该在身体里。例如,单个报头不足以描述详细的多语言错误消息。

  1. 无状态约束(以及缓存和分层系统约束)确保了服务的良好扩展。当你可以在客户端上做同样的事情时,你肯定不想在服务器上维护数百万个会话……

    如果用户使用主客户端授予第三方客户端访问权限,则第三方客户端将获得一个访问令牌。之后,第三方客户端将访问令牌与每个请求一起发送。还有更复杂的解决方案,比如你可以为每个请求签名等等。欲了解更多细节,请参阅OAuth手册。

相关文献

    <李> # EYZ0
    Roy Thomas Fielding (REST作者)论文
    2000,加州大学欧文分校 <李> # EYZ0
    Markus Lanthaler (JSON-LD合著者,Hydra作者)论文
    2014,奥地利格拉茨理工大学

关于REST返回代码:混合HTTP协议代码和REST结果的代码是错误的

然而,我看到许多实现混合了它们,许多开发人员可能不同意我的观点。

HTTP返回码与HTTP Request本身相关。REST调用是使用超文本传输协议(Hypertext Transfer Protocol)请求完成的,它的工作级别比调用的REST方法本身要低。REST是一种概念/方法,它的输出是业务/逻辑结果,而HTTP结果代码是运输结果。

例如,当你调用/users/时返回“404 Not found”是令人困惑的,因为它可能意味着:

  • URI错误(HTTP)
  • 没有找到用户(REST)

“403禁止/拒绝访问”可能是指:

  • 需要特别许可。浏览器可以通过询问用户/密码来处理它。(HTTP)
  • 服务器上配置的访问权限错误。(HTTP)
  • 您需要进行身份验证(REST)

这个列表可能会继续出现“500服务器错误”(Apache/Nginx HTTP抛出错误或REST中的业务约束错误)或其他HTTP错误等……

从代码中,很难理解失败的原因是什么,HTTP(传输)失败还是REST(逻辑)失败。

如果HTTP请求物理上成功执行,总是应该返回200个代码,不管是否找到记录。因为URI资源是发现并且是由http服务器处理的。是的,它可能返回一个空集。是否有可能收到一个空网页200作为http结果,对吗?

相反,你可以返回200个HTTP代码和一个带有空数组/对象的JSON,或者使用bool result/success标志来通知所执行的操作状态。

此外,一些互联网提供商可能会拦截您的请求并返回404 http代码。这并不意味着您的数据找不到,而是在传输级别上出了问题。

从# EYZ0:

2004年7月,英国电信供应商BT集团部署了Cleanfeed 内容阻塞系统,对任何请求返回404错误 被“互联网观察”认定为可能非法的内容 的基础。其他isp同样返回一个HTTP 403“禁止”错误 环境。使用虚假404错误作为一种手段 泰国和突尼斯也报道了隐藏的审查制度。在 突尼斯在2011年革命之前审查制度非常严格, 人们意识到了虚假404错误的本质,并创建了 一个名为“阿马尔404”的虚构人物,他代表着“看不见的东西” 审查”。< / p >