
1. 先把HTTP和HTTPS这两兄弟说清楚1.1 协议是什么不只是一个网址前缀平时打开浏览器、敲网址的时候地址栏前面那串http://或者https://估计没多少人会认真看反正能打开网站就行。但做技术的人迟早会发现这两串字母背后藏着的规矩决定了你写的接口能不能通、用户的数据安不安全、公司网站在浏览器里会不会被标成不安全。HTTP的全称是HyperText Transfer Protocol超文本传输协议。它定义了客户端浏览器、App和服务器之间怎么说话谁先开口、消息长什么样、收到之后怎么回应。可以把它理解成Web世界的快递规则——包裹怎么打包、面单怎么填、送到之后怎么签收全是这套协议说了算。这套规则从1991年诞生到现在已经进化出HTTP/1.0、HTTP/1.1、HTTP/2、HTTP/3好几个版本。而HTTPS并不是另一种完全独立的协议它本质上还是HTTP只不过在HTTP和传输层之间多了一层安全加密的壳全称是HTTP Over TLS默认端口也从80变成了443。打个比方HTTP是邮局送的普通平信信封透明路上谁都能看到内容HTTPS是把信装进保险箱再快递只有收件人用钥匙才能打开。协议这个概念也不是Web独有的嵌入式工程师天天跟CAN、UART、MIPI、Modbus、OPC UA这些协议打交道工业现场要用Modbus和OPC UA去读PLC、传感器、数控机床的运行状态数据监控项目里要解析海康摄像头的RTSP流还得分清主码流和子码流。所以理解了一类协议再学其他协议会快很多。这篇文章咱们就把HTTP和HTTPS这对最基础、最高频的协议彻底讲透。1.2 这篇文章能帮你解决什么先说清楚这篇内容适合谁免得浪费时间。如果你是刚入门想系统搞懂Web基础的后端开发者这里会把协议层的工作原理、报文结构、请求方法、状态码全部串一遍如果你是被线上接口突然打不开、被各种证书报错逼疯的运维和测试同学后面专门有一章节是真实排障实录如果你是想搞清楚HTTPS到底加密了什么的产品经理或前端加密握手部分用了大量类比不写数学公式也能看明白。我自己做后端和运维这些年HTTP和HTTPS相关的坑几乎都踩过一遍。就拿最近两年经常遇到的报错来说error response from daemon: get https://registry-1.docker.io/v2/: net/http这种拉镜像失败的问题http error 400. a request header field is too long这种Header过大的问题还有本地联调时被CORS策略拦得怀疑人生的经历全都不是靠背概念能解决的必须动手抓包、看链路、查配置才能定位。所以这篇不是教科书而是一份偏实战的协议手册看完你可以直接拿去用。2. HTTP协议全景拆解2.1 一次HTTP请求背后发生了什么先看最简单的场景在浏览器里输入一个网址回车后面发生了什么这里其实分成了DNS解析、TCP连接、发送HTTP请求、服务器处理、返回HTTP响应五个大环节。DNS负责把域名翻译成服务器能认的IP地址TCP负责建立一条可靠的双向管道HTTP则在这条管道上规定请求和响应的具体格式谁先发、怎么发、发完怎么收都清清楚楚。HTTP本身是典型的一问一答模式客户端发一个请求服务器给一个响应没有服务器主动推送一说HTTP/2的Server Push算特例实际使用中很少。这种模式的好处是简单可靠、状态清晰代价是它默认无状态——服务器不记得你上一次请求干了什么所以才需要Cookie、Session、Token这些机制来记住用户。以一次经典的GET请求为例完整的请求报文长这样GET /api/user?id123 HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 Accept: application/json Connection: keep-alive请求的第一行叫请求行包含方法GET、路径/api/user?id123、协议版本HTTP/1.1。接下来是请求头一堆key-value告诉服务器一些元信息比如Host表示访问的是哪个域名User-Agent表示浏览器或客户端的类型Accept表示客户端能接受什么格式的响应。请求头下面有一个空行空行之后才是请求体bodyGET请求一般没有bodyPOST请求的body放表单数据或者JSON。真正让很多新手困惑的是路径里的query string?id123和请求体body的区别。GET一般把参数放在URL里POST才把数据放进body。用快递来类比URL是写在包裹外面的地址body是包裹里面的东西。地址人人能看到里面的东西理论上只有拆包的人才知道。所以千万不要在URL里放密码、令牌这类敏感信息日志系统会把URL整个记下来等于把密码写在明处。2.2 请求方法怎么选才不踩坑HTTP定义了若干请求方法日常开发高频出现的其实只有四个GET、POST、PUT、DELETE另外还有PATCH、HEAD、OPTIONS等。很多人对GET和POST的理解就是GET拿数据、POST传数据这个说法大方向没错但严格来说不太站得住脚——GET也可以带bodyPOST也可以把参数放URL协议本身并没有硬性规定。真正约束它们的是场景语义和浏览器行为。在实践中我的经验是查询用GET创建/修改用POST整体更新用PUT局部更新用PATCH删除用DELETE。更重要的是记住GET是幂等且可缓存的POST不是。所谓幂等就是同一个请求执行一次和执行一百次对服务器的最终影响是一样的。所以千万不要用GET去执行删除用户这种有副作用的操作搜索引擎爬虫会在你不知情的情况下把这种URL爬一遍那场面会相当酸爽。还有一个被忽略的细节浏览器和服务器对URL的长度都是有限制的一般超过2000字符的GET请求就可能出问题而POST把数据放body理论上可以传输大得多。大表单、文件上传这类场景老老实实选POST别想着把数据塞进URL里偷懒。另外PUT和DELETE虽然语义清晰但有些浏览器和Web框架对这两个方法的支持比较隐晦一旦遇到前置代理或防火墙拦截请求会被改写成POST联调时要留意这一点。2.3 状态码速查和最容易误读的几个HTTP响应的第一行是状态行格式是HTTP/1.1 200 OK。那个三位数字就是状态码它告诉客户端这次请求的结果。状态码分五类我整理了一张速查表基本覆盖了日常能遇到的所有情况分类范围含义常见例子1xx100-199信息类还在处理中100 Continue2xx200-299请求成功200 OK201 Created204 No Content3xx300-399需要重定向301永久跳转302临时跳转304缓存未修改4xx400-499客户端错误400参数错误401未认证403禁止访问404找不到5xx500-599服务器错误500内部错误502网关错误503服务不可用504网关超时这里最容易踩坑的是304。很多优化新手看到日志里一堆304以为出错了其实304是服务器在告诉浏览器你缓存的那份还能用不用重新下载它是正常的缓存机制反而说明你的缓存策略在生效。另一个坑是302和301的区别302是临时跳转浏览器以后还会继续访问原地址301是永久跳转浏览器和搜索引擎会记住新地址。做网站迁移时必须用301否则旧域名的权重和收录会白白流失。还有一个容易被误读的是403和401。401是没登录或者登录态过期需要先认证403是你登录了但你就是没权限看。排查接口权限问题时先分清这俩能少走很多弯路。而404除了路径不存在还可能是接口网关配置错误把请求转发到了一个不存在的服务上。状态码只是线索别把它当结论。2.4 连接复用、缓存和HTTP版本演进早期的HTTP/1.0每次请求都要新建一个TCP连接请求完就断开。如果页面里有100个资源就要建立100次连接效率极低网络延迟高的时候体验更是灾难。HTTP/1.1引入了Keep-Alive持久连接默认在同一个TCP连接上可以连续发多个请求这就是http连接复用的由来。但HTTP/1.1有个著名的队头阻塞问题同一个连接上的请求必须排队前一个没有响应完后一个就只能等着。一个页面里的几十个资源挤在同一条连接上谁先谁后还得按顺序来。HTTP/2用多路复用解决了这个问题它在同一连接上并行传输多个流再加上头部压缩HPACK、二进制分帧这些机制性能比1.1提升非常明显。而最新的HTTP/3更彻底直接换掉了底层运输协议从TCP换成了基于UDP的QUIC把连接建立的握手时间大幅压缩在弱网和高丢包环境下尤其有优势。现在主流浏览器和CDN基本都支持HTTP/2和HTTP/3。作为开发者至少要能说清楚这几个版本的区别。面试和排查性能问题时如果有人问你页面加载慢怎么办你先看协议版本再看有没有启用连接复用这条排查路径比盲目加服务器靠谱得多。值得一提是工程中还经常遇到应用层协议混用的情况比如页面前端走HTTPS但后端服务之间走内部HTTP这类架构本身没问题但一定要确保公网入口是加密的否则数据照样裸奔。2.5 容易被忽略的请求头与Content-Type很多人在前后端联调时吃过数据拿到了但解析不了的亏根源往往是一个小小的Content-Type头。这个头告诉对方body是什么格式application/json表示JSONapplication/x-www-form-urlencoded表示表单text/html表示网页multipart/form-data表示文件上传。我遇到过一个很经典的线上事故后端某个接口明明返回的是JSON数据Content-Type却被框架默认设置成了text/html前端用response.json()直接解析崩溃排查了几个小时才找到原因。所以联调时第一件事就是检查响应头里的Content-Type对不对。用curl -i命令可以看到完整的响应头几秒钟就能定位这类问题。再比如Cookie、Authorization这些头它们的安全性跟Header的传递机制有关。Cookie会自动附加到同域名的每次请求上但如果你把几个KB的业务数据塞进Cookie一次请求就会带几KB的冗余数据不仅浪费带宽还可能触发服务器对Header大小的限制——这就是http error 400. a request header field is too long这类报错最常见的来源。我在后面排查章节里会专门讲这个。Header永远是该放的放不该放的别放。3. HTTPS协议深入解析3.1 为什么说HTTP是裸奔的HTTP的全部内容都是明文传输的这意味着在客户端和服务器之间的任何一跳——路由器节点、公共场所的Wi-Fi热点、机房设备——只要有人能把流量截下来就能直接看到请求里的全部内容用户名、密码、身份证号、银行卡信息、聊天记录全都一览无余。更严重的是明文传输不仅会泄露还能被篡改。中间人可以把你的请求改掉你想下载一个软件补丁中间人给你换成木马你想访问银行的页面中间人给你换成钓鱼页面。这就是著名的中间人攻击MITM。所以现在几乎所有严肃的网站、App API都强制HTTPS浏览器也会在地址栏直接提示不安全来警告用户。这不是什么可选项而是Web应用的基础底线。我在实际项目中见过一个让人捏把汗的案例一个老旧的内部系统登录接口走HTTP密码用Base64编码后放在URL里传给后端。Base64只是编码不是加密任何人拿到这段字符串都能解码出明文密码。后来安全扫描直接把它标为高危漏洞整改的时候把整个登录流程重写了一遍。别以为内网系统就安全内网同样有被攻破的风险密码明文传输在任何场景下都不该出现。3.2 两种加密怎么分工合作HTTPS的加密不是只用一种算法而是对称加密和非对称加密的组合拳。对称加密比如AES、ChaCha20速度快适合加密大量正文数据但有个致命问题通信双方怎么安全地共享同一个密钥如果密钥在网上一来一回地明文传输被截获了加密就形同虚设。这就是著名的密钥配送问题。非对称加密则用一对密钥公钥和私钥。公钥分发给别人私钥自己保管用公钥加密的内容只有对应的私钥能解开反之亦然。它彻底解决了密钥配送问题但缺点是运算慢不适合加密大量数据。HTTPS的做法是让两者搭档握手阶段用非对称加密安全地协商出一个会话密钥session key之后正文数据全部用这个会话密钥做对称加密。这样既解决了密钥分发难题又保证了传输速度。换句话说非对称加密是送钥匙的人对称加密是用钥匙锁门的人分工明确各司其职。现代TLS握手还普遍采用ECDHE这类临时密钥交换算法即使服务器的长期私钥泄露也无法回推历史会话内容这就是前向保密Forward Secrecy的价值。3.3 TLS握手流程拆解很多人一听到TLS握手就头疼其实抓住几个关键消息就够理解全流程了。以最常见的TLS 1.2完整握手为例客户端发送ClientHello包含支持的TLS版本、加密套件列表、一个随机数A。服务器回应ServerHello选定加密套件和版本发回自己的随机数B并附上数字证书证书里面有公钥。客户端验证证书的合法性验证通过后用随机数A、B再加一个自己生成的随机数C通过协商好的算法推导出会话密钥然后用服务器的公钥把相关参数加密后发给服务器。服务器用私钥解密得到相同的会话密钥双方用这个会话密钥加密发送Finished确认消息握手中断后续应用数据全部走对称加密。TLS 1.3把握手从2-RTT压缩到了1-RTT一步到位还强制要求前向保密砍掉了很多不安全的旧算法安全性和速度都更好。如果你在做新系统直接用TLS 1.3老系统兼容不了再向下兼容TLS 1.2。要注意的是TLS 1.0和1.1早就被时代淘汰了主流浏览器已停止支持如果服务还开着这些旧版本赶紧关掉否则安全评分难看不说还可能成为攻击入口。3.4 证书体系信任是怎么建立的客户端凭什么相信服务器发过来的公钥是真的靠数字证书。证书由CA证书颁发机构签发证书本身带有CA的签名而CA的根证书被预置在操作系统和浏览器里。这个信任关系是链式的根CA签发中间CA中间CA再签发服务器证书环环相扣只要根部可信整条链就可信。这跟现实世界的护照体系很像你不用亲自认识每个人只要信任签发护照的机构再验证护照本身没被篡改就行。实际开发中最常遇到的情况是测试环境用自签名证书自己用OpenSSL生成的证书。自签名证书不被浏览器信任访问时会出现您的连接不是私密连接的警告。生产环境要么买付费证书要么用Lets Encrypt这类免费证书云厂商阿里云、腾讯云等也提供免费的一年期DV证书。这里有个实用技巧本地开发调试HTTPS时可以自己生成自签名证书然后把它导入系统的受信任根证书存储里浏览器就不报警了。注意自签名证书只适合开发和测试千万别带到生产环境。还有一个排查HTTPS问题时极易忽略的点系统时间。证书的生效和过期时间严格依赖系统时钟如果服务器或本地设备的时间偏差过大TLS握手会直接失败报错往往很笼统。我遇到过一台嵌入式设备无法访问HTTPS接口折腾半天发现是RTC电池没电了时间停在两年前证书校验当然过不了。同步时间是排查TLS问题时的必修课。3.5 证书类型怎么选DV、OV、EV企业申请证书时经常看到DV、OV、EV这几个缩写它们代表不同的验证等级和信任程度。DVDomain Validation只验证域名所有权申请最快几分钟就能签下来适合个人博客、中小网站、API接口。OVOrganization Validation会验证企业组织的真实身份证书信息里会显示公司名称适合企业官网、交易平台。EVExtended Validation是最高等级验证流程最严格能在浏览器地址栏直接显示企业名称绿色小锁加公司名但现在浏览器对EV的展示越来越淡化很多场景已经看不出区别。从成本和技术角度看绝大多数场景用DV证书就足够了因为DV和OV在加密强度上没有任何差异区别只在信任背书层面。Lets Encrypt签发的就是DV证书完全免费配合certbot可以自动续期是目前最省心的选择。企业对外业务如果客户对安全认证有要求可以升级到OV证书EV除非有合规需求否则性价比一般。4. HTTP和HTTPS实战对比与迁移实操4.1 HTTPS到底牺牲了多少性能十年前大家不上HTTPS理由很统一加密有性能开销服务器扛不住、页面变慢。今天这个理由基本站不住脚了。HTTPS的额外成本主要是握手阶段多出几次加密运算用ECDHE这种椭圆曲线算法一次完整握手的CPU开销非常小。而正文对称加密在现代CPU上已经支持硬件加速AES-NI指令集开销几乎可以忽略不计。真正让HTTPS体验变差的是握手带来的额外网络往返RTT。在网络延迟高的情况下多一次往返会让首次访问明显慢个几百毫秒。这也是实际生产里要启用会话恢复Session Resumption的原因——客户端和服务器在第一次握手后缓存会话票据下次直接恢复省掉完整握手。再加上HTTP/2、HTTP/3配合HTTPS事实上HTTP/2和HTTP/3强制要求加密如今HTTPS的综合性能完全可以超越没做优化的纯HTTP。做新项目直接上HTTPS没有犹豫的必要。迁历史项目则要评估证书配置、服务器算力、CDN支持情况。还有一个冷知识HTTPS配合HTTP/2以后网站的资源加载方式也变了——以前为了减少请求数要合并JS、CSS、图片雪碧图HTTP/2多路复用之后反而建议把小文件拆开并行加载性能更好。协议在演进优化思路也得跟着变。4.2 从HTTP迁移到HTTPS的完整流程这里给一套我实际执行过多次的迁移流程以Nginx为例非常通用申请证书。有域名优先用Lets Encrypt的certbot一条命令搞定certbot --nginx -d www.example.com自动配置和续期非常省心。国内服务器也可以用云厂商的免费证书一般一年一换。配置Nginx监听443端口指向证书文件。核心配置片段如下server { listen 443 ssl; server_name www.example.com; ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; }把80端口全部301重定向到HTTPSserver { listen 80; server_name www.example.com; return 301 https://$host$request_uri; }全站检查有没有写死http://的资源引用。图片、脚本、接口地址全部改成相对路径或自动适配协议避免混合内容Mixed Content被浏览器拦截。现在Chrome对混合内容非常严格页面里只要有一个HTTP资源的script或iframe整个页面就可能被标记为不安全甚至被拦截。有条件就开启HSTS响应头让浏览器强制使用HTTPS访问杜绝被降级攻击add_header Strict-Transport-Security max-age31536000; includeSubDomains always;我在迁移过程中踩过一次很深的坑先改了某个接口为HTTPS但前端页面里一个静态资源还写着http直接导致整个页面在Chrome里被标记为混合内容脚本被拦截功能全挂。所以迁移不是换个证书那么简单全链路的资源引用都要过一遍。另外提醒一点加HSTS之前要想清楚一旦启用浏览器在max-age时间内只接受HTTPS访问如果你的证书配置有问题用户会被死死挡在门外连降级用HTTP的机会都没有。先小范围灰度再逐步放开。5. 常见问题与排查技巧实录5.1 Docker镜像拉取失败的真实排查热词里有一条非常典型的报错error response from daemon: get https://registry-1.docker.io/v2/: net/http。我在公司内网帮同事排查过好多次这类问题表面看是Docker拉镜像失败实际上原因五花八门最常见的是网络代理设置问题Docker守护进程需要配置代理才能访问公共镜像仓库其次是配了私有仓库但没把仓库地址加到insecure-registries里还有一种容易被忽略的情况是系统时钟偏差——证书校验依赖系统时间时间不对会直接导致TLS验证失败。排查顺序建议是先看/etc/docker/daemon.json里的代理和仓库配置再检查系统时间最后看防火墙和DNS。别一上来就怀疑镜像仓库挂了大概率是你环境的问题。这类问题还有一个通用排查姿势先手动访问一下仓库地址看TLS握手和HTTP响应能快速定位是网络层、证书层还是服务端的问题curl -v https://registry-1.docker.io/v2/如果这一步能正常返回401之类的响应说明网络和TLS没问题问题出在Docker客户端配置如果卡在握手阶段多半是代理或时间问题。这个排查思路适用于所有HTTPS请求失败的问题不只是Docker。5.2 JMeter录制HTTPS脚本的证书配置做接口测试的同学经常要在JMeter里录制HTTPS脚本十有八九第一次都会卡在证书上。JMeter录制HTTPS的原理是把自己伪装成中间代理浏览器需要信任它生成的那个根证书ApacheJMeterTemporaryRootCA.crt否则HTTPS请求在握手阶段就被浏览器拦下了什么都录不到。正确步骤先在JMeter的bin目录下找到这个证书文件双击安装到受信任的根证书颁发机构然后在浏览器里把代理指向JMeter监听的端口默认8888同时勾选对所有协议均使用相同代理服务器。如果是Chrome还要注意开启使用系统代理设置。做完这些再录制HTTPS页面或接口JMeter里就能看到明文请求内容——因为JMeter作为中间人已经把HTTPS解密了这就是https明文捕获的实际含义。这里有一个需要时刻警惕的安全点任何能安装根证书的工具理论上都能解密你机器上的HTTPS流量。所以不要把工作电脑随便装陌生软件生成的证书企业里的流量审计软件、抓包工具同样具备这种能力。技术在帮你调试的同时也在提醒你信任根证书是一件非常严肃的事。5.3 Header过大与Content-Type不匹配http error 400. a request header field is too long这种报错我见过最典型的原因是Cookie太大。有些站点或开发者在Cookie里塞了一堆业务数据一次请求把几KB甚至几十KB的Cookie全带上Nginx默认的large_client_header_buffers只有4个8KB超了就报400。解决办法先用浏览器开发者工具看请求的Cookie和Header大小确认来源然后在Nginx里调大缓冲比如large_client_header_buffers 8 16k;但更根本的做法是别把业务数据塞进Cookie。Cookie每次请求都会自动携带存放的数据越多每个请求的冗余越大尤其移动端弱网环境下这种浪费非常明显。同理Authorization令牌如果太长也会占Header空间规范做法是放在请求头里只传必要的值大体积的配置数据走接口查询。再比如Content-Type不匹配前面第二章提过这里再说一个排查技巧遇到响应解析失败先用curl -i看响应头确认Content-Type和实际body内容是否一致再看状态码和字符编码。前后端联调时Content-Type、Charset、Accept三个头是不是能对得上真的是最容易出幺蛾子的地方而且往往是前后端各自都觉得没问题凑在一起就出问题。5.4 本地联调时的CORS跨域问题热词里有一条典型的CORS报错access to xmlhttprequest at http://127.0.0.1:8000/myapp/center from origin ... has been blocked by CORS policy。这个问题在本地开发环境极其常见前端起在3000端口后端起在8000端口端口不同就算不同的源浏览器就会拦截跨域请求。排查思路很清晰第一看后端有没有设置Access-Control-Allow-Origin响应头没有的话在网关或后端中间件里加上第二如果前端请求带自定义Header或Cookie还要设置Access-Control-Allow-Headers和Access-Control-Allow-Credentials: true第三如果请求方法是PUT、DELETE这类较重的方法浏览器会先发一个OPTIONS预检请求后端要能正确响应预检否则请求根本不会进入真正的业务逻辑。很多人在后端写了CORS配置但仍然报错多半是预检这一步没处理好。这里分享一个经验CORS配置尽量统一放在网关层做别让每个微服务各配各的后期改起来想哭。还有生产环境的Access-Control-Allow-Origin千万别随手写*带Cookie的请求和*是不兼容的要么明确指定可信域名要么用动态白名单。把跨域配置写成开放通配符的线上事故我见过不止一次。6. 写在最后协议之外的经验做了这么多年Web开发我的感受是HTTP和HTTPS这对兄弟协议几乎每天都在跟开发者打交道但真正吃透的人真不多。很多人只记住了HTTPS比HTTP安全却不知道安全到底是怎么实现的只知道看着状态码猜问题却不知道状态码背后还有重定向、缓存、预检这些联动机制。对于想进阶的开发者我强烈建议花一个下午的时间用Wireshark抓一次包分别看HTTP和HTTPS的报文差异。你会直观看到HTTP的请求头、body全都暴露在明面上而HTTPS抓到的全是密文那一刻对加密到底保护了什么的理解比看十篇文章都深刻。再配合抓包亲手把TLS握手流程走一遍看ClientHello、ServerHello、证书交换这些消息怎么互动很多之前只能死记硬背的东西一下就通了。最后分享几个我每天都在用的小习惯遇到任何接口问题第一步先用curl -v看完整请求和响应链路别急着打开浏览器F12排查网络问题时先分清是DNS、TCP、TLS还是HTTP层的问题层与层之间的问题不要混在一起查否则只会越查越乱还有一个很多人不知道的经验——日志里多打印协议版本、状态码、耗时、Content-Type这四个字段线上排查问题时能省一半时间。协议这些东西看着枯燥但它是整个互联网地基里最扎实的部分。把这个地基打牢了后面学负载均衡、网关、消息队列、RPC框架都会轻松很多因为它们全都跑在这套体系之上。