http概述

DinS          Written on 2017/12/17

本文介绍http的基本概念,代码中使用了asio的相关内容,建议读者阅读《asio基础使用方法》。本文重点在理解http,所以不会仔细讲解socket。

一、网络通信协议之http

Internet的基本协议是TCP/IP协议,然而在TCP/IP模型最上层的是应用层(Application layer),它包含所有高层的协议。高层协议有:文件传输协议FTP、电子邮件传输协议SMTP、域名系统服务DNS、网络新闻传输协议NNTP和HTTP协议等。

那么什么是http协议?
先从身边的经验说起,我们每天都会在浏览器地址栏中输入网址,这个网址叫做URL(Uniform Resource Locator)。这个东西确定了我们要浏览的地址,实际上也就是远程主机上的某个资源。然后浏览器变戏法式地把这个资源展现在了浏览器的视图区。看完本专题你就明白浏览器的基本工作原理了。

我们先来看看URL的内容,比如这个
http://www.boost.org/doc/libs/1_65_1/doc/html/boost_asio/tutorial.html。
开头出现了http(Hyper Text Transfer Protocol),这是我们要讲解的主角。通常这个可以不写,因为网页的世界已经全部是http了。
后面跟着的www表示Web服务器。
后面的boost.org表示域名。域名实质上就是ip地址的别名,只不过一堆数人类大脑难以记忆所以取了个别名。如果你直接在地址栏输入146.20.110.251也能够显示boost官网的主页。
后面的一系列/doc/libs…表示在这个服务器上的目录。
最后的tutorial.html就是一个文件。
因此浏览器做的就是按照这个路径找到tutorial.html文件,然后显示出来。

除了http外,其他部分都挺容易理解的。那么http起到了什么关键性的作用呢?http的本质又是什么呢?

二、通过实验理解http

让我们先来看看正常的http通讯的结果是什么样子的,以下例子摘自asio文档。

(注:这里演示的代码已经不能完全到达效果了,因为在展示的boost的这个例子中服务器已经换成https协议了。然而通讯还是流畅的,只不过看不到LICENSE文件而已。)

重点内容已经标红了。从表面的语义来看,这句话就是用http连接www.boost.org。后面紧跟的是GET然后一系列东西。GET是http里常用的一种请求。然后就是发送,再就是打印接收信息。
看一下输出结果:

这就是一次http通讯,这并没有帮助我们理解http的本质,但是或多或少说明了一些问题。
看过Asio专题的应该明白,我们这里使用的ip::tcp::iostream是一个建立在tcp协议上的socket,只不过经过了高级抽象所以看不到socket的影子。换句话说我们实际上是在用socket来通信,那么http跟socket就有某种联系了。说不定http还是socket。

为了验证我们的猜想,让我们直接使用socket来尝试通信。

省略了main()。

这是一个典型的socket,只不过传输内容复制了之前http通讯时的内容。
让我们看一下运行结果:

一模一样!!!这只能说明一个问题:
http本质上是基于TCP的socket,只不过发送socket的内容有固定格式而已

到这一步http以及其他的一系列网络通信协议都不再神秘了,也不用觉得高深莫测了,把http理解为格式固定的socket,就万事大吉了。

三、http的格式

那么接下来的问题就是http的格式问题。一个完整的http请求包含以下内容:

可以看出分成了4段,每段之间用\r\n分隔。下面分别讲解。

1.请求行

请求行表示客户端向http服务器发起一个特定的请求。
这一行中有3个小部分,每部分用空格分隔。
比如刚才的例子中:GET /LICENSE_1_0.txt HTTP/1.1\r\n,GET表示请求方法。/LICENSE_1_0.txt表示URI,即请求服务端的地址。HTTP/1.1表示协议版本。
请求方法有GET、POST、HEAD、PUT、DELETE、OPTIONS等,最常用的是GET和POST。
GET的作用就是查询,不改变内容。POST除了查询外还可以改变内容。比如说,当你打开了一个网页,那么用的就是GET。如果你在论坛上发帖,那么就是POST。
除了这一点外,二者还有一个区别:安全性。
GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),比如login.action?name=hyddd&password=idontknow&verify=%E4%BD%A0%E5%A5%BD,
这样浏览器可能会保存这些信息,如果涉及了敏感信息,比如密码,那么相当于直接把密码告诉别人了。
POST则把提交的数据放置在HTTP包的包体中,即后面的请求数据那一部分,这些信息不会直接出现在URL中,是安全的。不过这个安全仅仅是相对于GET而言,如果有抓包软件则也会一览无余。如果真的涉及敏感信息,还是要用加密算法。关于加密可参考《crypto++框架介绍》。

URI/URL通常都指向一个资源,比如例子中对应的就是LICENSE_1_0.txt这个文本文件。也经常看到只有一个/的情况,这个表示服务器的根目录。一大堆/就是深入服务器的子目录。

协议一般固定都是HTTP/1.1,代表http协议1.1版本。还存在1.0版本,不过用的很少了。二者主要区别在于HTTP/1.0每次请求都需要建立新的TCP连接,连接不能复用。HTTP/1.1新的请求可以在上次请求建立的TCP连接之上发送,连接可以复用。优点是减少重复进行TCP三次握手的开销,提高效率。另外1.1版本还增加了几个header。还有如果我记得没错1.1版支持文件上传。

总而言之,请求行表达的意思就是我是用某某版本的http,对服务器上的某某资源进行某某操作。
GET /LICENSE_1_0.txt HTTP/1.1\r\n翻译过来就是使用1.1版本协议,获取LICENSE_1_0.txt的内容。

2.请求头部(header)

以Key/Value形式成对表示头部参数,以英文冒号分隔。
请求头部的用意是告诉服务器这次请求的附带信息。
http里有若干规定好的请求头,另外程序员也可以自己增加。
绝大多数请求头都是可选的,但是对于POST请求来说Content-Length必须出现。

常用的头部有:

Host: XXX

指出请求的目的地,也就是服务器IP。host加URI就可以锁定唯一资源。以上例而言,host加URI拼接得到的就是www.boost.org/LICENSE_1_0.txt。

User-Agent: XXX

指明浏览器类型,仅针对浏览器发出的http请求有效。

Connection: Keep-Alive或close

表示是否需要持久连接。含义见http协议区别。

Accept: XXX

浏览器可接受的MIME类型。
MIME类型的用意是设定某种扩展名的文件用一种应用程序来打开的方式类型,比如Accept: image/jpeg表示客户端希望接收jpeg格式的资源。Accept: */*就是接收任意资源。默认接收text/html文本。

Content-Length: XXX

表示请求消息正文的长度,即请求数据那一部分的长度。

Content-Type: application/x-www-form-urlencoded

表明数据编码格式,默认就是给出的特别长的这个,另一个是multipart/form-data。另外还可以有别的,看后面的例子。

Cookie: XXX

这个东西比较重要,也比较深,建议单独寻找相关资源学习。

其他的一些头部可以自行搜索

3.空行

这个作用是告诉服务器请求头部结束了。因为每一个头部的末尾都有一个\r\n,用来区分不同的头部,所以如果一个\r\n后面再接一个\r\n就说明请求头部结束了。

4.请求数据

正文部分,post过去的内容就在这里,具体内容和格式就是客户端和服务端自行约定了,比如可以是json串,也可以是二进制流。

以上是客户端发送http请求的格式。服务端的响应也有固定格式,基本上是不言自明的,读者可以参考上面试验的结果。
返回结果中的Content-Type比较重要,表明了MIME类型,浏览器会根据这个做不同处理以显示正确内容。

四、http文件上传

上面介绍的都是http通信,http上传则是另外一种情况。
一个比较好的概括见https://www.cnblogs.com/yydcdut/p/3736667.html和https://www.cnblogs.com/tylerdonet/p/5722858.html。
在最初的 http 协议中,没有上传文件方面的功能,后来因为发展需要增加了对应功能。要做http文件上传,Content-Type必须是multipart/form-data,而一旦使用了multipart/form-data,请求参数的格式也需要严格符合规定。
先直观看一下http文件上传发出的信息到底是什么样子的。

图片来源:https://www.cnblogs.com/fish-li/archive/2011/07/17/2108884.html

大图点这里

这里有个boundary指明边界,任意字符串即可,不过一般要复杂一点,避免与正常文件内容混淆。上传可以有多个段,每段之间用–boundary分隔。

注意在真正的文件内容之前还有一些描述信息,照猫画虎即可。