HTTP 规范:没有数据传输的 PUT,因为服务器知道数据的散列

HTTP Spec: PUT without data transfer, since hash of data is known to server

HTTP/WebDav 规范是否允许此客户端-服务器对话?

  1. 客户端:我想将数据 PUT 到 /user1/foo.mkv,它的哈希和为:HASH
  2. 服务器:好的,PUT成功,你不需要发送数据,因为我已经知道这个哈希和的数据。

注意:此 PUT 是初始上传。这不是更新。

如果可能,可以实现更快的文件同步。

用例:WebDAV 服务器为每个用户托管一个目录。几个用户上传了最喜欢的视频 foo.mkv。在此示例中,最喜欢的视频已存储在此位置:/user2/myfoo.mkv。第二次及以后的上传不需要发送任何数据,因为服务器已经知道内容。这样可以减少很多网络负载。

先决条件:

在自定义客户端和服务器中实现这一点非常容易。但这不是我想要的。

我的问题:是否存在允许此类对话的 RFC 或其他标准?

如果还没有标准,那么如何着手实现这个梦想呢?

安全考虑

通过上面的对话框,它将能够访问已知哈希的内容。例如,恶意客户端知道有一个文件的哈希和为 1234567...。他可以执行以上两个步骤,然后客户端可以使用 GET 下载数据。

扩展对话框的解决方法:

  1. 客户端:我想 PUT 具有以下哈希和的数据:HASH
  2. 服务器:好的,PUT 会成功,但是为了确保你有数据,请将字节 N 到 M 发送给我。我需要这个来确保你有哈希和 数据。
  3. 客户端:数据的字节数 N 到 M 为 abcde...
  4. 服务器:好的,你的字节和我的匹配。我信任你。上传成功,无需再发送数据

如何完成?

由于好像还没有spec,这部分的问题还是:

如何实现这个梦想?

根据您的描述,似乎应该使用 ETags。

它专门设计用于将标签(通常是 MD5 哈希,但可以是任何内容)与资源的内容(and/or 位置)相关联,以便您稍后可以判断资源是否已更改。

PUT 请求受 ETag 支持,并且 commonly used with the If-Match header for optimistic concurrency control

但是,您的用例略有不同,因为您试图防止 PUT 到具有相同内容的资源,而 If-Match header用于允许PUT到具有相同内容的资源。

对于您的情况,您可以改为使用 If-None-Match header:

The meaning of "If-None-Match: *" is that the method MUST NOT be performed if the representation selected by the origin server (or by a cache, possibly using the Vary mechanism, see section 14.44) exists, and SHOULD be performed if the representation does not exist. This feature is intended to be useful in preventing races between PUT operations.


WebDAV also supports Etags 虽然它的使用方式可能取决于实现:

Note that the meaning of an ETag in a PUT response is not clearly defined either in this document or in RFC 2616 (i.e., whether the ETag means that the resource is octet-for-octet equivalent to the body of the PUT request, or whether the server could have made minor changes in the formatting or content of the document upon storage). This is an HTTP issue, not purely a WebDAV issue.


如果您要实现自己的客户端,我会这样做:

  1. 客户端向资源发送 HEAD 请求检查 ETag
    • 如果客户端发现它与已有的匹配,则不要发送任何其他内容
    • 如果不匹配,则发送带有 If-None-Matches header
    • 的 PUT 请求

更新

从你更新的问题来看,现在看来很清楚,当收到 PUT 请求时,你想检查服务器上的 ALL 资源,看在请求被接受。这意味着还要检查与指定为 PUT 请求目标的位置不同的资源。

据我所知,没有专门处理这种情况的现有规范。然而,ETag 机制(和 HTTP 协议)被设计为足够通用和灵活以处理许多情况,这就是其中之一。

当然,这只是意味着您无法利用标准的 HTTP 服务器逻辑——您需要在客户端和服务器端都自定义代码。

假设

在我进入可能的实现之前,需要做出一些假设。

  1. 如前所述,您需要同时控制服务器和客户端
  2. 需要就基于内容生成 ETag 的算法达成一致。这可以是 MD5、SHA1、SHA2-256、SHA3、它们的组合等。我只是提到算法输出作为 ETag,但如何实现取决于您。

可能的实现

如果简单的案例不适合您,则已将这些内容从最简单到复杂的顺序排列。

可能的实现 1

这假设您的服务器实现允许您读取请求 header 并在收到整个请求之前做出响应。

  1. 客户端计算要上传的 file/resource 的 ETag。
  2. 客户端向服务器发送一个 PUT 请求(位置无关紧要),其中 header If-None-Match 包含 ETag 并继续正常发送 body。
  3. 服务器检查带有 ETag 的资源是否已经存在。
  4. 服务器:
    • 如果 ETag 已经存在,立即 return 一个 412 响应代码。可选地终止连接以阻止客户端继续发送资源(注意:HTTP 规范不建议这样做,尽管没有明确禁止。请参见下面的注释 1)。是的,会浪费一点带宽,但您不必等待整个请求完成。
    • 如果ETag不存在,等待请求正常完成。
  5. 客户:
    • 如果收到 412 响应,将其解释为资源已经存在并且需要中止请求 -- 停止发送数据。

可能的实现 2

这稍微复杂一些,但更符合 HTTP 规范。此外,如果您的服务器架构 doesn't allow you to read the headers 在收到整个请求之前,这可能会起作用。

  1. 客户端计算要上传的 file/resource 的 ETag。
  2. 客户端向服务器发送 PUT 请求(位置无关紧要),其中 header If-None-Match 包含 ETag 和 Expect: 100-continue header。此时请求 body 尚未发送。
  3. 服务器检查带有 ETag 的资源是否已经存在。
  4. 服务器:
    • 如果ETag已经存在,return一个412 response
    • 如果ETag不存在,发送一个100 response并等待请求正常完成。
  5. 客户:
    • 如果收到 412 响应,将其解释为资源已经存在,因此请求被中止。
    • 如果收到100响应,继续正常发送body

可能的实现 3

此实现可能需要最多的工作,但应该与所有主要库/体系结构广泛兼容。另一个客户上传的风险很小尽管在两个请求之间有相同内容的文件。

  1. 客户端计算要上传的 file/resource 的 ETag。
  2. 客户端向位于 /check-etag/<etag> 的服务器发送一个 HEAD 请求(没有 body),其中 <etag> 是 ETag。这将检查 ETag 是否已存在于服务器上。
  3. /check-etag/* 处的服务器代码检查是否已存在具有该 ETag 的资源。
  4. 服务器:
    • 如果 ETag 已经存在,return 200 响应。
    • 如果 ETag 不存在,发送 404 响应。
  5. 客户:
    • 如果收到 200 响应,将其解释为资源已经存在并且不继续 PUT 请求。
    • 如果收到 404 响应,则向预期目标发送正常的 PUT 请求。

注意事项

尽管具体实施取决于您,但这里有几点需要考虑:

  1. 添加或更新资源时,ETag 和位置应存储在数据库中以便快速检索。每当资源被上传时,服务器重新计算每个资源的哈希值是不必要的低效率。 ETag 和 location 字段也应该有一个索引,以便快速检索。
  2. 如果两个客户端同时上传具有相同 ETag 的资源,您可能希望在第一个完成后立即中止第二个。
  3. 对 ETag 使用散列意味着有可能发生冲突(两个资源将具有相同的散列),但在实践中,如果使用良好的散列,则可能性极小。请注意,已知 MD5 对故意 collision attacks 是弱的。如果你偏执,你可以连接多个散列来使碰撞的机会小得多。
  4. 关于您的 "security consideration",我仍然不明白了解哈希如何导致资源检索。服务器只会并且应该只告诉您特定的 ETag 是否存在。在不泄露位置的情况下,客户端不可能检索到该文件。即使客户端知道位置,服务器也应该实施其他安全控制,例如身份验证和授权以限制访问。仅将资源位置用作限制访问的一种方式只是默默无闻的安全性,特别是因为从您提到的内容来看,路径似乎遵循用户名的模式。

备注

  1. RFC 2616 表明不应这样做:

If an origin server receives a request that does not include an Expect request-header field with the "100-continue" expectation, the request includes a request body, and the server responds with a final status code before reading the entire request body from the transport connection, then the server SHOULD NOT close the transport connection until it has read the entire request, or until the client closes the connection. Otherwise, the client might not reliably receive the response message.

此外,请勿在未发送任何状态代码的情况下从服务器端关闭连接,因为客户端很可能会重试请求:

If an HTTP/1.1 client sends a request which includes a request body, but which does not include an Expect request-header field with the "100-continue" expectation, and if the client is not directly connected to an HTTP/1.1 origin server, and if the client sees the connection close before receiving any status from the server, the client SHOULD retry the request.