http/https 协议栈

最近代码中碰到许多网络相关问题,借此机会整理一下 http/https 协议相关的知识。

基础概念

http 协议处于 TCP/IP 分层协议中的应用层,他规定了一个客户端与服务端通信的规范,如今的 Web 就是在该协议下发展而来。

  • http 是一种无状态的协议,即对通信状态不做持久化处理,发送之后就不管了。因为要保证快速处理大量事务。
  • 可使用 Cookie 方式维护状态。
  • keep-alive + pipeline 持久链接管线化,无需等待相应可以一直发送请求,维持 TCP 链接不断开。

http 数据的传输流程

发送端的客户端在应用层一个HTTP请求。接着,传输层 TCP 协议把从应用层处接收到的数据 HTTP 请求报文进行分割,并在各个报文上打上标记序号及端口号后转发给网络层。 在网络层 IP 协议增加作为通信目的地的 MAC 地址转发给链路层。接受端的服务器在链路层接收到数据,按序往上层发送,一直到应用层。

图解1

图解2

http 数据组成

请求报文组成

请求报文是由请求方法、请求 URI、协议版本、可选的请求首部字段和内容实体构成的。

图解3

响应报文组成

响应报文基本上由协议版本、状态码(表示请求成功或失败的数字代码)、用以解释状态码的原因短语、可选的响应首部字段以及实体主体构成。

图解4

http header

HTTP 首部字段根据实际用途被分为以下 4 种类型。通用首部字段(General Header Fields)请求报文和响应报文两方都会使用的首部。

  • 请求首部字段(Request Header Fields)从客户端向服务器端发送请求报文时使用的首部。补充了请求的附加内容、客户端信息、响应内容相关优先级等信息。
  • 响应首部字段(Response Header Fields)从服务器端向客户端返回响应报文时使用的首部。补充了响应的附加内容,也会要求客户端附加额外的内容信息。
  • 实体首部字段(Entity Header Fields)针对请求报文和响应报文的实体部分使用的首部。补充了资源内容更新时间等与实体有关的信息。

常见的首部字段 wiki

http Method

  • GET 请求资料
  • POST 与 GET 类似,但可以传递更多的数据,可以上传信息修改服务数据。
  • PUT 上传文件,不安全一般不用
  • HEAD 获得文件首部确认 URL 有效性,不需要主体信息
  • DELETE 删除 URL 指定文件,不安全一般不使用
    • 删除成功 200
    • 接受删除请求但没有立即执行 202
    • 删除请求已执行,没返回资源 204
  • OPTIONS 询问服务器对指定 URL 支持的方法
  • TRACE 追踪路径

GET 与 POST 区别

作为后台开发最长用的两个请求,十分有必要深入了解下他们的区别。

GET

  • GET 请求参数是放在 url 后即 query 中。
1
2
GET: /index.html?name=niku&pw=xxxx HTTP/1.1
Host: localhost

比如上面的路由参数 后开始,参数以 & 分割。

POST

  • POST 参数放在 body 中不会显式暴露在链接上(如 from json 等)。
1
2
3
4
5
6
7
// POST 首部
POST /index.html HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded

// body
name=niku&pw=xxxx
  • post 上传文件, RFC1867 协议,扩充了 post 的 Content-Type,multipart/form-data 类型用以支持向服务器发送二进制数据。

发送 post 请求时候,表单 <form> 属性 enctype 共有二个值可选,这个属性管理的是表单的 MIME 编码:

  1. application/x-www-form-urlencoded(默认值) 这种编码效率较低

    form 表单在你不写 enctype 属性时,也默认为其添加了 enctype 属性值,默认值是 enctype=“application/x- www-form-urlencoded”。

  2. multipart/form-data 传输数据为二进制类型,比如图片、mp3、文件、视频。
    该格式会生成一个 boundary 字符串来分割请求头与请求体的,具体的是以一个 boundary=${boundary} 来进行分割,伪码如下:

    1
    2
    3
    4
    5
    6
    7
    
    ...
    Content-Type: multipart/form-data; boundary=${boundary} 
    
    --${boundary}
    ...
    ...
    --${boundary}--
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    --分隔符(boundary)[换行]
    Content-Disposition: form-data; name="参数名"[换行] 
    [换行]
    参数值[换行]
    --分隔符(boundary)[换行]
    Content-Disposition: form-data; name="图片名"; filename="图片文件名"[换行]
    Content-Type: 类型[换行]
    [换行]
    图片文件的二进制内容[换行]
    --分隔符(boundary)--

安全性

POST 比 GET 安全,因为数据在地址栏上不可见,然而,从传输的角度来说,他们都是不安全的, 因为 HTTP 在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文只有使用 HTTPS 才能加密安全。

发送方式

对于 GET 方式的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应 200(返回数据)。
对于 POST,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok。 并不是所有浏览器都会在 POST 中发送两次包,Firefox 就只发送一次。

http 状态码

状态码的职责是当客户端向服务器端发送请求时,描述返回的请求结果。借助状态码,用户可以知道服务器端是正常处理了请求,还是出现了错误。状态码需要遵守 RFC2616 协议规定。

图解5

2XX 成功

  • 200 请求正常处理
  • 204 服务端请求处理成功,但响应无需发送主体资源
  • 206 范围请求,只返回范围内的主体

3XX 重定向

  • 熟悉的 301 重定向
  • 302 临时重定向
  • 303 需要客户端用 GET 方法重定向到另一个 URL

4XX 客户端错误

  • 400 请求错误
  • 403 服务端拒绝客户端对某资源的请求,即客户端无权访问
  • 404 not found 服务端没有该资源,或者服务端不想说明理由

5XX 服务端错误

  • 500 服务端错误
  • 503 服务端过载或维护

Chrome DevTools 演示

图解6
图解7

以访问我的博客为例,这里可以看到发送了许多请求,第一个是站点的访问请求。

  • Request Method 为 GET
  • 状态码 200
  • 远端地址 这里我用了 clash 代理,所以是本地的 7890 端口。正常来讲应该是服务器的 IP

之后是响应首部(response headers)字段和请求首部(request headers)。

https

HTTP+ 加密 + 认证 + 完整性保护 = HTTPS
https 是在 http 协议基础上做了一层加密,比如用 SSL(Secure Socket Layer)或 TLS(Transport Layer Security), 现在建立网站基本都需要申请一个 SSL 证书,我的博客部署在 Vercel 上, Vercel 会自动申请一个 SSL 证书绑定到服务器。

在采用 SSL 后,HTTP 就拥有了 HTTPS 的加密、证书和完整性保护这些功能。 SSL 是独立于 HTTP 的协议,所以不光是 HTTP 协议,其他运行在应用层的 SMTP 和 Telnet 等协议均可配合 SSL 协议使用。

SSL 加密

SSL 采用一种叫做公开密钥加密(Public-key cryptography)的加密处理方式。 公开密钥加密使用一对非对称的密钥。一把叫做私有密钥(private key),另一把叫做公开密钥(public key)。

使用公开密钥加密方式,发送密文的一方使用对方的公开密钥进行加密处理,对方收到被加密的信息后,再使用自己的私有密钥进行解密。利用这种方式,不需要发送用来解密的私有密钥,也不必担心密钥被攻击者窃听而盗走。

https 缺点

  • 使用加密,访问会比 http 慢许多倍
  • 加密通讯会消耗 CPU 资源

开发中的 http 运作流程

有了以上铺垫,这里再介绍 Go 的 Web 服务流程。

图解8

  1. 创建 Listen Socket, 监听指定的端口,等待客户端请求到来。
  2. Listen Socket 接受客户端的请求,得到 Client Socket, 接下来通过 Client Socket 与客户端通信。
  3. 处理客户端的请求,首先从 Client Socket 读取 HTTP 请求的协议头,如果是 POST 方法,还可能要读取客户端提交的数据,然后交给相应的 handler 处理请求,handler 处理完毕准备好客户端需要的数据,通过 Client Socket 写给客户端。

Go 是通过一个函数 ListenAndServe 来处理这些事情的,这个底层其实这样处理的:初始化一个 server 对象,然后调用了 net.Listen("tcp", addr),也就是底层用 TCP 协议搭建了一个服务,然后监控我们设置的端口。

监控端口之后,调用了 srv.Serve(net.Listener) 函数,这个函数就是处理接收客户端的请求信息。并且每个请求创建一个 goroutine ,高并发地处理每个请求。

之后是请求与函数绑定即路由,来匹配 url 跳转到其相应的 handle 函数。比如 http.HandleFunc("/", sayhelloName) 这里绑定 URL 为 \ 的路由转到 sayhelloName 函数处理, 之后通过 response 将结果返回到客户端。

图解9


引用
《 图解 HTTP 》
0%