HTTP 接口

在我们设计服务器或客户端的代码之前,让我们先来思考一下两者均会涉及的一点:双方通信的 HTTP 接口。

我们会使用 JSON 作为请求和响应正文的格式,就像第二十章中的文件服务器一样,我们尝试充分利用 HTTP 方法。所有接口均以/talks路径为中心。不以/talks开头的路径则用于提供静态文件服务,即用于实现客户端系统的 HTML 和 JavaScript 代码。

访问/talksGET请求会返回如下所示的 JSON 文档。

  1. [{"title": "Unituning",
  2. "presenter": "Jamal",
  3. "summary": "Modifying your cycle for extra style",
  4. "comment": []}]

我们可以发送PUT请求到类似于/talks/Unituning之类的 URL 上来创建新对话,在第二个斜杠后的那部分是对话的名称。PUT请求正文应当包含一个 JSON 对象,其中有一个presenter属性和一个summary属性。

因为对话标题可以包含空格和其他无法正常出现在 URL 中的字符,因此我们必须使用encodeURIComponent函数来编码标题字符串,并构建 URL。

  1. console.log("/talks/" + encodeURIComponent("How to Idle"));
  2. // → /talks/How%20to%20Idle

下面这个请求用于创建关于“空转”的对话。

  1. PUT /talks/How%20to%20Idle HTTP/1.1
  2. Content-Type: application/json
  3. Content-Length: 92
  4. {"presenter": "Maureen",
  5. "summary": "Standing still on a unicycle"}

我们也可以使用GET请求通过这些 URL 获取对话的 JSON 数据,或使用DELETE请求通过这些 URL 删除对话。

为了在对话中添加一条评论,可以向诸如/talks/Unituning/comments的 URL 发送POST请求,JSON 正文包含author属性和message属性。

  1. POST /talks/Unituning/comments HTTP/1.1
  2. Content-Type: application/json
  3. Content-Length: 72
  4. {"author": "Iman",
  5. "message": "Will you talk about raising a cycle?"}

为了支持长轮询,如果没有新的信息可用,发送到/talksGET请求可能会包含额外的标题,通知服务器延迟响应。 我们将使用通常用于管理缓存的一对协议头:ETagIf-None-Match

服务器可能在响应中包含ETag(“实体标签”)协议头。 它的值是标识资源当前版本的字符串。 当客户稍后再次请求该资源时,可以通过包含一个If-None-Match头来进行条件请求,该头的值保存相同的字符串。 如果资源没有改变,服务器将响应状态码 304,这意味着“未修改”,告诉客户端它的缓存版本仍然是最新的。 当标签与服务器不匹配时,服务器正常响应。

我们需要这样的东西,通过它客户端可以告诉服务器它有哪个版本的对话列表,仅当列表发生变化时,服务器才会响应。 但服务器不是立即返回 304 响应,它应该停止响应,并且仅当有新东西的可用,或已经过去了给定的时间时才返回。 为了将长轮询请求与常规条件请求区分开来,我们给他们另一个标头Prefer: wait=90,告诉服务器客户端最多等待 90 秒的响应。

服务器将保留版本号,每次对话更改时更新,并将其用作ETag值。 客户端可以在对话变更时通知此类要求:

  1. GET /talks HTTP/1.1
  2. If-None-Match: "4"
  3. Prefer: wait=90
  4. (time passes)
  5. HTTP/1.1 200 OK
  6. Content-Type: application/json
  7. ETag: "5"
  8. Content-Length: 295
  9. [....]

这里描述的协议并没有任何访问控制。每个人都可以评论、修改对话或删除对话。因为因特网中充满了流氓,因此将这类没有进一步保护的系统放在网络上最后可能并不是很好。