nginx之rewrite模块

rewrite模块即ngxhttprewrite_module模块,主要功能是改写请求URI,是nginx默认安装的模块,用于重写url进行内部跳转和重定向等。

rewrite模块的指令有break, if, return, rewrite, set等,其中rewrite是比较关键的。

rewrite 指令

指令语法:rewrite regex replacement[flag];
  • 默认值:none
  • 应用位置:server、location、if
  • rewrite是实现URL重定向的重要指令,他根据regex(正则表达式)来匹配内容跳转到replacement,结尾是flag标记

简单的小例子:

rewrite ^/(.*) http://www.baidu.com/ permanent;     # 匹配成功后跳转到百度,执行永久301跳转

regex是PCRE 风格的,如果regex匹配URI,那么URI就会被替换成replacement,replacement 就是新的URI。如果rewrite同一个上下文中有多个这样的正则,匹配会依照rewrite指令出现的顺序先后依次进行下去,匹配到一个之后并不会终止,而是继续往下匹配,直到返回最后一个匹配上的为止。如果想要中止继续往下匹配,可以使用第三个参数flag。

regex 正则表达式:

字符

描述

\

将后面接着的字符标记为一个特殊字符或者一个原义字符或一个向后引用

^

匹配输入字符串的起始位置

$

匹配输入字符串的结束位置

*

匹配前面的字符零次或者多次

+

匹配前面字符串一次或者多次

?

匹配前面字符串的零次或者一次

.

匹配除“\n”之外的所有单个字符

(pattern)

匹配括号内的pattern

replacement

要替换的url

flag

标记符号

说明

last

本条规则匹配完成后继续向下匹配新的location URI规则

break

本条规则匹配完成后终止,不在匹配任何规则

redirect

返回302临时重定向

permanent

返回301永久重定向


  • 如果有last参数,那么停止处理任何rewrite相关的指令,立即用替换后的新URI开始下一轮的location匹配
  • redirect: replacement 如果不包含协议,仍然是一个新的的URI,那么就用新的URI匹配的location去处理请求,不会返回30x跳转。但是redirect参数可以让这种情况也返回30x(默认302)状态码,就像新的URI包含http://和https://等一样。这样的话,浏览器看到302,就会再发起一次请求,真正返回响应结果的就是这第二个请求。
  • break:停止处理任何rewrite的相关指令,就如同break 指令本身一样。last的break的相同点在于,立即停止执行所有当前上下文的rewrite模块指令;不同点在于last参数接着用新的URI马上搜寻新的location,而break不会搜寻新的location,直接用这个新的URI来处理请求,这样能避免重复rewite。因此,在server上下文中使用last,而在location上下文中使用break。
  • 和redirect参数一样,只不过直接返回301永久重定向。虽说URI有了新的,但是要拼接成完整的URL还需要当前请求的scheme,以及由servernameinredirect和portin_redirect指令决定的HOST和PORT.还有一个比较有意思的应用,就是如果replacement中包含请求参数,那么默认情况下旧URI中的请求参数也会拼接在replacement后面作为新的URI,如果不想这么做,可以在replacement的最后面加上?
rewrite ^/users/(.*)$ /show?user=$1? last;

生成的url是/show?user=xxx

如果想带上旧url上的参数,则不加?

rewrite ^/users/(.*)$ /show?user=$1 last;

生成的url为/show?user=xxx&abc=xxx

server中:

server {
    ...
    rewrite ^(/download/.*)/show/(.*)\..*$ $1/img/$2.png last;
    rewrite ^(/download/.*)/show/(.*)\..*$ $1/img/$2.png  last;
    return  403; #没有匹配上,那就返回403
    ...
}

location中:

location /download/ {
    rewrite ^(/download/.*)/show/(.*)\..*$ $1/img/$2.png break;
    rewrite ^(/download/.*)/show/(.*)\..*$ $1/mp3/$2.png  break;
    return  403;
}

总结

  • 首先在server上下文中依照顺序执行rewrite模块指令;
  • 如果server中行了rewrite重写,那么以新URI发起内部跳转,直接匹配location,不会再执行server里的rewrite指令,然后新URI直接匹配location
  • 如果匹配上某个location,那么其中的rewrite模块指令同样依照顺序执行
  • 如果再次导致URI的rewrite,那么再一次进行内部跳转去匹配location,但跳转的总次数不能超过10次
  • 除非特殊说明,大部分地方字符串的不需要引号括住,字符串和变量的拼接也不需要引号

break指令

上下文:server, location, if

停止处理任何rewrite的相关指令。如果出现在location里面,那么所有后面的rewrite模块指令都不会再执行,也不发起内部重定向,而是直接用新的URI进一步处理请求。

if指令

基本语法:if (condition) { ... }
上下文:server, location

根据条件condition的真假决定是否加载{...}中的配置,{...}中的配置可以继承外面的配置,也可以对外面已有配置指令进行覆写。

if指令的condition:

条件

判断内容

变量

如果变量值为空字符串或以 0 开始的字符串则为 false

=、!=

比较一个变量和字符串是否相等

~、~*

使用正则表达式匹配变量

-f、!-f

检查一个文件是否存在

-d、!-d

检查一个目录是否存在

-e、!-e

检查一个文件、目录、符号链接是否存在

-x、!-x

检查一个文件是否可执行

如: -f /path/to 检验文件是否存在;!-f /path/to 检验文件是否不存在

条件condition是针对变量而言的,变量既可以是系统变量,也可以是自定义的。

当condition为变量$var本身时,当且仅当变量值为空字符或者0时,条件为false,其余情况皆为 true。 可以用变量$var通过"="或者"!="与字符串相比较,即$var = xxx或者$var != xxx,也可以匹配正则表达式。

  • $var ~ Reg 表示大小写敏感匹配
  • $var ~* Reg 表示大小写不敏感匹配
  • $var !~ Reg 表示大小写敏感不匹配
  • $var !~* Reg 表示大小写不敏感不匹配

return指令

基本语法:return code [text];或者return code URL;或者return URL;
上下文:server, location, if

停止任何的进一步处理,并且将指定状态码返回给客户端。如果状态码为444(此状态码是非标准的),那么直接关闭此TCP连接。

return的参数有四种形式:

  • return code: return 503表示服务暂时不可用,return 444表示关闭tcp。
  • return code text 因为要带响应内容,因此code不能是具有跳转功能的30x
  • return code URL 此时URI可以为URI做内部跳转,也可以是具有“http://”或者“https://”等协议的绝对URL,直接返回客户端,而code是30x(301, 302, 303, 307,308)
  • return URL 此时code默认为302,而URL必须是带“http://”等协议的绝对URL

官方解释:

Stops processing and returns the specified code to a client. The non-standard code 444 closes a connection without sending a response header.

Starting from version 0.8.42, it is possible to specify either a redirect URL (for codes 301, 302, 303, 307, and 308) or the response body text (for other codes). A response body text and redirect URL can contain variables. As a special case, a redirect URL can be specified as a URI local to this server, in which case the full redirect URL is formed according to the request scheme ($scheme) and the server_name_in_redirect and port_in_redirect directives.

In addition, a URL for temporary redirect with the code 302 can be specified as the sole parameter. Such a parameter should start with the “http://”, “https://”, or “$scheme” string. A URL can contain variables.

Only the following codes could be returned before version 0.7.51: 204, 400, 402 — 406, 408, 410, 411, 413, 416, and 500 — 504.
The code 307 was not treated as a redirect until versions 1.1.16 and 1.0.13.
The code 308 was not treated as a redirect until version 1.13.0.

见:http://nginx.org/en/docs/http/ngxhttprewrite_module.html

set 指令

基本语法:set $variable value;
上下文:server, location, if

这是一个有用的指令,用来定义变量,变量的值可以包含字符串,另外的变量或者是二者结合。

set $var = $http_x_forwarded_for;

rewrite_log

基本语法:rewrite_log on | off;
上下文:http, server, location, if

如果开启 on,那么当发生rewrite时,会产生一个notice级别的日志;否则不会产生任何日志。默认情况下是不产生的,但在调试的时候可以将其置为on。

nginx内置变量

  • $args, 请求中的参数;
  • $content_length, HTTP请求信息里的"Content-Length";
  • $content_type, 请求信息里的"Content-Type";
  • $document_root, 针对当前请求的根路径设置值;
  • $document_uri, 与$uri相同;
  • $host, 请求信息中的"Host",如果请求中没有Host行,则等于设置的服务器名;
  • $limit_rate, 对连接速率的限制;
  • $request_method, 请求的方法,比如"GET"、"POST"等;
  • $remote_addr, 客户端地址;
  • $remote_port, 客户端端口号;
  • $remote_user, 客户端用户名,认证用;
  • $request_filename, 当前请求的文件路径名
  • $requestbodyfile
  • $request_uri, 请求的URI,带查询字符串;
  • $query_string, 与$args相同;
  • $scheme, 所用的协议,比如http或者是https,比如rewrite ^(.+)$ $scheme://example.com$1 redirect;
  • $server_protocol, 请求的协议版本,"HTTP/1.0"或"HTTP/1.1";
  • $server_addr, 服务器地址,如果没有用listen指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费);
  • $server_name, 请求到达的服务器名;
  • $server_port, 请求到达的服务器端口号;
  • $uri, 请求的URI,可能和最初的值有不同,比如经过重定向之类的。

这些变量可以用于上述指令中,也可用于输出日志

示例

示例1: server中:

server {
    ...
    rewrite ^(/download/.*)/show/(.*)\..*$ $1/img/$2.png last;
    rewrite ^(/download/.*)/show/(.*)\..*$ $1/img/$2.png  last;
    return  403; #没有匹配上,那就返回403
    ...
}

示例2: location中:

location /download/ {
    rewrite ^(/download/.*)/show/(.*)\..*$ $1/img/$2.png break;
    rewrite ^(/download/.*)/show/(.*)\..*$ $1/mp3/$2.png  break;
    return  403;
}

示例3 :

if( !-e $request_filename )
{
rewrite ^/(.*)$ index.php last;
}

当访问的文件和目录不存在时,重定向到某个php文件

示例4 :

rewrite ^/(\d+)/(.+)/ /$2?id=$1 last;

目录对换 /123456/xxxx ====> /xxxx?id=123456

示例5 :

if( $http_user_agent ~ MSIE)
{
rewrite ^(.*)$ /ie/$1 break;
}

如果客户端使用的是IE浏览器,则重定向到/ie目录下

示例6

location ~ ^/(cron|templates)/
{
deny all;
break;
}

禁止访问多个目录

示例7:

location ~ .*\.(sh|flv|mp3)$
{
return 403;
}

禁止访问以.sh,.flv,.mp3为文件后缀名的文件

示例8:

location ~ ^/data
{
deny all;
}

禁止访问以/data开头的文件

示例9:

location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
    expires 30d;
}
location ~ .*\.(js|css)$
{
    expires 1h;
}

设置某些类型文件的浏览器缓存时间

示例10:

location ^~ /html/scripts/loadhead_1.js {
    access_log off;
    root /opt/lampp/htdocs/web;
    expires 600;
    break;
}

设定某个文件的过期时间;这里为600秒,并不记录访问日志

示例11:

location ~(favicon.ico) {
log_not_found off;
expires 99d;
break;
}
location ~(robots.txt) {
log_not_found off;
expires 7d;
break;
}

给favicon.ico和robots.txt设置过期时间;这里为favicon.ico为99天,robots.txt为7天并不记录404错误日志

示例12:

location ~* ^.+\.(jpg|jpeg|gif|png|swf|rar|zip|css|js)$ {
    valid_referers none blocked *.nixi8.com nixi8.com localhost 192.168.1.208; #定义none(空,直接访问),blocked(被防火墙标记过的来路),
    nixi8.com的二级域名和一级域名,localhost,192.168.1.208
    if ($invalid_referer) { # 如果不是上面定义的其中一个
        rewrite ^/ http://www.nixi8.com/none.gif;  # 就重写到一张gif图片上;
        return 412;
         break;
    }

    access_log   off;  # 关闭日志,降低服务器的损耗
    root /opt/lampp/htdocs/web; 
    expires 3d; //所有文件3天缓存
    break;
}

图片防盗链。

示例13:

if (-d $request_filename){
    rewrite ^/(.*)([^/])$ http://$host/$1$2/ permanent;
}
目录自动加/:用([^/])匹配最后一个非'/'的字符,然后自己强行再添加一个'/'($2变量后的那个)

示例14:

root /opt/htdocs/www;
allow 208.97.167.194; 
allow 222.33.1.2; 
allow 231.152.49.4;
deny all;
auth_basic “C1G_ADMIN”;
auth_basic_user_file htpasswd;

只允许固定ip访问网站,并加上密码。

示例15:

rewrite ^/job-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /job/$1/$2/jobshow_$3.html last;

将多级目录下的文件转成一个文件,增强seo效果/job-123-456-789.html 指向/job/123/456/789.html

示例16:将根目录下某个文件夹指向2级目录

如/shanghaijob/ 指向 /area/shanghai/
如果你将last改成permanent,那么浏览器地址栏显是/location/shanghai/
rewrite ^/([0-9a-z]+)job/(.*)$ /area/$1/$2last;
上面例子有个问题是访问/shanghai时将不会匹配
rewrite ^/([0-9a-z]+)job$ /area/$1/ last;
rewrite ^/([0-9a-z]+)job/(.*)$ /area/$1/$2last;
这样/shanghai 也可以访问了,但页面中的相对链接无法使用,
如./list_1.html真实地址是/area/shanghia/list_1.html会变成/list_1.html,导至无法访问。
那我加上自动跳转也是不行咯
(-d $request_filename)它有个条件是必需为真实目录,而我的rewrite不是的,所以没有效果
if (-d $request_filename){
rewrite ^/(.*)([^/])$ http://$host/$1$2/permanent;
}
知道原因后就好办了,让我手动跳转吧
rewrite ^/([0-9a-z]+)job$ /$1job/permanent;
rewrite ^/([0-9a-z]+)job/(.*)$ /area/$1/$2last;

示例17:域名跳转

server
{
listen 80;
server_name jump.linuxidc.com;
index index.html index.htm index.php;
root /opt/lampp/htdocs/www;
rewrite ^/ http://www.linuxidc.com/;
access_log off;
}

示例18:多级域名跳转

server_name www.linuxidc.comwww.linuxidc.net;
index index.html index.htm index.php;
root /opt/lampp/htdocs;
if ($host ~ "linuxidc\.net") {
rewrite ^(.*) http://www.linuxidc.com$1permanent;
}

示例19:

if ($slow) {
    limit_rate 10k;
    break;
}

限流. Nginx的http核心模块ngxhttpcoremodule中提供limitrate这个指令可以用于控制速度,limitrateafter用于设置http请求传输多少字节后开始限速。

示例20:

if ($http_user_agent ~ MSIE) {
    rewrite ^(.*)$ /msie/$1 break;
}

if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
    set $id $1;
}

if ($request_method = POST) {
    return 405;
}

if ($slow) {
    limit_rate 10k;
}

if ($invalid_referer) {
    return 403;
}

示例21:

if ( $query_string ~* ".*[;'<>].*" ) {
    return 404;
}

通过判断 URI 中是否有 ’、;、>、< 等字符可以快速过滤掉可能发生 SQL 注入的请求,然后直接返回 404 Not Found,可用于防sql注入。

示例22:

location / {
    if (!-e $request_filename){
        rewrite ^(.*)$ /app.php break;
    }
}

将不存在的请求定义到 app.php 处理

注意:在server中使用rewrite ,我们使用的flag是last,但是在location中,我们却只能用break,这是因为如果在location的rewrite也使用last,便会再次以新的URI重新发起内部重定向,再次进行location匹配,而新的URI中极有可能和旧的URI一样再次匹配到相同location中,这样死循环发生了。当循环到第10次时,Nginx会终止这样无意义的循环,并返回500错误。

参考

  1. https://www.cnblogs.com/minirice/p/8872093.html
  2. https://www.cnblogs.com/brianzhu/p/8624703.html
  3. http://nginx.org/en/docs/http/ngxhttprewrite_module.html
  4. nginx内置预定义变量 http://www.nginx.cn/273.html
  5. nginx rewrite 指令 http://www.nginx.cn/216.html
  6. http://nginx.org/en/docs/http/ngxhttprewrite_module.html

本文分享自微信公众号 - 开发架构二三事(gh_d6f166e26398)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-05

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏生信技能树

随心所欲对指定R包进行升级与降级

很多时候,我们其实并不需要动R本身的版本,可能只是想修改某个R包版本,比如单细胞领域最火的 Seurat 包, 就有这个问题:

31710
来自专栏CherishTheYouth

点击菜单选项,右侧主体区新增子界面(Tab)的实现

今天记录一下一种前端页面的效果的实现,这种效果很常见,一般用于网站后台系统的前端页面。一般后台系统会分为顶部导航栏,左边的菜单栏和右边的主体区。有一种效果是这样...

21720
来自专栏算法与编程之美

Python应用 | 我喜欢看什么美剧(一)

《权利的游戏》、《天赋异禀》等耳熟能详的美剧,面对如此繁多的美剧,此时不禁会问自己,我喜欢看什么美剧呢?

11930
来自专栏web前端教室

如何破解前端工程师的局限性

2、全栈开发,就是会前端的,也会后端的技术。要求是全能,就是从前到后一把梭了。但是实践证明,这种全栈开发,一般到后来就是全都会,但全都不精通。

9320
来自专栏我的小碗汤

ingress-nginx实现灰度和金丝雀发布

nginx-ingress作为K8S集群对外的流量入口,充当K8S集群内各个service的反向代理。日常工作中我们经常需要对服务进行版本更新升级,为此我们经常...

87040
来自专栏天马行空布鲁斯

关于html的input元素,property和attribute的区别

之前在项目中遇到一个很tricky的关于html的input元素的问题,个人觉得挺有意思,于是记录下来。这个问题也是在ui的自动化测试中,可能会碰到的一个问题。

16910
来自专栏女程序员的日常_Lin

Vue SSR

vue.js是构建客户端应用程序的框架,在默认情况下,在浏览器输出Vue组件,进行生成DOM和操作DOM。Vue SSR 就是实现将组件渲染为服务器端的HTML...

81510
来自专栏CherishTheYouth

.net mvc + layui做图片上传(一)

  图片上传和展示是互联网应用中比较常见的一个功能,最近做的一个门户网站项目就有多个需要上传图片的功能模块。关于这部分内容,本来功能不复杂,但后面做起来却还是出...

17920
来自专栏终身开发者

Python Web Flask源码解读(三)——模板渲染过程

前面对 Flask启动流程和路由原理都进行了源码走读。今天我们看看模板渲染的过程。

10420
来自专栏无敌码农

Java SPI机制的运行原理是什么?

SPI的全称是(Service Provider Interface)是服务提供接口的意思。如果我们不写框架性代码或者开发插件的话,对于SPI机制可能不会那么熟...

10910

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励