Overview

正向代理(如 VPN)隐藏了真正的客户端,而反向代理隐藏了真正的服务端。

Nginx 是一个高性能的 HTTP 和反向代理服务器。

反向代理除了隐藏服务端增强安全性,还起到负载均衡的作用,最常见的是四层与七层负载均衡。
四层 (传输层 TCP/UDP) 负载均衡本质是转发,接收到数据包后通过改写数据包的地址信息 (IP+ 端口) 将流量转发到对应的服务器,常见的应用于四层的负载均衡器是 LVS,参考 Linux Virtual Server
七层 (应用层 HTTP、DNS 等) 负载均衡本质是内容交换,可以通过如 URL、GeoIP 等等灵活的规则将流量转发到不同服务器,Nginx 属于七层负载均衡。参考:四层、七层负载均衡的区别?

几个负载均衡策略:

  • 轮询:默认方式,可以带权重;
  • ip_hash:使用 hash 算法使相同 IP 多次访问可以分配到相同服务器上;
  • fair:第三方,根据后台服务器响应时间动态地调整,响应时间短的优先分配;
  • url_hash:第三方,对访问的 url 进行 hash 以分配服务器,适合缓存服务器;
  • sticky session:第三方,根据 cookie 将同一用户的多次访问分配到同一服务器,和 ip_hash 相比更加均匀。

常用命令:

# 查看版本号
./nginx -v
# 启动
./nginx
# 指定配置文件启动
./nginx -c /xxx/xxx.conf
# 直接停止
./nginx -s stop
# 处理完手头任务后停止
./nginx -s quit
# 验证配置文件
./nginx -t
./nginx -t -c /xxx/xxx.conf
# 重载配置(启动时指定的哪个配置文件,就重新加载哪个)
./nginx -s reload

核心配置

配置文件参考:

# 全局块,配置影响Nginx全局行为
user       www www;   # 用户组 Default: nobody
worker_processes  5;  # 允许生成 worker process 数 Default: 1 
error_log  logs/error.log;  # 日志路径
pid        logs/nginx.pid;  # nginx进程pid存放路径
worker_rlimit_nofile 8192;  # nginx进程最多打开 fd 数量
 
# events块,配置影响Nginx与用户的链接行为
events {
  accept_mutex on;  # 设置网路连接序列化,防止惊群现象发生,默认为 on
  multi_accept on;  # 设置一个进程是否同时接受多个网络连接,默认为 off
  use epoll;        # 事件驱动模型,select|poll|kqueue|epoll|resig|/dev/poll|eventport
  worker_connections  4096;  # 每个进程最大连接数 Default: 1024
}
 
# http块,配置代理、缓存、日志,及第三方插件等
http {
  include    conf/mime.types;  # 文件扩展名与文件类型映射表
  #include    /etc/nginx/proxy.conf;
  #include    /etc/nginx/fastcgi.conf;
  index    index.html index.htm index.php;
 
  default_type application/octet-stream; # 默认文件类型,默认为 text/plain
  # 自定义 log 格式命名为 main
  log_format   main '$remote_addr - $remote_user [$time_local]  $status '
                    '"$request" $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
  access_log   logs/access.log  main; # 指定日志位置与格式
  sendfile     on; # 是否使用sendfile(零拷贝),可以配置在http块,server块,location块中
  keepalive_timeout 65; # 连接超时时间,默认为75s,可以配置在http块,server块,location块中
  #tcp_nopush   on; # 默认off,用于控制TCP报文的PSF标志,当PSF为TRUE时会立即读取或发送缓冲区中的数据
  #server_names_hash_bucket_size 128; # this seems to be required for some vhosts
 
  # server块,配置虚拟主机相关参数
  
  # --- 部署静态资源示例 ---
  server { 
    keepalive_requests 120; # 单连接请求上限次数。
    listen       4545;      # 监听端口
    server_name  127.0.0.1; # 监听地址       
    location  ~*^.+$ {      # 请求的url过滤,正则匹配,~为区分大小写,~*为不区分大小写。
      root   html;		    # 资源路径,相对路径为nginx/
      index  index.html index.htm;# 默认页
      deny 127.0.0.1;    # 拒绝的ip
      allow 172.18.5.54; # 允许的ip           
    }
    # 代理后端的 404、5xx 到 error_pages
    proxy_intercept_errors on;
 
    # eg:静态资源配置
    # location 后的路径末尾有无斜杠不影响匹配
    location /dist/ {
      expires 365d;
      # root 会将请求路径拼接在末尾
      # alias 会将请求路径部分替换后拼接在末尾
      alias ./html/dist/; # alias 末尾建议要加 /
      index index.html index.htm;
      # 兼容 SPA 路由,使用 try_files 将找不到的文件重定向到 index.html
      try_files $uri $uri/ /dist/index.html;
      # 配置 index.html 禁止缓存,前端打包其他资源注意要加 hash 后缀确保缓存失效
      # 正则使用 [.] 表示'.'而不是'\.',是因为此处反斜杠导致一些编辑器代码高亮异常
      if ($request_filename ~ .*[.](html|htm)?$) {
        add_header Cache-Control "private,no-store,no-cache,must-revalidate,proxy-revalidate";
      }
      # 添加跨域支持
      add_header 'Access-Control-Allow-Origin' '*';
      if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,PATCH,OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Range,Range';
        add_header 'Access-Control-Max-Age' 1728000;
        return 204;
      }
    }
 
    # 配置返回静态 Json 文件示例
    location /getJson/ {
      default_type application/json;
      alias json/;
      add_header Cache-Control no-cache; # 不缓存
    }
	
    # 错误页,可以配置在http块,server块,location块中
    error_page  404              /404.html;
    error_page  500 502 503 504  /500.html;
    location = /50x.html{
      root html;
    }
 
    # eg:反向代理配置
    location /gateway/ {
      proxy_pass http://backend_server;
      # 跨域配置
      if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Max-Age' 3600;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
      }
      add_header 'Access-Control-Allow-Origin' '*';
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
      add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
      add_header 'Access-Control-Allow-Credentials' 'true';
      
      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_set_header X-Forwarded-Proto $scheme;
    }
    
  }
 
  # 负载均衡配置
  upstream backend_server {
  	#ip_hash; # 使用ip_hash
    server 127.0.0.3:8000 weight=5; # 指定权重
    server 127.0.0.3:8001 weight=5;
    server 192.168.0.1:8000;
    server 192.168.0.1:8001;
  }
 
}

location 匹配顺序

参考官方文档:location

location 匹配顺序遵循以下规则:精确 — 前缀 — 正则 — 普通

  1. 精确匹配 (=)
location = /50x.html {
  root   html;
}
  1. 前缀匹配 (^~)
location ^~ /images/ {
  # 匹配所有以 /images/ 开头的请求
}
  1. 正则匹配 (~ 和 ~*)~ 表示区分大小写,~* 表示不区分大小写,按配置文件的书写顺序匹配
location ~ \.php$ {
  proxy_pass   http://127.0.0.1;
}
  1. 普通前缀匹配:会选择匹配最长的
location /doc/ {
  # 匹配所有请求
}
  1. 通用匹配 (/)
location / {
    # 通用匹配,处理所有未匹配到其他 location 的请求
}

root vs alias

root 用于设置请求路径的根目录,默认指向 nginx 安装目录下的 ./html 目录;root 可以出现在 serverlocation 块中,location 中的 root 会覆盖 server 中的 root 配置。nginx 会将请求的 uri 追加到 root 指定的路径后,形成最终的文件路径。例如:

location /static/ {
  root /var/www/app/;
}

如果请求 uri 是 /static/image.png,nginx 会将其映射到 /var/www/app/static/image.png

alias 只能出现在 location 块中,与 root 不同它会替换请求路径中的匹配部分。

location /static/ {
  alias /var/www/app/static/;
}

如果请求 uri 是 /static/image.png,Nginx 会将其映射到 /var/www/app/static/image.png

注意:root 后的路径末尾加不加斜杠行为都一样,alias 路径末尾一定要加 /

重写请求路径

当使用 location 块来匹配特定的路径时,根据配置的不同,可以删除、保留或替换路径中的部分内容。

示例

代理 neo4j

# 代理 neo4j 的基于 TCP 的 bolt 协议,必须使用 stream 模块
stream {
    upstream bolt {
        server   localhost:7687;
    }
    server {
        listen 17687;#对外暴露bolt端口
        proxy_pass bolt; 
    }
}
 
location /neo4j-browser/ {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_buffering off;
    proxy_pass http://localhost:7474/browser/;#页面访问地址
}
location /db/data/ {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_buffering off;
    proxy_pass http://localhost:7474/db/data/;
}

代理 mysql

跨域问题

跨域问题

问题描述

浏览器发送 HTTP 请求,当协议、域名、端口号有任一个与当前不同时,即不满足同源策略,就会存在跨域问题,<img><script>、表单提交操作不受同源策略影响。

同源策略是为了防止本地敏感数据例如 Cookie、Storage 等被恶意脚本访问、修改。例如前端部署在 a.com,代码中引用了 b.com 的 b.js 文件,这个 b.js 文件就无法直接读取 a.com 下的 Cookie 等数据,但也有例外,如 a.com 中暴露了全局变量、b.js 也可以操作页面 DOM。

解决跨域的常用手段有 JSONP,代理方式,CORS。

JSONP

JSONP 利用了 <script> 标签不受同源策略限制的特点,只支持 get 请求且不够灵活,不建议使用。

代理方式

代理方式不是最终手段,只适用于开发阶段或前端资源和代理服务器同源的情况。浏览器请求同源的代理服务器,代理服务器不受同源策略影响,可以正常与后端服务器通信。

CORS

跨源资源共享(CORS,Cross-Origin Resource Sharing),是解决跨域的最根本手段。

浏览器将发送的请求分为两类:简单请求非简单请求

简单请求需要满足以下条件:

  1. 请求方法:只能是 GETHEADPOST 之一;
  2. 请求头:只包含简单头部,即 AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type;
  3. Content-Type:只能是 application/x-www-form-urlencodedmultipart/form-datatext/plain

不满足上述的就是非简单请求,下面是两种请求的不同处理方法:

对于简单请求,且是跨域请求,则自动在请求头添加一个 Origin 字段,来说明本次请求来自的源,服务器根据这个字段与事先的规则配置来判断是否允许这个源。如果允许,则会在响应头上设置 Access-Control-Allow-Origin 等字段,浏览器拿到响应信息后根据这些字段判断根据同源策略是否要滤掉该响应信息。如果服务端不认可这个源,它也会返回正常的响应,只不过没有额外的响应头,浏览器判断后会过滤掉这个响应。

即就是,跨域请求可以被服务器接收并响应,只不过被浏览器过滤了,只有后端配置了 CORS,设置了响应头,浏览器拿到响应进行判断后,予以放行。

对于非简单请求,在第一次跨域请求之前,浏览器会发送一个预检请求(Option 类型),包含了上面提到的 Origin 还有请求方式等信息,作用就是和服务器沟通是否认可这个源,如果服务器认可,则做出响应,浏览器会缓存这个响应的信息,在有效期内,以后发送跨域请求时都不会再额外发送预检请求。

下面是响应头的一些字段说明:

  • Access-Control-Allow-Origin:必须,认可的源,要么等于请求头的 Origin,要么等于 *,表示通通认可。
  • Access-Control-Request-Method:必须,认可的请求方法。
  • Access-Control-Allow-Credentials:可选,表示服务器端是否想要接收 Cookie,需要注意的是,即便设置为 true,浏览器也不一定发送 Cookie,还必须要满足域名匹配,并且再前端发送请求时配置 withCredentials 属性,并且 Access-Control-Allow-Origin 不能配置为 *。
  • Access-Control-Max-Age:可选,指定本次预检请求的缓存有效期。

SpringBoot CORS 配置

  • 配置类方式
@Configuration
public class WebConfig implements WebMvcConfigurer {
 
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 注册一个 CORS 映射
        registry.addMapping("/**") // 所有的请求路径
            .allowedOrigins("*") // 允许所有源
            .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的方法
            .allowedHeaders("*") // 允许所有的请求头
            .allowCredentials(true) // 是否允许携带凭证(Cookie等)
            .maxAge(3600); // 预检请求的有效期
    }
}
  • 配置文件方式
spring:
  mvc:
    cors:
      mappings:
        "/**":
          allowed-origins: "*"
          allowed-methods: "GET,POST,PUT,DELETE,OPTIONS"
          allowed-headers: "*"
          allow-credentials: true
          max-age: 3600

Nginx CORS 配置

注意,不仅 options 请求要设置 cors 响应头,实际请求也需要设置。

  • 静态资源跨域配置(添加到 location 块中)
# 添加跨域支持
add_header 'Access-Control-Allow-Origin' '*';
if ($request_method = 'OPTIONS') {
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Credentials' 'true';
  add_header 'Access-Control-Allow-Methods' 'GET,POST,PUT,DELETE,PATCH,OPTIONS';
  add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Range,Range';
  add_header 'Access-Control-Max-Age' 1728000;
  return 204;
}
  • 后端接口代理跨域配置(添加到 location 块中)
location / {
  if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Allow-Origin' '*';
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
      add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
      add_header 'Access-Control-Allow-Credentials' 'true';
      add_header 'Access-Control-Max-Age' 3600;
      add_header 'Content-Type' 'text/plain; charset=utf-8';
      add_header 'Content-Length' 0;
      return 204;
  }
 
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
  add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
  add_header 'Access-Control-Allow-Credentials' 'true';
 
  proxy_pass http://backend_server;
  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_set_header X-Forwarded-Proto $scheme;
}
指向原始笔记的链接