您的位置:晶晶的博客>Linux>nginx配置总结

nginx配置总结

nginx基本成了日常开发部署中的必备工具,因为常用所以做一个知识体系小结汇总。

一、location

语法规则:

Syntax:	location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
Default:	—
Context:	server, location

location配置块是可以放置在server块和location块下的,也就是说location块可以嵌套的。

关于location的说明权威文档在此:http://nginx.org/en/docs/http/ngx_http_core_module.html#location,怎奈是个英文版。

为了便于说明对location语法做了一个示意图,并且为了文字描述方便示意图中生造了几个词汇,这些词汇仅是为了方便本文的描述而造并不是官方词汇。

nginx location

1.1、location用来做什么

权威释义:Sets configuration depending on a request URI.

半吊子翻译:对请求URI进行个性化配置。 也就是说location配置块用于对匹配到URI进行个性化定义,注意哈是URI不是URL,具体区别请面向百度编程。

权威释义:A location can either be defined by a prefix string, or by a regular expression. Regular expressions are specified with the preceding “~*” modifier (for case-insensitive matching), or the “~” modifier (for case-sensitive matching).

半吊子翻译:location所定义匹配的URI可以使用前缀方式也可以使用正则匹配方式,正则匹配方式有区分大小写和不区分大小写两种模式。

官方的E文还是不明白啥意思,拿示意图来说:指定匹配模式修饰符和匹配值就可以对匹配到URI如何处理进行个性化定义。

匹配模式修饰符有多种:

  • 省略匹配模式 : 即示意图中生造词汇匹配模式修饰符不写给空,也就是官方文档中所描述的a prefix string前缀匹配,URI必然是以正斜杠/开头,所以前缀匹配规则时示意图中生造词汇匹配值必然需以正斜杠开头;
  • = : 完整精确匹配,一旦匹配上就直接使用该location执行不再执行其他location的匹配规则检查;
  • ~* : 不区分大小写正则匹配;
  • ~ : 区分大小写正则匹配;
  • ^~ : 很遗憾看起来有波浪符以为是正则匹配,其实就是一个普通前缀匹配,只不过因nginx对多个location处理流程的顺序原因,该匹配规则可以理解为带限定多location流程的前缀匹配,当指定有该匹配模式修饰符的location区块被匹配到时就会终止可能会存在的正则匹配,因为正则匹配范围更广可能会包含前缀匹配,这个时候就有优先级和顺序问题了;

1.2、location的优先级

权威释义:

To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

location blocks can be nested, with some exceptions mentioned below.

For case-insensitive operating systems such as macOS and Cygwin, matching with prefix strings ignores a case (0.7.7). However, comparison is limited to one-byte locales.

Regular expressions can contain captures (0.7.40) that can later be used in other directives.

If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.

Also, using the “=” modifier it is possible to define an exact match of URI and location. If an exact match is found, the search terminates. For example, if a “/” request happens frequently, defining “location = /” will speed up the processing of these requests, as search terminates right after the first comparison. Such a location cannot obviously contain nested locations.

然后官方说明还特别附了一段某些版本例外的注释说明:

In versions from 0.7.1 to 0.8.41, if a request matched the prefix location without the “=” and “^~” modifiers, the search also terminated and regular expressions were not checked.

半吊子翻译:Nginx服务器会首先会检查多个location中是否有普通的前缀匹配,如果有多个普通前缀匹配,会先记住匹配度最高的那个。然后再检查正则匹配,这里的正则匹配是有顺序的,从上到下依次匹配,一旦匹配成功,则结束检查并使用这个正则匹配到的location块处理此请求。如果正则匹配全部失败,就会使用刚才记录普通uri匹配度最高的那个location块处理此请求。

对于^~匹配模式修饰符官方的描述:If the longest matching prefix location has the “^~” modifier then regular expressions are not checked.。也就是说在nginx对多location处理顺序里,如果普通前缀匹配有匹配到然后正则匹配也有匹配到,这个时候如果普通前缀匹配location区块里有使用到^~这个匹配模式修饰符就会忽略检查正则匹配的流程直接使用这普通前缀匹配,是一个提升普通前缀匹配优先级的语法。然后对于官方特别附上的版本例外的注释说明可以忽略了,这是官方文档严谨性的体现。现在nginx的版本早到了1.x.x系列了,0.x.x版本就忘了她吧~

所以nginx的location顺序到底是怎样的呢?找到一张图总结的很好,注意这张流程图是从上往下看的:

总结一下:对一个请求进来,nginx首先使用普通前缀匹配去检查,这个时候是没有顺序的,而且如果普通前缀匹配有使用到=^~匹配模式修饰符且被匹配到了,就会终止后续的正则匹配检查直接使用这个被=^~修饰符所修饰的并且被匹配到location;如果普通前缀没匹配到或者匹配到了但是匹配到的并没有被=^~修饰符所修饰,则会继续检查正则匹配,注意正则匹配是有顺序的,是从上到下依次匹配,一旦有匹配成功,则停止后面的匹配。

二、pathinfo

请查看:《Nginx支持PHP的PATHINFO模式配置分析》一文。

三、URL重写

URL重写日常用到频率很高,可用于URL美化、跳转和伪静态。

面向百度编程:https://baike.baidu.com/item/URL%E9%87%8D%E5%86%99

权威文档:https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#rewrite

URL重写站在使用层面上来理解就是:用户访问的URL是aaa使用URL重写后实际处理的URL就变成了bbb,而且这个bbb用户是看不到的。任然没有感觉?用户访问的地址是:https://www.domain.com/a/b/c,URL重写后URL就变成了https://www.domain.com/?module=a&controller=b&action=c,我们的代码接收并处理的是URL重写后的样子。当然这个具体例子只是URL重写威力的一个示例,具体使用层面上URL重写是非常灵活的。

语法规则:

Syntax:	rewrite regex replacement [flag];
Default:	—
Context:	server, location, if

可以看到rewrite指令需要用的正则表达式,可以放置在server块、location块以及if条件判断中。

nginx的rewrite语法

本博客也使用了nginx:https://blog.jjonline.cn/用例子来理解rewrite指令。

3.1、/aa下所有请求跳转到指定地址

url中/aa开头的所有请求跳转到百度:即访问的URL为https://blog.jjonline.cn/aa的,因为是前缀匹配只要路由地址以/aa开头都会被跳转到百度首页。

location /aa {
      # 选项redirect则是302
      # 选项permanent则是301
      rewrite / https://www.baidu.com redirect;
      # 当然也可以站内跳转,下方注释配置启用的话访问 /aa前缀的会自动302跳转到/bbb前缀下
      # rewrite / /bbb redirect; 
}

3.2、重写/bb下所有请求到/cc下

url中/bb开头的所有请求实际处理时变更为/cc下,用户浏览器上的地址依然是/bb,需要注意的是这种就不是用户端浏览器跳转,而是内部一种隐式的URL变化。

location /bb {
    # 假设用户访问的地址是https://blog.jjonline.cn/bb/getUser?a=1&p=101
    # 用户浏览器的地址不会变化
    # 经过此处rewrite后,后端程序接收到的url变为如下:
    # https://blog.jjonline.cn/cc?p=/getUser&a=1&p=101
    # rewrite原样保留原始请求中的query键值对到重写后的url
    # URL规范里键值对的键名是可以有多个的,所以这里不必考虑我新增的query键值对和url附加的冲突了怎么办的问题
    rewrite /bb(.*) /cc?p=$1 last;
}

# 这里有一个调试技巧,通过return 204仅返回头部,以及添加自定义header头来返回需要调试的内容进行debug
# 利用add_header指令将当前的参数输出到header,然后返回204就可以看到$uri、$query_string当前的值
location /cc {
    add_header 'X-Request-Uri' $uri;
    add_header 'X-Request-Uri' $request_uri;
    add_header 'X-Request-Query' $query_string;
    return 204;
}

执行命令:curl --head https://blog.jjonline.cn/bb/getUser\?a\=1\&p\=101会输出如下内容(注意命令行下query键值对会被加上转义符也就是反斜杠这是正常的):

HTTP/2 204 
server: nginx
x-request-uri: /cc
x-request-uri: /bb/getUser?a=1&p=101
x-request-query: p=/getUser&a=1&p=101

URL重写指令也是可以配合反向代理来使用的。

四、反向代理

面向百度编程:https://baike.baidu.com/item/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86

权威文档:http://nginx.org/en/docs/http/ngx_http_proxy_module.html

反向代理简单浅薄的理解就是:进入的nginx的请求经过nginx的一些设置和前期处理转发到我们具体的web服务后端由我们的后端进行处理和输出,就像在我们的web服务后端之前加了一层包装。

4.1、整站代理:最精简的proxy_pass

一般整站代理最精简的proxy_pass即可跑起来,譬如在9080端口启动一个golang实现的api接口服务,nginx整站代理参考配置:

location / { 
    # 接口服务需要跨域,所以要添加Access-Control-系列的header头
    add_header 'Access-Control-Allow-Origin' $http_origin;
    add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'X-App-Client,X-App-ID,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
    add_header 'Access-Control-Expose-Headers' 'Content-Disposition';
    
    # 很多前后端分离的api会有所谓预检options类型的请求
    # 预检Options类型请求没必要转发到后端再进行处理
    # 直接由nginx返回204类型带Access-Control-系列的header头的响应即可
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' $http_origin;
        add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'X-App-Client,X-App-ID,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
        add_header 'Access-Control-Expose-Headers' 'Content-Disposition';
        add_header 'Access-Control-Max-Age' '7200';
        return 204;
    }
    
    # 给后端添加一些代理参数头
    # proxy_set_header指令设置的header头是golang后端能拿到的
    proxy_http_version 1.1;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forward-For $remote_addr;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Appengine-Remote-Addr $remote_addr;
    proxy_set_header X-Request-ID $request_id;

    # 核心的proxy_pass指令
    proxy_pass http://127.0.0.1:9080;
}

4.2、配合upstream更高级复杂的proxy_pass

upstream指令用于定义一组分布式代理的服务,可以为每一个后端服务指定许多详细的参数。

upstream权威文档:http://nginx.org/en/docs/http/ngx_http_upstream_module.html

upstream backend {
    server backend1.example.com weight=5;
    server 127.0.0.1:9080       max_fails=3 fail_timeout=30s;
    server unix:/tmp/backend3;
    server backup1.example.com  backup;
}

location / {
    ### 注意此处proxy_pass指定的写法
    proxy_pass http://backend;
}

4.3、反向代理例子:指定前缀代理

需求:https://www.domain.com作为网站入口也是前端SPA项目部署的入口,将所有后端api代理到https://www.domain.com/api路径下。

情形一:后端api路由也全部是为api开头的,例如:/api/home/list 

这种情形是正常理解范畴,后端开发的接口前端直接按后端路由调用即可。

location ^~ /api/ {
        # 代理地址是否带后缀斜杠是有影响的
        # 不带后缀斜杠则请求路径会原样传递过去
        # 即 proxy_pass http://127.0.0.1:9080; 和 proxy_pass http://127.0.0.1:9080/; 两种写法是有一定差异的
        proxy_pass http://127.0.0.1:9080;
}

情形二:后端api路由不为api开头的需要去掉api前缀,例如:/home/list 

这种情形是不太合理但是现实中也有不少项目这样玩,带来的问题就是:后端开发的api前端调用时均需加上统一的/api前缀。

 # 去除前缀方案①:proxy_pass代理地址带斜杠
# 注意当proxy_pass带后缀斜杠时此处前缀匹配写法里是要带后缀斜杠的
# 即/api/需要完整
# 假设访问地址为:https://www.domain.com/api/home/list
# proxy_pass带后缀斜杠,如果此处写成/api那么后端接收到的路由就是//home/list,前面会有两个斜杠
location ^~ /api/ {
        # 代理地址是否带后缀斜杠是有影响的
        # 简单理解:带后缀斜杠则会把location里`匹配值`所匹配到的部分给剔除掉
        # 这样就好理解为何上方`匹配值`一定要写成/api/而不是/api
        proxy_pass http://127.0.0.1:9080/;
}

当然去除代理前缀还有rewrite重写方案:

 # 去除前缀方案②:rewrite重写路由
location ^~ /api/ {
        # rewrite指令重写路由:通过正则匹配取出api之外的部分,然后用捕获子组重设了请求URI
        # 传递给后端的URI是已经被重设过的
        rewrite ^/api/(.*)$ /$1 break;

        proxy_pass http://127.0.0.1:9080;

        # 因为URI经过rewrite指令 请求/api/home/list实际已经被重写成了/home/list
        # 所以传递给后端时proxy_pass带不带后缀斜杠已经不影响,也就是说重写前提下述这种带后缀斜杠的写法也是没有问题的,但是并不推荐这样写
        # proxy_pass http://127.0.0.1:9080/;
}

4.4、反向代理的同时支持静态文件

需求:https://www.domain.com作为一个已存在的网站的入口,现在要添加一个新代理项目到https://www.domain.com/project路径下,这个项目既有后端api也有css、js静态文件要直接通过nginx处理。

这个需求比较罕见,不过也是存在的,为了实现这个需求需要项目本身的一些配合和调整,否则的话会影响已有项目。

这个带静态资源服务的后端需要做好一个路径前缀规划,举例说明:

  1. 部署在project路径下的项目所有路由均需要以/project作为前缀,包括接口api和静态文件;
  2. 静态文件的引入路径也需要放在一个固定的目录下,作为nginx处理的标识符,不然的话无法区分是接口api还是静态文件,假设以static目录为主目录:即引入的js、css的路径前缀固定了为/project/static
  3. project项目既然有静态文件,必然会有一个项目根目录,假设为/www/wwwroot/domain/wwwroot/,那必然它的静态文件就全部放在/www/wwwroot/domain/wwwroot/static路径下了;

最后按上述项目设计好的规则nginx反向代理和静态资源代理配置参考如下:

location /project/ {
    # 指定这个代理项目的根目录
    root /www/wwwroot/domain/wwwroot/;

    # 因为项目要部署在 /project 的前缀下
    # 静态资源的路由必然是 /project/static/js/jquery.min.js 类似这种URI
    # 而实际project文件夹是不存在的,所以需要嵌套location进行正则匹配取出真实的路径
    # 然后 try_files 指令直接读取硬盘上该路径下的文件并返回
    location ~* ^/project/static/(.*)$ {
        try_files /static/$1 $uri;
        break;
    }

    # 非静态文件的请求就通过代理转发到这个后端服务的URI上
    proxy_http_version 1.1;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forward-For $remote_addr;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Appengine-Remote-Addr $remote_addr;
    proxy_set_header X-Request-ID $request_id;

    proxy_pass http://127.0.0.1:9080;
}

访问https://www.domain.com/project返回了一个html文件,html文件里引入的js、css的写法就是如下几种了:

  • 带域名完整模式:https://www.domain.com/project/static/css/jquery.min.css 这种方法没有任何理解上的难度,对需要切换域名的场景不友好,不推荐;
  • 绝对路径模式:/project/static/css/jquery.min.css 这种是推荐写法;
  • 相对路径模式:static/css/jquery.min.css,这种就要保障所有页面路由均只在/project下,即这种URLhttps://www.domain.com/project/a/1.html就GG了,所以也不推荐;

----

本篇偏向于运维层面了,技多不压身咯~

nginx的配置用的多了会发现,写nginx的配置有那么点儿编程的意思了,有一个openResty就把这种想法变成了使用lua语言的web编程和web-server高度集成的似乎是直接在nginx上编程一样有趣,可以去了解下。

参考资料:

① http://nginx.org/en/docs/http/ngx_http_core_module.html

② https://www.cnblogs.com/zhaof/p/5945576.html

③ http://nginx.org/en/docs/http/ngx_http_upstream_module.html

④ http://nginx.org/en/docs/http/ngx_http_rewrite_module.html

转载请注明本文标题和链接:《nginx配置总结
分享到:

相关推荐

哟嚯,本文评论功能关闭啦~