HTTP协议详解
1. 认识HTTP协议
超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。
HTTP的发展是由蒂姆·伯纳斯-李于1989年在欧洲核子研究组织(CERN)所发起。HTTP的标准制定由万维网协会(World Wide Web Consortium,W3C)和互联网工程任务组(Internet Engineering Task Force,IETF)进行协调,最终发布了一系列的RFC,其中最著名的是1999年6月公布的 RFC 2616,定义了HTTP协议中现今广泛使用的一个版本——HTTP 1.1。
2014年12月,互联网工程任务组(IETF)的Hypertext Transfer Protocol Bis(httpbis)工作小组将HTTP/2标准提议递交至IESG进行讨论,于2015年2月17日被批准。 HTTP/2标准于2015年5月以RFC 7540正式发表,取代HTTP 1.1成为HTTP的实现标准。
2. HTTP协议概述
HTTP是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。我们称这个客户端为用户代理程序(user agent)。应答的服务器上存储着一些资源,比如HTML文件和图像。我们称这个应答服务器为源服务器(origin server)。在用户代理和源服务器中间可能存在多个“中间层”,比如代理服务器、网关或者隧道(tunnel)。
尽管TCP/IP协议是互联网上最流行的应用,HTTP协议中,并没有规定必须使用它或它支持的层。事实上,HTTP可以在任何互联网协议上,或其他网络上实现。HTTP假定其下层协议提供可靠的传输。因此,任何能够提供这种保证的协议都可以被其使用。因此也就是其在TCP/IP协议族使用TCP作为其传输层。
通常,由HTTP客户端发起一个请求,创建一个到服务器指定端口(默认是80端口)的TCP连接。HTTP服务器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比如"HTTP/1.1 200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息。
3. HTTP工作原理
HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。HTTP协议采用了请求/响应模型。客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。
HTTP协议请求/响应步骤
- 客户端连接到Web服务器:
一个HTTP客户端,通常是浏览器,与Web服务器的HTTP端口(默认为80)建立一个TCP套接字连接。 - 发送HTTP请求:
通过TCP套接字,客户端向Web服务器发送一个文本的请求报文,一个请求报文由首行、头部字段、空行和正文4部分组成。 - 服务器接受请求并返回HTTP响应:
Web服务器解析请求,定位请求资源。服务器将资源复本写到TCP套接字,由客户端读取。一个响应由状态行、响应头部、空行和响应数据4部分组成。 - 释放连接TCP连接:
若connection 模式为close,则服务器主动关闭TCP连接,客户端被动关闭连接,释放TCP连接;若connection 模式为keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求; - 客户端浏览器解析HTML内容:
客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应头告知以下为若干字节的HTML文档和文档的字符集。客户端浏览器读取响应数据HTML,根据HTML的语法对其进行格式化,并在浏览器窗口中显示。
HTTP是基于TCP/IP协议之上的应用层协议
基于 请求-响应 的模式:HTTP协议规定,请求从客户端发出,最后服务器端响应该请求并 返回。换句话说,肯定是先从客户端开始建立通信的,服务器端在没有 接收到请求之前不会发送响应。
4. HTTP协议请求格式
1.首行
首行包含三大要素:请求方法、URL、协议版本。以空格进行间隔,以\r\n进行结尾,在请求数据中的第一行。
1.1 请求方法
描述这个请求的目的,HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源:
- GET:向指定的资源发出“显示”请求。使用GET方法应该只用在读取数据,而不应当被用于产生“副作用”的操作中,例如在Web Application中。其中一个原因是GET可能会被网络蜘蛛等随意访问。
- HEAD:与GET方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中“关于该资源的信息”(元信息或称元数据)。
- POST:向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会创建新的资源或修改现有资源,或二者皆有。
- PUT:向指定资源位置上传其最新内容。
- DELETE:请求服务器删除Request-URI所标识的资源。
- TRACE:回显服务器收到的请求,主要用于测试或诊断。
- OPTIONS:这个方法可使服务器传回该资源所支持的所有HTTP请求方法。用’*'来代替资源名称,向Web服务器发送OPTIONS请求,可以测试服务器功能是否正常运作。
- CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。
GET方法和POST方法的区别:
- GET提交的数据会放在URL之后,也就是请求行里面,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=han&age=21。 POST方法是把提交的数据放在HTTP包的正文中。
- GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.
1.2 URL
统一资源定位符,就是我们俗称的网址,唯一定位网络中的一个资源。通过内部包含的要素实现对网络资源的定位。
协议方案名称://用户名:密码@y域名或IP地址:端口/资源路径?查询字符串#片段标识符
-
协议方案名称:描述通信所用的协议。
-
用户名密码:身份认证信息,由于存在安全隐患,现在很少使用。
-
域名:如baidu.com,最终通过域名解析得到服务器的IP地址。
-
端口:IP地址和端口就能唯一确定网络上的某一台主机上的某一个进程,HTTP服务默认使用80端口,HTTPS服务默认使用443端口。
-
资源路径:定位指定计算机上的指定路径下的某个实体资源。
-
查询字符串:客户端提交给服务端少量的数据,格式为key=val&key=val。
urlencode:URL编码,如果提交的数据中具有特殊字符,就有可能与URL中的间隔符造成其歧义,因此需要进行URL编码,遇到特殊字符后,将这些特殊字符转换为16进制的数字字符,用%作为前缀以标识。如c++ URL编码后为 c%2B%2B。
urldecode:URL解码,对URL编码后的数据进行解析,得到原始数据。遇到%后则将紧随其后的两个字符转换为16进制的数字,第一个数字乘以16,加上第二个数字得到特殊数字的ASCII码值。
-
片段标识符:HTML网页的一个标签ID,可以让网页直接滑动到指定的位置。
1.3 协议版本
-
0.9版本:不算成熟的版本,只有GET方法进行超文本数据传输,格式协议不够完善。
-
1.0版本:规范了协议格式,支持了多媒体数据流的传输,相较于0.9版本,主要规范了协议格式,支持了更多的功能以及数据传输方式。
-
1.1版本:对HTTP协议进行了一系列的重新设计,相较于1.0版本,主要在于性能上的改进,以及一些其他功能的添加。
如缓存控制:在一些资源没有发生改变的情况下不需要重新传输。
长连接改进:短连接指的是建立连接后发送一个请求,得到响应之后就关闭连接,虽然简单清晰,但是性能跟不上;长连接指的是发送请求得到响应后并不会 关闭连接,当下次再来一次请求是还会使用当前这个连接,但这个连接不是永久存在的,当两端长时间没有交往时会自动断开,也就是一次连接 中可以进行多次请求。
管线化管理:请求连续发送同时处理,节省处理时间,按序响应。缺点:队头阻塞,如果第一个资源处理的时间长,就算后面的资源准备好了也不能响应。
-
2.0版本:相对于1.1版本改动更大,并且由明文字符串传输改为二进制流传输,不再向前兼容。
服务器端主动推送依赖数据:服务端主动推送数据是指请求一次,服务端会一次性推送所有数据,而不是一个请求,对应一个响应的数据。
多路复用:在头部添加请求信息,不用按序相应,解决了队头阻塞问题。
2.头部
请求头部用来描述本次请求的关键字段信息,由key:val形式的键值对组成,并且每个键值对以\r\n作为结尾。
请求常见头部:
-
Connection-控制长/短连接,keep-alive表示长连接,close表示短连接。
-
User-Agent-客户端浏览器以及系统版本信息。
-
Accept-客户端向服务器描述自己所能接收的数据属性。
-
Content-Length-描述正文长度。
当多个请求或相应连续发送的时候,就会存在粘包问题(多个数据连接在一起无法区分每个数据的起始位置),这个请求头部就可以解决这问题,拿到正文的长度之后取出指定长度的正文,解决粘包问题。
-
ontent-type-描述正文的数据类型。
3.空行
就是\r\n,用于间隔头部与正文。头部的最后一个字段以\r\n结尾,加上空行的\r\n,就会组成连续的\r\n\r\n,这就是头部结束的标志。
4.正文
先获取完整头部,通过头部中的Content-Lenght获取正文长度,然后获取指定长度的正文,通过这种方式每次获取完整一条http请求数据,正文就是提交给服务端的数据。(注意:GET请求没有正文,POST提交的数据放在正文中)。
5. HTTP协议响应格式
1.首行
首行包含三大要素:协议版本,响应状态码,状态码描述。以空格进行间隔,以\r\n进行结尾,在请求数据中的第一行。
1.1 协议版本
0.9、1.0、1.1、2.0版本。
1.2 响应状态码
-
1xx消息——请求已被服务器接收,继续处理。如101-协议切换响应。
-
2xx成功——请求已成功被服务器接收、理解、并接受。如200-OK。
-
3xx重定向——需要后续操作才能完成这一请求,当一个资源链接发生变化,但是保持原链接依旧可用,就需要将原链接重定向到新的连接,与Location搭配使用。如301-永久重定向,下次请求直接请求新链接,302-临时重定向,下次请求依旧请求原链接。
-
4xx请求错误——请求含有词法错误或者无法被执行。如400-格式请求错误,404-请求的资源不存在。
-
5xx服务器错误——服务器在处理某个正确请求时发生错误。500-服务器内部错误,502-代理请求错误,504-代理请求超时。
1.3 状态码描述
对状态码的文字描述,本身并不具有实际意义。
2. 头部
请求头部用来描述本次请求的关键字段信息,由key:val形式的键值对组成,并且每个键值对以\r\n作为结尾。
响应常见头部
- Connection-控制长/短连接
- Content-type-描述正文的数据类型
- Content-Length-描述正文长度。
- Set-Cookie-服务端通过set-cookie向客户端传递信息,会被保存在客户端浏览器的cookie文件中,搭配请求头部中的Cookie使用。
**cookie机制:**HTTP是一个无状态协议,早期版本中的短连接,一次通信连接就会断开,但是随着协议的发展,客户端的标志状态越来越重要,cookie就是用于在HTTP协议中维持客户端通信状态的机制。比如登录淘宝的时候需要输入账号密码,当切换页面的时候需要保持登录状态,因此就有了cookie机制,服务端将客户端的一些状态信息通过Set-Cookie字段发送给客户端,客户端将这些信息保存在客户端中的cookie文件中,下次请求服务器的时候将cookie信息从文件中取出来,通过请求头部Cookie发送给客户端,就不用重复的输入账号密码了。
但是由于cookie机制就是不断地在客户端与服务器之间传输客户端的身份及状态信息,这样会存在安全隐患,因此又出现了session机制,也就是会话机制。
**session机制:**服务器会为每一个客户端创建一个会话,会话中包含客户端的身份以及状态信息,然后将其保存在服务器数据库中,每个会话都有一个唯一ID,然后将session_id作为Set-Cookie的字段传输给客户端,下次会话的时候客户端就会把session_id发送给服务器,服务器就能在数据库中找到会话信息从而获取客户端的状态信息。
cookie和session的区别:
- cookie数据存放在客户端的浏览器上,session数据放在服务器上。
- cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗考虑到安全应当使用session。
- 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。(Session对象没有对存储的数据量的限制,其中可以保存更为复杂的数据类型)
3. 空行
就是\r\n,用于间隔头部与正文。头部的最后一个字段以\r\n结尾,加上空行的\r\n,就会组成连续的\r\n\r\n,这就是头部结束的标志。
4. 正文
响应给客户端的数据。
5. 实现简易HTTP服务器
作为一个HTTP服务器应该做些什么
- 应该一行数据一行数据的进行接收,当遇到空行的时候头部到此结束。
- 按照HTTP协议格式解析HTTP请求头部。
- 根据头部字段中Connect-Length字段的值指定正文的长度。(GET没有正文)
- 根据请求方法,资源路径等确定客户端的请求是什么然后进行处理。
- 最后进行响应。
#include "TCPSocket.hpp"
#include <unordered_map>
#include <sstream>
using namespace std;
int main(int argc, char* argv[])
{
int port = stoi(argv[1]);
TCPSocket listen_sock;
//1.创建套接字
listen_sock.Socket();
//2.绑定地址信息
listen_sock.Bind("0.0.0.0", port);
//3.开始监听
listen_sock.Listen();
//5.使用新建连接收发数据
while(1)
{
//4.获取新建连接
TCPSocket new_sock;
string cli_ip;
int cli_port;
listen_sock.Accept(&new_sock, &cli_ip, &cli_port);
cout<<"new connect"<<cli_ip<<":"<<cli_port<<endl;
string buf;
new_sock.Recv(&buf);
cout << "req:[" << buf << endl;
stringstream ss;
ss << "HTTP/1.1 200 OK\r\n";
ss << "Connection: close\r\n";
ss << "Content-Type: text/html\r\n";
string body = "<html><body><h1>hello world</h1></body></html>";
ss << "Content-Length: " << body.size() << "\r\n";
ss << "\r\n";
ss << body;
new_sock.Send(ss.str());
new_sock.Close();
}
//6.关闭套接字
listen_sock.Close();
return 0;
}
6. HTTPS协议
本质上来说还是HTTP协议,只不过进行了一层加密–SSL加密(针对TCP协议的)。相比于HTTP协议,安全性更高,HTTP使用的是80端口,HTTPS使用的是443端口。
加密传输:分为两个方面。数据加密和身份验证,就算数据加密了,但是对方身份不对,加密也就没有意义了。
-
身份验证:引入一个第三方权威机构,必须是双方都信任的机构,双方到权威机构颁发一个身份证书,在通信前将证书发送给对方,对方根据证书中的信息判断对方的身份,并且进行认证通过后身份验证成功,再进行通信。
-
数据加密:
对称加密:使用相同的秘钥进行加密解密,通信前将秘钥交给对方,自己使用秘钥进行数据加密,对方使用相同的秘钥进行数据解密。好处:加密解密效率高。缺点:秘钥容易被劫持,容易被破解。
非对称加密:加密解密使用的秘钥不同,生成一对秘钥(公钥与私钥),其中公钥进行加密,私钥进行解密,在通信前将公钥传给对方,对方使用公钥进行数据加密,数据到达后使用私钥进行解密。好处:安全度非常高,不怕公钥被劫持、缺点:加密解密效率很低。混合加密:先进性非对称加密,通信前,将公钥交给对方,对方拿着公钥加密对称秘钥的协商过程,这样对称秘钥就无法被劫持了,协商完毕后,使用协商出来的对称秘钥进行通信,既保证了安全,也保证了效率。
SSL加密过程:
- 服务器生成一对秘钥,公钥与私钥。
- 服务器带着公钥去权威机构申请证书。
- 通信时,TCP建立连接成功后,首先将证书发送给客户端。
- 客户端收到证书,进行解析,得到各项数据,然后去权威机构进行身份验证。
- 验证成功后,使用公钥加密自己所支持的对称加密算法列表以及一个随机数发送给服务器。
- 服务器收到后使用私钥解密,得到客户端支持的对称加密算法列表以及随机数。
- 服务器将自己所支持的对称加密算法列表以及一个随机数发送给客户端。
- 客户端与服务器各自根据算法列表以及随机数生成一个对称密钥,以后通信就是用这个对称密钥进行通信。