飞翔飞翔
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL教程
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON教程
  • 工具

    • Markdown指南
  • Git

    • GitFlow
  • Quartz

    • Quartz教程
  • Java

    • Java设计模式
  • 缓存

    • Redis教程
联系
阿里云
  • 学习路径
  • HTTP 基础

    • 认识HTTP协议与应用层定位
    • HTTP消息格式与报文结构
    • HTTP请求方法与幂等性
    • HTTP状态码详解
  • 连接与缓存

    • HTTP持久连接与版本演进
    • HTTP缓存机制
  • 状态与协商

    • Cookie与Session状态管理
    • HTTP重定向与内容协商
    • HTTP条件请求与范围请求
  • 安全与加密

    • HTTP认证机制
    • HTTPS与TLS握手
  • 协议演进

    • HTTP2核心特性
    • HTTP3与QUIC
  • 架构与实战

    • HTTP代理服务器与Web缓存
    • HTTP常见攻击与防御
    • HTTP实践工具与抓包分析

答案:HTTP条件请求与范围请求

为什么 ETag 比 Last-Modified 更可靠?如果服务器在 1 秒内多次修改文件,Last-Modified 会出什么问题?

答案:

ETag 本质上是为资源内容生成的"指纹"(通常是哈希值),而 Last-Modified 只是文件修改的时间戳。打个比方:翼王让图妹管理飞翔公司的静态资源服务器,如果一份 api-doc-v2.pdf 文件在 1 秒内被星宇连续修改了 3 次(比如先改标题、再改内容、最后修正错别字),Last-Modified 只能精确到秒级,3 次修改的时间戳完全相同,服务器和客户端都无法区分这 3 个不同版本。

更隐蔽的问题是:某些文件修改后内容可能完全一致(比如只是 touch 了一下文件,或者重新打包但哈希没变),Last-Modified 会变化,导致缓存失效;而 ETag 基于内容计算,内容没变 ETag 就不变,缓存可以继续使用。

在飞翔公司的 CDN 配置中,凌叔就遇到过这个问题——feixiang-app.js 的 Last-Modified 每次构建都会更新,但实际代码没变化,导致全国节点频繁回源。后来改用 ETag(基于文件 MD5),缓存命中率提升了 40%。

# 模拟 ETag 生成(Linux 环境)
etag=$(md5sum api-doc.pdf | awk '{print $1}')
echo "ETag: \"$etag\""
# 输出类似:ETag: "a3f5c8d2e1b..."

假设图妹和星宇同时编辑同一个文档,如何用 If-Match 实现"乐观锁"防止覆盖?画出请求时序图。

答案:

飞翔公司的技术文档库使用乐观锁防止多人协作时互相覆盖。核心思路是:每个文档版本对应一个唯一的 ETag,客户端更新时必须携带 If-Match: <etag>,服务器只有匹配成功才允许写入。

请求时序图:

图妹                    服务器                    星宇
  |                        |                        |
  |--- GET /doc/design.md ->|                        |
  |    (获取版本,ETag: "v1")|                        |
  |<-- 200 + ETag: "v1" ---|                        |
  |                        |                        |
  |                        |<-- GET /doc/design.md -|
  |                        |    (获取版本,ETag: "v1")|
  |                        |-- 200 + ETag: "v1" --->|
  |                        |                        |
  |                        |                        | 星宇先完成编辑
  |                        |<-- PUT + If-Match: "v1" |
  |                        |    (内容:星宇的修改)    |
  |                        |-- 200 + ETag: "v2" ---->|
  |                        |    (更新成功,版本变为v2)|
  |                        |                        |
  | 图妹后完成编辑          |                        |
  |--- PUT + If-Match: "v1" ->|                    |
  |    (内容:图妹的修改)    |                        |
  |    但此时服务器版本已是v2 |                        |
  |<-- 412 Precondition Failed|                      |
  |                        |                        |
  | 图妹收到412,重新GET获取v2|                       |
  | 合并自己的修改后再提交    |                        |

代码示例(Node.js 模拟):

// 服务器端处理逻辑
app.put('/doc/:name', (req, res) => {
    const clientETag = req.headers['if-match'];
    const currentETag = getDocETag(req.params.name); // 当前版本
    
    if (clientETag !== currentETag) {
        // 版本冲突!返回 412
        return res.status(412).json({
            error: "文档已被他人修改,请重新获取最新版本",
            currentETag: currentETag
        });
    }
    
    // 版本匹配,允许更新
    saveDoc(req.params.name, req.body);
    const newETag = generateNewETag();
    res.setHeader('ETag', newETag);
    res.status(200).send("更新成功");
});

这种机制在飞翔公司的 Wiki 系统中每天都在运行,雁姐(产品经理)和波比(设计师)同时编辑需求文档时,系统会友好提示"页面已过期",而不是默默覆盖。


断点续传时,如果服务器不支持 Range 请求,会返回什么状态码?客户端应该如何降级处理?

答案:

如果服务器不支持 Range 请求,它会直接忽略 Range 请求头,返回标准的 200 OK 和完整的响应体。注意不是返回错误状态码——因为 HTTP 规范允许服务器忽略它不认识的请求头,这是"优雅降级"的设计哲学。

在飞翔公司的文件下载中心,风速(运维工程师)遇到过这种情况:某个旧版 Nginx 配置遗漏了 add_header Accept-Ranges bytes,客户端请求断点续传时,服务器直接返回 200 和整个文件。

客户端降级策略:

  1. 检测响应状态码:收到 200 而非 206,说明服务器不支持 Range
  2. 检测响应头:看是否有 Accept-Ranges: bytes,如果没有则标记该资源不支持断点续传
  3. 重新下载:丢弃已下载的部分(或根据 Content-Length 判断是否兼容),从头开始全量下载
# 客户端用 curl 测试服务器是否支持 Range
curl -I -H "Range: bytes=0-99" http://cdn.feixiang.net/video/intro.mp4

# 支持 Range 的响应:
# HTTP/1.1 206 Partial Content
# Accept-Ranges: bytes
# Content-Range: bytes 0-99/1024000

# 不支持 Range 的响应:
# HTTP/1.1 200 OK
# (没有 Accept-Ranges 头,返回完整内容)
# Python 客户端降级逻辑示例
import requests

url = "http://cdn.feixiang.net/large-file.zip"
headers = {"Range": "bytes=102400-204800"}
resp = requests.get(url, headers=headers, stream=True)

if resp.status_code == 206:
    # 断点续传成功,追加写入文件
    with open("file.zip", "ab") as f:
        f.write(resp.content)
elif resp.status_code == 200:
    # 服务器不支持 Range,降级为全量下载
    print("警告:服务器不支持断点续传,重新全量下载...")
    with open("file.zip", "wb") as f:
        for chunk in resp.iter_content(chunk_size=8192):
            f.write(chunk)

靓晴(前端负责人)在飞翔网盘项目中就实现了这套降级逻辑:先尝试 Range 请求,如果收到 200,就自动切换为普通下载,同时给用户一个提示"该资源不支持断点续传"。