lolly/docs/06-nginx-rewrite.md
xfy 9cae5ad8cf chore(init): initialize project with nginx documentation
- Add Go module initialization (go 1.26)
- Add comprehensive NGINX documentation covering:
  - Overview, installation, HTTP core module
  - Proxy/load balancing, SSL/TLS, URL rewrite
  - Compression/caching, logging/monitoring
  - Security, TCP/UDP stream, mail proxy
  - Performance tuning, Git commit guide
- Add standard Go .gitignore

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-02 11:45:53 +08:00

516 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# NGINX URL 重写与请求处理指南
## 1. rewrite 模块概述
`ngx_http_rewrite_module` 模块用于:
- 使用 PCRE 正则表达式更改请求 URI
- 返回重定向
- 有条件地选择配置
### 处理顺序
1. 按顺序执行 server 级别的 rewrite 指令
2. 根据请求 URI 搜索 location
3. 按顺序执行 location 内的 rewrite 指令
4. 如果 URI 被 rewrite 更改,重复循环(不超过 10 次)
---
## 2. rewrite 指令
### 语法
`rewrite regex replacement [flag];`
```nginx
# 基础重写
rewrite ^/old/(.*)$ /new/$1 permanent;
# 重定向
rewrite ^/download/(.*)$ /files/$1 redirect;
# 内部重写
rewrite ^/images/(.*)\.jpg$ /pics/$1.png last;
```
### Flags 参数
| Flag | 说明 |
|------|------|
| `last` | 停止当前指令集处理,开始搜索新 location |
| `break` | 停止当前指令集处理,继续在当前 location 中处理 |
| `redirect` | 返回 302 临时重定向 |
| `permanent` | 返回 301 永久重定向 |
### 示例
```nginx
server {
# Server 级别 - 使用 last
rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 last;
rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra last;
location /download/ {
# Location 级别 - 使用 break
rewrite ^(/download/.*)/media/(.*)\..*$ $1/mp3/$2.mp3 break;
rewrite ^(/download/.*)/audio/(.*)\..*$ $1/mp3/$2.ra break;
}
}
```
### 参数处理
```nginx
# replacement 包含新参数时,旧参数自动追加
rewrite ^/users/(.*)$ /show?user=$1? last;
# ? 结尾表示不追加旧参数
```
---
## 3. return 指令
### 语法
```nginx
return code [text];
return code URL;
return URL;
```
### 状态码使用
| 状态码 | 类型 | 用途 |
|--------|------|------|
| 301 | 永久重定向 | URL 永久变更 |
| 302 | 临时重定向 | URL 临时变更 |
| 303 | 临时重定向 | POST 后重定向到 GET |
| 307 | 临时重定向 | 保持请求方法 |
| 308 | 永久重定向 | 保持请求方法 |
| 444 | 非标准 | 关闭连接(不发送响应) |
### 示例
```nginx
# 返回文本
location /status {
return 200 "OK";
}
# 返回 JSON
location /json {
default_type application/json;
return 200 '{"status": "ok"}';
}
# 重定向
location /old {
return 301 /new;
}
# 域名重定向
server {
listen 80;
server_name old.example.com;
return 301 http://new.example.com$request_uri;
}
# HTTP 到 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
# 拒绝请求
location /private {
return 403;
}
# 关闭连接(不发送响应)
server {
listen 80 default_server;
server_name _;
return 444;
}
```
---
## 4. if 指令
### 语法
`if (condition) { ... }`
### 条件类型
| 类型 | 示例 | 说明 |
|------|------|------|
| 变量判断 | `if ($variable)` | 空字符串或 "0" 为假 |
| 字符串相等 | `if ($a = "value")` | 等于 |
| 字符串不等 | `if ($a != "value")` | 不等于 |
| 正则匹配 | `if ($a ~ pattern)` | 区分大小写 |
| 正则匹配 | `if ($a ~* pattern)` | 不区分大小写 |
| 正则不匹配 | `if ($a !~ pattern)` | 不匹配 |
| 文件存在 | `if (-f $uri)` | 文件存在 |
| 文件不存在 | `if (!-f $uri)` | 文件不存在 |
| 目录存在 | `if (-d $uri)` | 目录存在 |
| 目录不存在 | `if (!-d $uri)` | 目录不存在 |
| 文件/目录存在 | `if (-e $uri)` | 存在 |
| 可执行 | `if (-x $uri)` | 可执行文件 |
### 示例
```nginx
# 浏览器判断
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}
# 请求方法判断
if ($request_method = POST) {
return 405;
}
# 文件不存在时
if (!-f $request_filename) {
rewrite ^(.*)$ /index.php last;
}
# 防盗链
if ($invalid_referer) {
return 403;
}
# 限制速度
if ($slow) {
limit_rate 10k;
break;
}
```
### 注意事项
- `if` 块会创建单独的配置上下文
- 避免在 if 中使用某些指令(可能导致意外行为)
- 推荐使用 `try_files``map` 替代复杂条件判断
---
## 5. break 指令
停止处理当前 rewrite 模块指令集。
```nginx
location /download/ {
if ($forbidden) {
return 403;
}
if ($slow) {
limit_rate 10k;
break; # 停止 rewrite 模块处理,继续 location 处理
}
rewrite ^(.*)$ /files/$1 break;
}
```
---
## 6. set 指令
设置变量值。
```nginx
set $variable value;
# 示例
set $mobile false;
if ($http_user_agent ~ "Mobile") {
set $mobile true;
}
location /api {
if ($mobile = true) {
proxy_pass http://mobile_backend;
}
proxy_pass http://desktop_backend;
}
```
---
## 7. map 指令(替代复杂 if
### 语法
`map source $variable { ... }`
### 示例
```nginx
http {
# 浏览器映射
map $http_user_agent $mobile {
default false;
"~*Mobile" true;
"~*Android" true;
"~*iPhone" true;
}
# 环境映射
map $host $backend {
default http://backend.prod;
dev.example.com http://backend.dev;
test.example.com http://backend.test;
}
# 日志级别映射
map $status $loggable {
~^[23] 0; # 2xx/3xx 不记录
default 1;
}
server {
location / {
if ($mobile = true) {
proxy_pass http://mobile_backend;
}
proxy_pass $backend;
}
access_log /var/log/nginx/access.log combined if=$loggable;
}
}
```
---
## 8. 常见重写场景
### 域名重定向
```nginx
# 旧域名到新域名
server {
listen 80;
server_name old.example.com;
return 301 http://new.example.com$request_uri;
}
# 多域名统一
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
# www 到非 www
server {
listen 80;
server_name www.example.com;
return 301 http://example.com$request_uri;
}
# 非 www 到 www
server {
listen 80;
server_name example.com;
return 301 http://www.example.com$request_uri;
}
```
### HTTP 到 HTTPS
```nginx
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl;
server_name example.com;
# ...
}
```
### URL 结构变更
```nginx
# 产品页面重写
rewrite ^/products/([0-9]+)$ /product?id=$1 last;
# 分类页面
rewrite ^/category/([a-z]+)$ /cat?name=$1 last;
# API 版本迁移
rewrite ^/api/v1/(.*)$ /api/v2/$1 permanent;
```
### 静态文件处理
```nginx
location / {
try_files $uri $uri/ @fallback;
}
location @fallback {
rewrite ^(.*)$ /index.php last;
}
# 图片路径重写
rewrite ^/images/(.*)\.(gif|jpg|png)$ /static/$1.$2 last;
```
### PHP 应用重写
```nginx
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass localhost:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
```
### WordPress 重写
```nginx
location / {
try_files $uri $uri/ /index.php?$args;
}
# WordPress 固定链接支持
rewrite /wp-admin$ /wp-admin/ permanent;
```
---
## 9. 防盗链配置
```nginx
location ~* \.(gif|jpg|png|swf|flv)$ {
valid_referers none blocked server_names *.example.com example.*;
if ($invalid_referer) {
return 403;
# 或返回替代图片
# return 301 http://example.com/nolink.png;
}
}
```
---
## 10. rewrite_log 指令
启用重写日志记录。
```nginx
rewrite_log on; # 将 rewrite 处理结果记录到 error_lognotice 级别)
```
---
## 11. 内部实现说明
rewrite 指令在配置阶段编译为内部指令,请求处理时由虚拟栈机器解释执行。
**配置示例编译结果**
```nginx
location /download/ {
if ($forbidden) {
return 403;
}
rewrite ^/(.*)$ /files/$1 break;
}
```
编译为内部指令:
```
variable $forbidden
check against zero
return 403
end of code
match of regular expression
copy $1
copy "/files/"
end of regular expression
end of code
```
---
## 12. 最佳实践
### 优先使用 return 而非 rewrite
```nginx
# 推荐
return 301 /new;
# 不推荐
rewrite ^(.*)$ /new permanent;
```
### 使用 try_files 替代复杂的 if 文件检查
```nginx
# 推荐
location / {
try_files $uri $uri/ @fallback;
}
# 不推荐
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php last;
}
}
```
### 使用 map 替代重复的 if 判断
```nginx
# 推荐
http {
map $http_user_agent $is_mobile {
default false;
"~*Mobile" true;
}
}
# 不推荐
location / {
if ($http_user_agent ~ Mobile) {
set $is_mobile true;
}
}
```
### Location 级别使用 break
```nginx
location /download/ {
rewrite ^(.*)$ /files/$1 break;
# 使用 break 而非 last避免 10 次循环错误
}
```
### 注意循环限制
rewrite 循环不超过 10 次,超过会返回 500 错误。
```nginx
# 错误示例(无限循环)
rewrite ^/test$ /test last;
```