URL 重写 #

概述 #

URL 重写是 Web 服务器的重要功能,用于修改请求的 URL 路径,实现友好的 URL 结构、路由分发等功能。

text
┌─────────────────────────────────────────────────────────────┐
│                    URL 重写 vs 重定向                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  重写(Rewrite)                                             │
│  ─────────────                                              │
│  客户端: /old-path                                          │
│  服务器: 内部转换为 /new-path                                │
│  客户端: 看到 /old-path(URL 不变)                          │
│                                                             │
│  重定向(Redirect)                                          │
│  ─────────────                                              │
│  客户端: /old-path                                          │
│  服务器: 返回 301/302 + Location: /new-path                 │
│  客户端: URL 变为 /new-path                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

rewrite 指令 #

基本重写 #

caddyfile
example.com {
    # 将 /old 重写为 /new
    rewrite /old /new
    
    respond "Content at /new"
}

正则重写 #

caddyfile
example.com {
    # 使用正则表达式重写
    rewrite /api/v1/* /api/{path.2}
    
    reverse_proxy localhost:3000
}

条件重写 #

caddyfile
example.com {
    # 只重写特定路径
    @api path /api/old/*
    rewrite @api /api/new/{path.3}
    
    reverse_proxy localhost:3000
}

路径变量 #

caddyfile
example.com {
    # {path} - 完整路径
    # {path.0} - 整个路径
    # {path.1} - 第一段
    # {path.2} - 第二段
    # {path.3} - 第三段
    
    rewrite /users/*/posts/* /api/users/{path.2}/posts/{path.4}
    
    reverse_proxy localhost:3000
}

查询参数重写 #

caddyfile
example.com {
    # 保留查询参数
    rewrite /search /api/search?{query}
    
    reverse_proxy localhost:3000
}

uri 指令 #

去除路径前缀 #

caddyfile
example.com {
    # /api/users -> /users
    handle /api/* {
        uri strip_prefix /api
        reverse_proxy localhost:3000
    }
}

添加路径前缀 #

caddyfile
example.com {
    # /users -> /api/v1/users
    uri prepend /api/v1
    reverse_proxy localhost:3000
}

去除路径后缀 #

caddyfile
example.com {
    # /page.html -> /page
    uri strip_suffix .html
    file_server
}

替换路径 #

caddyfile
example.com {
    # 替换路径中的部分
    uri replace /old/ /new/
    reverse_proxy localhost:3000
}

正则替换 #

caddyfile
example.com {
    # 正则表达式替换
    uri path_regexp ^/old/(.*)$ /new/$1
    reverse_proxy localhost:3000
}

redir 指令 #

基本重定向 #

caddyfile
example.com {
    # 临时重定向(302)
    redir /old /new
    
    # 永久重定向(301)
    redir /old /new permanent
    
    # 临时重定向(302)
    redir /old /new temporary
}

HTTP 状态码 #

caddyfile
example.com {
    # 301 - 永久重定向
    redir /old /new 301
    
    # 302 - 临时重定向
    redir /old /new 302
    
    # 303 - See Other
    redir /old /new 303
    
    # 307 - 临时重定向(保留方法)
    redir /old /new 307
    
    # 308 - 永久重定向(保留方法)
    redir /old /new 308
}

条件重定向 #

caddyfile
example.com {
    # 只重定向特定路径
    @old path /old-page
    redir @old /new-page permanent
    
    respond "Content"
}

正则重定向 #

caddyfile
example.com {
    # 正则表达式重定向
    redir /blog/* /posts/{path.2} permanent
    
    respond "Content"
}

域名重定向 #

caddyfile
# www 到非 www
www.example.com {
    redir https://example.com{uri} permanent
}

# 非 www 到 www
example.com {
    redir https://www.example.com{uri} permanent
}

HTTP 到 HTTPS #

caddyfile
# Caddy 默认自动处理
# 也可以手动配置
http://example.com {
    redir https://example.com{uri} permanent
}

域名迁移 #

caddyfile
old-domain.com {
    redir https://new-domain.com{uri} permanent
}

*.old-domain.com {
    redir https://{labels.0}.new-domain.com{uri} permanent
}

handle_path 指令 #

handle_path 自动剥离匹配的路径前缀:

caddyfile
example.com {
    # /api/users -> /users(发送到后端)
    handle_path /api/* {
        reverse_proxy localhost:3000
    }
    
    # /static/css/style.css -> /css/style.css
    handle_path /static/* {
        root * /var/www/static
        file_server
    }
}

高级重写示例 #

SPA 路由 #

caddyfile
example.com {
    root * /var/www/app
    file_server
    
    # 所有非静态文件请求返回 index.html
    @notStatic {
        not path *.js *.css *.png *.jpg *.svg *.woff *.woff2
    }
    rewrite @notStatic /index.html
}

API 版本路由 #

caddyfile
api.example.com {
    # /v1/users -> /users(后端处理版本)
    handle_path /v1/* {
        reverse_proxy localhost:3001 {
            header_up X-API-Version "1"
        }
    }
    
    # /v2/users -> /users
    handle_path /v2/* {
        reverse_proxy localhost:3002 {
            header_up X-API-Version "2"
        }
    }
}

多语言路由 #

caddyfile
example.com {
    # /en/page -> /page?lang=en
    handle /en/* {
        uri strip_prefix /en
        rewrite * {uri}?lang=en
        reverse_proxy localhost:3000
    }
    
    # /zh/page -> /page?lang=zh
    handle /zh/* {
        uri strip_prefix /zh
        rewrite * {uri}?lang=zh
        reverse_proxy localhost:3000
    }
}

美化 URL #

caddyfile
example.com {
    # /products/123 -> /products?id=123
    rewrite /products/* /products?id={path.2}
    
    # /category/electronics -> /category?name=electronics
    rewrite /category/* /category?name={path.2}
    
    reverse_proxy localhost:3000
}

移除文件扩展名 #

caddyfile
example.com {
    # /page -> /page.html
    @cleanPath {
        path /*
        not path *.html *.css *.js
        file {
            try_files {path}.html
        }
    }
    rewrite @cleanPath {path}.html
    
    file_server
}

WordPress 风格 URL #

caddyfile
example.com {
    root * /var/www/wordpress
    php_fastcgi localhost:9000
    file_server
    
    # WordPress 固定链接
    @wp {
        not {
            path /wp-admin/*
            path /wp-includes/*
            path /wp-content/*
            path *.php
            path *.css
            path *.js
            path *.png
            path *.jpg
        }
    }
    rewrite @wp /index.php?{query}
}

重写匹配器 #

基于路径匹配 #

caddyfile
example.com {
    @api path /api/* /v1/* /v2/*
    rewrite @api /backend{uri}
    
    reverse_proxy localhost:3000
}

基于方法匹配 #

caddyfile
example.com {
    @post method POST
    rewrite @post /api/create
    
    @get method GET
    rewrite @get /api/read
    
    reverse_proxy localhost:3000
}

基于头部匹配 #

caddyfile
example.com {
    @mobile header User-Agent *Mobile*
    rewrite @mobile /mobile{uri}
    
    respond "Content"
}

组合匹配 #

caddyfile
example.com {
    @apiPost {
        path /api/*
        method POST
    }
    rewrite @apiPost /api/v2{uri}
    
    reverse_proxy localhost:3000
}

完整示例 #

电商网站路由 #

caddyfile
shop.example.com {
    root * /var/www/shop
    file_server
    
    # 产品页面
    rewrite /product/* /products/{path.2}
    
    # 分类页面
    rewrite /category/* /categories/{path.2}
    
    # 搜索
    rewrite /search/* /search?q={query}
    
    # API 代理
    handle /api/* {
        reverse_proxy localhost:3000
    }
    
    # 静态资源
    handle /static/* {
        uri strip_prefix /static
        file_server
    }
    
    # SPA 回退
    @notStatic not path /api/* /static/*
    rewrite @notStatic /index.html
}

博客系统路由 #

caddyfile
blog.example.com {
    root * /var/www/blog
    file_server
    
    # 文章 URL: /post/slug -> /posts?slug=slug
    rewrite /post/* /posts?slug={path.2}
    
    # 分类 URL: /category/name -> /categories?name=name
    rewrite /category/* /categories?name={path.2}
    
    # 标签 URL: /tag/name -> /tags?name=name
    rewrite /tag/* /tags?name={path.2}
    
    # 归档 URL: /archive/2024/01 -> /archive?year=2024&month=01
    rewrite /archive/*/* /archive?year={path.2}&month={path.3}
    
    # API 代理
    handle /api/* {
        reverse_proxy localhost:3000
    }
}

微服务网关路由 #

caddyfile
api.example.com {
    # 用户服务
    handle_path /users/* {
        reverse_proxy user-service:3000
    }
    
    # 订单服务
    handle_path /orders/* {
        reverse_proxy order-service:3000
    }
    
    # 产品服务
    handle_path /products/* {
        reverse_proxy product-service:3000
    }
    
    # 支付服务
    handle_path /payments/* {
        reverse_proxy payment-service:3000
    }
    
    # 默认路由
    handle {
        respond "Not Found" 404
    }
}

重写最佳实践 #

1. 使用 handle_path 简化配置 #

caddyfile
# 推荐
handle_path /api/* {
    reverse_proxy localhost:3000
}

# 不推荐
handle /api/* {
    uri strip_prefix /api
    reverse_proxy localhost:3000
}

2. 合理使用重定向状态码 #

caddyfile
# 永久变更
redir /old /new permanent  # 301

# 临时变更
redir /maintenance /maintenance-page temporary  # 302

3. 保留查询参数 #

caddyfile
# 保留查询参数
rewrite /old /new?{query}

# 或使用变量
rewrite /old /new?{query}&extra=value

4. 避免重定向循环 #

caddyfile
# 确保重定向不会循环
@notNew not path /new/*
redir @notNew /new{uri}

下一步 #

现在你已经掌握了 URL 重写配置,接下来学习 API 配置 了解如何使用 Caddy 的管理 API!

最后更新:2026-03-28