HTTP协议
1 发展脉络
1.1、1991 HTTP/0.9
- 建立TCP连接、客户端发送请求(只有GET命令)、服务端返回请求(只能返回html格式字符串)后就关闭TCP连接
1.2、1996.5 HTTP/1.0
- 请求命令:增加 POST、HEAD命令;任何格式的内容都可以发送(文字、图像、视频、二进制文件等);HTTP请求和回应的格式中加入了头信息(HTTP header)用于描述一些元数据(如Content-Type等)。
- 主要缺点:每个TCP连接只能发送一个请求,发送数据完毕,连接就关闭,如果还要请求其他资源,就必须再新建一个连接。有些浏览器借助 Connection: keep-alive 请求头要求服务端不关闭TCP,服务端同样回复此字段,从而达到TCP复用,直到客户端或服务器主动关闭连接。但此字段在此版本中不是标准字段。
1.3、1997.1 HTTP/1.1
- 请求命令:增加 PUT、PATCH、HEAD、 OPTIONS、DELETE
- 最大变化:引入了持久连接(persistent connection),即TCP连接默认不关闭,可以被多个请求复用,不用声明Connection: keep-alive。 客户端和服务器发现对方一段时间没有活动就可以主动关闭连接。不过,规范的做法是,客户端在最后一个请求时,发送 Connection: close ,明确要求服务器关闭TCP连接。 目前,对于同一个域名,大多数浏览器允许同时建立6个持久连接。
- 管道机制:在同一个TCP连接里面,客户端可以同时发送多个请求
- Content-Length 字段:因一个TCP连接现可传送多个回应,因此借助此字段区分数据包是属于哪一个回应的。
- Transfer-Encoding字段:使用Content-Length字段的前提是服务器发送回应前必须知道回应的数据长度。对于一些很耗时的动态操作来说,这意味着服务器要等到所有操作完成才能发送数据,显然这样的效率不高。此时可不使用Content-Length字段,而借助 Transfer-Encoding: chunked 产生一块数据,就发送一块。
Host
字段:客户端请求的头信息新增了Host
字段,用来指定服务器的域名。有了Host字段,就可以将请求发往同一台服务器上的不同网站,为虚拟主机的兴起打下了基础。- 主要缺点:虽然1.1版允许复用TCP连接,但是同一个TCP连接里面,所有的数据通信是按次序进行的。服务器只有处理完一个回应,才会进行下一个回应。要是前面的回应特别慢,后面就会有许多请求排队等着。这称为"队头堵塞"(Head-of-line blocking)。
1.4、2009 SPDY
- 由Google研制,主要解决 HTTP/1.1 效率不高的问题。 此协议在Chrome浏览器上证明可行以后,就被当作 HTTP/2 的基础,主要特性都在 HTTP/2 之中得到继承。
1.5、2015 HTTP/2
- 二进制协议:是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为"帧"(frame):头信息帧和数据帧。(HTTP/1.1 版的头信息肯定是文本(ASCII编码),数据体可以是文本、图片等各自格式数据)
- 多工(Multiplexing):HTTP/2 复用TCP连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,而且不用按照顺序一一对应,这样就避免了"队头堵塞",实现了双向实时通信。
- 头信息压缩机制(header compression):HTTP 协议不带状态,每次请求都必须附上所有信息使得请求的很多字段都是重复的,浪费带宽影响速度。HTTP/2引入压缩机制,一方面,头信息使用gzip或compress压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段而只发送索引号,这样就提高速度了。
- 服务器推送(server push):HTTP/2 允许服务器未经请求,主动向客户端发送资源。
总结下,发展过程主要围绕持久连接、连接复用、数据分块传输、丰富类型支持 等方面展开。
题外话:OPTIONS方法哪里被使用到?如CORS中浏览器在正式发请求前会先发个OPTION请求去询问服务器是否允许接下来的跨域请求。
2、HTTP协议(HTTP/1.1)
(详见:HTTP教程)
2.1、消息格式
1、请求消息(四部分):
请求行(请求方法、请求路径、协议版本)
请求头(键值对列表,一个键值对一行)
回车换行
请求数据
注:HTTP 1.0规定,请求头的key、value都只能是ascll字符
2、响应消息(四部分):
状态行或响应行(协议版本、状态码、状态说明)
消息报头(键值对列表,一个键值对一行)
回车换行
响应正文
2.2、请求方法
各方法的具体作用可以参阅javax.servlet.http.HTTPServlet类的方法说明。
总结:
-
- GET:无副作用,幂等,不可带 Request Body
- PUT:副作用,幂等,可以带 Request Body
- POST:副作用,非幂等,可以带 Request Body
- DELETE:副作用,幂等,不可带 Request Body
注:
1、GET与POST的区别
2、使用POST还是PUT(详情参考:REST简介):综合考虑 资源ID生成、幂等性、是否将创建和更新合并在一个操作 三点
个人理解,选择的标准在于操作是否是幂等的,所谓幂等,即多次操作所造成的结果(side effect,译为副作用,感觉很别扭) 是否一样,若一样则用put,否则用post。
注意这里的side effect不是指请求得到的结果,而是造成的结果。因此GET、DELETE、HEAD等都是幂等的,而POST不是。由于PUT操作应符合幂等性,所以其应用于资源更新时,请求者应该指定资源的所有字段,若只有更新部分字段则应用POST。
3、OPTIONS请求的使用场景:CORS中与目标服务器的跨域协商用的是OPTIONS,具体可参阅:浏览器同源策略与CORS - MarchOn
2.3、状态码
几个典型状态码:
200:请求成功,一般用于GET与POST请求。
204:No Content。表示该请求已经成功了,但客户端客户不需要离开当前页面。默认情况下 204 响应是可缓存的,一个 ETag 标头包含在此类响应中。 使用示例:可借此防止form表单跳转
206:断点续传
300:多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择。
301:永久重定向,表示请求的资源已被永久移动到新URI。返回的location字段指定新URI,浏览器会自动重定向到新URI(一次访问对应两次服务器请求),地址栏的地址也会变。浏览器会缓存新URI,除非缓存失效,否则今后任何向旧URI的请求浏览器都会在发起请求前读取缓存并直接向新URI发起(即一次访问对应一次服务器请求)。
注:带'location' response header的重定向需要目标服务器配置CORS白名单才能成功重定向到目标站点,否则会被browser同源策略限制重定向。故提供自定义header让前端配合进行重定向。下同。
302:临时重定向。与301类似,浏览器也会自动重定向到新URI。与301的区别:地址栏的地址不变;不缓存新URI,故每次访问会对应两次服务器请求。
303:查看其它地址。与301类似,使用GET和POST请求查看。
304:未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源,客户端收到此响应码后会从本地缓存取资源。
305:使用代理。所请求的资源必须通过代理访问。
307:临时重定向。与302类似,区别在于301重定向后为GET方法,而307重定向后所用的HTTP请求方法与重定向前的一致。
308:永久重定向。与301类似,区别同上。
400:坏请求。客户端请求的语法有误,服务端无法理解。
401:未认证。请求要求用户的身份认证。
402:Payment Required
403:未授权,禁止访问。服务端理解客户端的请求,但拒绝执行请求。
404:Not Found。
405:Method Not Allowed
408:请求超时。服务端等待客户端发送的请求时间过长,超时。
410:gone,表示资源已经永久删除,不可用了。资源标记为过时(301)一段时间后,通过此状态码告知客户端资源已永久移除。通常是管理员认为设置的,若不清楚是否永久移除,则用404。
416:Range Not Satisfied,断点续传时请求的范围越界时返回此状态码
500:服务器内部错误,无法完成请求。
501:服务端不支持请求的功能,无法完成请求。如若服务器不支持除上述GET、POST等方法外的请求方法,则返回501。可参阅javax.servlet.http.HttpServlet#service方法的逻辑。
502:Bad Gateway。充当网格或代理的服务器从远程服务器接收到了一个无效请求。
504:Gateway Timeout。充当网关或代理服务器未及时从远端服务器获取请求。
505:服务端HTTP版本不支持,无法完成请求。
重定向相关:301、302 重定向的注意点(307、308同理):
301、302时若response的"Location" header有值,则浏览器会自动进行重定向,前端开发者并无法干预:此时即使js代码有针对status==302的判断也不会走这段js逻辑,因为浏览器发现Location有值时就进行重定向了,这先于开发者的js逻辑执行。可见,这种重定向通常是服务端发起的指令,开发者干预不了。
前端重定向:前端开发者通过js代码(window.location="xxx")进行重定向。上述第二种除非目标站点将请求来源站点添加到其CORS白名单,否则请求会受到浏览器的同源限制,从而重定向失败,此时可改用前端重定向方案——服务端将重定向目标地址放到自定义的其他response header而非"Location",前端js判断是301/302时候,取该header的值并跳转过去。
关于301、302、303、307、308响应码,发展历史及其区别可参阅这篇文章(301、308是永久重定向,其他是临时重定向)。
301、302:前者是永久重定向、后者是临时重定向。不论是POST、GET,重定向后方法都为GET。
307、308:前者是临时重定向、后者是永久重定向。不论是POST、GET,重定向后维持原方法。可见,这两个在某些场景很有用,如对于一个POST请求,期望重定向后仍为POST请求,此时可用此。(然而实际上很多浏览器并不遵守此标准,重定向后直接发GET请求。这是先有现状后有标准,但现状不根据标准做修改的结果)
301、302的适用场景的区别:永久重定向可减少服务器访问压力但无法分析链接点击行为;临时重定向相反。
301,代表 永久重定向,也就是说第一次请求拿到长链接后,下次浏览器再去请求短链的话,不会向短网址服务器请求了,而是直接从浏览器的缓存里拿,这样在 server 层面就无法获取到短网址的点击数了,如果这个链接刚好是某个活动的链接,也就无法分析此活动的效果。所以我们一般不采用 301。
302,代表 临时重定向,也就是说每次去请求短链都会去请求短网址服务器(除非响应中用 Cache-Control 或 Expired 暗示浏览器缓存),这样就便于 server 统计点击数,所以虽然用 302 会给 server 增加一点压力,但在数据异常重要的今天,这点代码是值得的,所以推荐使用 302!
断点续传相关:206、416
HTTP协议中的请求路径格式为: 协议://主机:端口/相对地址?参数#Hash ,其中若端口是80则可以默认不写。如 http://www.cnblogs.com/z-sm/p/6243378.html#blogTitle3
scheme://host[:port#]/path/.../[?query-string][#anchor] scheme 指定底层使用的协议(例如:http, https, ftp) host HTTP服务器的IP地址或者域名 port# HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如 http://www.cnblogs.com:8080/ path 访问资源的路径 query-string 发送给http服务器的数据 anchor 锚
2.4、主要请求头或响应头
Content-Type:响应头,表示响应的数据类型
(http主要 MediaType)
Java里通过 request.getServletContext().getMimeType(fileName) 可获取到。
HTTP缓存相关的请求头和响应头:Date、Cache-Control、Last-Modified、Etag、If-Modified-Since、If-None-Match、Pragma。
相关状态码:200、304
用法详见:https://www.cnblogs.com/z-sm/p/10246261.html
Content-Encoding:响应头,表示返回的数据的压缩方式,如gzip。
浏览器收到数据时会用该响应头指定的压缩方式对数据解压。
Transfer-Encoding:响应头,表示内容的传输方式。
常用的是chunked,表示分块传输,在数据量很大,并且在请求未能完全处理完成之前无法知晓响应的体积大小的情况下非常有用。在这种传输方式下,服务器不再数据缓存完才发送,而是会直接把数据发送给客户端而无需进行缓冲或确定响应的精确大小——后者会增加延迟。此外,这种传输方式允许服务器在最后发送响应头(非此方式下是不允许的)。
主要使用场景:在要传输大量的数据,但是在请求在没有被处理完之前响应的长度是无法获得的。例如,当需要用从数据库中查询获得的数据生成一个大的HTML表格的时候,或者需要传输大量的图片的时候。具体可参阅:https://www.cnblogs.com/z-sm/p/5478495.html、https://zh.wikipedia.org/wiki/%E5%88%86%E5%9D%97%E4%BC%A0%E8%BE%93%E7%BC%96%E7%A0%81
服务端在设置了Transfer-Encoding为chunked后会忽略content-length设置(即后者不再设置到响应头)
Content-Length:响应头,表示数据长度。
在未启用分块传输时,服务器会缓存指定大小的数据后才发送给浏览器、浏览器会根据该值来确定数据是否接收完毕,若设得比实际内容小,则浏览器显示的内容会截断、若大则会处于pending状态。
Connection: keep-alive 表示启用TCP长连接,以进行TCP连接的复用(详情可参阅这篇文章)
长连接:一次HTTP请求中,服务端数据发送完后不立即关闭TCP连接而是保持着连接,以供下次HTTP请求复用。
当然,不是永远不关闭而是可配置的一段时间内无数据则服务端主动关闭。例如SpringBoot可通过 server.connection-timeout 配置、Tomcat默认是60s。
几乎所有的HTTP客户端(浏览器、Java8的HttpURLConnection、Apache HttpClient等)默认都会开启长连接,通常是保留多个TCP长连接来承载HTTP请求。
长连接不能替代应用层的心跳检测机制。前者用于保持连接可用,而后者用于检测应用是否还在(即连接是否还需要用)。
TCP 也有keepalive属性,是TCP的保活机制,类似于心跳功能,用于确定连接是否还有效。服务端客户端间当一方长时间无收到另一方数据时就会发送探测报文看对方是否在线,若连续若干次探测无应答则关闭连接。主要用于某放突然宕机或网线断了等情况下另一个TCP连接的关闭。详情参阅这篇文章。
进程关闭时内核会走四次挥手过程故另一端立马知道对方下线了;主机突然宕或拔掉网线机时无法走四次挥手,若另一端会继续发送数据或TCP开启了保活机制则会关闭连接(超时重传次数或探测报文连续无响应的次数超过阈值后关闭),否则就不会关闭连接了。
Linux中默认是2小时内无数据交互则每75s发一个探测报文,连续9次无应答则关闭,即它最快2h11m15s才能发现对方不在了。可见时长很长,是内核给所有基于TCP协议的程序的一个兜底方案,应用层通常要自己实现时长更短的保活方案,如HTTP 60s、应用层的心跳等。
范围传输(断点下载)相关请求头和响应头:Range、If-Range、Accept-Ranges、Content-Range等。可和分块传输一起使用。
相关状态码:200、206、416
用法详见:marchon-http断点下载协议
Security Related HTTP Response Headers:(参阅:https://docs.spring.io/spring-security/site/docs/5.3.4.RELEASE/reference/html5/#headers)
X-Content-Type-Options:nosniff。响应头,服务端用它指示客户端(浏览器)不可修改响应的Content-Type类型,也就是不要根据文件内容来推断MIME类型覆盖响应的值,否则内容嗅探可能会把不可置信的MIME类型转变为可执行的MIME类型,带来安全隐患。只用于script、style两种类型的响应。
X-Frame-Options:响应头,用来指定当前站点是否允许被作为iframe嵌入在其他页面,以防御点击挟持(clickjacking)。取值:
deny
sameorigin
allow-from https://example.com
X-XSS-Protection
Strict-Transport-Security
Content-Security-Policy
Content-Security-Policy-Report-Only
Referrer-Policy
Feature-Policy
Clear-Site-Data
Host、Origin、Referer都是发起请求时request中的一种header,区别:
目标站点信息:Host:用来描述请求将被发送的目的地。值包括且仅包括域名和端口号;所有类型的request中都会包含此header信息。如: Host: static.cnblogs.com
源站点信息:
Referer:用来说明请求是从哪个站点发起的。值包括协议、域名、查询参数,但不包括锚点(或称hash);所有类型的request中都会有此字段,如果是从浏览器直接输入地址发起request则值为空。值示例: https://www.cnblogs.com/css/blog-common.min.css?v=KY 。
通常用于统计和阻止盗链。
由于Referer中会包含查询参数,故原始的URI中的查询参数若包含ID或密码等敏感信息则可能导致信息泄露,所以说别把敏感信息放URI中!但由于hash不被包括在内,所以确实需要的话可以将敏感信息放在URI的hash中,OAuth2.0的第二种授权模式就采用了这种方式返回token。
Origin:用来说明请求是从哪个站点发起的。值包括且仅包括协议和域名;这个参数存在于同域的POST请求和CORS跨域请求中,可以看到response有对应的header:Access-Control-Allow-Origin。
2.6、http数据边界的确定
上面介绍的 Transfer-Encoding 和 Content-Length 都可用于确定http消息的边界。其中,前者的优先级比后者高,也就是若有前者则后者会被忽略。故具体而言有如下几种(2*2=4种组合)情况:
有前者而不管有无后者:按照chunked的模式,以实际取到的body为准。
无前者:
有后者:length 比 body 大则阻塞等待数据传完;小则会截断数据。
无后者:和Content-Length=0 的处理一样。
其他
WebDAV(Web-based Distributed Authoring and Versioning) 协议是基于 HTTP 协议的通信协议,在GET、POST、HEAD等几个HTTP标准方法以外添加了一些新的方法,使应用程序可以:对Web Server直接读写文件、支持写文件锁定(Locking)及解锁(Unlock)、支持文件的版本控制。
因为基于HTTP,在广域网上共享文件有天然的优势,移动端文件管理APP也大多支持WebDAV协议。使用HTTPS还能保安全性。Apache和Nginx支持WebDAV,可作为WebDAV文件共享服务器软件。也可以使用专门的WebDAV软件部署。
在HTTP已有verb上增加了7个:LOCK, UNLOCK, MKCOL, PROPFIND, COPY, MOVE, PROPPATCH
参考资料