GET 与 HEAD 请求对齐到同一响应元数据,只有 GET 携带正文。

HEAD 应描述与 GET 相同的表示

HEAD 是 GET 的元数据视图;状态与表示头应讲同一个故事。

六月 22, 2026 · 1 分钟 · XB Field Notes
一枚被主机、路径、传输与同站边界约束的浏览器 Cookie。

Cookie 作用域应比应用边界更窄

发送给过多主机或路径的 Cookie,会放大这些边界上每个错误的冲击。

六月 15, 2026 · 1 分钟 · XB Field Notes
以观察、判断和行动三个面板呈现 HTTP 响应结果的审阅。

HTTP 状态码描述这次响应的契约

状态码需要放在请求方法的上下文里理解,它告诉客户端怎样解释本次 HTTP 响应。首位数字只能缩小类别,不能说明完整业务结果。例如,任务已经接受,仍可能没有完成;三开头的响应,也未必表示页面搬家。服务器应根据实际完成的事情选择具体状态,并提供相应响应头和内容,避免让一个看起来合理的数字承担它从未承诺的含义。 成功不只有一种含义 二百表示请求按照相应方法的语义成功。成功读取任务状态资源,可以如实返回“任务仍在等待”的表示;这不代表任务本身已经结束。二零一表示创建了一个或多个资源,提供 Location 时,它用于识别主要的新建资源。接口需要说明客户端接下来应操作哪个资源,而不是把所有二开头的响应都解释成“用户最初想要的整个业务已经完成”。 二零二表示请求已经接受处理,但尚未完成,也不会对最终一定执行成功作出保证。应提供观察进度的途径,并定义最终成功与失败,否则客户端很难区分任务缓慢与任务丢失。二零四表示请求成功完成且没有响应内容;不能因为框架平常总是序列化 JSON,就给这种响应也附加正文。具体状态对应的消息约束需要贯穿最终输出。 三开头也可能是在验证已有表示 三零一与三零八表达永久重定向,三零二与三零七表达临时重定向,并具有不同的方法保留规则。三零三可以让调用方在处理步骤之后转而读取另一个资源。请求携带正文或具有副作用时,更要明确选择:可能改变方法的跳转,与必须保留方法的跳转,不能随意互换。客户端是否跟随,也应依据它实际要完成的动作判断。 三零四是另一种情况:它回应条件 GET 或 HEAD,表示无需重新传输选中的表示。接收方按缓存规则使用合适的已存内容,并更新相关元数据。三零四没有响应内容,也不是要求客户端沿 Location 导航。此时显示为空,是协议预期,并不能证明服务器把文件弄丢了。如果调用方没有相应缓存表示,就不能把这个空正文直接当作完整资源交给上层。 坏掉的样子 把所有四开头都归为“不能重试”,会误处理限流等情况;把所有五开头都归为“立即重试”,又可能重复非幂等操作或加剧过载。五零三覆盖暂时的服务不可用,包括过载或维护,并不只用于计划停机。重试决策还要结合方法、服务器提示和客户端预算。状态类别提供线索,不是能够无条件套用的完整重试算法。 四零四没有保证缺失只是暂时的:它可以表示找不到当前表示,也可以表示服务器不愿披露该表示。四一零才传达可能永久不可用的含义。同样,四零三并不普遍证明调用者已通过身份验证。业务代码不能在这些数字上附加比协议更强的假设;响应正文也应符合接口已声明的契约,否则下游即使正确识别了状态,仍可能误解实际处理结果。 快速验证 保存为 status-contract.py,运行 python3 status-contract.py。实验通过真实回环 HTTP 请求操作一个很小的内存任务状态和固定资源。这里使用的 Python HTTP 客户端不会自动跟随 Location,也没有实现缓存,所以脚本中每一步后续动作都明确写出。它不需要真实队列或数据库,避免把未验证的后台处理能力混进状态语义实验。 from http.client import HTTPConnection from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from threading import Thread import json job = {'state': 'absent'} class Handler(BaseHTTPRequestHandler): def reply(self, status, data=b'', headers=None): self.send_response(status) if status not in (204, 304): self.send_header('Content-Length', str(len(data))) for key, value in (headers or {}).items(): self.send_header(key, value) self.end_headers() if status not in (204, 304): self.wfile.write(data) def do_POST(self): if self.path != '/jobs': self.reply(404) return job['state'] = 'pending' self.reply(202, b'{"state":"pending"}', {'Content-Type': 'application/json', 'Location': '/jobs/42'}) def do_GET(self): if self.path == '/jobs/42': if job['state'] == 'complete': self.reply(303, headers={'Location': '/results/42'}) elif job['state'] == 'pending': self.reply(200, b'{"state":"pending"}', {'Content-Type': 'application/json'}) else: self.reply(404) elif self.path == '/results/42' and job['state'] == 'complete': self.reply(200, b'{"value":7}', {'Content-Type': 'application/json'}) elif self.path == '/asset': if self.headers.get('If-None-Match') == '"v1"': self.reply(304, headers={'ETag': '"v1"'}) else: self.reply(200, b'artifact', {'ETag': '"v1"'}) else: self.reply(404) def log_message(self, *args): pass server = ThreadingHTTPServer(('127.0.0.1', 0), Handler) thread = Thread(target=server.serve_forever) thread.start() try: responses = [] def request(method, path, headers=None): connection = HTTPConnection('127.0.0.1', server.server_port, timeout=5) try: connection.request(method, path, headers=headers or {}) response = connection.getresponse() value = {'status': response.status, 'body': response.read().decode(), 'location': response.getheader('Location'), 'etag': response.getheader('ETag')} responses.append(value) return value finally: connection.close() accepted = request('POST', '/jobs') assert accepted['status'] == 202 and job['state'] == 'pending' assert request('GET', '/results/42')['status'] == 404 assert json.loads(request('GET', accepted['location'])['body']) == {'state': 'pending'} job['state'] = 'complete' # Explicit fixture transition, not a background worker. redirect = request('GET', '/jobs/42') assert redirect['status'] == 303 and redirect['location'] == '/results/42' assert json.loads(request('GET', redirect['location'])['body']) == {'value': 7} cached = request('GET', '/asset') validation = request('GET', '/asset', {'If-None-Match': cached['etag']}) assert validation['status'] == 304 and validation['body'] == '' and validation['location'] is None assert cached['body'] == 'artifact' print(json.dumps({'responses': responses, 'reuse_cached_body': cached['body']})) finally: server.shutdown() thread.join() server.server_close() POST 首先返回二零二,任务此时仍为等待状态;结果地址仍返回四零四,读取状态资源则返回二百和 pending 表示。之后脚本显式把内存状态改成完成,这不是已经测试过的后台工作进程。状态端点随即返回三零三,客户端再读取 Location 指向的位置,取得数值为七的结果。每个数字描述的都是当前响应,并没有提前承诺后面的步骤一定成功。 ...

六月 14, 2026 · 2 分钟 · XB Field Notes
一个文件表示在到达浏览器之前先经过媒体类型标签。

Content-Type 应描述你实际发送的字节

文件名只是一个提示,响应头才是面向浏览器的表示契约。

六月 11, 2026 · 1 分钟 · XB Field Notes