Real world HTTP

详解 HTTP 协议基础与 Go 语言实现

HTTP/1.0 的语法:4个基本元素

  • 方法和路径
  • 首部
  • 主体
  • 状态码

HTTP协议自 1990 年至今已经成为当代互联网基础,并且仍然在不断升级。

HTTP 的默认端口是 80, 端口基本上是用来确定其用途的, Web 或者 FTP 等客户端如果无特别指定那么默认使用80(HTTPS 默认443), 但是可以通过指定不同的端口来运行,客户端通过地址+指定端口号的方式来访问服务。

MIME类型

目前广泛普及的网站大多数以 HTML文件为基础,如今使用 JavaScript 和样式表来提供丰富的用户体验是必不可少的,浏览器管理者个文件类型对应的操作,用来标识文件类型的标识符就是 MIME 类型。

现在的Web服务器在发送HTML时, 会在服务器返回的响应头中设置 MIME 类型

1
Content-Type: text/html; charset=utf-8

方法

HTTP/1.0中发送请求时的GET部分就是方法,1992年的版本中提出了许多方法 GET HEAD POST PUT DELETE

URL 国际化

URL 域名一开始只支持英文,数字以及连字符, 但是2003年确认了用来标识国际化域名的 Punycode 编码规范,之后使用该规范的浏览器就支持国际化域名了。

GET 请求时的主体

在HTTP的方法中有一些方法是不希望包含主体的,例如使用 GET方法时候通常使用查询字符串, 但是实际上你也可以使用 curl 来为 GET方法添加请求体

1
curl -X get --data "hello world" http://localhost:8888

但是这并不是RFC推荐的做法,在任何一个HTTP请求消息都可以包含消息主体, 但是服务器可以根据实现拒绝接受。并且 RFC7231 中强调了只有 HTTP/1.1 中添加的 TEACE 方法不可以包含请求体。

HTTP/1.0的语义:浏览器基本功能的背后

使用 x-www-form-urlencoded 发送表单

通过 HTML 的 form 标签可以发送表单。在 curl 中为:

1
curl --http1.0 -d title="the art of community" -d author="Jono Bacon" https://localhost:9999

这里 curl 命令会设置首部 Content-Type:application/x-www-form-urlencoded 这个时候主体就会变成使用等号拼接的字符串:

1
title=the art of community*author=Jono Bacon

如果原文中有等号和&符号就需要会进行转义。分别转化为 %3d 和 %26, 空格会转化为 %20 。

使用 multipart/form-data 发送文件

在 HTML 表单中可以选择 multipart/form-data这一编码格式, 比较复杂不过可以发送文件。在使用 x-www-form-urlencoded 的情况下,名称与内容一一对应,而在使用multipart/form-data 的情况下,每个项目会额外的添加元信息作为标签,以标识当前数据的格式,方便服务器进行读取。

内容协商

由于服务器和客户端是分开开发和维护的, 所以二者的格式和设置并不总是一致的,为了优化通信方法, 服务器和客户端在一个请求中共享彼此的最有设置, 这种结构就是内容协商。
一般情况下会在头部字段中表面可以处理的 MIME 类型,显示语言,字符集,和压缩类型。

认证

通用 HTTP 身份验证框架可以被多个验证方案所使用, 不同的验证方案在安全强度以及客户端或者服务器端软件中获得到的难易程度上有所不同 — MDN

最常见的验证方案是“基本验证方案”(”Basic”),该方案会在下面进行详细阐述。IANA 维护了一系列的验证方案,除此之外还有其他类型的验证方案由虚拟主机服务提供,例如 Amazon AWS。常见的验证方案包括:

Basic (查看 RFC 7617,base64 编码凭证。详情请参阅下文.),
Bearer (查看 RFC 6750,bearer 令牌通过 OAuth 2.0 保护资源),
Digest (查看 RFC 7616,只有 md5 散列 在 Firefox 中支持,查看 bug 472823 用于 SHA 加密支持),
HOBA (查看 RFC 7486(草案),HTTP Origin-Bound 认证,基于数字签名),
Mutual (查看 draft-ietf-httpauth-mutual),
AWS4-HMAC-SHA256 (查看 AWS docs).

Basic 方案是使用 base64编码的,是可逆的
Digest 使用 MD5 散列,是不可逆的,所以会比 Basic 更为安全一些

缓存

通过缓存机制,可以让用户重用已经下载的资源,从而提高加载速度,节省网络资源。

  • 基于更新时间的缓存

Last-Modified 是一个响应首部,其中包含源头服务器认定的资源做出修改的日期及时间。它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。由于精确度比 ETag 要低,所以这是一个备用机制。包含有 If-Modified-Since 或 If-Unmodified-Since 首部的条件请求会使用这个字段。

Expires 响应头包含日期/时间,即在此时候之后,响应过期, 如果在Cache-Control响应头设置了 “max-age” 或者 “s-max-age” 指令,那么 Expires 头会被忽略。

Cache-Control 通用消息头字段,被用于在 http 请求和响应中,通过指定指令来实现缓存机制。缓存指令是单向的,这意味着在请求中设置的指令,不一定被包含在响应中。

Cache-Control: max-age=
Cache-Control: max-stale[=]
Cache-Control: min-fresh=
Cache-control: no-cache
Cache-control: no-store
Cache-control: no-transform
Cache-control: only-if-cached

ETag HTTP 响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,Web 服务器不需要发送完整的响应。而如果内容发生了变化,使用 ETag 有助于防止资源的同时更新相互覆盖(“空中碰撞”)

Vary 是一个 HTTP 响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复 (response) 还是向源服务器请求一个新的回复。它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers)

HTTP/1.1 的语法: 追求高速化和安全性

HTTP1.0 和 HTTP/1.1在语法方面的不同之处:

  • 通信的高速化 Keep-Alive 默认有效
  • 支持使用TLS进行加密通信
  • 添加新的方法 PUT DELETE 成为了必不可少的方法添加了 OPTION TRACE 和 CONNECT方法
  • 协议升级
  • 只是使用名称的虚拟主机
  • 支持 Chunk 形式的数据传输
  • DATA URI方案

通过 Keep-Alive 提高通信速度

HTTP1.0 中规定, 浏览器与服务器之间的同时连接最好为4个,在HTTP1.1中,考虑Keep-Alive 的效果所以将同时连接数调整为2个,为了进一步实现高速化,现在浏览器通常同时连接数为6个,另外服务器端还提供了域名分片技术,即将资源配置在多个域名是,以此进行更多数量的通信。

在未使用 Keep-Alive 的情况下,每次请求完毕都要关闭通信,而使用了 Keep-Alive之后连接会被重用,从而降低了TCP/IP的等待时间,提升了速度。

管道技术

管道技术也是提高通信速度的功能,虽然已经标准化胆大会还为广泛应用, 管道技术是指在上一个请求结束前发送下一个请求,从而消除等待时间,以此来提高效率和性能。

TLS

对通信线路进行加密的 TLS (TransportLayer Security)与HTTP1.1同时实现了标准化。
从在中途对HTTP通信进行中继的网关来看, TLS 是加密的, 不能查看和修改通信内容的双向通信,

Chunk

HTTP1.1开始支持一些新的数据格式,其中有一个叫做 Chunk 的数据传输方式, 具体来说就是将数据分成多分进行发送, 使用 Chunk的情况下,比较好事的数据传输可以提前进行。

浏览器中无法使用 Chunk 上传年数据, 虽然可以在 JavaScript中划分范围, 根据范围上传数据,但是这不是标准方法,所以在服务器中还需要合称为一个文件。

HTTP1.1的语义: HTTP的扩展功能

下载文件并保存到本地

浏览器根据服务器发来的MIME类型来处理文件, 当用浏览器打开一个图片文件的连接如果服务器返回的MIME的类型为image/png 等图片类型,浏览器就会吧该文件当做图片进行显示。同样 PDF 类型的 MIME 类型也会进行显示。 当遇到浏览器无法支持的类型是,浏览器就会进行下载操作, 浏览器会根据 Content-Disposition 响应头的内容来改变动作。

暂停和恢复下载

暂停和恢复下载,就是指定文件范围进行下载,如果服务器支持指定范围进行下载,就会在响应头加入 Accept-Ranges 字段

网站之间公用的认证和授权平台

  1. 单点登陆

企业内部使用的 Web 服务和系统不断增加, 就需要考虑单点登录SSO(single sing on),单点登录并非分开管理各个系统的账户,在实现单点登录的情况下用户只需要登陆一次,就可以访问所有系统

  1. Kerberos
    还有一个方法实现单点登录,就是将哟用户管理结构汇总为一个,供所有系统使用, 企业内部大多使用 RFC 2251 中定义的 LDAP (lightweight directory acess protocoal) 作为共通范围, 其中包括 OpenLDAP 和ActiveDirectory 等, LDAP不是用于单点登录的结构,而是对于用户组织和服务器内部信息进行统一管理的数据库,

  2. OpenID
    OpenID是一种能够使用已经注册的Web服务的用户信息来登陆其他服务的结构, 它不会集中对ID进行管理,

  3. OAuth
    OAuth 是用于授权的结构, 仍在被广泛使用, 2012 年的RFC6749和RFC6750定义了最新版本的 OAuth2.0 ,目前仍在发展。
    OpenID 和 OAuth的页面跳转方式相似, 当用户使用新的 Web 服务时候,页面就会跳转到持有的账户服务,如果用户点击确认按钮,就会跳转到最开始打开的Web 服务页面,之后用户就可以使用服务了,客户端解除不到用户ID 和密码。

虽然在页面跳转的方式相似, 但是与认证相比,授权的影响范围更广。

  1. OpenID Connect

OpenID Connect 在 OAuth 2.0 的基础上进行了扩展,除了授权之外还可以用来进行认证。 于2014年发布标准, 由于它既可以用于授权,又可以用于认证,所以用途更为广泛。

  1. JWT

认证凌攀用来证明“已认证” 的事实, JWT(JSON Web Token) 是用于认证凌攀的一种解决方案,它基于 JSON 通过添加签名来方式篡改, JWT 定义于 RFC 7519 中。
JWT 将3个 base64 字符串用 . 来拼接形成的字符串, 分别是 头部,payload和签名, 头部储存有签名形式,payload储存有信息,
读取程序先根据头部记载的签名算法来验证签名

HTTP/2 和 HTTP/3的语法:重新定义协议

HTTP/2和3中未变化的内容

HTTP/1.1 为止的协议是参考电子邮件或者新闻组定制的,使用文本表示, 而 HTTP/2和3实现了巨大的飞跃,为了降低通信开销,他们使用了二进制协议,添加了符合 HTTP特性的首部压缩技术,并且还模拟了TCP层,一如了高级的控制结构。

基础的HTTP 方法 头部 和状态码以及body等 HTTP提供的 4个基本元素没有变化, 虽然从协议中看发生了巨大变化,但是从浏览器和web程序使用的情况来看并没有什么差别。

HTTP/2 的改进

  • 修改为使用流(类似1.1中的管道技术)来多路手法二进制数据
  • 实现了流内部的优先级设置,以及从服务端进行通信的服务器推送技术
  • 首部可以压缩

使用流实现告诉通信

相比较之前的HTTP协议, HTTP/2最大的变化就是, 协议基于文本的形式改为基于二进制的形式, 数据以帧为单位进行发送和接受,在1.1中每个请求都拥有自己的TCP socket 因此对于一个源服务器, 会有 2~6个TCP连接并行执行。

在HTTP2中每个TCP连接内都会创建一个名为流的虚拟 TCP socket来进行通信, 使用帧中附带的标志位可以轻松的创建和关闭流,并且不需要像普通的TCP socket那样进行握手。

HTTP2 的应用程序层

HTTP2中也删除一些信息, 首先是状态码中的原因删除了,HTTP1.1是文本协议, 在查找字段结尾,服务器需要按字节进行读取,直到找到空行, 由于还存在错误处理,所以服务器只能逐步进行处理,很难进行并行处理,HTTP2是二进制协议,响应头的开头是帧的大小,在TCP socket层数据会议帧为单位分割,因此接收方可以立即清空 TCP socket缓冲区,快速的向通信对象请求接下来的数据。

流量控制

HTTP2 相当于四层网络模型中的应用程序层, 但是其内部持有与传输层相近的功能,在HTTP2中实现了流量控制,这与 TCP socket 基本相同的功能。 当然 TCP会进行顺序控制和重传, 因此实现起来非常简单, TCP socket和 HTPP2 的流之间的关系类似于操作系统的系统操作系统线程和应用程序中的线程。

流量控制是为了高翔传输而使用的通信数据量的控制处理, 如果通信中使用的机器之间的速度相差非常大,那么比较慢的机器人就无法处理完成数据,流量控制就是防止这种情况, 实现流量控制的具体方式是对通信目标的窗口大小进行控制, 窗口大小就是能够接受数据的空环控去的大小。
当接收区收到包而空出缓冲区时,就会使用 WINDOW_UPDATE 帧,将新空出的缓冲区等阿晓返回给服务器,服务器收到之后就会发送相应大小的数据。

HRRP3

SPDY 是HTTP2 的基础, 而 HTTP3是在 QUIC标准化的过程中形成的。

QUIC

HTTP2 在与HTTP 同一层的TCP socket 上实现的,但是谷歌为了进一步提升速度,在 UDP socket 上提供了 QUIC协议, TCP协议在开始时候需要进行多次通信,另外 TCP 会修正错误和排列春素, 但为此需要返回相应的接受通知, 高级功能的性能会有所下降

UPD是比TCP轻量级的协议, UDP去掉了 TCP的重传,拥塞控制等高级功能,所以在第一次连接时候就可以轻松的执行协商处理, QUIC实现了许多 TCP 的功能, 比如丢包重传, 拥塞控制等

谷歌在 2013 年发布了 QUIC 并在2015年提出了标准化的最初方案, Google 版本和 IETF版本逐渐出现了差异, 为此为了区分差异谷歌版本的叫做 gQUIC IETF版本的叫做 QUIC成为 iQUIC, 其中 HTTP over QUIC 决定命名为 HTTP3

HTTP3的层

与到HTTP2为止的协议, Google实现的QUIC是一个超大协议,汇总了HTTP层, 加密层, 以及重传数据处理层,之后 IETF版本的 QUIC 在讨论过程中刚进行了各种修改,对 gQUIC进行了很大的改进, 在HTTP3中加密由单独的变成直接利用 TLS1.3的握手活密码套件进行 HTTP3 大致分为两层

  • QUIC 传输: 流控制等TCP的向上兼容层和TLS1.3
  • HTTP over QUIC: 从HTTP2的功能中取出掉与QUIC重复的功能,将 HPACK换为 QPACK

QUIC 协议以 UDP为主,实现了重传,拥塞控制和加密等,简单的讲就是替换了 TCP和TLS 层。
QUIC建立绘画之后,其中会有多个通信的伪会话流,QUIC 确保流的内部顺序, 但不确定流之间的顺序,这样一个流失败不会影响其他流

QUIC 更适合 21世纪的网络,因为网络基础已经十分完善

从客户端的角度看RESTful API

REST(Tepresentational State Tranfer) 描述性状态迁移, RESTful 用来形容遵循 REST架构规范。
REST 以HTTP 语法为协议基础, 通信的语义也效仿HTTP,特性如下:

  • API是通过Web服务器提供的
  • 像 GET/user/{userid}/repositiries那样,通过向玲发送方法来获取服务
  • 通过状态码通知客户端是否API成功
  • URL用来标识资源位置, 作为服务的界面它非常重要
  • 有时也会更具需要发送 GET参数和POST参数等附加信息
  • 服务器的返回值大多是JSON 之类的结构化文本或者图像等数据

从客户端角度来看可以期待服务器实现以下内容

  • URL 是标识资源层的路径,仅由名词构成
  • 通过向资源发送HTTP方法来进行获取,更新,添加等操作
  • 可以通过查看状态码来判断请求是否被正确处理
  • 无论调用多少次GET方法,都不会改变状态
  • 客户端并不管了状态,每次请求都是独立执行的
  • 不存在事务