HTTP的原理与工作机制

HTTP 的定义

       ⼀种⽹络传输协议,位于 TCP / IP 协议族的最顶层——应⽤层。

HTTP

       Hypertext Transfer Protocol,超⽂本传输协议,和 HTML (Hypertext Markup Language 超⽂本标记语⾔) ⼀起诞⽣,⽤于在⽹络上请求和传输 HTML 内容。

超⽂本

       即“扩展型⽂本”,在电脑中显示的、含有可以指向其它文本链接(hyperlink)的文本。Hypertext,超文本,在此处你可以理解为指的就是HTML,如下图所示:

HTML超文本

HTTP 的工作方式

HTTP的工作方式

浏览器

       ⽤户输⼊地址后回⻋或点击链接 -> 浏览器拼装 HTTP 报⽂并发送请求给服务器 -> 服务器处理请求后发送响应报⽂给浏览器 -> 浏览器解析响应报⽂并使⽤渲染引擎显示到界⾯。

⼿机 App

       ⽤户点击或界⾯⾃动触发联⽹需求 -> Android 代码调⽤拼装 HTTP 报⽂并发送请求到服务器 -> 服务器处理请求后发送响应报⽂给⼿机 -> Android 代码处理响应报⽂并作出相应处理(如储存数据、加⼯数据、显示数据到界⾯)。

URL 和 HTTP 报⽂(重要)

URL 格式

URL转HTTP示例

       三部分:协议类型、服务器地址(和端⼝号)、路径(Path)。

       协议类型://服务器地址[:端⼝号]路径。

报文格式

请求报⽂(Request)

响应报⽂(Response)

响应报⽂(Response)

响应报⽂(Response)

Request Method 请求⽅法(重要)

GET

  • ⽤于获取资源
  • 不发送 Body
1
2
GET /users/1 HTTP/1.1
Host: api.github.com

       对应 Retrofit 的代码:

1
2
@GET("/users/{id}")
Call<User> getUser(@Path("id") String id, @Query("gender") String gender);

POST

  • ⽤于增加或修改资源
  • 发送给服务器的内容写在 Body ⾥⾯
1
2
3
4
5
6
POST /users HTTP/1.1
Host: api.github.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

name=rengwuxian&gender=male

       对应 Retrofit 的代码:

1
2
3
4
@FormUrlEncoded
@POST("/users")
Call<User> addUser(@Field("name") String name, @Field("gender") String
gender);

PUT

  • ⽤于修改资源
  • 发送给服务器的内容写在 Body ⾥⾯
1
2
3
4
5
6
PUT /users/1 HTTP/1.1
Host: api.github.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 13

gender=female

       对应 Retrofit 的代码:

1
2
3
4
@FormUrlEncoded
@PUT("/users/{id}")
Call<User> updateGender(@Path("id") String id, @Field("gender") String
gender);

DELETE

  • ⽤于删除资源
  • 不发送 Body
1
2
DELETE /users/1 HTTP/1.1
Host: api.github.com

       对应 Retrofit 的代码:

1
2
@DELETE("/users/{id}")
Call<User> getUser(@Path("id") String id, @Query("gender") String gender);
1
2
3
GET、PUT 和 DELETE 是幂等的,在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。GET获取资源,取一次取多次对服务器没有影响。PUT 同样,例如修改用户性别,修改一次变成女性与修改十次变成女性其结果是一样的。DELETE 删除一个用户,可以删掉,再执行删除该用户,该用户已删除无法再次删除,但不会删除其它用户,不会造成其它结果。而 POST 则不同,例如现在增加一个新用户,再执行一次 POST,便又增加了一个新用户,服务器改变了,不是幂等。

POST 作用覆盖了 PUT,因此 PUT 很少使用。
  • 和 GET 使⽤⽅法完全相同,唯⼀区别在于,返回的响应中没有 Body;
  • HEAD 实际用途:做下载的时候,可能需要先确定文件有多大,是否支持断点续传或者多线程下载等等,此时可以先使用 HEAD,获得相关信息。

Status Code 状态码(重要)

       三位数字,⽤于对响应结果做出类型化描述(如“获取成功”、“内容未找到”)。

       状态码由常见性排列如下:

2xx

       成功。

常用状态码 解释
200 最典型的是 200(OK)。
201 创建成功,比如创建一个用户成功。

4xx

       客户端(浏览器、手机应用)错误。

常用状态码 解释
400 客户端请求错误,例如请求参数不正确。
401 认证失败,比如请求需要登录的资源而未登录。
403 被禁⽌。
404 找不到内容。

5xx

       服务器错误。

常用状态码 解释
500 服务器内部错误,服务器资源不足,数据库内容找不到等等。

       5xx与4xx区分开来主要是用于调试。

3xx

       重定向。

常用状态码 解释
301 永久移动。例如在输入网址http://hencoder.com 跳转到了https://hencoder.com , 表明当前请求的URL已被修改,客户端会自动再执行一次请求,一般都是永久性的网址迁移。其主要意义在于搜索引擎,例如使用301将用户从一个URL引入另一个URL,搜索引擎会将旧网站的流量认为是新网站的流量,这样保留了之前的网站运营成果。
302 暂时移动。比如一个页面暂时出了问题需要修复,此时将用户引导到另一个临时性的页面。
304 内容未改变。例如刷新了https://hencoder.com ,服务器表明请求的网站内容并未变化,可以使用之前的缓存。

1xx

       临时性消息。

常用状态码 解释
100 继续发送。客户端向服务器传输比较大的消息时,需要分段传输,服务器每接受到一段信息会返回100的状态码,客户端则会继续传输下一段信息。
101 正在切换协议。比如 HTTP2 和 HTTP1.1 是不兼容的,浏览器如果尝试 HTTP2 的请求时,会先尝试发请求报文,header 内会询问服务器是否支持 HTTP2 ,如果服务器支持,会返回101的状态码,则接下来的消息可以使用 HTTP2 的格式,这是一个具有兼容性的请求,如果服务器不支持 HTTP2 ,则会返回状态码是200的消息,客户端继续使用 HTTP1.1 。

Header ⾸部

       HTTP 消息的元数据(metadata),数据的属性,例如发送给服务器消息的长度、格式,返回消息的字符集,是否有数据压缩等等。

Host

       ⽬标主机。注意:不是在⽹络上⽤于寻址的(寻址通过DNS获得对应域名的IP地址),⽽是在⽬标服务器上⽤于定位⼦服务器的。

Content-Length

       内容(Body)的长度(字节)。

       在发送信息时(请求或者响应报文),除了文本信息之外也可能会发送二进制数据。通常通过分隔符来确定内容结束,比如换行符。由于二进制数据不受限制,如果二进制内容中本身就有换行符,则可通过改长度来判断内容是否结束。

Content-Type(重要)

       内容(Body)的类型。

text/html

       html 文本,用于浏览器页面响应的类型, Body 中返回 html ⽂本。格式如下:

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 853

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
......

application/x-www-form-urlencoded

       Web ⻚⾯纯⽂本表单的提交⽅式,普通表单,encoded URL 格式,只能传输文本,不能传输二进制数据。

纯⽂本表单的提交⽅式

       格式如下:

1
2
3
4
5
6
POST /users HTTP/1.1
Host: api.github.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

name=rengwuxian&gender=male

       对应 Retrofit 的代码:

1
2
3
4
@FormUrlEncoded
@POST("/users")
Call<User> addUser(@Field("name") String name, @Field("gender") String
gender);

multitype/form-data

       Web ⻚⾯含有⼆进制⽂件时的提交⽅式,多部分形式,一般用于传输包含二进制内容的多项内容。

含有⼆进制⽂件时的提交⽅式

       格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /users HTTP/1.1
Host: hencoder.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Length: 2382

------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"

rengwuxian
------WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="avatar"; filename="avatar.jpg"
Content-Type: image/jpeg

JFIFHHvOwX9jximQrWa......
------WebKitFormBoundary7MA4YWxkTrZu0gW--
1
boundary=后面是分界线,如上面的格式中的“----WebKitFormBoundary7MA4YWxkTrZu0gW”。multitype/form-data 类型可以传输纯文本,但是浪费空间与流量。

       对应 Retrofit 的代码:

1
2
3
4
5
6
7
8
9
10
@Multipart
@POST("/users")
Call<User> addUser(@Part("name") RequestBody name, @Part("avatar")
RequestBody avatar);
...
RequestBody namePart = RequestBody.create(MediaType.parse("text/plain"),
nameStr);
RequestBody avatarPart = RequestBody.create(MediaType.parse("image/jpeg"),
avatarFile);
api.addUser(namePart, avatarPart);

application/json

       json 形式,用于 Web Api 的响应或 POST / PUT 请求。

       请求中提交 JSON:

1
2
3
4
5
6
POST /users HTTP/1.1
Host: hencoder.com
Content-Type: application/json; charset=utf-8
Content-Length: 38

{"name":"rengwuxian","gender":"male"}

       对应 Retrofit 的代码:

1
2
3
4
5
@POST("/users")
Call<User> addUser(@Body("user") User user);
...
// 需要使⽤ JSON 相关的 Converter
api.addUser(user);
1
与表单提交相比,Json请求提交格式比较自由,你可以在JSON请求中增加额外内容。

       响应中返回 JSON:

1
2
3
4
5
6
7
HTTP/1.1 200 OK
content-type: application/json; charset=utf-8
content-length: 234

[{"login":"mojombo","id":1,"node_id":"MDQ6VXNl
cjE=","avatar_url":"https://avatars0.githubuse
rcontent.com/u/1?v=4","gravat......

image/jpeg , application/zip …

       单文件,用于 Web Api 响应或 POST / PUT 请求。

       请求中提交⼆进制内容:

1
2
3
4
5
6
POST /user/1/avatar HTTP/1.1
Host: hencoder.com
Content-Type: image/jpeg
Content-Length: 1575

JFIFHH9......

       对应 Retrofit 的代码:

1
2
3
4
5
6
@POST("users/{id}/avatar")
Call<User> updateAvatar(@Path("id") String id, @Body RequestBody avatar);
...
RequestBody avatarBody = RequestBody.create(MediaType.parse("image/jpeg"),
avatarFile);
api.updateAvatar(id, avatarBody)

       响应中返回⼆进制内容:

1
2
3
4
5
HTTP/1.1 200 OK
content-type: image/jpeg
content-length: 1575

JFIFHH9......

Transfer: chunked

       分块传输编码(Chunked Transfer Encoding),⽤于当响应发起时,内容⻓度还没能确定的情况下。和 Content-Length 不同时使⽤。⽤途是尽早给出响应,减少⽤户等待。格式如下(最后传输 0 表示内容结束):

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Content-Type: text/html
Transfer-Encoding: chunked
4
Chun
9
ked Trans
12
fer Encoding
0

Location

       指定重定向的⽬标 URL,返回301之后的目标地址。浏览器或者网络请求库会自动做请求。

User-Agent

       ⽤户代理,即是谁实际发送请求、接受响应的,例如⼿机浏览器、某款⼿机 App。

Range / Accept-Range

       在目标服务器支持分段取内容支持分段下载时,指定 Body 的内容范围,按范围取数据。

  • Accept-Range: bytes 响应报⽂中出现,表示服务器⽀持按字节来取范围数据;
  • Range: bytes=<start>-<end> 请求报⽂中出现,表示要取哪段数据;
  • Content-Range:<start>-<end>/total 响应报⽂中出现,表示发送的是哪段数据。

作⽤:断点续传、多线程下载。

       发送 Cookie / 设置 Cookie。请参考《登录与授权》一节中的Cookie。

Authorization

       授权信息。请参考《登录与授权》一节中的Authorization。

部分其它 Headers

  • Accept: 客户端能接受的数据类型。如 text/html
  • Accept-Charset: 客户端接受的字符集。如 utf-8
  • Accept-Encoding: 客户端接受的压缩编码类型。如 gzip
  • Content-Encoding:压缩类型。如 gzip

Cache

       作⽤:在客户端或中间⽹络节点缓存数据,降低从服务器取数据的频率,以提⾼⽹络性能。

Cache 和 Buffer 的区别(重要)

名称 解释 作用
Cache 缓存 资源等现在已经使用完,等一会儿可能还会使用,暂时放着。例如LruCache,一个缓存策略,最新使用策略。如网络图片的请求。
Buffer 缓冲 针对工作流,有生产的上游和消费的下游,上游多生产一些,给下游稍后使用。一般有两个原因:1,上游生产太快,下游消费不了,存着;2,下游暂时没消费,但等一会儿会大量消费,上游提前生产并存着。如麦当劳的食物生产,网络视频流加载缓冲。
1
Cache 等一会儿不一定使用,而 Buffer 等一会儿一定会用。

REST

       REST 的定义众说纷纭,没有统⼀答案。此处的观点: REST HTTP 即正确使⽤ HTTP。包括:

  • 使⽤资源的格式来定义 URL
  • 规范地使⽤ method 来定义⽹络请求操作
  • 规范地使⽤ status code 来表示响应状态
  • 其它符合 HTTP 规范的设计准则

参考资料:
腾讯课堂 HenCoder

Fork me on GitHub