应用层协议 HTTP复习笔记整理自《Linux 网络编程 / 5 应用层协议HTTP》结合自己写的 SocketTCP HTTP 服务器实测补充。适用复习 可直接粘到 CSDN。一、HTTP 是什么HTTPHyperText Transfer Protocol超文本传输协议是应用层协议规定客户端浏览器和服务器之间怎么通信、怎么交换超文本HTML。关键点应用层协议本质就是约定。我们自己写 Socket 程序时可以自定义协议比如上一节的自定义协议与序列化而 HTTP 是前辈们定好的、现成好用的一个直接拿来用即可。关于无连接、无状态课件原话是HTTP 是无连接、无状态的协议即每次请求都需要建立新的连接。这里需要分两句看说法是否准确说明无状态✅ 准确服务器不记得你上一次请求是谁、做了什么。每次请求都是独立的。要记住就得靠 Cookie / Session无连接⚠️ 仅限 HTTP/1.0HTTP/1.0 确实是一次请求一次 TCP 连接发完就关。但HTTP/1.1 起默认长连接keep-alive一个 TCP 连接上可以跑多个请求所以更准确的表述是HTTP 本身是无状态的是否无连接取决于版本和配置。我自己的服务器目前响应头写的是Connection: close短连接而浏览器每次都发Connection: keep-alive—— 这就是 HTTP/1.0 和 1.1 的差异在真实报文里的体现。二、认识 URL平时说的网址就是 URLUniform Resource Locator统一资源定位符。完整格式http://192.168.109.128:8888/index.html?usergaopwd123#top └┬┘ └────────┬────────┘ └┬┘ └────┬────┘ └────┬────┘ └┬┘ 协议 主机 端口 路径 查询串 片段部分作用我的服务器对应协议http://告诉浏览器用 HTTP走 TCP—主机服务器 IP 或域名192.168.109.128虚拟机 IP端口找机器上的哪个程序8888由TcpServer server(8888, ...)决定路径要哪个资源Parse()后拼成./wwwroot/index.html查询串?后面的参数Parse()拆进_args片段#top页面内锚点不会发给服务器—注意浏览器默认连 80 端口只有显式写:8888才会去连 8888。HTTP 服务器用 80 只是习惯不是强制。urlencode 与 urldecode像/?:这些字符在 URL 里已经有特殊含义如果参数值里要用到它们必须先转义否则会被当成语法解析。编码规则把字符转成 16 进制UTF-8 字节每个字节前加%。原字符编码后原因%2B在 URL 里可能被当成空格空格%20或空格不能直接出现在 URL 里/%2F路径分隔符中%E4%B8%ADUTF-8 三个字节各自转义urldecode就是逆过程。实践提示我现在的服务器没做 URL 解码所以%E4%B8%AD会原样拼进文件路径导致找不到文件。这是待办功能之一。而且要注意顺序必须先解码再做路径安全检查。否则攻击者用%2e%2e即..就能绕过路径穿越检查。三、HTTP 报文格式请求和响应结构完全一样都是首行 报头若干行 Key: Value 空行 ← 分隔符必须有 正文可为空每行以\r\n结尾空行是报头结束、正文开始的唯一标志——这是解析时最重要的锚点3.1 请求报文POST /form.html?usergao HTTP/1.1 ← 请求行[方法] [url] [版本] Host: 192.168.109.128:8888 ← 报头 Content-Length: 18 Content-Type: application/x-www-form-urlencoded ← 空行 usergaopwd123456 ← 正文GET 通常没有上面这份是我自己服务器实测抓到的真实报文。我的HttpRequest::Deserialize()就是按这个结构拆的切第一行用stringstream按空格分成 method / url / version循环切后面的行按: 分成键值对塞进_headers遇到空行就停空行之后剩下的全是_body有Content-Length但正文长度不够 → 返回 false说明报文没收全继续 recv3.2 响应报文HTTP/1.1 200 OK ← 状态行[版本] [状态码] [描述] Connection: close ← 报头 Content-Length: 1704 Content-Type: text/html ← 空行 !DOCTYPE html... ← 正文HTML 页面就在这里为什么必须有Content-LengthTCP 是字节流没有消息边界。浏览器靠这个数字才知道正文到哪结束否则会一直等下去。做长连接时更是必需。四、HTTP 方法方法用途说明GET请求指定资源最常用。参数放在 URL 的?后面可见、长度受限POST提交数据参数放在请求正文里可传大量数据相对不可见PUT传输文件/更新资源RESTful API 里用于更新HEAD只要响应头不要正文用于确认资源是否存在、看最后修改时间省流量DELETE删除资源PUT 的反操作OPTIONS查询支持哪些方法跨域CORS预检请求用的就是它GET vs POST面试常考对比项GETPOST参数位置URL 查询串请求正文可见性明文在地址栏不在地址栏数据量受 URL 长度限制理论无限制语义获取幂等、安全提交非幂等缓存/书签可缓存、可收藏一般不行注意POST 并不比 GET 安全——两者都是明文传输要安全必须用 HTTPS。所谓POST 更安全只是参数不在地址栏而已。用 curl 验证方法curl -X OPTIONS -i http://127.0.0.1:8888/-X指定方法-i显示响应头五、HTTP 状态码分类记忆首位含义典型1xx信息请求在处理中100 Continue2xx成功200 OK、201 Created、204 No Content3xx重定向要进一步操作301、302、3044xx客户端错误400、401、403、4045xx服务器错误500、502、503常见状态码速查码含义场景200OK请求成功201Created新建资源成功204No Content成功但无内容返回如删除成功301Moved Permanently永久重定向浏览器会缓存跳转302Found临时重定向不缓存304Not Modified命中缓存资源未修改不返回正文400Bad Request请求格式错误401Unauthorized未认证/未登录403Forbidden无权限东西存在但不给你404Not Found资源不存在500Internal Server Error服务器代码崩了502Bad Gateway网关/代理拿不到上游响应503Service Unavailable服务过载或维护中403 和 404 的区别我实测过访问/nope.html我的服务器返回 404文件不存在访问含..的路径返回 403存在但不给你。重定向依赖 Location3xx 重定向必须配合Location响应头告诉浏览器下一步去哪HTTP/1.1 301 Moved Permanently Location: https://www.new-url.com301 vs 307都是跳转区别在于跳转时是否允许改变请求方法307 强制保持原方法302 历史上允许被改成 GET。六、常见 Header字段含义样例Host请求的主机和端口HTTP/1.1 必需支持虚拟主机Host: www.example.com:8080Content-Type正文的媒体类型MIMEtext/html、application/jsonContent-Length正文字节数Content-Length: 150User-Agent客户端环境OS 浏览器Mozilla/5.0 (Windows NT 10.0; ...)Referer从哪个页面跳来的用于防盗链、统计Accept能接受什么类型的响应text/html,image/webp,*/*;q0.8Accept-Encoding支持的压缩格式gzip, deflate, brAccept-Language语言偏好zh-CN,zh;q0.9,en;q0.8Cookie客户端带的 cookiesession_idabc123; user_id1Location配合 3xx 重定向Location: /new.htmlConnectionkeep-alive或close见下节Cache-Control缓存策略max-age3600、no-cacheETag/Last-Modified缓存校验配合 304资源指纹/最后修改时间Content-Type 为什么重要浏览器拿到的是字节流靠Content-Type决定怎么处理text/html→ 交给渲染引擎渲染成网页text/css→ 当样式表应用image/png→ 交给图片解码器application/octet-stream→ 不认识 →下载所以服务器端必须有一张后缀 → MIME 类型的映射表我的Util::SuffixToType()就是干这个的否则浏览器会把你的网页当成文件下载下来。服务器不告知类型时浏览器会嗅探MIME sniffing内容猜类型这有安全风险伪装图片的 HTML 被当网页执行。最佳实践是加响应头X-Content-Type-Options: nosniff。Connection 与长连接版本默认行为HTTP/1.0短连接一次请求后关闭要长连接需显式写Connection: keep-aliveHTTP/1.1默认长连接要关闭需显式写Connection: close长连接好处明显访问一个带 CSS、图片的页面短连接要为每个资源重建一次 TCP三次握手 四次挥手长连接一次握手全搞定。做长连接的前提是每个响应都要有明确的Content-Length否则浏览器不知道一个响应到哪结束无法复用连接。七、最简单的 HTTP 服务器只要按协议格式拼字符串就能让浏览器显示内容// 核心就三步收请求 → 拼响应 → 发回去charinput_buf[1024*10]{0};ssize_t read_sizeread(client_fd,input_buf,sizeof(input_buf)-1);printf([Request] %s,input_buf);// 打印看看浏览器发了啥charbuf[1024]{0};constchar*helloh1hello world/h1;sprintf(buf,HTTP/1.0 200 OK\nContent-Length:%lu\n\n%s,strlen(hello),hello);write(client_fd,buf,strlen(buf));要点响应必须包含状态行、Content-Length、空行、正文用 9090 或其他端口都行80 只是习惯浏览器还会额外请求GET /favicon.ico网站图标所以你会看到两次请求我的版本把这套东西拆成了三层TcpServer只管 socket 和线程、HttpServer::HttpHandler收/解析/业务/发、Http.hpp协议序列化。核心逻辑一样但换业务时服务器框架不用动。八、HTTP 版本演进版本年份核心技术0.91991只有 GET只传 HTML无报头1.01996增加 POST/HEAD、报头、状态码、MIME、缓存每连接一次请求1.11999长连接、管道化、chunked 传输、Host 头虚拟主机2.02015多路复用、二进制帧、头部压缩、服务器推送3.02022基于QUICUDP减少握手、解决队头阻塞重点理解 1.1 → 2.01.1 的管道化有队头阻塞问题前一个响应没回来后面的请求堵着2.0 用多路复用让多个请求在同一个连接上并行乱序传输彻底解决。九、动手实验# 1. 只显示响应头curl-ihttp://127.0.0.1:8888/# 2. 只看响应头不发正文HEAD 方法curl--headhttp://127.0.0.1:8888/# 3. 指定方法curl-XPOST-dusergaopwd123http://127.0.0.1:8888/form.html# 4. 原样发送路径测路径穿越必须用这个否则 curl 会先规范化curl--path-as-ishttp://127.0.0.1:8888/../../etc/passwd# 5. 看某个资源返回什么类型curl-s-o/dev/null-w%{http_code} %{content_type}\nhttp://127.0.0.1:8888/style.css实验题把返回的状态码改成 404 / 403 / 500看浏览器显示什么观察服务器打印的请求里为什么有GET /favicon.ico故意把Content-Type写错比如 html 返回text/plain看浏览器是不是把源码显示出来了十、一页纸复习清单HTTP 报文四部分首行、报头、空行、正文每行\r\n结尾请求行 方法 url 版本状态行 版本 状态码 描述空行是报头结束的唯一标志解析全靠它GET 参数在 URLPOST 参数在正文状态码首位分类2 成功、3 重定向、4 客户端错、5 服务器错403 不给看404 没有3xx 必须配LocationContent-Type决定浏览器渲染还是下载Content-Length是长连接的前提HTTP/1.1 默认长连接、无状态无状态靠 Cookie/Session 补urlencode特殊字符转%XX服务端要先解码再校验笔记知识点代码位置请求反序列化按空行切Http.hpp→HttpRequest::Deserialize()url 拆路径 查询串Http.hpp→HttpRequest::Parse()响应序列化Http.hpp→HttpResponse::Serialize()后缀 → Content-TypeHttp.hpp→Util::SuffixToType()字节流攒成报文解决粘包/半包HttpBuffer.hpp→Recv()/Data()业务处理与 404/403HttpServer.hpp→HttpHandler()端口、回调注册TcpServer.cc