首页 / 资讯中心 / 文章详情

HTTP/HTTPS协议详解:从报文字段到安全加密与线上排查

HTTP/HTTPS协议详解:从报文字段到安全加密与线上排查 ★ FEATURED ARTICLE
1. 从一次接口排查说起HTTP 到底在链路里扮演什么角色前阵子遇到一件挺典型的事——某个内部系统突然偶发变慢单个接口平均耗时涨了将近一倍但服务端日志显示 CPU、内存都正常。排查到最后问题出在客户端和服务端之间的连接复用策略上客户端没有正确复用 TCP 连接每次请求都重新建连握手开销把耗时拉上去了。这个问题如果不懂 HTTP 协议的工作方式很容易在业务代码里翻半天找不到根源。所以这篇文章想系统聊聊 HTTP 和 HTTPS 协议的基础。无论你是写前端、写后端还是做运维、做测试协议这个东西其实每天都跟你在打交道。你敲下的每一个 URL、接口返回的每一段 JSON、浏览器里那个 Network 面板全都是 HTTP 协议的体现。把它吃透了调试接口、排查线上问题、理解各种框架的设计都会顺很多。先给新手一个最粗的定位HTTP 是应用层协议它解决的是两端怎么对话的问题而 TCP 解决的是数据怎么可靠地传过去的问题。换句话说TCP 是路HTTP 是路上跑的车车里面装的是什么货物由应用自己决定。HTTPS 则是在 HTTP 和 TCP 之间加了一层安全机制让路上跑的车变成装甲车防偷看、防篡改。1.1 输入一个网址后背后发生了什么想象你在浏览器里输入一个网址并按下回车这一瞬间系统做了一连串的事情浏览器先解析 URL提取出协议名、域名、端口和路径。比如https://example.com/products就拆成协议https、域名example.com、默认端口 443、路径/products。域名解析。浏览器会先去查本地 DNS 缓存没有的话就去问 DNS 服务器拿到域名对应的 IP 地址。建立连接。如果是 HTTPS还要先完成 TLS 握手协商出加密密钥这个过程后面会细讲。浏览器按照 HTTP 协议规则拼装一个请求报文通过 TCP 连接发送给服务器。服务器解析请求处理后返回一个响应报文。浏览器解析响应内容如果是 HTML 就渲染页面同时继续发起页面里引用的 JS、CSS、图片等子资源的请求。这个流程看起来简单但每一步都有协议在约束。比如 DNS 解析用的不是 HTTP 而是 DNS 协议TLS 握手的报文格式由 TLS 协议定义而 TCP 的三次握手又独立于 HTTP 存在。很多刚入门的同学会把HTTP 连接和TCP 连接混为一谈其实 HTTP 本身并不负责建立连接它只是规定了一旦有连接报文该长什么样、双方该怎么理解这个报文。1.2 为什么说 HTTP 是无状态的HTTP 的无状态指的是服务器默认不保存客户端上一次请求的任何信息每个请求都是独立的、平等的。就像你每次去窗口买饭师傅不会记得你上顿吃了什么他只看你这回递过去的单子。这个特性带来两个后果好处服务器不需要为每个用户维护一份状态处理逻辑简单也容易横向扩展。一个服务集群后面挂十台机器用户随便请求哪台处理结果都一样因为每台机器都不依赖记忆。坏处有状态的业务做不了。比如登录、购物车、浏览记录这些都需要记住你是谁。于是出现了 Cookie、Session、Token 这套机制本质上是在无状态的协议之上人为地补出状态来。Cookie 是服务器下发、浏览器保存的一段小数据后续请求自动带上Session 是在服务端保存用户状态通过一个 session id 和 Cookie 关联Token尤其是 JWT 这种则干脆把用户信息加密签名后直接交给客户端服务端验证签名即可。理解 HTTP 无状态这个本质你就能明白为什么会有这么多会话保持的组件出现——它们都是在跟无状态这个底层设定斗智斗勇。2. 报文拆开看请求、响应、状态码与头部字段HTTP 的报文其实是很直白的文本格式起码 HTTP/1.x 是这样的。一个请求报文由三部分组成请求行、请求头、请求体。响应报文则是状态行、响应头、响应体。2.1 请求报文方法、URL 和头部先看一个典型的 GET 请求长什么样GET /products?id123 HTTP/1.1 Host: example.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Accept: text/html,application/json Accept-Encoding: gzip, deflate, br Connection: keep-alive第一行叫请求行依次是方法、路径、协议版本。接下来的每一行是一个头部字段用冒号分隔字段名和值。GET 请求一般没有请求体带参数就直接拼在 URL 的查询字符串里。常用的方法语义要拎清楚方法语义是否幂等典型场景GET获取资源是查询列表、打开页面POST提交数据、创建资源否表单提交、创建订单PUT整体更新资源是替换某条记录PATCH部分更新资源否修改记录的某个字段DELETE删除资源是删除一条记录HEAD只取响应头不取响应体是探测资源是否存在OPTIONS询问服务器支持哪些能力是CORS 预检请求很多人有个误区觉得 POST 比 GET 安全。其实这两个方法在网络传输层面同样不安全都是明文除非整条链路走 HTTPS。GET 和 POST 的真正区别在于语义和约定GET 是去取数据不应该产生副作用所以可以被缓存、可以被收藏为书签POST 是去发数据会对服务端资源产生变更所以浏览器默认不会缓存 POST 请求。要是拿 GET 去执行删除操作不仅违反语义还可能被预取、被爬虫、被缓存机制坑得怀疑人生。请求头里还有一个容易被忽略的 Host 字段。HTTP/1.1 开始强制要求带它因为一台服务器上可能同时托管几十个域名服务器需要靠 Host 头区分用户到底访问的是哪个站点。你在后端框架里拿request.headers[host]拼绝对链接拼错就是这里出了问题。2.2 响应报文与状态码别只记得 200 和 404响应报文的第一行是状态行像这样HTTP/1.1 200 OK Content-Type: text/html; charsetutf-8 Content-Length: 3456 Cache-Control: max-age3600状态码是三位的数字第一位数字就说明了类别状态码含义常见场景1xx信息性响应101 切换协议websocket 建连时用2xx成功200 正常返回204 无内容但成功3xx重定向301 永久跳转302 临时跳转304 未修改4xx客户端错误400 参数错误401 未认证403 无权限404 找不到429 请求太频繁5xx服务端错误500 内部错误502 上游错误503 服务不可用504 超时这里单独说几个容易踩坑的403 和 404 的语义差别。403 是我知道你要访问的东西在哪但你没有权限看404 是没有这个东西。从安全角度讲很多系统故意把不存在的资源返回 403防止攻击者探测目录结构。但从调试角度讲如果你接口明明存在却返回 403大概率是权限、鉴权中间件的问题而不是路由写错了。304 Not Modified。配合ETag或Last-Modified使用。浏览器缓存了资源后会带上If-None-Match或If-Modified-Since去问服务器这个资源变过没有服务器说没变就返回 304 空 body浏览器直接用本地缓存。这个机制能省不少流量。429 Too Many Requests。限流策略的标配响应。如果你在压测或者爬虫里频繁撞见它说明你的请求频率超过了服务端的阈值别硬刚退避重试才是正解。2.3 头部字段是协议的元信息头部字段是 HTTP 里最容易被忽略、又最有信息量的部分。按作用可以分几类内容协商类Accept客户端能接受什么类型、Content-Type实际发送的实体类型、Accept-Language、Accept-Encoding。服务和客户端靠着这一组字段互相商量咱们怎么传输、传什么格式。缓存控制类Cache-Control、Expires、ETag、Last-Modified。前端同学部署新版本后老是看到旧页面十有八九是Cache-Control配得不对。连接管理类Connection: keep-alive、Transfer-Encoding: chunked。后者表示响应体是分块传输的没有预先声明长度服务器边生成边发。跨域相关类Origin、Access-Control-Allow-Origin。浏览器同源策略之下的跨域调度都靠它们。群里常有后端新人问为什么我接口返回的数据前端看不到一打开 DevTools 却能看到接口是 200此时你先看响应头里有没有Access-Control-Allow-Origin十次有八次是漏了它浏览器把响应拦下来了。3. 版本演进不是刷版本号是解决当时的真问题HTTP 经过了几个大版本很多人只是知道数字大了却不懂背后到底改了什么。其实每一个版本的出现都是在解决当时最痛的问题。3.1 HTTP/1.0 到 HTTP/1.1连接复用和虚拟主机HTTP/1.0 时代每一次请求都新建一个 TCP 连接请求完就断开。当时页面简单资源少问题不明显。但页面一旦复杂起来一个页面几十个资源每个资源都建连、断开光是三次握手加四次挥手的开销就能把页面拖垮。HTTP/1.1 做了几个关键改进最核心的是引入了Connection: keep-alive默认复用 TCP 连接。同一条连接上可以连续发送多个请求省掉了反复建连的开销。另外它强制增加Host头让一台服务器通过不同 Host 区分多个网站虚拟主机才流行起来。3.2 HTTP/1.1 的队头阻塞并发连接救不了根本HTTP/1.1 虽然复用了连接但有个规矩同一条连接上的请求必须排队前一个响应没回来后一个请求就不能发。这叫队头阻塞Head-of-Line Blocking。浏览器想了个绕法——每个域名同时开 6 条 TCP 连接平均分担请求。这能缓解但不能根治。因为 TCP 本身也有可靠性保证如果中间某个数据包丢了TCP 层会重传重传期间后续的数据包即使到了也不能交给上层处理这就是 TCP 层面的队头阻塞。HTTP/1.1 的多连接方案只是把阻塞面摊薄了一旦网络质量差依然会明显感知到卡顿。3.3 HTTP/2 和 HTTP/3多路复用与新的传输思路HTTP/2 的核心改动是把报文拆成一个个二进制帧同一条连接上可以同时交错传输多个请求和响应的帧一个响应被拆成多个帧也不怕帧头带着流 ID能重新组装起来。这样就实现了真正的多路复用应用层的队头阻塞被解决了。同时 HTTP/2 还做了头部压缩HPACK重复的头部字段不再整个传输而是通过静态表和动态表用索引代替省了大量字节。服务端推送也是一大特色服务器可以在客户端没问之前就把需要的资源推过去。HTTP/2 虽然解决了应用层队头阻塞但 TCP 层那个丢包重传导致的队头阻塞还在。于是 HTTP/3 干脆换了个思路不再用 TCP而是用基于 UDP 的 QUIC 协议。QUIC 在用户态实现了可靠传输和加密连接建立更快还把队头阻塞降到了单个流内部一条流丢了包不影响其他流。很多大流量场景已经在用 HTTP/3不过现阶段更多是支持但不强制常见基础设施的兼容性也在快速完善。版本演进的最大启示是协议设计永远是在当时网络环境下做的权衡。现在各种面试爱问HTTP/1.1 和 2 的区别你不需要背列表抓住队头阻塞是怎么一步步被解决的这条主线就能把整个版本演进串起来。4. HTTPS 的安全链路从加密算法到证书体系HTTP 本身是明文传输的这意味着任何人都能在链路上看到你发了什么。要解决这个问题就得上 HTTPS。HTTPS 并不是新的应用层协议而是把 HTTP 报文放进了一个加密通道里传输这个加密通道在 TCP 之上由 TLS 协议实现。4.1 HTTP 裸奔的三种风险在没有加密的情况下HTTP 主要面临三类攻击窃听报文在网络上逐跳传输每一跳的中间设备都能看到明文内容。密码、Cookie、订单信息等于裸奔。篡改中间人不仅能看还能改。改掉返回的 HTML、注入一段恶意脚本用户毫无感知。冒充客户端没法验证服务器的真实身份。你连上一个银行的网站但这个网站可能是攻击者伪装的。HTTPS 的三个核心目标正好对应这三种风险机密性防止窃听、完整性防止篡改、身份认证防止冒充。4.2 混合加密非对称握手、对称传输加密算法分两类对称加密和非对称加密。对称加密加解密用同一个密钥速度快但问题是怎么把密钥安全地交给对方——如果密钥通过网络明文传输那加密就没有意义。非对称加密用一对公私钥公钥可以随便分发私钥自己保存用公钥加密的数据只有私钥能解开解决了密钥分发问题但性能比对称加密慢得多。TLS 的解决方案是混合加密握手阶段用非对称加密协商出一个临时会话密钥之后所有的业务数据都用这个会话密钥做对称加密。这样既保证密钥分发的安全又保证了实际传输的性能。现在的 TLS 握手主流用的是 ECDHE 密钥交换。它有个很漂亮的特性通信双方各自生成临时私钥互换公钥然后各自计算出一个相同的会话密钥。即使中间人监听了整个交换过程也推不出这个会话密钥因为私钥从来没有出现在网络上。ECDHE 还支持前向保密——即使服务器长期的私钥泄露之前抓取的加密流量也无法被解密。4.3 证书、CA 与信任链身份不是自己说了算非对称加密解决了密钥怎么安全分发的问题但还有一个漏洞你收到的公钥到底是谁的公钥攻击者完全可以冒充服务器把自己的公钥发给你。所以 HTTPS 需要一个权威的第三方来背书——证书颁发机构CA。证书上写着域名、公钥、有效期、签发者等信息然后用 CA 的私钥对这份信息签名。浏览器内置了一批受信任的根证书验证时一层层往上找形成一条信任链站点证书由中间证书签发中间证书由根证书签发。只要根证书在浏览器信任列表里链路完整、域名匹配、有效期没问题浏览器就认为这个站点可信。实际部署中证书相关的坑一般集中在几个地方证书过期最常见没有配置到期提醒的系统经常会栽在这。证书链不完整只部署了站点证书没附带中间证书部分客户端尤其是安卓设备验证失败。域名不匹配证书上写的是example.com用户访问的是www.example.com直接报错。自签名证书用于内网测试没问题部署到公网正常客户端一律不信任。自己签发一套内网 CA 用于测试环境是完全合理的做法把 CA 根证书装进测试设备的信任列表就行。但千万别为图省事在生产环境用自签名证书——用户浏览器会直接提示不安全这等于把 HTTPS 的意义丢掉了一大半。5. 把协议知识变成排查工具说了那么多原理最终还是要落回能不能帮我解决问题。协议知识最大的价值在于出问题时你能不看业务代码就先定位出问题在哪一层。5.1 最轻量的检查手段curl 和浏览器开发者工具命令行里最实用的就是 curl。加一个-v参数就能看到整个请求和响应过程curl -v https://example.com/api/list输出里除了业务 JSON还会包含 TCP 连接建立过程、TLS 握手信息、发送的请求头和收到的响应头。想只看响应头用-I发 HEAD 请求想自定义头部用-H想看耗时细节用-w配合变量curl -o /dev/null -s -w DNS解析: %{time_namelookup}s\nTCP连接: %{time_connect}s\nTLS握手: %{time_appconnect}s\n首字节: %{time_starttransfer}s\n总耗时: %{time_total}s\n https://example.com这个命令能一眼看出时间花在哪一段如果 TCP 连接耗时高多半是网络链路或后端 accept 队列的问题如果 TLS 握手耗时高可能是 SSL 握手协商慢或者证书链太长如果首字节耗时高那是服务端处理慢和网络无关。浏览器开发者工具的 Network 面板也一样。点开一个请求能看到完整的请求头、响应头、状态码、耗时瀑布图。TTFBTime to First Byte长说明服务端响应慢Content Download 长说明响应体传输慢或网络带宽不够。很多所谓接口慢的问题在瀑布图下 10 秒就能定位出方向。5.2 几个常见疑难点的定位思路我见过不少同学问为什么我后端已经返回了前端还是转圈。这种情况通常不是协议问题而是前端解析或渲染的问题但排查时依然要靠协议来排除。先看响应状态码和Content-Type确认后端返回的是不是前端期望的格式再看看 JSON 有没有被截断Content-Length和实际 body 大小是否一致。如果数据量特别大还要注意分块传输是否正常结束。还有一类高频问题是改完代码缓存不生效。服务端明明更新了文件客户端拿到的还是旧的。这时要看响应头里的Cache-Control和ETag。如果是max-age86400这种长缓存那就跟服务器协商短缓存或者加版本号如果返回了 304 说明浏览器在询问服务器且服务器认为未修改问题可能出在服务器对If-Modified-Since的处理上。HTTP/2 有没有真正生效同样可以从响应头判断协议版本会显示为HTTP/2.0同时打开连接面板可以看到多个请求复用在同一条连接上。如果部署了 HTTP/2 但连的是旧版系统库或者某些网络设备不支持实际还是 HTTP/1.1抓包看一眼就知道。5.3 保持对协议的敏感度协议知识的积累不需要死记硬背关键是多看真实请求。我个人的习惯是无论前后端平时调试接口时多看一眼请求头和响应头而不是只盯着返回数据。很多系统设计上的问题比如跨域漏配、缓存策略混乱、连接不复用、证书快过期其实早就在头部字段里露出了苗头。新技术层出不穷但底层协议的变化非常缓慢。你看任何框架、任何中间件遇到不懂的配置项先想想它是在 HTTP 的哪一层起作用、它改写了哪个头部字段、它调整了哪个阶段的时序。用这个思路去看很多复杂的问题都会变得清晰起来。HTTP 和 HTTPS 是互联网应用真正的公共语言早一天吃透早一天少走弯路。
阅读完成 · 觉得有帮助?
咨询建站