定义
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写。是用于从万维网服务器传输超文本到本地浏览器的传送协议。使用TCP端口是:80
URI&URL
- URI:统一资源标识符,可以唯一标识一个资源。就像人的身份证号一样是唯一的。
- URL:统一资源定位符,是URI的子集。例如叫张三的人有很多,但是我们可以通过定位来确定到底是哪个张三。例如:中国/北京/朝阳/大望路26号/和平里小区/2号楼三单元301室/张三
URL由什么组成?
http://www.baidu.com/index.html?name=sss&key=123#mao=3345
- http://:协议protocol,表示使用什么协议进行请求
- www.baidu.com:主机域名host,表示你要访问的主机地址
- index.html:文件路径path,表示你要访问主机下哪个文件
- ?name=sss&key=123:请求参数params
- #mao=3345:锚,用于定位到网页中的某个位置
在Java中我们可以使用URL类的API来分别获取上面的各个部分:
val url = URL("http://www.baidu.com/index.html?name=sss&key=123#mao=3345")
val protocol = url.protocol
val host = url.host
val path = url.path
val query = url.query
val ref = url.ref
HTTP的请求过程
假设现在是在浏览器上发起请求的是上面提到的地址,那么肯定要发起HTTP请求。首先会把?后面的数据拿到,然后在应用层(HTTP协议就是应用层协议)把数据加上HTTP头,也就是HTTP报文。接着向传输层发送,传输层会加上TCP头和端口号然后发往网络层。在网络层又会加上IP头,包含目标的IP地址,然后发往物理层。在物理层再加上MAC头,包含了目标MAC或者网关MAC还有源MAC地址。接着就会在网络上传输,去寻找目标主机。找到之后就会一层一层的去掉之前加上的那些头去解析数据。
一次完整的HTTP请求过程
- 首先会进行DNS域名解析(本地浏览器缓存、操作系统缓存、DNS服务器),把www.xxx.com解析成像101.2.33这样的IP地址
- 三次握手建立TCP连接
- 客户端向服务端发送请求命令,如Get/www.xxx.com/http/1.1
- 客户端发送请求头信息
- 服务器应答,如Http/1.1 200 OK
- 服务器返回响应信息
- 服务器向客户端发送数据
- 服务器关闭TCP连接
HTTP协议报文结构
请求报文
如上图所示,请求报文由4部分组成:
- 请求行:包含请求方法(如常见的GET、POST等),URL(如上面路径中主机地址和参数中间的“/index.html”)和协议版本(如HTTP/1.1、HTTP/2.0)。它们中间由空格分开,最后以回车符 \r 和换行符 \n 结束
请求方法 | 作用 | 加入版本 |
---|---|---|
GET | 向服务器请求获取URL指定的资源。如果要向服务器传递参数则参数只能包含在URL中。GET请求的请求报文中没有“报文主体”这一部分 | HTTP1.0 |
POST | 向URL指定的资源提交数据(如提交表单或上传文件)。数据就是请求报文中的“报文主体” | HTTP1.0 |
HEAD | 类似于GET请求,只不过返回的响应中没有具体的内容,用于获取报头 | HTTP1.0 |
PUT | 向URL指定的的位置上传资源。假设权限允许,如果URL处已有资源则进行替换,反之就在该处新建该资源 | HTTP1.1 |
DELETE | 请求服务器删除URL指定的资源 | HTTP1.1 |
CONNECT | 预留给能够将连接改为管道方式的代理服务器。 | HTTP1.1 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断。原来的请求报文会包含在响应报文的“报文主体”中 | HTTP1.1 |
OPTIONS | 允许客户端查看服务器的性能。 | HTTP1.1 |
PATCH | 对PUT方法的补充,用来对已知资源进行局部更新。 | HTTP1.1 |
- 请求头:用来告知服务器该请求和客户端本身的一些额外信息。每个请求头都是一个键值对,每个请求头都单独占一行,末尾都是回车符和换行符。在所有的请求头中只有Host请求头是必须的
请求头 | 作用 | 示例 |
---|---|---|
Host | 请求的网站域名,它允许在同一服务器上建立多个不同域名的网站 | Host:www.baidu.com |
Accept | 说明客户端可接收的资源类型 | Accept:text/html;image/apng |
Accept-Charset | 说明客户端可接收的字符集 | Accept-Charset:utf-8 |
Accept-Language | 说明客户端可接收的语言 | Accept-Language:zh-CN |
Accept-Encoding | 说明客户端可接收的数据压缩类型 | Accept-Encoding:gzip |
Cache-Control | 说明本次请求和响应链的缓存机制 | Cache-Control:no-cache |
Content-Type | 提交的数据(即报文主体)的类型 | Content-Type:application/x-www-form-urlencoded |
Content-Length | 提交数据的大小,以字节为单位 | Content-Length:1024 |
Range | 只请求资源的一部分,它是断点续传的基础 | Range:bytes=237-593 |
Referer | 来源网页的地址,浏览器正是通过来源网页上的一个链接打开了当前请求的网页 | Referer:http://www.xxx.com/login.php |
User-Agent | 浏览器和操作系统的信息 | User-Agent:Mozilla/5.0 (compatible;MSIE: 5.5;Window NT 5.0) |
- 空行:在请求头部的后面是一个空行,它只包含一个回车符和一个换行符,不包含其它任何内容,连空格也不能包含。这个空行用于标记请求头部已结束;它是必须要有的,即便使用GET这样不包含报文主体的请求方法时也要有这个空行。
-
报文主体:报文主体就是要提交给服务器的数据。比如当我们使用POST方法提交表单时,表单中的内容就包含在请求报文的报文主体中。在某些情况下请求报文中是没有报文主体的,比如使用GET方法,此时若要向服务器传递参数,那么参数就只能包含在URL的查询字符串(query string)中。
报文主体中的数据格式由Content-Type请求头指定,主要有两种格式,我们这里简单提一下。当Content-Type的值为application/x-www-form-urlencoded时,那么提交的数据就是以&分隔的多个键值对,每个键值对的键和值用等号连接,且数据还要经过URL编码。假设有一个让用户输入姓名、年龄和国籍的表单,那么提交的数据格式是这样的:name=Mike&age=22&country=America。
当需要上传大量的二进制数据(比如文件)的时候,应该使用multipart/form-data格式。此时Content-Type的值为"multipart/form-data; boundary=3vkqffBXJh"。其中3vkqffBXJh是自定义的分隔符,它用于分隔提交数据中的不同部分。你或浏览器可以随意指定它,不过它应该要能和其它内容相区别。
此时,提交数据的格式就像下面这样。数据中的每一部分都以--3vkqffBXJh的一行开始,它的形式是在分隔符前面添加两个连字符。紧接着的是这部分数据的消息头,消息头用于说明该部分数据的相关信息,类似于请求头。每个消息头占据单独的一行,其中Content-Disposition消息头是必需的而其它消息头是可选的。在所有消息头之后是一个空行,空行后才是该部分数据的实际内容,空行用于分隔消息头和实际数据。
--3vkqffBXJh
Content-Disposition: form-data; name="name"
Mike
--3vkqffBXJh
Content-Disposition: form-data; name="age"
22
--3vkqffBXJh
Content-Disposition: form-data; name="country"
America
--3vkqffBXJh
Content-Disposition: form-data; name="icon"; filename="user-icon.png"
Content-Type: image/png
这里是user-icon.png文件的二进制数据...
--3vkqffBXJh--
Content-Disposition的值中第一部分是固定不变的form-data,表明该部分数据是表单内容。第二部分是name="fieldName"这样的形式,指明该部分数据属于表单中的哪一个字段。如果这部分数据是文件的话,那么可能还有形如filename="fileName"的第三部分,它指定该文件的初始名称。对于文件数据,可能还有一个Content-Type消息头用于指定该文件的MIME类型。
此时整个报文主体以--3vkqffBXJh--的一行结束。注意,这一行的特殊之处在于分隔符的前后均有两个连字符。multipart/form-data格式的数据中每一行都以一个回车符和换行符结尾。
响应报文
如上图所示,响应报文也由四部分组成:
-
状态行:包括协议版本、状态码和状态短语。和请求报文中的请求行一样,中间也是由空格分开,最后以回车符和换行符结束
状态码是服务器返回的表示响应状态的代码,状态短语是对该响应状态的一个简单描述。常见的状态码和它们对应的状态短语如下表所示:
状态码 | 状态短语 | 意义 |
---|---|---|
200 | OK | 请求处理成功,所请求的内容将随该响应报文一起返回 |
301 | Moved Permanently | 请求的资源已被永久移动到了新的位置。新的URL应该包含在Location的响应头中 |
302 | Found | 请求的资源现在临时移到新的位置 |
304 | Not Modified | 请求的资源经上次请求后没有任何改变,客户端可以继续使用缓存资源 |
400 | Bad Request | 请求参数或者语义有误,服务器无法理解 |
401 | Unauthorized | 请求需要验证用户 |
403 | Forbbiden | 服务器拒绝访问该资源 |
404 | Not Found | 请求失败,服务器上没有所请求的资源 |
408 | Request Timeout | 请求超时,客户端没有在服务器预备等待的时间内完成一次请求的发送 |
500 | Internal Server Error | 服务器遇到一个未知错误,无法响应请求 |
503 | Service Unavaliable | 服务器暂时无法响应请求,稍后可能恢复 |
- 响应头:和请求头差不多,用于传递一些附加信息。格式与请求头一致,不再赘述
响应头 | 作用 | 示例 |
---|---|---|
Allow | 对一个资源所允许的请求方法 | Allow:GET,POST,HEAD |
Cache-Control | 告知客户端是否可以缓存该资源,时间单位为秒 | Cache-Control:max-age=3600 |
Content-Encoding | 说明响应的数据是如何压缩的 | Content-Encoding:gzip |
Content-Length | 响应数据的长度,以字节为单位 | Content-Length:224 |
Content-Location | 所请求资源的一个候选地址 | Content-Location:/software/web.php |
Content-Type | 响应的数据类型 | Content-Type:text/html;charset=UTF-8 |
Last-Modified | 请求资源最后被修改的时间 | Last-Modified:tue,23 Jul 2018 21:25:13 GMT |
Location | 重定向的地址 | Location:http://www.xx.com |
Server | web服务器的软件信息 | Server:Apache/2.4.6(Unix) |
- 空行:在响应头部的后面是一个空行,它只包含一个回车符和一个换行符,不包含其它任何内容,即便是空格也不能有。它用于指定响应头部已结束,该空行是必不可少的,即便响应报文中没有报文主体也要有这个空行。
- 报文主体:响应报文中的报文主体就是服务端返回的数据,它的类型由响应头"Content-Type"说明。在某些响应报文中是没有报文主体(即响应数据)的,比如状态码为304(Not Modified)的响应报文或者某些请求错误时的响应报文。
HTTP缓存机制及原理
上图展示的是第一次请求的时候的时序图。第一次请求肯定是没有缓存的,那么紧接着就会从服务器请求,请求成功了服务器则会返回数据和数据的缓存规则。然后客户端就会在本地备份一份。那么当下一次请求的时候如果缓存可用或者缓存没过期则可用直接使用缓存的数据。
-
对比缓存:顾名思义,需要比较判断是否可用使用缓存。上面说了服务器会返回数据和缓存标识,并且客户端会进行备份。那当下一次请求的时候,客户端则会去缓存中获取标识,然后将标识发送到服务器。服务器根据缓存标识进行判断,判断成功后返回304状态码,告诉客户端比较成功,可用使用缓存数据。这种情况对应的缓存标识为Cache-Control:no-cache。时序图对应如下:
如果对比成功返回304之后则需要再去缓存当中取数据,反之对比失败的话服务器会返回新的数据和新的缓存标识,那么客户端就需要把新的数据和标识再缓存到缓存系统当中。
对比缓存的好处:1.缓存的有效性可以由服务器动态做验证 2.节省流量和时间,因为如果对比成功的话就直接返回304就可以了,不需要返回数据
实现:1.Last-Modified/ If-Modified-Since:第一次请求的时候服务器返回Last-Modified响应头,这个响应头代表了资源上一次被修改的时间。那么当下一次请求的时候就客户端就需要带上If-Modified-Since去请求。那么服务端就需要拿这个If-Modified-Since和LastModified去对比。如果If-Modified-Since >= LastModified,那么服务器则返回200并返回新的数据,反之客户端只返回304,告诉客户端可以使用缓存。
- Etag/If-None-Match:Etag是该资源在服务器的唯一标识。在第一次请求的时候服务器会返回此标识。那么下次请求的时候客户端则需要使用If-None-Match带上服务器之前返回的Etag发送到服务器。然后服务器来判断这两个标识别是否相等。不相等,返回200和新数据,反之仅返回304。这种方式的优先级高与Last-Modified,同时存在以Etag为准。
-
强制缓存:
强制缓存一般是使用header中的Expires和Cache-Control来实现:
Expires:服务器直接返回数据的到期时间。如果下一次请求时,当次请求的时间小于这个到期时间,那么就直接使用缓存的数据。
Cache-Control:Http1.1提出的,为了弥补上面Expires的缺陷。因为服务器的时间和客户端的时间有可能存在偏差。它所对应的值和具体的意义如下表所示:
值 | 意义 |
---|---|
private | 内容只缓存到私有缓存中(仅客户端可以缓存,代理服务器不可缓存) |
public | 任何缓存都可以进行缓存,即使响应默认是不可缓存或仅私有缓存可存的情况 |
max-age=xxx | 缓存的内容将在 xxx 秒后失效, 这个选项只在HTTP 1.1可用, 并如果和Last-Modified一起使用时, 优先级较高 |
no-cache | 必须先与服务器确认返回的响应是否被更改,然后才能使用该响应来满足后续对同一个网址的请求。因此,如果存在合适的验证令牌 (ETag),no-cache 会发起往返通信来验证缓存的响应,如果资源未被更改,可以避免下载。 |
no-store | 所有内容都不会被缓存到缓存或 Internet 临时文件中,相当于每一次请求都直接访问服务器 |
must-revalidation/proxy-revalidation | 如果缓存的内容失效,请求必须发送到服务器/代理以进行重新验证 |
网友评论