ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HTTP 深度解析

HTTP 深度解析 HTTP 深度解析从 URL 到 Cookie 的完整实践一、为什么还需要一篇 HTTP 文章前面三篇文章我们已经构建了完整的网络知识体系从物理层到应用层的分层模型从 TCP 可靠传输到 IP 路由寻址再到 HTTPS 的加密握手。但对于 HTTP 协议本身还有一大块日常开发高频使用、面试中高频出现的内容没有展开。比如你每天都在用 URL但你真的理解它每一部分的含义吗你知道为什么 URL 中有些字符必须转义吗你知道 GET 和 POST 的本质区别不是参数放哪而是幂等性吗你知道 Cookie 到底是怎么让无状态的 HTTP 变成有状态的吗这些就是本文要讲的内容。二、URL定位互联网资源的六把钥匙2.1 一个 URL 到底由什么组成我们先看一个完整的 URLhttps://user:passv.bitedu.vip:8080/personInf/student?userId10000classId100#section把它拆成六个部分协议方案 | 用户信息 | 主机名 | 端口 | 路径 | 查询字符串 | 片段标识 https:// | user:pass | v.bitedu.vip | :8080 | /personInf/student | ?userId10000classId100 | #section协议方案告诉客户端用什么协议通信。最常见的当然是http和https但 URL 的协议方案不局限于此——ftp://是文件传输mailto:是发邮件jdbc:mysql://是数据库连接字符串。协议方案后面必须跟双斜杠//这是早期 URL 规范定义的现在已经成了标准。用户信息的格式是用户名:密码。这个字段在早期互联网中用于认证但现在已经几乎没人用了。原因很直接密码写在 URL 里太危险了。URL 会被记录在浏览器历史、服务器日志、代理服务器日志里明文密码随时可能泄露。现代网站都把认证信息放到 Header如Authorization或 Cookie 里URL 里的用户信息字段基本废了。主机名就是域名通过 DNS 解析成 IP 地址。你可以用ping命令验证ping v.bitedu.vip会返回真实 IP比如 118.24.113.28。URL 中也可以直接写 IP 地址比如http://118.24.113.28/只是不好记而已。端口号指定 TCP 端口。如果省略浏览器会根据协议自动选择默认值http 用 80https 用 443ftp 用 21。这就是为什么你在浏览器输入http://example.com时不需要写:80。路径表示资源在服务器上的层次化位置。但要注意路径不一定对应实际的物理文件。在 Spring MVC 中GetMapping(/personInf/student)publicStudentgetStudent(RequestParamLonguserId){...}这个路径只是路由规则的一个键背后可能对应数据库查询、业务逻辑计算等等。它和文件系统没有直接关系。查询字符串是问号后面的键值对用分隔多个键值对用分隔键和值。你可以把它理解为函数调用的参数列表// 函数调用getUserById(userId10000,classId100)// 等价的 URL query string?userId10000classId100键和值的命名完全由后端程序员自定义没有标准约束。片段标识是#后面的部分主要用于页面内跳转。比如https://cn.vuejs.org/v2/guide/#起步会让浏览器滚动到 id 为起步的元素。这里有一个非常重要的细节片段标识不会发送给服务器。当你访问上面的 URL 时服务器收到的请求行是GET /v2/guide/ HTTP/1.1服务器完全不知道你是因为#起步才跳转过来的。这是前端用来做单页应用SPA路由的基础——Vue Router 的 hash 模式、React Router 的 hash 模式都是利用这个特性实现的。2.2 URL 中哪些部分可以省略部分可省略吗省略后的含义协议名可以默认为http://用户信息可以几乎总是省略端口号可以http→80, https→443路径可以省略后相当于/查询字符串可以没有任何参数片段标识可以页面内无跳转HTML 标签中还有一套特殊的省略规则。当src或href属性只写路径时会自动使用当前页面的域名和协议!-- 假设当前页面是 https://example.com/a/page.html --imgsrcimage.png!-- 实际访问 https://example.com/a/image.png --ahref/other/index.html!-- 实际访问 https://example.com/other/index.html --前者是相对路径相对于当前目录后者是绝对路径从域名根目录开始。2.3 URL Encode为什么特殊字符必须转义URL 中有许多字符被赋予了特殊含义/是路径分隔符?是查询字符串开始是键值对分隔符是键值分隔符#是片段标识开始在某些场景下表示空格%是转义符本身。如果一个参数值中包含这些字符怎么办比如你想搜索abc直接拼接到 URL 中会变成?qabc。服务器收到后无法判断这是一个参数qabc还是两个参数qabc。所以需要对特殊字符进行转义编码。规则是将字符转为十六进制前面加%格式为%XY。原始字符编码后%2B%3D空格%20或中文测试UTF-8%E6%B5%8B%E8%AF%95浏览器会自动处理这个问题。如果你在地址栏输入?qabc浏览器实际发送的是?qa%2Bb%3Dc。服务器收到后会自动做 URL Decode 还原成原始字符。常见误区“GET 只能传文本POST 能传二进制”这个说法不准确。GET 的 query string 虽然不能直接放二进制数据但可以先 Base64 编码再 URL Encode就能传任意数据了。只是不方便阅读、长度受限罢了。三、HTTP Method不只是 GET 和 POST3.1 GET获取资源的默认操作GET 是最常用的 HTTP 方法几乎所有浏览器地址栏的请求都是 GET。它的特点是首行第一部分是GETquery string 可以为空或不为空header 部分有若干键值对body 部分为空触发 GET 请求的场景包括浏览器地址栏输入 URL、HTML 中的link/img/script/a标签加载资源、JavaScript 的fetch()/axios.get()等。关于 GET 的 URL 长度问题网上有些资料说GET 请求最多 1024KB这是错误的。HTTP/1.1 标准RFC 2616原文明确指出“Hypertext Transfer Protocol – HTTP/1.1” does not specify any requirement for URL length.标准中没有规定 URL 的长度限制实际限制来自浏览器和服务器的实现Chrome 约 2MBFirefox 约 65KB~2MBNginx 默认约 8KB可配置Apache 默认约 8KB。这不是协议的限制而是软件实现的限制。就像 TCP 本身不限制数据包大小但以太网 MTU 限制为 1500 字节一样。3.2 POST提交数据的标准方式POST 的特点是首行第一部分是POSTquery string 一般为空也可以不为空header 部分有若干键值对body 部分一般不为空数据格式由Content-Type指定POST 多用于提交用户输入的数据给服务器比如登录页面、下单页面、文件上传等。3.3 GET 和 POST 的本质区别这是面试中被问到烂的问题但绝大多数人只答了一半。完整的回答应该从三个层面展开语义层面GET 的语义是获取资源它是幂等且安全的——多次执行效果相同不修改服务器状态。POST 的语义是提交数据它是非幂等的——每次执行可能产生不同的副作用。实现层面特性GETPOST参数位置URL 的 query string请求体Body长度限制受浏览器/服务器实现限制理论上无限制浏览器缓存可以被缓存不会被缓存浏览器书签可以收藏不可以收藏浏览器回退不会重新提交会提示确认重新提交常见误解纠正有些资料说POST 比 GET 安全这是不科学的。是否安全取决于前端在传输密码等敏感信息时是否进行加密和 GET/POST 无关。HTTPS 下 GET 参数同样是加密传输的。有些资料说GET 传输数据量小POST 传输数据量大这也是不科学的。标准没有规定 GET 的 URL 长度也没有规定 POST 的 body 长度。传输数据量完全取决于浏览器和服务器的实现。有些资料说GET 只能传文本POST 能传二进制同样不准确。GET 的 query string 虽然无法直接传输二进制但可以对二进制数据进行 URL Encode 后传输。3.4 其他 Method 的详细语义Method语义幂等性典型用途PUT整体替换资源是更新整个用户资料DELETE删除指定资源是删除评论HEAD同 GET 但不返回 body是检查资源是否存在OPTIONS返回服务器支持的方法是CORS 预检请求幂等性的定义多次执行相同请求结果与一次执行相同。为什么 PUT 和 DELETE 是幂等的PUT /users/123 {name:张三}第一次把用户改成张三第二次还是同样的结果。DELETE /users/123第一次删除了用户第二次再删会返回 404但不会多删一次。为什么 POST 通常不是幂等的POST /orders创建订单每次执行都会产生一个新的订单记录。两次执行就是两条记录。RESTful API 的设计规范就是基于这个原则GET /users/123获取PUT /users/123整体替换PATCH /users/123部分更新DELETE /users/123删除。Method 的语义决定了 API 的行为。四、Header键值对构成的元数据仓库4.1 Host服务器的地址和端口即使你把 IP 写在 URL 里HTTP 请求的 header 中仍然会有Host字段GET /index.html HTTP/1.1 Host: www.example.com:80为什么需要 Host因为一个 IP 地址可能对应多个网站虚拟主机。比如腾讯云的一台服务器可能同时托管着site-a.com、site-b.com、site-c.com。如果没有 Host header服务器收到请求后不知道要把数据返回给哪个站点。HTTP/1.1 标准强制要求必须有 Host header。4.2 Content-LengthBody 的确切长度Content-Length: 105表示 Body 部分的字节数是 105。为什么需要它因为 TCP 是面向字节流的不像 UDP 有固定的报文边界。如果没有 Content-Length接收方可能永远不知道 Body 何时结束。这和前面讲过的 TCP 粘包问题是同一个根源TCP 不保留应用层的消息边界所以必须在应用层自己定义消息结束的标记。Content-Length 就是 HTTP 定义的标记方式之一。4.3 Content-TypeBody 的数据格式这是最关键的 header 之一决定了服务器如何解析 Body。HTTP 协议定义了三种主流的 Body 格式application/x-www-form-urlencoded传统的表单提交格式titletestcontenthellotagsjava,backend就像 URL 的 query string 一样是keyvaluekey2value2的形式。适合简单的表单提交但无法上传文件。multipart/form-data用于文件上传格式复杂但功能强大Content-Type: multipart/form-data; boundary----WebKitFormBoundaryrGKCBY7qhFd3Trw ------WebKitFormBoundaryrGKCBY7qhFd3TrwA Content-Disposition: form-data; nametext title ------WebKitFormBoundaryrGKCBY7qhFd3TrwA Content-Disposition: form-data; namefile; filenamechrome.png Content-Type: image/png PNG 二进制数据... ------WebKitFormBoundaryrGKCBY7qhFd3TrwA--boundary定义了各个部分的边界每部分有自己的 metadata文件名、类型等。这种格式可以传任意类型的数据二进制、文本混合适合文件上传。application/json现代 API 最常用的格式{username:zhangsan,password:123456}服务器通过Content-Type: application/json知道要用 JSON 解析器来解析 Body。JSON 结构清晰、嵌套方便、支持复杂数据类型数组、对象、布尔值等而且 JavaScript 原生支持JSON.parse()、JSON.stringify()相比 XML 更简洁、占用更少带宽。如何选择简单表单提交用application/x-www-form-urlencoded文件上传用multipart/form-data现代 API 通信用application/json。4.4 User-Agent浏览器的自我陈述User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36这个字符串包含了操作系统Windows 10、浏览器引擎WebKit/Chrome、浏览器版本Chrome 91.0.4472.77等信息。为什么这么长这是历史遗留问题。早期 Netscape 浏览器引入 UA 时只是简单声明Mozilla N.0后来为了兼容性各大厂商都延续了这种方式把自己的信息塞进去。User-Agent 的实际用途包括服务器根据 UA 返回不同版本的页面移动版/桌面版、爬虫检测很多网站会拦截非正常 UA 的请求、统计分析各浏览器市场份额。很多爬虫会用最新的 Chrome UA 伪装身份但实际上它们的代码根本没有实现渲染能力。4.5 Referer来源追踪Referer: https://gitee.com/login表示我是从哪个页面跳转到当前页面的。注意它是 Referer 而不是 Referrer这是个拼写错误但沿用至今HTTP/1.0 规范 RFC 1945 中的拼写错误HTTP/1.1 决定保留以确保兼容性。如果直接从地址栏输入 URL、从书签或邮件中的链接点击就没有 Referer。Referer 的用途包括防盗链检查请求是否来自合法站点、流量分析看用户从哪来、安全防止 CSRF 攻击的一部分手段。4.6 Cookie会话身份的载体Cookie: usernamezhangsan; gitee-session-nM1Rhbk1QUUxQdWk1VEZVQ1BvZXYybG13ZUJFNGR1V0pSYTZyTllECookie 是键值对的集合不同的键值对之间用;分隔。每个域名下的 Cookie 是隔离的gitee.com的 Cookie 不会发给baidu.comlogin.gitee.com的 Cookie 也不会发给www.gitee.com。Cookie 可以通过Expires或Max-Age设置过期时间也可以在响应中设为session cookie浏览器关闭就删除。五、登录流程Cookie/Token 如何实现有状态的会话管理5.1 从抓包看登录全过程以 Gitee 为例完整观察一遍登录第 1 步清除 Cookie打开开发者工具 → Network → Cookies手动删除所有gitee.com相关的 Cookie。第 2 步发起登录请求POST https://gitee.com/login HTTP/1.1 Host: gitee.com Content-Type: application/x-www-form-urlencoded encrypt_keypasswordutf8%E2%9C%93authenticity_token36ZqO9tglSN6EB6pF6f2Gt%2B...注意这里传的是密码但没有看到任何 session token——此时我们确实还没有身份标识。第 3 步服务器返回 302 重定向 Set-CookieHTTP/1.1 302 Found Location: https://gitee.com/HGtz2222 Set-Cookie: oschina_new_userfalse; path/ Set-Cookie: gitee_usertrue; path/ Set-Cookie: gitee-session-nM1Rhbk1QUUxQdWk1VEZVQ1BvZXYybG13ZUJFNGR1V0pSYTZyTllE; path/这里有三个Set-Cookie最关键的是第三个gitee-session-n。它的值是一串加密的长字符串这就是会话令牌Session Token。第 4 步后续请求自动携带 Cookie现在访问个人主页GET https://gitee.com/HGtz2222 HTTP/1.1 Cookie: oschina_new_userfalse; user_localezh-CN; yp_riddler_id1ce4a551-a160-4; gitee-session-nM1Rhbk1QUUxQdWk1VEZVQ1BvZXYybG13ZUJFNGR1V0pSYTZyTllE看到了吗浏览器自动把上一步服务器设置的 Cookie 带上去了。这就是会话保持的秘密。第 5 步服务器验证 Cookie服务器收到请求后从 Cookie 中提取gitee-session-n用它去 Session 存储Redis、内存等查找对应的用户信息。找到就放行找不到就返回 401/302 跳到登录页。医院看病的比喻挂号提供身份证 → 拿到就诊卡Session Token看病每次出示就诊卡 → 医生识别身份 → 开药结束注销就诊卡 → Session 失效再来看病办一张新的就诊卡 → 得到新的 Token5.2 Cookie vs Token两种身份验证模式前面讲的是Cookie-based Session模式。还有一种流行方式是Token-based以 JWT 为例Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...两者对比特性Cookie-based SessionToken (JWT)状态存储服务器端Session Store客户端JWT Self-contained携带方式浏览器自动携带前端手动放入 Header跨域受同源策略限制不受限CSRF 防护需要额外机制天然免疫XSS 风险HttpOnly 可防localStorage 易被窃取扩展性需共享 SessionRedis无状态天然水平扩展主动失效服务端删除即失效过期前无法强制失效Token 的工作方式服务器生成一张带签名的卡片JWT给你每次出示这张卡片时服务器验证签名即可无需查数据库。卡片过期自然失效。实际项目中的混合用法很多系统会结合两种方式——用 Cookie 存一个 refresh tokenHttpOnly 防 XSS用 Token 存 access token短有效期Token 过期时用 Refresh Token 换取新 Token。这样既安全又灵活。六、深入思考题Q1如果 GET 不允许有 Body为什么实际中很多人会往 GET 请求的 Body 里放数据RFC 规范说 GET 不应该有 Body但并没有禁止。实际中 axios、Postman 都允许 GET 有 Body。但这会导致问题不符合规范可能被某些服务器拒绝缓存行为不一致有些缓存系统忽略 Body语义混乱GET 应该只是获取不应该有 payload。最佳实践严格遵守规范GET 不放 Body参数全部走 query string。Q2302 重定向和 301 有什么区别307/308 呢状态码名称浏览器行为301Moved Permanently永久重定向浏览器会缓存下次直接访问新地址302Found临时重定向浏览器不会缓存307Temporary Redirect同 302但严格要求保持原方法和 Body308Permanent Redirect同 301但严格要求保持原方法和 Body为什么需要 307/308因为历史上很多浏览器对 301/302 的处理不规范当你用 POST 请求一个返回 302 的 URL 时浏览器可能会把后续请求变成 GET丢失 Body。307/308 明确要求浏览器必须保持原方法和 Body。Q3为什么 referer 拼写成referer而不是referrer这是 HTTP/1.0 规范RFC 1945中的拼写错误。到了 HTTP/1.1RFC 2068标准决定保留这个错误以确保兼容性。所以正确的拼写应该是referrer源指涉者但 Header 字段名永远是Referer。七、经典面试题补充1. URL 中哪些部分是可选的省略后会默认什么值协议名默认为 http://、用户信息省略、端口号http→80, https→443、路径→/、查询字符串→无参数、片段标识→无跳转。HTML 标签中还可以省略域名自动使用当前页面域名。2. application/x-www-form-urlencoded 和 multipart/form-data 的区别前者是简单的keyvaluekey2value2格式只能传文本后者用 boundary 分隔支持任意类型数据如文件上传但格式复杂。3. Cookie 和 Token 的核心区别Cookie 存储在服务器端Session浏览器仅持有 IDToken如 JWT包含完整用户信息存在客户端。Cookie 有 CSRF 风险但可通过 HttpOnly 缓解Token 无状态易于扩展但难以主动失效。4. PUT 和 POST 的区别PUT 是幂等的用于整体替换资源POST 是非幂等的用于创建新资源。PUT 要求客户端知道资源标识POST 则由服务器分配 ID。5. Content-Type 有哪些常见值分别对应什么格式application/x-www-form-urlencoded传统表单、multipart/form-data文件上传、application/json现代 API、text/htmlHTML 页面、image/png图片等。八、总结本文深入讲解了 HTTP 协议的工程细节URL 的六个组成部分、可省略规则、URL Encode 的必要性HTTP Method 的语义与幂等性、GET/POST 的本质区别Header 的实际用途、三种 Body 格式的适用场景以及 Cookie/Session 如何实现有状态的会话管理。这些内容补充了前三篇文章没有展开的应用层细节构成了完整的 HTTP 知识体系。
返回列表