10 | 应该如何理解请求方法?

上一讲我介绍了HTTP的报文结构,它是由header+body构成,请求头里有请求方法和请求目标,响应头里有状态码和原因短语,今天要说的就是请求头里的请求方法。

标准请求方法

HTTP协议里为什么要有“请求方法”这个东西呢?

这就要从HTTP协议设计时的定位说起了。还记得吗?蒂姆·伯纳斯-李最初设想的是要用HTTP协议构建一个超链接文档系统,使用URI来定位这些文档,也就是资源。那么,该怎么在协议里操作这些资源呢?

很显然,需要有某种“动作的指示”,告诉操作这些资源的方式。所以,就这么出现了“请求方法”。它的实际含义就是客户端发出了一个“动作指令”,要求服务器端对URI定位的资源执行这个动作。

目前HTTP/1.1规定了八种方法,单词都必须是大写的形式,我先简单地列把它们列出来,后面再详细讲解。

  1. GET:获取资源,可以理解为读取或者下载数据;
  2. HEAD:获取资源的元信息;
  3. POST:向资源提交数据,相当于写入或上传数据;
  4. PUT:类似POST;
  5. DELETE:删除资源;
  6. CONNECT:建立特殊的连接隧道;
  7. OPTIONS:列出可对资源实行的方法;
  8. TRACE:追踪请求-响应的传输路径。

看看这些方法,是不是有点像对文件或数据库的“增删改查”操作,只不过这些动作操作的目标不是本地资源,而是远程服务器上的资源,所以只能由客户端“请求”或者“指示”服务器来完成。

既然请求方法是一个“指示”,那么客户端自然就没有决定权,服务器掌控着所有资源,也就有绝对的决策权力。它收到HTTP请求报文后,看到里面的请求方法,可以执行也可以拒绝,或者改变动作的含义,毕竟HTTP是一个“协议”,两边都要“商量着来”。

比如,你发起了一个GET请求,想获取“/orders”这个文件,但这个文件保密级别比较高,不是谁都能看的,服务器就可以有如下的几种响应方式:

  1. 假装这个文件不存在,直接返回一个404 Not found报文;
  2. 稍微友好一点,明确告诉你有这个文件,但不允许访问,返回一个403 Forbidden;
  3. 再宽松一些,返回405 Method Not Allowed,然后用Allow头告诉你可以用HEAD方法获取文件的元信息。

GET/HEAD

虽然HTTP/1.1里规定了八种请求方法,但只有前四个是比较常用的,所以我们先来看一下这四个方法。

GET方法应该是HTTP协议里最知名的请求方法了,也应该是用的最多的,自0.9版出现并一直被保留至今,是名副其实的“元老”。

它的含义是请求从服务器获取资源,这个资源既可以是静态的文本、页面、图片、视频,也可以是由PHP、Java动态生成的页面或者其他格式的数据。

GET方法虽然基本动作比较简单,但搭配URI和其他头字段就能实现对资源更精细的操作。

例如,在URI后使用“#”,就可以在获取页面后直接定位到某个标签所在的位置;使用If-Modified-Since字段就变成了“有条件的请求”,仅当资源被修改时才会执行获取动作;使用Range字段就是“范围请求”,只获取资源的一部分数据。

HEAD方法与GET方法类似,也是请求从服务器获取资源,服务器的处理机制也是一样的,但服务器不会返回请求的实体数据,只会传回响应头,也就是资源的“元信息”。

HEAD方法可以看做是GET方法的一个“简化版”或者“轻量版”。因为它的响应头与GET完全相同,所以可以用在很多并不真正需要资源的场合,避免传输body数据的浪费。

比如,想要检查一个文件是否存在,只要发个HEAD请求就可以了,没有必要用GET把整个文件都取下来。再比如,要检查文件是否有最新版本,同样也应该用HEAD,服务器会在响应头里把文件的修改时间传回来。

你可以在实验环境里试一下这两个方法,运行Telnet,分别向URI“/10-1”发送GET和HEAD请求,观察一下响应头是否一致。

GET /10-1 HTTP/1.1
Host: www.chrono.com


HEAD /10-1 HTTP/1.1
Host: www.chrono.com

POST/PUT

接下来要说的是POSTPUT方法,这两个方法也很像。

GET和HEAD方法是从服务器获取数据,而POST和PUT方法则是相反操作,向URI指定的资源提交数据,数据就放在报文的body里。

POST也是一个经常用到的请求方法,使用频率应该是仅次于GET,应用的场景也非常多,只要向服务器发送数据,用的大多数都是POST。

比如,你上论坛灌水,敲了一堆字后点击“发帖”按钮,浏览器就执行了一次POST请求,把你的文字放进报文的body里,然后拼好POST请求头,通过TCP协议发给服务器。

又比如,你上购物网站,看到了一件心仪的商品,点击“加入购物车”,这时也会有POST请求,浏览器会把商品ID发给服务器,服务器再把ID写入你的购物车相关的数据库记录。

PUT的作用与POST类似,也可以向服务器提交数据,但与POST存在微妙的不同,通常POST表示的是“新建”“create”的含义,而PUT则是“修改”“update”的含义。

在实际应用中,PUT用到的比较少。而且,因为它与POST的语义、功能太过近似,有的服务器甚至就直接禁止使用PUT方法,只用POST方法上传数据。

实验环境的“/10-2”模拟了POST和PUT方法的处理过程,你仍然可以用Telnet发送测试请求,看看运行的效果。注意,在发送请求时,头字段“Content-Length”一定要写对,是空行后body的长度:

POST /10-2 HTTP/1.1
Host: www.chrono.com
Content-Length: 17

POST DATA IS HERE

PUT /10-2 HTTP/1.1
Host: www.chrono.com
Content-Length: 16

PUT DATA IS HERE

其他方法

讲完了GET/HEAD/POST/PUT,还剩下四个标准请求方法,它们属于比较“冷僻”的方法,应用的不是很多。

DELETE方法指示服务器删除资源,因为这个动作危险性太大,所以通常服务器不会执行真正的删除操作,而是对资源做一个删除标记。当然,更多的时候服务器就直接不处理DELETE请求。

CONNECT是一个比较特殊的方法,要求服务器为客户端和另一台远程服务器建立一条特殊的连接隧道,这时Web服务器在中间充当了代理的角色。

OPTIONS方法要求服务器列出可对资源实行的操作方法,在响应头的Allow字段里返回。它的功能很有限,用处也不大,有的服务器(例如Nginx)干脆就没有实现对它的支持。

TRACE方法多用于对HTTP链路的测试或诊断,可以显示出请求-响应的传输路径。它的本意是好的,但存在漏洞,会泄漏网站的信息,所以Web服务器通常也是禁止使用。

扩展方法

虽然HTTP/1.1里规定了八种请求方法,但它并没有限制我们只能用这八种方法,这也体现了HTTP协议良好的扩展性,我们可以任意添加请求动作,只要请求方和响应方都能理解就行。

例如著名的愚人节玩笑RFC2324,它定义了协议HTCPCP,即“超文本咖啡壶控制协议”,为HTTP协议增加了用来煮咖啡的BREW方法,要求添牛奶的WHEN方法。

此外,还有一些得到了实际应用的请求方法(WebDAV),例如MKCOL、COPY、MOVE、LOCK、UNLOCK、PATCH等。如果有合适的场景,你也可以把它们应用到自己的系统里,比如用LOCK方法锁定资源暂时不允许修改,或者使用PATCH方法给资源打个小补丁,部分更新数据。但因为这些方法是非标准的,所以需要为客户端和服务器编写额外的代码才能添加支持。

当然了,你也完全可以根据实际需求,自己发明新的方法,比如“PULL”拉取某些资源到本地,“PURGE”清理某个目录下的所有缓存数据。

安全与幂等

关于请求方法还有两个面试时有可能会问到、比较重要的概念:安全幂等

在HTTP协议里,所谓的“安全”是指请求方法不会“破坏”服务器上的资源,即不会对服务器上的资源造成实质的修改。

按照这个定义,只有GET和HEAD方法是“安全”的,因为它们是“只读”操作,只要服务器不故意曲解请求方法的处理方式,无论GET和HEAD操作多少次,服务器上的数据都是“安全的”。

而POST/PUT/DELETE操作会修改服务器上的资源,增加或删除数据,所以是“不安全”的。

所谓的“幂等”实际上是一个数学用语,被借用到了HTTP协议里,意思是多次执行相同的操作,结果也都是相同的,即多次“幂”后结果“相等”。

很显然,GET和HEAD既是安全的也是幂等的,DELETE可以多次删除同一个资源,效果都是“资源不存在”,所以也是幂等的。

POST和PUT的幂等性质就略费解一点。

按照RFC里的语义,POST是“新增或提交数据”,多次提交数据会创建多个资源,所以不是幂等的;而PUT是“替换或更新数据”,多次更新一个资源,资源还是会第一次更新的状态,所以是幂等的。

我对你的建议是,你可以对比一下SQL来加深理解:把POST理解成INSERT,把PUT理解成UPDATE,这样就很清楚了。多次INSERT会添加多条记录,而多次UPDATE只操作一条记录,而且效果相同。

小结

今天我们学习了HTTP报文里请求方法相关的知识,简单小结一下。

  1. 请求方法是客户端发出的、要求服务器执行的、对资源的一种操作;
  2. 请求方法是对服务器的“指示”,真正应如何处理由服务器来决定;
  3. 最常用的请求方法是GET和POST,分别是获取数据和发送数据;
  4. HEAD方法是轻量级的GET,用来获取资源的元信息;
  5. PUT基本上是POST的同义词,多用于更新数据;
  6. “安全”与“幂等”是描述请求方法的两个重要属性,具有理论指导意义,可以帮助我们设计系统。

课下作业

  1. 你能把GET/POST等请求方法对应到数据库的“增删改查”操作吗?请求头应该如何设计呢?
  2. 你觉得TRACE/OPTIONS/CONNECT方法能够用GET或POST间接实现吗?

欢迎你把自己的答案写在留言区,与我和其他同学一起讨论。如果你觉得有所收获,欢迎你把文章分享给你的朋友。

unpreview

精选留言

  • geekskai

    2020-01-07 23:08:39

    老师,这是我第三次刷这个专栏了,面试的时候,有个问题,想请教您一下,还希望你能解答一下:请求行 + 头部信息 + 空白行 + body ,有被问到说空白行的意义,我一直以为就是纯粹来标识 headers 的结束,但是面试官说不止这个功能,我后面看了HTTP 权威指南 也没有找到,Stack Overflow 也没找到。。。希望老师可以跟我说一下。
    作者回复

    按照http协议,空白行就是为了分隔header和body,因为http是纯文本的协议。

    不知道面试官还能有什么新的解释,也许是故弄玄虚。

    遇到这样的人可以及时请教,不能让他话只说一半。

    2020-01-08 11:35:46

  • 2019-06-19 21:00:04

    OPTIONS 方法还是用的很多的,CORS跨域请求必须用到OPTIONS方法了
    作者回复

    我接触的领域里options用的比较少,可能有点孤陋寡闻了。

    2019-06-20 08:55:10

  • nora

    2019-11-26 09:00:29


    pwq309同学的解释,可以说是很清晰了。

    跨域资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
    在现在前端最常用的 cors 跨域中,浏览器都是用 OPTIONS 方法发预检请求的
    作者回复

    赞!

    2019-11-26 14:03:47

  • 你好旅行者

    2019-06-19 10:44:00

    关于Post和Get之间的区别,我一直很困惑,因为其实我们也可以在Get的请求体里写参数,用Get去修改资源;或者在Post的请求头上去写参数,用它去获取资源。所以他们两者之间到底有什么区别呢?希望老师可以指教一下!
    作者回复

    就是个使用的习惯和约定,就像是红绿灯,不是强制要求你必须遵守,但大家都按照这样做沟通起来顺畅。

    要理解协议的含义,要求你遵守,但不强制。

    2019-06-19 11:00:41

  • 壹笙☞漂泊

    2019-06-19 10:19:25

    答题:
    1、增:POST 删:DELETE 改:PUT 查:GET
    请求头如何设计,这个问题。。。不太明白。
    2、我认为可以,因为http协议具有很好的灵活性。具体的对资源操作是由服务器决定的。

    总结:
    Http/1.1规定了八种方法,单词必须都是大写的形式。
    1. GET:获取资源,可以理解为读取或者下载数据
    2. HEAD:获取资源的元信息;
    3. POST:向资源提交数据,相当于写入或上传数据;
    4. PUT:类似POST;
    5. DELETE:删除资源;
    6. CONNECT:建立特殊的连接隧道;
    7. OPTIONS:列出可对资源实行的方法;
    8. TRACE:追踪请求-响应的传输路径。

    GET/HEAD
    ——从服务器获取资源
    HEAD和GET类似,也是从服务器获取资源,但是不会返回请求的实体数据,只有响应头(元信息),是GET的简易版,如果不需要资源的话,可以避免传输body数据的浪费。

    POST/PUT
    ——向服务器提交数据,数据在body里
    PUT和POST作用类似,有微妙不同,通常POST标识新建,PUT标识修改

    DELETE
    ——删除资源,危险性大,很少用

    CONNECT
    ——要求服务器为客户端和另一台远程服务器建立一条特殊的链接,这时Web服务器充当代理的角色

    OPTIONS
    ——要求服务器列出可对资源实行的操作方法,在响应头Allow字段里返回。功能有限,用处不大。Nginx没支持

    TRACE
    ——用于对HTTP链路的测试或诊断,可以显示出请求 - 响应的传输路径。存在漏洞,会泄露网站的信息,所以通常也是禁止使用

    安全与幂等
    安全:在HTTP协议里,所谓的安全,是指请求方法不会对服务器上的资源造成实质的修改,so 只有GET和HEAD是安全的,因为是只读操作。
    幂等:多次执行相同的操作,结果也都是相同的。so GET和HEAD 即是安全的也是幂等的,DELETE可以多次删除同一个资源,效果都是“资源不存在”,所以也是幂等。POST是新增或提交数据,多次提交会创建多个资源,所以不是幂等的。PUT是替换或更新数据,多次更新一个资源,资源还是第一次更新的状态。所以是幂等的。
    幂等:GET、HEAD、DELETE、PUT
    非幂等:POST

    作者回复

    总结的非常好。
    问题里的“请求头如何设计”,意思是说相关的curd参数应该放在什么地方,比如用query参数或者是字段,只是一个提示,不是要必须如何如何做。

    2019-06-19 11:02:51

  • 潇潇雨歇

    2020-06-12 22:43:13

    1、
    GET:查
    POST:增
    PUT:改
    DELETE:删
    请求行按 请求方法 请求资源 http版本号
    GET参数直接放在URI里,其他的放body
    2、应该可以
    作者回复

    对。

    2020-06-13 12:45:51

  • 彧豪

    2019-07-23 20:18:37

    老师,关于options请求,我有一些疑问,就是这个请求似乎是不受前端开发和服务端开发控制的是不是?get,post或者delete之前都有可能发出,那它的出现时机或机理是怎样的呢,为何会发出options请求?我发现我平时用webpack的代理来开发,代理到测试/生产环境的时候,get/post/delete请求之前没有options请求,但是部署到测试环境或者生产环境之后就会在get/post/delete请求之前会发出一个options请求,然后我司的其他前端和java工程师都说不上个所以然来,我也不懂,所以想要问下老师看看,希望老师能指点迷津
    作者回复

    options请求会在allow字段里列出可以对资源实施的操作方法,比如有的资源只支持get,有的只支持post/delete。

    所以有的时候回先发一个options,来确定应该如何操作资源。

    不过这个方法用的很少,其实没太大用处,因为即使资源不支持某种方法,直接返回一个405或者其他的状态码就可以了。

    2019-07-24 09:06:56

  • 浩浩

    2019-06-19 01:04:45

    老师有个问题想问一下,我之前用比较旧的http工具类发请求,使用get方法时不能使用请求体,但使用软件工具能使用请求体,不同方法对请求的格式要求是不是一样,还有就是不同方法的区别是不是体现在服务器的响应上,用post请求访问一张照片会是什么样的,暂时还没试过
    作者回复

    任何请求报文都可以带请求体,与方法无关,老的工具可能对协议支持的不好。

    后面你理解的对,请求方法最终如何处理还是要依赖于服务器,如果愿意,服务器也可以用get来上传数据,用post获取数据,现在的get/post都是依据协议标准来执行,所以是get获取post上传。

    2019-06-19 09:07:05

  • 大小兵

    2019-06-19 01:13:33

    真希望快点更新啊,看的不过瘾!
    作者回复

    慢慢来。

    2019-06-19 09:00:38

  • 业余爱好者

    2019-06-19 00:54:09

    之前做一个网站的cms,觉得又是一套crud,毫无新意,闲得慌,于是玩了一波restful"架构"。严格按照http规范,比如,查询都用GET,新增用POST,更新用PUT,删除用DELETE,url的设计也按照rest风格设计。现在想想,tomcat支持这几种http方法也是万幸,不然的话,又得加班换成get/post了。

    这段经历我认识到,http只是一种协议,不同的服务器,还有客户端,比如浏览器都可以有自己的实现。虽然各自在实现上有所取舍,但大体上,按照协议规范来,不会差。

    协议,是个好东西。。
    作者回复

    请求方法的设计思想非常好,动词可以表示各种操作,所以非常适合RESTful。

    2019-06-19 09:02:02

  • 彧豪

    2019-06-19 10:37:42

    老师,话说我昨天实际项目终于到一个问题:
    get请求带上查询字符串例如?name=a+b,但是打开chrome的控制台network选项卡发现请求url那是对的,是xxx?name=a+b,但是最后的查询字符串那那里是name: a b,+号变成了空格,java那边收到的也是a b……
    最后的解决方法是我这边encodeURIComponent一下,java那边URLDecoder.decode一下即可
    但是我不明白为何会如此,为何get请求的查询字符串中带+号,浏览器会将其变为空格
    在浏览器控制台network选项卡底端的query string parameters那有个"view URL encoded"按钮,点了之后name:a b变为name:a+b,这就意味着空格被转义成了+号,那是不是这个a和b之间的字符(串)实际不是空格呢?毕竟我暂时想不到什么方法能将空格转义为+号
    关于+号变空格的问题希望老师能指点迷津,感谢
    作者回复

    下一讲里面的url编码就会谈到。
    uri里面有些字符是不允许出现的,需要编码和解码,而+正好就被解码成了空格。

    你刚才也说了,会用到encodeURI这样的函数。

    2019-06-19 12:16:08

  • Geek_steven_wang

    2019-08-13 06:51:27

    既然不同服务器支持的方法不同,是不是最好把不支持的方法在服务器侧明确返回404 或503。这样更安全。
    关于幂等,其实是协议规定了get delete put 为幂等,服务器开发时要尽量实现为幂等,其实是对服务器实现的一个要求,当然开发人员可以不实现。
    作者回复

    是的,你理解的很对。

    协议就是一个要求通信双方都遵守的约定,当然也可以不遵守。

    2019-08-13 09:02:38

  • 1900

    2019-06-19 09:43:29

    “幂等”有什么具体的落地场景么?它重要的原因在哪里呢?
    作者回复

    在RESTful设计的时候,要考虑动作对服务器内部状态的影响。

    这个比较理论,一般不太需要关心。

    2019-06-19 11:05:41

  • Geek_Maggie

    2021-03-11 23:14:58

    你能把 GET/POST 等请求方法对应到数据库的“增删改查”操作吗?
    GET:查
    POST:增
    DELETE:删
    PUT:改

    请求头应该如何设计呢?
    按照:请求方法+URI+HTTP版本号

    你觉得 TRACE/OPTIONS/CONNECT 方法能够用 GET 或 POST 间接实现吗?
    可以
    TRACE:显示出请求 - 响应的传输路径,能用GET请求间接实现;
    作者回复

    回答的很好,不过要注意,http版本号应该算是历史遗留问题了,没什么太大用处,只是为了兼容而保留。

    2021-03-12 07:39:41

  • 勇敢黄瓜

    2020-04-23 15:21:07

    1. 你能把 GET/POST 等请求方法对应到数据库的“增删改查”操作吗?请求头应该如何设计呢?

    GET请求对应select操作;POST请求对应insert操作;GET请求,可以将用户密码放在header,将查询参数,放在请求参数中;POST请求,可以将用户密码放在header,将数据放在body,将新增数据的位置放在url以及请求参数;

    2. 你觉得 TRACE/OPTIONS/CONNECT 方法能够用 GET 或 POST 间接实现吗?
    TRACE用于跟踪请求,可以在GET或POST请求的响应头加上诊断相关的参数,比如请求耗时等;
    OPTIONS用于获取请求方法,可以用GET请求访问'/options'之类的uri,服务端响应中返回允许方法;
    CONNECT用于创建隧道,需要用POST请求触发隧道创建;
    作者回复

    回答的挺好。

    对于第一个,注意HTTP是明文协议,直接传输密码不够安全。

    2020-04-23 17:40:50

  • 蓝配鸡

    2019-12-30 09:45:59

    既然服务器有着决定权,有没有可能请求方法是HEAD,服务器相应一个带有body的response?
    作者回复

    当然可以了,只是这样不符合客户端的预期,但不按规矩出牌也拦不住,客户端只能“默默承受”。

    2019-12-30 10:18:58

  • 晴天

    2019-08-06 19:46:55

    学到新的知识点,幂等,很不错,老师辛苦了
    作者回复

    不客气。

    2019-08-06 20:41:01

  • 陈1016

    2019-06-28 15:27:51

    老师您好,关于 “DELETE 可以多次删除同一个资源,效果都是‘资源不存在’,所以也是幂等” 这句话,我个人理解是:假设服务器接受这个请求,允许对应操作。如果初始时有资源,第一次,就是“操作成功”,第二次开始,后续都是“资源不存在”;如果一开始没有这个资源,那么,一直都是“资源不存在”。那么,DELETE,是否可以算作“可变幂等”?或者,换个说法:删除操作成功后,从下一次开始的后续DELETE操作,都是“幂等”
    作者回复

    “幂等”说的是多次操作效果相同,所以不管资源是否存在,操作多次以后结果都是一样的,所以delete是“幂等”的。

    你说的“可变幂等”没有这个说法,所以不成立。

    2019-06-28 17:22:27

  • 2019-06-19 21:14:06

    老师 WebDav 这一块会详细讲嘛? 有这块的需求,或者有没有好的文档
    作者回复

    这块我基本没用过,它也不是http标准里面的,抱歉了。

    2019-06-20 08:55:44

  • 10

    2019-06-19 17:01:25

    我采用POST /10-2 HTTP/1.1的指令写了10-2的内容为“POST DATA IS HERE”,然后我采用GET /10-2 HTTP/1.1的指令去读内容 返回的“200 OK”,但实体body的内容只是一个“0”,而非前面写的“POST DATA IS HERE”
    请问难道我前面的POST指令没有写成功么?
    作者回复

    测试用的URI“10-2”不支持存储数据,所以post的数据只能在当次请求生效。

    另外发现这两测试uri有小bug,已经修复,请及时git pull更新。

    2019-06-19 17:40:16