06 Nginx经典应用

2020年11月4日 study

2021年5月12日 review

01 Nginx全局异常兜底数据返回

任何接口都是可能出错,4xx、5xx等。如果业务没有做好统一的错误管理,直接暴露给用户,无疑是看不懂。

假如后端某个业务出错,nginx层也需要进行转换。

让前端知道Http响应是200,其实是将错误的状态码定向至200,返回了全局兜底数据。

upstream lbs {
        server 47.115.23.41:8080 max_fails=2 fail_timeout=60s;
        server 47.115.23.41:8081 max_fails=2 fail_timeout=60s;
}
server {
    listen       80;
    server_name  47.115.23.41;

    location / {
        proxy_pass http://lbs;
        proxy_redirect default;

	# save the real ip of user
        proxy_set_header HOST $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        proxy_next_upstream error timeout http_503 non_idempotent;

	# turn on configuration of error intercept, must be turned on
        proxy_intercept_errors on;
    }

    access_log  logs/time_access.log  main;

    # if not add "=200", then return the original http error code; 
    # otherwise if there is an error status code such as 500, it will return to the user 200 status and jump to "/default_api"
    error_page   404 500 502 503 504 =200 /default_api;
    location = /default_api {
        default_type application/json;
        return 200 '{"code":"-1","msg":"invoke fail, not found"}';
    }
}

注意:

  • nginx.conf中最好不要复制中文进去,不然可能会出现乱码,所以注释写成英文;
  • 如果正常访问,都能得到正确的结果;如果访问出错,都会返回200 OK以及自定义Json给前端,前端可通过"code":"-1"来给用户进行提示错误信息;(这样做的目的是防止后端未定义错误状态码,或者定义不全,而给用户返回一些看不懂的信息。)

02 Nginx封禁恶意IP

  • 网络攻击常见的有:TCP洪水攻击、注入攻击、DOS等,比较难防的有DDOS等;
  • 数据安全,防止对手爬虫恶意爬取,封禁IP;
  • 一般就是封禁IP
    • linux server的层面封禁IP:iptables(推荐)
    • nginx的层面封禁IP,方式多种(但request还是会进来,让nginx返回403,占用资源)

Nginx作为网关,可以有效的封禁IP:

  • 单独网站屏蔽IP的方法,把include xxx; 放到网址对应的在server{}语句块(虚拟主机);
  • 所有网站屏蔽IP的方法,把include xxx; 放到http{}语句块。

在/usr/local/nginx/conf目录下touch blacklist.conf用于专门存放封禁的IP,格式为deny ip地址;;例如:

(先在nginx.conf中记录的access.log中找到访问的IP,把它加入黑名单进行测试)

  • blacklist.conf
deny 14.30.22.148;
  • 然后在nginx.conf引入
http {
    ......
    include blacklist.conf;
    server {
    ......    
    }

}

  • ./nginx -s reload:重新加载配置,不中断服务;
  • 拓展:自动化封禁IP思路
    • 编写shell脚本;
    • awk统计access.log,记录每秒访问超过60次的IP,然后配合nginx或者iptables进行封禁;
    • crontab定时跑脚本;(Linux crontab是用来定期执行程序的命令)

03 Nginx配置浏览器跨域

跨域:浏览器同源策略1995年,同源政策由Netscape公司引入浏览器。目前,所有浏览器都实行这个政策。 最初它的含义是指,A网页设置的cookie,B网页不能打开,除非这两个网页“同源”。

所谓“同源”指的是“三个相同”:

  • 协议相同:http https
  • 域名相同:www.xdclass.net
  • 端口相同:80 81

In a word:浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。

浏览器控制台跨域提示:No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

也就是用postman调试接口没问题,前端也没问题,但是数据显示不出来,这可能是跨域的问题;

解决办法:

  • JSONP(目前很少使用了)

  • HTTP响应头配置允许跨域(业界常用,有如下两种方法)

    • nginx层配置
    • 后端代码中处理,通过拦截器配置
  • Nginx开启跨域配置(location下配置,一般会针对特定的接口,在header头信息里面加几个字段)

server {
    listen       80;
    server_name  47.115.23.41;

    location /api/ {
        add_header 'Access-Control-Allow-Origin' $http_origin;
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Headers' 'DNT,web-token,app-token,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Mx-ReqToken,X-Data-Type,X-Auth-Token,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header Access-Control-Allow-Methods 'GET,POST,OPTIONS';
        
          # if request method is OPTIONS, then return 200 OK, don't need to forward to back-end codes
	  if ($request_method = 'OPTIONS') {
           add_header 'Access-Control-Max-Age' 1728000;
           add_header 'Content-Type' 'text/plain; charset=utf-8';
		   add_header 'Content-Length' 0;
		   return 200;
	  }
        
        proxy_pass http://lbs;
        proxy_redirect default;

	# save the real ip of user
        proxy_set_header HOST $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        proxy_next_upstream error timeout http_503 non_idempotent;

	# turn on configuration of error intercept, must be turned on
        proxy_intercept_errors on;
    }
    
    location / {
        proxy_pass http://lbs;
        proxy_redirect default;
    }

    access_log  logs/time_access.log  main;

    # if not add "=200", then return the original http error code; 
    # otherwise if there is an error status code such as 500, it will return to the user 200 status and jump to "/default_api"
    error_page   404 500 502 503 504 =200 /default_api;
    location = /default_api {
        default_type application/json;
        return 200 '{"code":"-1","msg":"invoke fail, not found"}';
    }
}

  • add_header 'Access-Control-Allow-Origin' $http_origin;:允许的源,$http_origin表示任何一个源都可以传过去;
  • add_header 'Access-Control-Allow-Credentials' 'true';:请求的时候可以根据cookie来进行配置;
  • add_header 'Access-Control-Allow-Headers':常见的header有没有加进去;
  • add_header Access-Control-Allow-Methods 'GET,POST,OPTIONS';:哪些请求需要进行跨域;
  • if ($request_method = 'OPTIONS') {:预检请求,H5新规范,一般都会发一个OPTIONS请求,预检一下接口是否畅通;如果后端没有写好,即使配置了跨域,也会出现跨域问题;

04 路径匹配-Nginx的locatioin规则

  • 语法规则:
location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }

语法规则很简单,一个location关键字,后面跟着可选的修饰符,后面是要匹配的字符,花括号中是要执行的操作。

修饰符:

  • =:表示精确匹配。只有请求的url路径与后面的字符串完全相等时,才会命中。
  • ~:表示该规则是使用正则定义的,区分大小写。
  • ~*:表示该规则是使用正则定义的,不区分大小写。
  • ^~:表示如果该符号后面的字符是最佳匹配,采用该规则,不再进行后续的查找。

location有两种表示形式,一种是使用前缀字符,一种是使用正则。如果是正则的话,前面有~~*修饰符。

具体的匹配过程如下:

首先先检查使用前缀字符定义的location,选择最长匹配的项并记录下来。如果找到了精确匹配的location,也就是使用了=修饰符的location,结束查找,使用它的配置。然后按顺序查找使用正则定义的location,如果匹配则停止查找,使用它定义的配置。如果没有匹配的正则location,则使用前面记录的最长匹配前缀字符location。

基于以上的匹配过程,我们可以得到以下两点启示:

  1. 使用正则定义的location在配置文件中出现的顺序很重要。因为找到第一个匹配的正则后,查找就停止了,后面定义的正则就是再匹配也没有机会了。
  2. 使用精确匹配可以提高查找的速度。例如经常请求/的话,可以使用=来定义location。

总结:精准匹配 > 字符串匹配(若有多个匹配项匹配成功,那么选择匹配长的并记录) > 正则匹配

  • 示例:
location = / {
    return 1;
}

location / {
    return 2;
}

location /user/ {
    return 3;
}

location ^~ /images/ {
    return 4;
}

location ~* \.(gif|jpg|jpeg)$ {
    return 5;
}

请求/精准匹配1,不再往下查找。

请求/index.html匹配2。首先查找匹配的前缀字符,找到最长匹配是配置2,接着又按照顺序查找匹配的正则。结果没有找到,因此使用先前标记的最长匹配,即配置2。

请求/user/index.html匹配3。首先找到最长匹配3,由于后面没有匹配的正则,所以使用最长匹配3。

请求/user/1.jpg匹配5。首先进行前缀字符的查找,找到最长匹配项3,继续进行正则查找,找到匹配项5。因此使用5。

请求/images/1.jpg匹配4。首先进行前缀字符的查找,找到最长匹配4。但是,特殊的是它使用了^~修饰符,不再进行接下来的正则的匹配查找,因此使用4。这里,如果没有前面的修饰符,其实最终的匹配是5。

请求/documents/index.html匹配2。因为2表示任何以/开头的URL都匹配。在上面的配置中,只有2能满足,所以匹配2。

  • location @name的用法

@用来定义一个命名location。主要用于内部重定向,不能用来处理正常的请求。其用法如下:

location / {
    try_files $uri $uri/ @custom
}
location @custom {
    # ...do something
}

上例中,当尝试访问url找不到对应的文件就重定向到我们自定义的命名location(此处为custom)。

值得注意的是,命名location中不能再嵌套其它的命名location。

URL尾部的/需不需要

关于URL尾部的/有三点也需要说明一下。第一点与location配置有关,其他两点无关。

  1. location中的字符有没有/都没有影响。也就是说/user/和/user是一样的。
  2. 如果URL结构是https://domain.com/的形式,尾部有没有/都不会造成重定向。因为浏览器在发起请求的时候,默认加上了/。虽然很多浏览器在地址栏里也不会显示/。这一点,可以访问baidu验证一下。
  3. 如果URL的结构是https://domain.com/some-dir/。尾部如果缺少/将导致重定向。因为根据约定,URL尾部的/表示目录,没有/表示文件。所以访问/some-dir/时,服务器会自动去该目录下找对应的默认文件。如果访问/some-dir的话,服务器会先去找some-dir文件,找不到的话会将some-dir当成目录,重定向到/some-dir/,去该目录下找默认文件。可以去测试一下你的网站是不是这样的。

05 地址重定向-Nginx的rewrite规则

https://www.cnblogs.com/tugenhua0707/p/10798762.html

rewrite功能就是,使用nginx提供的全局变量或自己设置的变量,结合正则表达式和标志位实现url重写以及重定向。

语法:

一:理解“地址重写”与“地址转发”的含义。(地址重写与地址转发是两个不同的概念)

地址重写:是为了实现地址的标准化,比如我们可以在地址栏中中输入www.baidu.com我们也可以输入www.baidu.cn最后都会被重写到www.baidu.com上。浏览器的地址栏也会显示www.baidu.com

地址转发:它是指在网络数据传输过程中数据分组到达路由器或桥接器后,该设备通过检查分组地址并将数据转发到最近的局域网的过程。

因此地址重写和地址转发有以下不同点:

  1. 地址重写会改变浏览器中的地址,使之变成重写成浏览器最新的地址。而地址转发是不会改变浏览器的地址的。
  2. 地址重写会产生两次请求,而地址转发只会有一次请求。
  3. 地址转发一般发生在同一站点项目内部,而地址重写不受限制。
  4. 地址转发的速度比地址重定向快。(地址重写即是重定向

二:理解rewrite指令使用

该指令是通过正则表达式的使用来改变URI。可以同时存在一个或多个指令。需要按照顺序依次对URL进行匹配和处理。

rewrite只能放在server{}location{}if{}中,并且只能对域名后边的除去传递的参数外的字符串起作用,例如:

http://seanlook.com/a/we/index.php?id=1&u=str 只对 /a/we/index.php 重写

其基本语法结构如下:

rewrite regex replacement [flag];
  • rewrite的含义:该指令是实现URL重写的指令。
  • regex的含义:用于匹配URI的正则表达式。
  • replacement:将regex正则匹配到的内容替换成replacement。
  • flag: flag标记。

flag有如下值:

  • last: 本条规则匹配完成后,继续向下匹配新的location URI 规则。(不常用)
  • break: 本条规则匹配完成即终止,不再匹配后面的任何规则(不常用)。
  • redirect: 返回302临时重定向,浏览器地址会显示跳转新的URL地址。
  • permanent: 返回301永久重定向。浏览器地址会显示跳转新的URL地址。

例子:

rewrite ^/(.*) http://www.baidu.com/$1 permanent;

说明:

  • rewrite为固定关键字,表示开始进行rewrite匹配规则。
  • regex为^/(.*),这是一个正则表达式,匹配完整的域名和后面的路径地址。
  • replacement就是http://www.baidu.com/$1这块了,其中$1是取regex部分()里面的内容。如果匹配成功后跳转到的URL。
  • flag就是permanent,代表永久重定向的含义,即跳转到http://www.baidu.com/$1地址上。
server {
    listen       80;
    server_name  47.115.23.41;
    
    location /api {
        rewrite ^/(.*) https://www.baidu.com permanent;
    }
    error_page   404 500 502 503 504 =200 /default_api;
    location = /default_api {
        default_type application/json;
        return 200 '{"code":"-1","msg":"invoke fail, not found"}';
    }
  • 访问47.115.23.41/api/v1/pub/web会跳转到https://www.baidu.com/api/v1/pub/web从而报错Not Found;(47.115.23.41/api/v1/pub/web,这个地址是java jar包启动的接口地址)
  • 访问47.115.23.41/aa/xx/xx/xx...;如果aa=api,则会直接跳转到https://www.baidu.com,否则会跳到自定义返回Json的错误页面;
  • 访问47.115.23.41或者47.115.23.41/会直接跳转到https://www.baidu.com
  • rewrite ^/(.*) https://www.baidu.com/$1 permanent;;除了47.115.23.41/会直接跳转到https://www.baidu.com,其它都不行;

常用正则表达式:

字符 描述
^ 匹配输入字符串的起始位置
$ 匹配输入字符串的结束位置
  • | 匹配前面的字符串0次或者多次
  • | 匹配前面的字符串1次或者多次
    ? | 匹配前面的字符串0次或者1次
    . | 匹配除“\n”之外的所有单个字符
    (pattern) | 匹配括号内的pattern

应用场景:

  • 非法访问跳转,防盗链
  • 网站更换新域名
  • http跳转https
  • 不同地址访问同一个虚拟主机的资源

06 Nginx配置Websocket反向代理

配置:

server {
    listen 80;
    server_name xdclass.net;
    location / {
        proxy_pass http://lbs;
        proxy_read_timeout 300s; # websocket 空闲保持时长
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}

核心是下面的配置,其他和普通反向代理没区别, 表示请求服务器升级协议为WebSocket:

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

服务器处理完请求后,响应如下报文(状态码为101)

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: upgrade
posted @ 2021-05-12 22:16  ddhhdd  阅读(83)  评论(0)    收藏  举报