)
第 3 章HTTP 协议详解 — 示例与习题摘要本章通过 4 个可运行的 Python 示例深入讲解 HTTP 协议的核心机制手动构造请求文本以理解报文结构、状态码分类与含义、使用requests库发送各类请求以及 Cookie 与 Session 的交互流程。文末附 5 道配套习题选择、填空、判断、实践及详细答案帮助读者巩固 HTTP 知识并提升实战能力。示例示例 1手动构造 HTTP 请求文本# 用 Python 字符串手动构造一个完整的 HTTP 请求# 帮助理解 HTTP 协议的结构defbuild_get_request(host,path,paramsNone,headersNone):构造 GET 请求文本# 拼接查询参数ifparams:query.join(f{k}{v}fork,vinparams.items())pathf{path}?{query}lines[fGET{path}HTTP/1.1,fHost:{host}]ifheaders:fork,vinheaders.items():lines.append(f{k}:{v})lines.append()# 空行分隔头和体lines.append()# GET 通常没有请求体return\r\n.join(lines)defbuild_post_request(host,path,bodyNone,content_typeapplication/json,headersNone):构造 POST 请求文本importjsonifisinstance(body,dict)andcontent_typeapplication/json:body_strjson.dumps(body)elifisinstance(body,dict):body_str.join(f{k}{v}fork,vinbody.items())else:body_strstr(body)ifbodyelselines[fPOST{path}HTTP/1.1,fHost:{host},fContent-Type:{content_type},fContent-Length:{len(body_str)},]ifheaders:fork,vinheaders.items():lines.append(f{k}:{v})lines.append()# 空行lines.append(body_str)return\r\n.join(lines)# 构造 GET 请求print( GET 请求 )print(build_get_request(localhost:5000,/api/users,params{page:1,per_page:10},headers{Accept:application/json,Authorization:Bearer abc123}))print(\n POST 请求 )print(build_post_request(localhost:5000,/api/users,body{username:小明,email:xmexample.com},content_typeapplication/json))输出 GET 请求 GET /api/users?page1per_page10 HTTP/1.1 Host: localhost:5000 Accept: application/json Authorization: Bearer abc123 POST 请求 POST /api/users HTTP/1.1 Host: localhost:5000 Content-Type: application/json Content-Length: 48 {username: \u5c0f\u660e, email: xmexample.com}示例 2状态码判断工具definterpret_status(code):解释 HTTP 状态码ranges{range(100,200):信息响应,range(200,300):成功响应,range(300,400):重定向,range(400,500):客户端错误,range(500,600):服务端错误,}descriptions{200:OK - 请求成功,201:Created - 资源创建成功,204:No Content - 无内容删除成功,301:Moved Permanently - 永久重定向,302:Found - 临时重定向,304:Not Modified - 资源未修改,400:Bad Request - 请求参数有误,401:Unauthorized - 未认证,403:Forbidden - 无权限,404:Not Found - 资源不存在,405:Method Not Allowed - 方法不允许,409:Conflict - 冲突,422:Unprocessable Entity - 验证失败,500:Internal Server Error - 服务器内部错误,502:Bad Gateway - 网关错误,503:Service Unavailable - 服务不可用,504:Gateway Timeout - 网关超时,}categorynext((vforr,vinranges.items()ifcodeinr),未知)descdescriptions.get(code,未知状态码)# 判断是客户端还是服务端的问题if400code500:blame客户端的问题elif500code600:blame服务器的问题else:blamereturnf{code}[{category}]{desc}{blame}# 测试各种状态码test_codes[200,201,301,400,401,403,404,409,422,500,503]forcodeintest_codes:print(interpret_status(code))输出200 [成功响应] OK - 请求成功 201 [成功响应] Created - 资源创建成功 301 [重定向] Moved Permanently - 永久重定向 400 [客户端错误] Bad Request - 请求参数有误 客户端的问题 401 [客户端错误] Unauthorized - 未认证 客户端的问题 403 [客户端错误] Forbidden - 无权限 客户端的问题 404 [客户端错误] Not Found - 资源不存在 客户端的问题 409 [客户端错误] Conflict - 冲突 客户端的问题 422 [客户端错误] Unprocessable Entity - 验证失败 客户端的问题 500 [服务端错误] Internal Server Error - 服务器内部错误 服务器的问题 503 [服务端错误] Service Unavailable - 服务不可用 服务器的问题示例 3用 requests 发送各种请求importrequests BASE_URLhttps://httpbin.org# 1. GET 请求带查询参数defdemo_get():print( GET 请求 )resprequests.get(f{BASE_URL}/get,params{name:小明,age:18})print(f状态码:{resp.status_code})print(f响应类型:{resp.headers[Content-Type]})print(f服务器收到的参数:{resp.json()[args]})print()# 2. POST 请求发送 JSONdefdemo_post_json():print( POST JSON 请求 )resprequests.post(f{BASE_URL}/post,json{title:Flask 入门,content:Flask 是一个轻量级框架})print(f状态码:{resp.status_code})print(f服务器收到的 JSON:{resp.json()[json]})print()# 3. POST 请求发送表单defdemo_post_form():print( POST 表单请求 )resprequests.post(f{BASE_URL}/post,data{username:admin,password:123456})print(f状态码:{resp.status_code})print(fContent-Type:{resp.json()[headers][Content-Type]})print(f服务器收到的表单:{resp.json()[form]})print()# 4. 自定义请求头defdemo_custom_headers():print( 自定义请求头 )resprequests.get(f{BASE_URL}/headers,headers{User-Agent:MyPythonBot/1.0,X-Custom-Header:Hello-World,Authorization:Bearer my-token-123})print(f状态码:{resp.status_code})sent_headersresp.json()[headers]forkeyin[User-Agent,X-Custom-Header,Authorization]:print(f 发送的{key}:{sent_headers.get(key,N/A)})print()# 5. 处理错误响应defdemo_error_handling():print( 错误处理 )test_status[200,400,404,500]forstatusintest_status:resprequests.get(f{BASE_URL}/status/{status})ok✓ 成功ifresp.okelse✗ 失败print(f 请求 /status/{status}- 状态码{resp.status_code}{ok})print()# 运行所有示例demo_get()demo_post_json()demo_post_form()demo_custom_headers()demo_error_handling()示例 4模拟 Cookie 和 Session 流程# 用代码模拟理解 Cookie 和 Session 的交互流程classMockSession:模拟服务器端 Session 存储def__init__(self):self._data{}defcreate(self,user_id,username):importuuid session_idstr(uuid.uuid4())[:8]self._data[session_id]{user_id:user_id,username:username}returnsession_iddefget(self,session_id):returnself._data.get(session_id)defdelete(self,session_id):ifsession_idinself._data:delself._data[session_id]returnTruereturnFalseclassMockBrowser:模拟浏览器存储 Cookiedef__init__(self):self.cookies{}defset_cookie(self,key,value):self.cookies[key]valuedefget_cookie(self,key):returnself.cookies.get(key)defclear_cookies(self):self.cookies.clear()# 模拟登录流程server_sessionMockSession()browserMockBrowser()print( 1. 用户登录 )# 服务器验证成功后创建 Sessionsession_idserver_session.create(user_id1,username小明)# 服务器通过 Set-Cookie 返回 session_idbrowser.set_cookie(session_id,session_id)print(f 服务器创建 Session:{session_id})print(f 浏览器保存 Cookie:{browser.cookies})print(\n 2. 后续请求 )# 浏览器每次请求自动带上 Cookiesidbrowser.get_cookie(session_id)user_dataserver_session.get(sid)ifuser_data:print(f 服务器根据 session_id{sid}找到用户:{user_data})else:print( 未找到用户信息)print(\n 3. 用户退出 )sidbrowser.get_cookie(session_id)server_session.delete(sid)browser.clear_cookies()print(f 服务器删除 Session)print(f 浏览器清除 Cookie:{browser.cookies})print(\n 4. 退出后请求 )sidbrowser.get_cookie(session_id)user_dataserver_session.get(sid)print(f Session:{user_data})print(f 结论: 用户已退出需要重新登录)输出 1. 用户登录 服务器创建 Session: a1b2c3d4 浏览器保存 Cookie: {session_id: a1b2c3d4} 2. 后续请求 服务器根据 session_ida1b2c3d4 找到用户: {user_id: 1, username: 小明} 3. 用户退出 服务器删除 Session 浏览器清除 Cookie: {} 4. 退出后请求 Session: None 结论: 用户已退出需要重新登录习题习题 1选择题HTTP 状态码405表示什么含义A. 资源不存在B. 请求方法不允许C. 未认证D. 服务器内部错误HTTP 常用方法对比在实际开发中不同的 HTTP 方法对应不同的语义和用途。下表汇总了最常用的几种方法帮助你快速区分它们的作用、是否携带请求体、幂等性以及典型的成功状态码方法作用是否携带请求体幂等性典型状态码GET读取资源否幂等200 OKPOST创建资源是非幂等201 CreatedPUT整体更新资源是幂等200 OKPATCH部分更新资源是非幂等200 OKDELETE删除资源否幂等204 No Content说明幂等Idempotent指无论执行一次还是多次对服务器资源产生的最终效果都相同。GET、PUT、DELETE 都是幂等操作——重复发送不会产生额外副作用而 POST 每次都会创建新资源PATCH 每次可能基于当前状态做增量修改因此二者都是非幂等的。理解这些差异有助于在接口设计中选用合适的方法。习题 2选择题以下哪种 HTTP 方法通常用于创建新资源A. GETB. POSTC. PUTD. DELETE习题 3填空题HTTP 请求由请求行、请求头、空行和请求体四部分组成。其中请求行包含三个信息、和协议版本。习题 4判断题GET 请求的参数放在请求体中POST 请求的参数放在 URL 中。 习题 5实践题使用requests库编写一个脚本完成以下任务向https://httpbin.org/get发送 GET 请求带参数name你的名字coursepython向https://httpbin.org/post发送 POST 请求发送 JSON 数据{title: test, content: hello}向https://httpbin.org/status/404发送请求判断状态码并打印是客户端错误还是服务端错误打印每个请求的响应时间答案习题 1 答案B. 请求方法不允许解析405 Method Not Allowed 表示服务器支持该 URL但不支持该 HTTP 方法。例如对一个只接受 GET 的路由发送 POST 请求会返回 405。习题 2 答案B. POST解析POST 用于创建新资源返回 201 Created。PUT 用于更新已有资源。GET 用于读取DELETE 用于删除。习题 3 答案请求方法请求路径URL解析请求行格式为方法 路径 协议版本如GET /api/users HTTP/1.1。习题 4 答案错误×解析正好相反。GET 请求的参数放在 URL 查询字符串中如?name小明POST 请求的参数放在请求体中。习题 5 答案importrequests BASEhttps://httpbin.org# 1. GET 请求print( 1. GET 请求 )resprequests.get(f{BASE}/get,params{name:小明,course:python})print(f状态码:{resp.status_code})print(f响应时间:{resp.elapsed.total_seconds():.3f}秒)print(f服务器收到的参数:{resp.json()[args]})print()# 2. POST 请求print( 2. POST 请求 )resprequests.post(f{BASE}/post,json{title:test,content:hello})print(f状态码:{resp.status_code})print(f响应时间:{resp.elapsed.total_seconds():.3f}秒)print(f服务器收到的 JSON:{resp.json()[json]})print()# 3. 404 状态码print( 3. 404 状态码 )resprequests.get(f{BASE}/status/404)print(f状态码:{resp.status_code})print(f响应时间:{resp.elapsed.total_seconds():.3f}秒)if400resp.status_code500:print(这是客户端错误4xx)elif500resp.status_code600:print(这是服务端错误5xx)print()# 4. 对比不同状态码的响应时间print( 4. 状态码对比 )forcodein[200,400,404,500]:resprequests.get(f{BASE}/status/{code})category成功ifresp.okelse\客户端错误if400code500else服务端错误print(f /status/{code}-{resp.status_code}[{category}] ({resp.elapsed.total_seconds():.3f}秒))