HTTP 是什么


先看下一下HTTP的名字:“超文本传输协议”,它可以拆成三个部分,分别是:“超文本”,“传输” 和 “协议”。

  • 协议:HTTP是在一个用在计算机世界里的协议。它使用计算机能够理解的语言确立了一种计算机之间交流通信的规范,以及相关的各种控制和错误处理的方式。
  • HTTP是在一个计算机世界里专门用来再两点之间传输数据的约定和规范。
  • 所谓“文本”,就表示HTTP传输的不是TCP/UDP这些底层协议里被切分的杂乱无章的二进制包,而是完整的、有意义的数据,可以被浏览器、服务器这样的上层应用程序处理,所谓“超文本”,就是“超越了普通文本的文本”,它是文字、图片、音频、视频的混合体,最关键的是含有“超链接”,能够从一个“超文本”跳跃到另一个“超文本”,形成复杂的非线性、网状的结构关系。

HTTP是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范。

报文结构

HTTP 协议的请求和相应报文的结构基本相同,由三大部分组成:

  1. 起始行(start line):描述请求或响应的基本信息;
  2. 头部字段集合(header):使用key-value形式更详细地说明报文;
  3. 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片,视频等二进制数据。

这其中前两部分其实行和头部字段经常又合称为 “请求头” 或 “响应头”,消息正文又称为“实体”,但与“header对应”,很多时候就直接称为“body”。

HTTP协议规定报文必须有header,但可以没有body,而且在headr之后必须要有一个“空行”,也就是“CRLF”,十六进制的“0D0A”。

完整的HTTP报文就像是下图的这个样子:

image-20201112174427780

image-20201112192843966

第一行 “GET / HTTP/1.1” 就是请求行,而后面的 “host” “Connection” 等等都属于header,报文的最后是一个空白行结束,没有body

在很多时候,特别是浏览器发送GET请求的时候都是这样,HTTP报文经常是只有header而没有body。

请求行

请求行简要的描述了客户端想要如何操作服务器端的资源。

请求行由三部分构成:

  1. 请求方法:是一个动词,如GET/POST,表示对资源的操作;
  2. 请求目标:通常是一个URI,标记可请求方法要操作的资源;
  3. 版本号:表示报文使用的HTTP协议版本。

这三个部分通常使用空格(space)来分隔,最后要用CRLF换行表示结束

1
GET / HTTP/1.1

在这个请求行里,”GET“ 是请求方法,”/“是请求目标,”HTTP/1.1“ 是版本号,把这三部分连起来,意思就是”服务器你好,我想获取网站根目录下的默认默认文件,我用的协议版本号是1.1,请不要用1.0或者2.0回复我“。

状态行

状态行的意思是服务器响应的状态。

比起请求行来说,状态行要简单一些,同样也是由三部分构成:

  1. 版本号:表示报文使用的HTTP协议版本;

  2. 状态码:一个三位数,用代码形式表示处理的记过,比如200是成功,500是服务器错误;

  3. 原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因。

image-20201112194612463

1
HTTP/1.1 200 OK

意思就是:”浏览器你好,我已经处理完了你的请求,这个报文使用的协议版本号是1.1,状态码是200,一切OK“

1
HTTP/1.1 404 NOT Found

翻译成人话就是:”抱歉浏览器,刚才你的请求收到了,但我没找到你想要的资源,错误代码是404,接下来的事情你看着办吧。“

头部字段

请求行或状态行再加上头部字段集合就构成了HTTP报文里完整的请求头或响应头。

请求头和响应头的结构基本一样的,唯一的区别是起始行。

头部字段是key-value的形式,key和value之间用 “:”分割,最后用CRLF换行表示字段结束。比如在”Host:127.0.0.1“ 这一行里key就是”Host“,value就是”127.0.0.1“。

HTTP头部字段非常灵活,不仅可以使用标准里的Host、Connection等已有头,也可以任意添加自定义头,这就给HTTP协议带来了无限的可能。

常用头字段

HTTP协议规定了非常多的头部字段,实现各种各样的功能,但基本上可以分为四大类:

  1. 通用字段:在请求头和响应头里都可以出现;
  2. 请求字段:仅能出现在请求头里,进一步说明请求信息或者额外的附加条件;
  3. 响应字段:仅能出现在响应头里,补充说明响应报文的信息;
  4. 实体字段:它实际上属于通用字段,但专门描述body的额外信息。

对HTTP报文的解析和处理实际上主要就是对头部字段的处理,理解了头部字段也就理解了HTTP报文。

首先要说的就是Host字段,它属于请求字段,只能出现在请求头里,他同时也是唯一一个HTTP/1.1规范里要求必须出现 的字段,也就是说,如果请求头里没有Host,拿着就是一个错误的报文。

Host 字段告诉服务器这个请求应该由哪个主机来处理,当一台计算机上托管了多个虚拟主机的时候,服务器端就需要用Host来选择,有点像是一个简单的”路由重定向“。

User-Agent 是请求字段,只出现在请求头里。它使用一个字符串来米描述发起HTTP请求的客户端,服务器可以依据他来返回最合适此浏览器显示的页面。

但由于历史原因,User-Agent非常混乱,每个浏览器都自称是”Mozilla“ ”Chrome“ “Safari”,企图使用这个字段来互相伪装,导致User-Agent变得越来越长,最终变得毫无意义。

Date字段是一个通用字段,但通常出现在响应头里,表示HTTP报文创建时间,客户端可以使用这个时间再搭配其他字段决定缓存策略。

Server 字段是响应字段,只能出现在响应头里。他告诉客户端当前正在提供Web服务的软件名称个版本号,例如: ”Server:openresty/1.15.8.1“

Server字段也不是必须要出现的,因为这会把服务器的一部分信息暴露给外界,如果这个版本恰好存在bug,那么黑客就有可能利用bug攻陷服务器。所以,由的网站响应头里要么没有这个字段,要么就给出一个完全无关的描述信息。

比如GitHub,它的Server字段里就看不出是使用了Apache还是Nginx,他只显示为 ”GitHub.com“。

实体字段里要说的一个是Content-Length ,它表示报文里body的长度,也就是请求头或响应头空行后面数据的长度。

请求方法

目前HTTP/1.1规定了八种方法,单词都必须是大写的形式

  • GET:获取资源,可以理解为读取或者下载数据;

  • HEAD:获取资源的元信息;

  • POST:向资源提交数据,选当与写入或者上传数据;

  • PUT:类似POST;

  • DELETE:删除资源;

  • CONNECTION:建立特殊的连接隧道;

  • OPTIONS:列出可对资源实行的方法;

  • TRACE:追踪请求 - 响应的传输路径

    安全与幂等

    在HTTP协议里,所谓的“安全”是指请求方法不会”破坏”服务器上的资源,即不会对服务器上的资源造成实质的修改。

    按照这个定义,只有GET和HEAD方法是“安全” 的,因为他们是“只读”操作,只要服务器不故意曲解请求方法的处理方式,无论GET和HEAD操作多少次,服务器上的数据都是安全的。

    而POST/PUT/DELETE操作会修改服务器上的资源,增加或删除数据,所以是不“不安全”的。

    所谓的“幂等”实际上是一个数学用语,被借用到了HTTP协议里,意思是多次执行相同的操作,结果也都是相同的,即多次”幂”后结果“相等”。

    很显然,GET和HEAD既是安全的也是幂等的,DELETE可以多次删除同一个资源,效果都是“资源不存在”,所以也是幂等的。

    POST和PUT的幂等性质就略费解一点。

    按照RFC里的语义,POST是“新增或提交数据“,多次提交数据会创建多个资源,所以不是幂等的;而PUT是”替换或更新数据“,多次更新一个资源,资源还是会第一次更新的状态,所以是幂等的。

    建议

    对比SQL来加深理解:把POST理解成INSERT,把PUT理解成UPDATE,这样就很清楚了。多次INSET会添加多条记录,而多次UPDATE只会操作一次记录,而且效果相同。

URI

URI:统一资源标识符(Uniform Resource Identifier)。因为它经常出现在浏览器的地址栏里,所以俗称为“网络地址”,简称“网址”。

严格地说,URI不完全等同于网址,它包含有URL和URN两个部分,在HTTP世界里用的网址实际上是URL:统一资源定位符(Uniform Resource Locator)。但因为URL实在是太普及了,所以常常把这两者简单地视为相等。

img

  1. scheme:翻译成中文叫方案名 或者 协议名,表示资源应该使用哪种协议来访问,最长见的就是http,另外还有httpsftpfile

  2. scheme之后,必须是三个特定的字符://,它把scheme和后面的部分分离开。

  3. ://之后被称为authority,表示资源所在的主机名,通常的行驶时“host:post”,即主机名加端口号。

  4. path:用来标记资源所在位置的path,浏览器就可以链接服务器访问资源了。

  5. query:查询参数,在path之后,用一个“?”开始,但不包含“?”,表示对资源附加额外要求,它有一套自己的格式,是多个“key=value”的字符串,这些KV值用字符串“&”连接。

响应状态码

  • 响应状态码在响应报文里表示了服务器对请求的处理结果;
  • 状态码后的原因短语是简单的文字描述,可以自定义;
  • 状态码是十进制的三位数,分为五类,从100到599
  • 2xx 类状态码表示成功,常用的有200204 No Content 表示响应头后面没有body数据206
  • 3xx 类状态码表示重定向,常用的有301302304
  • 4xx 类状态码表示客户端错误,常用的有 400 Bad Request 通用的错误码403 Forbidden 服务器表示禁止访问资源404 NOT Found 资源在服务器上未找到
  • 5xx 类状态码表示服务端错误,常用的有 500501502503

总结

  • HTTP 报文结构就像是“大头儿子”,由“起始行 + 头部 + 空行 + 实体”组成,简单地说就是“header+body”;

  • HTTP 报文可以没有 body,但必须要有 header,而且 header 后也必须要有空行,形象地说就是“大头”必须要带着“脖子”;

  • 请求头由“请求行 + 头部字段”构成,响应头由“状态行 + 头部字段”构成;

  • 请求行有三部分:请求方法,请求目标和版本号;

  • 状态行也有三部分:版本号,状态码和原因字符串;

  • 头部字段是 key-value 的形式,用“:”分隔,不区分大小写,顺序任意,除了规定的标准头,也可以任意添加自定义字段,实现功能扩展;

  • HTTP/1.1 里唯一要求必须提供的头字段是 Host,它必须出现在请求头里,标记虚拟主机名;

  • 请求方法是客户端发出的、要求服务器执行的,对资源的一种操作;

  • 请求方法是对服务器的”指示“,真正应如何处理由服务器来决定;

  • 最常用的请求方法是GET和POST,分别是获取数据和发送数据;

  • HEAD方法是轻量级的GET,用来获取资源的元信息;

  • PUT基本上是POST的同义词,多用于更新数据;