# HTTP Method for REST API

#### HTTP 1.1 Method 相关RFC

**January 1997**

[RFC 2068 Hypertext Transfer Protocol -- HTTP/1.1 (Obsoleted)](https://tools.ietf.org/html/rfc2068)

**June 1999**

[RFC 2616 Hypertext Transfer Protocol -- HTTP/1.1 (Obsoleted)](https://tools.ietf.org/html/rfc2616)

**March 2010**

[RFC 5789 <strong>PATCH Method for HTTP](https://tools.ietf.org/html/rfc5789)

**June 2014**

[RFC 7230 Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing](https://tools.ietf.org/html/rfc7230)

[RFC 7231 Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content](https://tools.ietf.org/html/rfc7231)

#### HTTP Method 概览

HTTP Method 是体现 REST API 语义最重要的组成部分之一。例如最常见的CREATE/READ/UPDATE/DELETE （即CRUD 操作）映射到HTTP Method POST/GET/PUT/DELETE（虽然常见但并不严谨，详见下文）。

RFC 7231 定义了最常见的 8 种HTTP Method，加上RFC 5789 定义的PATCH，共9 种常见Method。

#### GET

GET 方法通常用于获得URI 所指定的资源。GET 的标准语义通过一系列Headers 定义了诸如条件检查，返回局部结果，以及缓存等功能。这些Headers 常被用于返回静态资源，并且精确支持其语义。但是在处理动态资源的时候，还有一种方法也很受欢迎，那就是URL Parameters，即问号（？）后面的参数。两者相比各有各的优劣：

- Headers：并非URL 的一部分，易于结构化，对于程序化处理比较友好，并且能够支持较大的Payload，也无需做URL Encode。但是通常需要程序化处理，且浏览器地址栏不可见。

- URL Parameters：URL 的一部分，浏览器地址栏可见，可单字符串收藏。但是长度受限比Headers 严重，且通常需要URL Encode。

比如，同样是过滤器，放置在Headers 和URL Parameters 里都有各自的好处。具体选择哪一种方案取决于项目API 的整体风格，以及该资源的属性。

#### POST

POST 通常用来被创建资源，该资源可以是持久化的，或者发起一个处理请求。后者很好的覆盖了大多数非“创建” 类的动作。

例如，我需要一杯咖啡这个请求，在有些语境下我们可以直接创建一个咖啡的资源，例如我不关心咖啡的来源，可以从星巴克买；有些情况下则需要从另一个资源来进行转换，比如从咖啡豆冲泡成咖啡。为了体现这种非创建型动作（冲泡），我们可以发起一个冲泡请求，请求里包含所有需要的资源，例如咖啡豆，咖啡机，冲泡参数等等。

在这个例子里，我们最终得到了一杯咖啡资源，但是这个请求事实上可以是任何动作，可以不创建资源，可以修改资源，删除资源等等。这也导致POST 经常被滥用，任何动作都可以用POST 来解决。使用POST 本身没什么不妥，问题在于对请求的适当抽象和归类，可以使用不代表可以滥用。

另外一点，POST 请求通常是非幂等的。这意味着同样的请求POST 一次或者多次会导致不同的系统状态。因此，发起POST 请求时应仔细检查是否需要保证 “有且仅有一次”或者 “最多一次” 的执行序。

#### PUT

PUT 通常被用来修改资源，但是当资源不存在时也可以创建资源。和POST 不一样的是，POST 通常是创建一个某种类型的资源，而PUT 是创建、修改指定的某一个资源。这也是为什么POST 请求通常不带资源标志符（ID），而PUT 请求一般都通过标志符指定特定资源。

另外一点就是，PUT 通常认为是覆盖式的操作，是幂等的。这意味着API 设计者应该保证对同一个资源的PUT 操作无论执行多少次都产生同一个系统状态。

#### PATCH

PATCH 的语义和PUT 类似，用于修改资源，但是只修改差集指定的部分。API 的定义者应该明确该PATCH 的格式以及如何应用于指定的资源。但是，和PUT 不一样的是，PATCH 不是幂等的，服务器决定了如何处理PATCH 请求中对资源的修改。从这点来说，PATCH 的行为更接近于POST 一个请求，两者都可以是对某个资源进行修改，而修改的内容则不一定像PUT 那样直接，也可能是间接的，过程化的。

在大多数情况下，PATCH 能做的事情，PUT都能做，实在不行还有POST，因此很多API 实现都不会再多此一举支持PATCH 操作。这样的实践遵守了API 的正交性原则，对于基础级别的API，也推荐这样实现。而在面向应用的API 中（例如BFF - Backend For Frontend ），则不受这样的规则限制。相反，那些API 会更多的向应用需求倾斜。相比PUT，PATCH 有时候能够提供更佳的语义以及更小的数据交换量。

#### DELETE

DELETE 的语义是删除指定的资源。一个常见的争论是，当删除一个不存在的资源的时候，是应该返回错误，还是应该返回成功。同样的争论也发生很多其他地方。例如在Java - HashMap 里，反复删除同一个Key 是不会报错的。但是在操作系统中，如果尝试对同一个资源地址进行删除操作，操作系统通常会抱怨该资源已不存在。

在RFC 7231 中，DELETE 操作是幂等的，这意味着按照该语义，重复DELETE 同一个资源应产生同样的系统状态。但是这并不意味着API 返回也必须一样。API 的行为常常受到API 使用者的用户体验影响，如果用户需要精确的反馈，报错会很有用；但是如果用户只在乎最终状态，并且希望简化资源的回收和状态管理，那么成功的静默返回也是一个不错的注意。

#### HEAD

HEAD 用于仅返资源回头部信息，通常包含资源的大小等属性，客户端可以以此决定后续GET 操作的具体实现，例如进度追踪，或者仅获取资源的部分结果等。RFC 7231 中，要求HEAD、GET 同一个资源必须返回一致的头部信息。在实际实现中，则常考虑HEAD 的实现比GET 要轻量。因为HEAD 不需要传输具体的资源内容，应考虑仅读取资源元数据，避免读取整个资源等重量级操作。

#### OPTIONS

OPTIONS 用于测试服务器处理资源的可选项，常见的例如针对某资源允许的Method，或者允许的跨域请求等。这里不多展开。

#### TRACE，CONNECT

在REST API 的环境里，TRACE，CONNECT 语义不常用，略过。

最后值得一提的是，从最早的RFC 2068 到最新的RFC 7230、7231，HTTP Method 都可以被定义为任意 “token”[1][2]。因此，在REST API 定义中，我们是可以自定义Method 的。这样做的理由常常是，他们比常见的这9 种Method 具有更加丰富，以及可能更加精确的语义。一个常见的梗是RFC 2324：<a href="https://tools.ietf.org/html/rfc2324">Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0)</a>，1998年的一个愚人节项目，也可以说是程序员的幽默 - 私以为用来展示HTTP 的可扩展性其实是挺好的例子。

但是，不这么做的理由看上去也很充分：

- 和项目上下文有关的扩展HTTP Method 并非广为人知，意味着各类框架的支持会非常有限，结果就是常常很难，甚至无法做到。比如时至这篇文章的发表日期，Spring 框架的HttpMethod 就只有除了CONNECT 之外的8 个，不支持扩展Method。

- 可扩展意味着灵活，但是控制不好也会变成桎梏，导致API 语义混乱，不易于理解。所以需要组织级别的强有力的统一管理，确保扩展Method 的规范化，正交化，去重等，而不是随心所欲的定义。

因此在大多数项目中，我看到的结果常常是为了管理、集成的方便而向传统的Method 妥协。更何况传统的Method 已经深入人心，几乎可以涵盖90% 以上的资源操作语义，我们也就不用费尽心思再去发明些什么。也许有一天物联网真的发达了，会出现支持类似RFC 2324 那种协议的茶壶吧；）。

[1] "token" 定义参见：[RFC 7230/Section 3.2.6 Field Value Components](https://tools.ietf.org/html/rfc7230#section-3.2.6)  
[2] 参见已注册的HTTP Method：[IANA Hypertext Transfer Protocol (HTTP) Method Registry](https://www.iana.org/assignments/http-methods/http-methods.xhtml)

