HTTP 协议学习笔记

前言

本文并不是对HTTP协议科普的文章,不会对HTTP协议的基础进行详细的阐述,但是会从几个大的层面来分析HTTP协议是怎么工作的,让大家加深对网络请求原理的理解,本文将分别对不同类型的请求的请求报文,响应报文进行分析

概念

HTTP协议到底是个什么东西,其实就是官方指定了一些格式,大家在通信的时候都按照这个协议个格式组装报文,
然后自己解析报文进行操作,实际开发中,大量的第三方框架会把解析报文的工作进行封装

这里的通信又是建立在tcp协议的基础上,使用socket进行通信,
tcp协议会封装出一个报文,包括请求头,请求data,然后这个请求data里面有包括http协议的完整报文

HTTP 请求类型

HTTP 协议一共定义了八种请求类型,也就是我们经常用到的method类型

  • GET 请求获取Request-URI所标识的资源
  • POST 在Request-URI所标识的资源后附加新的数据
  • HEAD 请求获取由Request-URI所标识的资源的响应消息报头
  • PUT 请求服务器存储一个资源,并用Request-URI作为其标识
  • DELETE 请求服务器删除Request-URI所标识的资源
  • TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断
  • CONNECT 保留将来使用
  • OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求

这八种类型,是在HTTP协议层面的定义,HTTP说,我定义了这么多请求类型,够大家使用的了,大家请求的时候不要再出现其他的类型了。

然后服务端收到完整报文的时候,解析请求类型,根据不同的类型去做不同的操作,比如接收到DELETE 类型的请求,就会删除指定的资源。

其实我们也完全可以只使用其中一种类型,然后通关传入的参数来决定是什么操作,比如同样是删除操作,我们使用GET 类型,并且传入两个参数,代表删除操作,以及删除的资源地址,同样能够达到DELETE的效果,但是这里面我们相当于自己又定义了一套”协议”

在我们实际的项目使用过程中,使用的最多的就是 GET POST 两种类型,在这两种类型里面,会衍生出多种不同的”操作”,比如 普通的接口请求,上传文件,下载文件等

HTTP 请求报文 响应报文

一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成,下图给出了请求报文的一般格式。

一个HTTP 响应报文与请求报文的格式类似 由状态行、响应头部、空行 和 响应数据4个部分组成,下图给出了响应报文的一般格式。

普通接口请求

在日常开发中,使用场景最广泛的操作就是普通接口请求了,而普通接口请求,又可以分为GET,POST两种请求类型

GET请求接口

将参数直接拼接在了[请求行]中的URL 后面 body(请求数据)中无内容,请求头中不需要Content-Type Content-Length

请求报文

1
2
3
4
5
6
7
GET /v1/test.html?param=1 HTTP/1.1
Host: www.test.com
Accept: */*
User-Agent: NetWorkTest/1.0 (iPhone; iOS 9.3.1; Scale/2.00)
Accept-Language: zh-Hans-CN;q=1
Accept-Encoding: gzip, deflate
Connection: keep-alive

POST请求接口

将参数放到body(请求包体)里面,同时必须在请求头中增加Content-Type Content-Length

contentType:application/x-www-form-urlencoded
contentLength: body数据的长度

application/x-www-form-urlencoded:提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码。大部分服务端语言都对这种方式有很好的支持

请求报文

1
2
3
4
5
6
7
8
9
10
11
POST /v1/test.html HTTP/1.1
Host: www.test.com
Content-Type: application/x-www-form-urlencoded
Connection: keep-alive
Accept: */*
User-Agent: NetWorkTest/1.0 (iPhone; iOS 9.3.1; Scale/2.00)
Accept-Language: zh-Hans-CN;q=1
Content-Length: 323
Accept-Encoding: gzip, deflate

param1=1&param2=2

响应报文

无论上面的GET 请求还是POST 请求,其响应报文的格式都是一样的

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Date: Tue, 24 May 2016 08:15:59 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 10504
Connection: keep-alive

{"code":"0","data":{"configlist":[{"tit......
...............
........

响应行中的200 是状态码,包括我们常见的404 502等等

文件上传

文件上传一般用POST请求,将文件数据放到body中,但是根据请求头中Content-Type的不同,上传文件又可以分为两种形式

  • Content-Type 直接指定文件类型,如image/jpeg, body中的数据为完整的图片数据
  • Content-Type:multipart/form-data

Content-Type 直接指定文件类型

这种方式比较简单,只需要指定Content-Type Content-Length 即可

Content-Type是上传文件的mine类型,Content-Length是上传文件数据的大小,将上传的数据放到请求报文的body中即可

1
2
3
4
5
6
7
8
9
10
11
POST /v1/upload/updateImgFile.html HTTP/1.1
Host: www.test.com
Content-Type: image/jpeg
Connection: keep-alive
Accept: */*
User-Agent: ALiuLian_Indiana/1.1 (iPhone; iOS 9.3.1; Scale/2.00)
Accept-Language: zh-Hans-CN;q=1
Content-Length: 14469
Accept-Encoding: gzip, deflate

xxxxx图片数据xxxxxxxx

multipart/form-data方式

上面第一种方式上传文件比较简单,但是灵活性不高,比如我们想在上传的时候携带一些参数,让服务端执行一些逻辑就没有办法做到

那multipart/form-data 其实就是表单上传的方式,表单的每一个field 可以是不同的数据类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
POST /v1/upload/updateImgFile.html HTTP/1.1
Host: www.test.com
Content-Type: multipart/form-data; boundary=Boundary+C619070F12F3DF1C
Connection: keep-alive
Accept: */*
User-Agent: ALiuLian_Indiana/1.1 (iPhone; iOS 9.3.1; Scale/2.00)
Accept-Language: zh-Hans-CN;q=1
Content-Length: 14469
Accept-Encoding: gzip, deflate

--Boundary+385E026CA83A2B1F
Content-Disposition: form-data; name="param1"

1025
--Boundary+385E026CA83A2B1F
Content-Disposition: form-data; name="attachment"; filename="121305061464079793header.png"
Content-Type: image/jpg

******图片数据******
--Boundary+385E026CA83A2B1F--

上面是一个完整的POST请求,通过multipart/form-data的方式上传文件,并且携带了param1参数

Content-Type 不仅要设置为multipart/form-data 后面还必须增加一个boundary,这是一个随机生成的字符串,后面用来分隔不同的field

field之间用--boundary 分隔 最后以--boundary--结束

最后上传图片的field Content-Disposition 里面增加了filename参数,用来告诉服务端,文件要以什么名字保存,当然,也可以不需要这个参数,由服务端来命名文件

注意请求的Content-Length

下载

下载一般使用GET请求,只需要指定资源的位置即可,服务端响应报文会把文件内容放到响应body里面,当然如果文件很大,报文可能会被分割成多帧发送,所以我门下载文件的时候,不是一次拿到所有数据,而是不断的进入回调,接收数据

全文件下载

请求报文

1
2
3
4
5
6
7
GET /20160524GO0Z7WYI.png HTTP/1.1
Host: image.xxxx.com
Accept: image/*;q=0.8
User-Agent: Indiana/27 CFNetwork/758.3.15 Darwin/15.4.0
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
Connection: keep-alive

响应报文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HTTP/1.1 200 OK
Server: AliyunOSS
Date: Tue, 24 May 2016 09:11:25 GMT
Content-Type: image/jpeg
Content-Length: 12418
Connection: keep-alive
x-oss-request-id: 57441ABC49C125CB26DB0BF9
x-oss-bucket-storage-type: standard
Accept-Ranges: bytes
ETag: "F30283D509A7787153F98BEC6D5474E3"
Last-Modified: Tue, 24 May 2016 09:05:33 GMT
x-oss-object-type: Normal
Content-Disposition: inline;filename=20160524GO0Z7WYI.png
Cache-Control: no-cache
x-oss-server-time: 15

*******图片数据***********

部分下载(断点续传)

只需要在普通下载的请求头中增加一个字段Range,来表示请求资源的数据偏移量,大小即可实现数据的指定部分下载。

Range: bytes=2000- 下载从2000字节开始,前面的不需要
Range: bytes=2000-3000 下载从2000字节到3000字节的内容,其他范围的内容不需要
Range: bytes=-3000 下载最后3000字节的内容,其他范围的内容不需要
Range: bytes=2000-3000,5000-8000 下载从2000字节到3000字节,5000到8000范围的内容,其他范围的内容不需要

既然能够指定下载一个文件的某一部分数据,那么断点续传就不难实现了,当我们下载文件的时候,首先查看本地是否已经有这个文件的临时数据,如果有的话,那么我们在下载的时候,就不用从0开始下载,而是指定下载的范围从当前本地数据的长度,一直到文件最后,当接收到数据之后直接append到本地文件后面即可

1
2
3
4
5
6
7
8
GET /20160524GO0Z7WYI.png HTTP/1.1
Host: image.xxxx.com
Accept: image/*;q=0.8
Range: bytes=200-299
User-Agent: Indiana/27 CFNetwork/758.3.15 Darwin/15.4.0
Accept-Language: zh-cn
Accept-Encoding: gzip, deflate
Connection: keep-alive

响应报文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
HTTP/1.1 206 OK       //注意如果请求使用了Range,这里返回206
Server: AliyunOSS
Date: Tue, 24 May 2016 09:11:25 GMT
Content-Type: image/jpeg
Content-Length: 12418
Connection: keep-alive
x-oss-request-id: 57441ABC49C125CB26DB0BF9
x-oss-bucket-storage-type: standard
Accept-Ranges: bytes
Content-Range: bytes 0-100/2350 //2350:文件总大小
ETag: "F30283D509A7787153F98BEC6D5474E3"
Last-Modified: Tue, 24 May 2016 09:05:33 GMT
x-oss-object-type: Normal
Content-Disposition: inline;filename=20160524GO0Z7WYI.png
Cache-Control: no-cache
x-oss-server-time: 15

*******图片数据***********

多线程下载工具

假设一个文件400byte 那么,我们可以将文件分为4个部分,0-100,101-200,201-300,301-400,同时开4个线程去同时下载,并建立4个临时文件保存起来,当四个下载任务都完成后,将文件按照顺序合并即可