当工作中遇到关于网络7层协议的问题时,时常会出现一个尴尬的局面,开发的同学认为这是网络组的问题,而网工的同学却认为是应用上的问题应该由开发组负责。今天希望通过这篇文章给网络7层做一个简单的说明。
关于七层协议的学习主要就是HTTP。HTTP是一个C/S协议,C/S就是客户/服务器,它由客户端发出HTTP请求,然后服务器端作出HTTP回复。在下文中提到的请求,一般指HTTP请求,与客户端相关;回复指的就是HTTP回复,与服务器端相关。以下是本文的提纲。
- HTTP的版本
- HTTP状态码 200,404,502
- HTTP头标 (HTTP Header)
- HTTPS
- HTTP认证(HTTP Authentication)
- HTTP Cookie
- Web Hook和Web Scoket
HTTP的版本
HTTP版本有三种,分别为HTTP1,HTTP2和HTTP3(UQIC)。现在我们看到的HTTP1,大多为HTTP1.1这个版本,比起最初的HTTP版本已经有点一定的改进。在HTTP2中提出了很多改变,以下是几个比较重要的改变,使HTTP2的性能得到提高。
- 多路复用(Multiplexing):在HTTP1.0,每个HTTP请求都要单独生成一个TCP连接,这个做法比较慢也耗资源。 在HTTP1.1引入了presistent conection这个机制,让相关的多个请求可以重用同一个TCP连接 。到了HTTP2,在同一个TCP连接中可以并行发送多个HTTP请求,这解决了"队头阻塞"(Head-of-Line Blocking)的问题,即头部中一个请求延迟导致所有后续请求都被延迟的问题。
- 二进制协议(Binary Protocol):HTTP1是文本协议,头部和内容都是以明文形式传输。HTTP2使用二进制格式传输数据,这使得协议更加紧凑且高效。
- **服务器推送(Server Push):**HTTP1 中客户端需要请求特定资源才能获取,服务器端无法主动推送。HTTP2 服务器可以在客户端请求之前主动将资源推送到客户端,这也提高了时效性。
对于HTTP2,目前主流浏览器(如Chrome, Firefox)都要求HTTP2使用加密的传输方式。值得一提的是这是浏览器开发商的要求,并非HTTP2协议的要求。想查看一个网站是否支持使用HTTP2,可以使用如下 curl命令。
bash
$ curl -I --http2 https://example.com
HTTP/2 200
HTTP3在2022年才被正式发布在RFC上,但实际上已经有一定数量的应用在使用这个协议。它的最特别之处就是使用了UQIC作为传输层协议(HTTP1和HTTP2用的都是TCP),而QUIC是一个基于UDP的协议。。。你一定会问这不会有数据丢失的问题吗?QUIC的名字来源于Quick UDP Internet Connections的首字母组合,正与其英文中的含义它希望使用UDP的快,它使用了多路的UDP连接来实现类似TCP的机制达到确保数据完整性的目的。
HTTP状态码 (HTTP Response Code)
HTTP状态码,英文为http status code或作http response code。H TTP回复中总会有这个状态码,让HTTP的请求方能直观地知道这个请求是否成功,或存在什么问题。常见的状态码主要有这3种类型: 200,400和500。如果你的请求得到的代码是4XX或者5XX这种形式,意味着有错误发生,理解状态码的含义在开发debug中很重要。
- 200类型:这个类型指的就是在200到299之间的这些状态码,如果你得到的是200开头的状态码,基本上就是你的请求已被服务器端接受了,没有什么问题。
- 400类型:在400到499之间的状态码,代表错误可能在客户端。这里说的客户也指的是请求端。
- 500类型:在500到599之间的状态码,代表错误可能发生在服务器端。
值得指出的是400类型代码所谓的客户端问题有时并非真的错误就发生在客户端上。最常见的例子为404这个错误代码,404指的是客户端尝试访问不存在的资源,但这个错误的原因往往是因为服务器端给客户提供了错误的URL链接。
以下是一些常见的HTTP状态码:
|-----|---------------------------------------|
| 200 | 请求已成功 |
| 401 | 请求未认证 |
| 403 | 请求不被允许 (例如请求企图删除重要资源) |
| 404 | 请求的资源未被发现 |
| 500 | 服务器内部问题 |
| 502 | 代理器接受了请求,但代理器的后端没有回应 (这个错误代码常在均衡器上出现) |
| 503 | 服务器当前无法处理请求 |
HTTP 头标 (HTTP Header)
HTTP回复中一般包含三个部分:HTTP状态码,HTTP头标和HTTP正文;HTTP头标在协议中扮演着重要的角色,它为HTTP提供了元数据。随着micro services架构的推广,我们经常能看HTTP的回复正文中用的数据是JSON而不是HTML,对于客户端如何知晓HTTP回复中的数据格式是JSON还是 HTML呢?答案就是通过HTTP头标,用HTTP头标 'Content-Type: application/json' , HTTP接收方就知道这是JSON的数据。 我们知道的HTTP cookie, HTTP授权 (authorization)也都是HTTP头标的一种。我们可以用 curl 命令中 -I 这个参数来看看一个HTTP回复中用了那些头标。
bash
$ curl -I --http2 https://www.example.com
HTTP/2 200
content-encoding: gzip
accept-ranges: bytes
age: 443844
cache-control: max-age=604800
content-type: text/html; charset=UTF-8
date: Wed, 10 Nov 2023 11:43:30 GMT
etag: "3147526947"
expires: Wed, 29 Nov 2023 11:43:30 GMT
last-modified: Thu, 17 Oct 2019 07:18:26 GMT
server: ECS (nyb/1D2E)
x-cache: HIT
content-length: 648
在下文中我们还会讲到其它的头标。这里我们先看看两个中比较常见的头标:1 User-Agent 和 2. Host。
User-Agent这个头标常用于为服务器提供用户的一些环境信息。最常见的用例就是在发HTTP请求时把用户的设备信息放在User-Agent中来,服务器就能通过这个信息知道用户使用的是Android 还是苹果的设备,从而提供更匹配的应用版本。
Host 这个头标用于指明目标服务器的域名。在很多应用或网站的部署中会使用同一个IP地址支持多个不同的服务,Host这个头标可用于指明该请求希望使用的服务。 对于不匹配的服务或域名,服务器即可拒绝执行请求。
以下是一个CURL测试,首先我用nslookup找到了example.com对应的IP地址,然后看看使用不同Host头标作请求时的情况。以下分别为使用头标Host: example.com和Host:google.com头标的输出。可以看到使用Host:google.com会得到404错误状态码,因为那个IP是对应 example.com这个域名的。
bash
$ curl -I -H 'Host: example.com' 93.184.216.34
HTTP/1.1 200 OK
$ curl -I -H 'Host: google.com' 93.184.216.34
HTTP/1.1 404 Not Found
HTTPS
对于需要确保信息安全的HTTP连接,我们使用HTTPS,即加密的HTTP。TLS(Transport Layer Security)是现在主流的加密协议,**它的加密发生在传输层上,所以HTTP头标,HTTP正文和HTTP状态码都会被保护起来,非客户端和服务器端都不能看到HTTP的任何内容。**这也是为什么我们可以把HTTP的验证和HTTP Cookie这些可能包含敏感信息的数据放在头标上。
TLS是现时主要的加密协议,它的前身是SSL(Secure Sockets Layer)。理论上 SSL应该在2015后就被停止使用 (deprecated),但江湖上依然流传着它的传说。 TLS1.0和TLS1.1版本也在2021时被RFC提出停止使用,预期主流的加密协议将会是TLS1.2 和TLS1.3 。
提到加密协议就不得不提证书这个概念。HTTP加密连接一般是由服务器端给客户端提供一个公开密钥,客户端即可使用该密钥加密数据,并只有持有私有密钥的服务器端可以解码。这个证书就是为了用来验证这个公开密钥的可信性的。一个TLS或SSL证书会包含以下几个内容:
- 域名:就是提供HTTPS连接的这个网站的域名。
- 公开密钥: 用于对HTTP内容进行加密的密钥。
- CA (Certificate authority):一个权威或具有公信力的机构,对提供证书的网站进行验证。CA会提供一份自己的证书,与网站自身提供的证书成为一个证书链。对于自身就有公信力的网站,它们可以把自己作为CA,这种证书就称为self-signed certificate。
- CA提供的签名:CA会用私有密钥对网站证书进行签名,在CA证书中有CA的公开密钥,收到证书的用户就可以用CA的密钥对该签名进行解码验证。
想要查看一个网站的证书有两种办法,你可以使用的你的浏览器提供的功能去查看,这里就不展示了。另一个方法就是使用命令行, openssl这个工具。以下是一个测试的例子,由于输出太多我只截取了部分内容。
$ openssl s_client -connect google.com:443
CONNECTED(00000006)
depth=0 OU = "No SNI provided; please fix your client.", CN = invalid2.invalid
verify error:num=18:self signed certificate
verify return:1
Server certificate
-----BEGIN CERTIFICATE-----
MIIDfDCCAmSgAwIBAgIJAJB2iRjpM5OgMA0GCSqGSIb3DQEBCwUAME4xMTAvBgNV
BAsMKE5vIFNOSSBwcm92aWRlZDsgcGxlYXNlIGZpeCB5b3VyIGNsaWVudC4xGTAX
...
...
gWbFcmkgBLYpE8iDWT3Kdluo1+6PHaDaLg2SacOY6Go=
-----END CERTIFICATE-----
New, TLSv1/SSLv3, Cipher is AEAD-AES256-GCM-SHA384
HTTP基本认证 (HTTP Authorization and Authentication)
当你登陆一个网站时你需要在页面输入用户名和密码来认证登陆,后续的HTTP请求就不用再认证了。服务器端是如何去保证你的请求都是你的请求呢?做法有两种,一种是使用cookie, 一种就是使用HTTP Authorization头标。这里我们先说HTTP Authorization。值得注意的是英文Authorization的意思是授权,认证的英文是Authentication。但HTTP中并没有HTTP Authentication这个头标,HTTP Authorization就被用做认证。使用HTTP头标作认证有两种比较常见的认证方式。
- 基本认证: 头标通常写成如下形式 Authorization: Basic <base64 code>。这里basic是关键词告知服务器这是基本认证,<base64 code>是对用户名和密码用base64计算后得到的编码。以下是一个使用python获得base64编码后,使用基本认证头标的例子。
bash
$ python -c 'import base64; print(base64.b64encode(b"username:password"))'
dXNlcm5hbWU6cGFzc3dvcmQ=
$
$ curl -I -H 'Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ' example.com
HTTP/1.1 200 OK
- 令牌(token)认证: 头标通常写成如下形式 Authorization: Bearer <JWT token>。这里Bearer是关键词告知服务器使用令牌,<JWT token> 就是JWT令牌,通常为一段较长的base64编码。JWT 令牌一般在首次登陆时由服务器生成并返回给客户端,客户端会有对应的代码(如javascript)把它保存在客户端浏览器的本地存储(local storage)上或会话存储(session storage)上。
由于身份认证数据较为敏感,所以不论是使用JWT令牌或Cookie,在存储认证数据(JWT令牌或Cookie)到浏览器端时,网站都会给它设定有效时间,一段时间后就会自动删除或失效。最后提一下,使用HTTP认证时一定要用HTTPS,它会对所有的HTTP的内容包括头标进行加密。
HTTP Cookie
前文已经提到了Cookie,由于它这个特别的名字,很多非技术人员知道网页中有cookie这么一个东西。Cookie是一种存储于客户浏览器端的数据,它给HTTP提供了缓存会话(session)状态的能力。这个说法可能有点抽象,我们来看看前文中的例子,大多数网站都只需要你在首次登陆时输入密码,后续的HTTP请求就不用再认证了。这是如何做到的?使用Cookie可以把用户与服务器的会话认证信息存储在客户端,每次发送请求时cookie带上这个信息就无需客户多次输入密码了。
读到这里你可能会问本地存储(local storage),会话存储(session storage)和cookie都可以在浏览器上存数据,那它们的区别在哪?Cookie跟这两个存储的区别在于它是投标,一旦生成后在后续的每次HTTP请求中都会被发送。
Cookie中的令一个常见用途就是存储客户偏好,例如一个网站它提供了中文和英语两种页面,当客户选择了使用中文后这个偏好就可以存储在Cookie上,客户就不需要每次都选择。你可能会问为什么不能存在服务器端呢?把这些偏好信息存在服务器上会给存储带来压力。大多数网站的架构都是服务器集群加均衡器的模式,这些服务器集群可能分布到不同国家或地区,把这些客户偏好信息放在服务器端即要求这些信息要在这些集群的存储器间进行同步。相对而言,把这些非关键信息存在客户端可能是更高效的做法。
Web Hook和Web Scoket
这里我们再讲两个跟HTTP相关的WEB技术 -- Web Hook和Web Scoket。
Web Hook, 是一种网页通知技术。客户端登入一个网页后,只要不关闭页面,服务器端就可以通过Web Hook技术向客户在后续的时间推送通知。
Web Socket,是一种让客户端和服务器端在网页上进行双向通信的协议。要说明Web Socket不是HTTP协议的一部分。它使用HTTP(或HTTPS)实现建立连接时的握手,一旦握手成功,就会转成使用自己的协议。在HTTP握手时,HTTP状态码101会被使用告知通信方要更新协议(upgrading protocol)。