F5社区好文推荐: 理解Nginx之反向代理_java

作者| 李煜峰| F5 研发实习




F5

概述

反向代理是指使用代理服务器来接受客户端的请求,然后转发给内网上游web服务器集群,之后再将上游web服务器的响应转发给客户端。在这个过程中,代理服务器对于客户端来说就相当于 web 服务器,上游web服务器对客户端来说是透明的。具体处理流程如图1 所示。

 F5社区好文推荐: 理解Nginx之反向代理_java_02

图1 Nginx反向代理服务器处理客户端请求流程

Nginx 虽然本身支持高并发,而且性能很强劲,但是处理复杂的业务逻辑并不是它的强项。这时会将 Nginx 用作反向代理服务器去接收高并发的请求,然后将请求转发给 Apache 等 Web 服务器去处理。那么如何使用 Nginx 反向代理这一功能呢,下面我们将介绍一些基本的配置方法。


F5

proxy_pass

首先是 Nginx 反向代理的最基本的配置项proxy_pass。

语法:proxy_pass URL;

默认值:无

配置块:location, if in location, limit_except

proxy_pass配置项可以将用户的请求转发给 URL 指定的服务器,其形式有以下几种:

1.IP或主机名+端口号

proxy_pass http://localhost:8001/uri/;

2.https

proxy_pass https://localhost:8001/uri/;

3.UNIX 句柄

proxy_pass http://unix:/tmp/backend.socket:/uri/;(官方写法,尚待验证)

4.upstream

upstream 和前几个用法都不相同,它是定义一个服务器组,然后Nginx 代理服务器根据默认或指定的负载均衡算法来选择其中之一进行转发。

用法:

   upstream my_upstream { 
        server localhost:8001; 
        server localhost:8002; 
   }
   server {
          location / {
                 proxy_pass http://my_upstream;
           }
   }

如果要指定 URI,则可写成如下格式:

proxy_pass http://my_upstream/uri/;

upstream默认负载均衡算法是轮询,除此之外还有根据权重进行轮询的weight,根据用户 IP 哈希选择服务器的 ip_hash等算法。

更加详细的用法可以参考官方文档 

http://nginx.org/en/docs/http/ngx_http_upstream_module.html#upstream


F5

proxy_pass 地址末尾斜杠的影响

在 Nginx 的配置过程中,地址末尾的斜杠的有时会让人摸不着头脑,但实际上真正造成 proxy_pass代理规则不同的是末尾有无 uri,而不是有无斜杠。也就是说 proxy_pass可以分为两种情况:一种是只有ip+port,比如http://localhost:8080;另一种是除了 ip+port外,还有资源标识符uri,包括单一的一个斜杠,比如 localhost:8000/ 和localhost:8000/test 都属于这种情况。

F5社区好文推荐: 理解Nginx之反向代理_java_03

图2 proxy_pass的两种形式

下面我们将通过 8 个小例子详细分析一下 proxy_pass 的这两种形式。

在看例子之前,要明确的一点是location 默认的匹配规则是前缀匹配,和^~类似,但是它的优先级在正则表达式之后。

1. proxy_pass的url只有ip+port,此时将保留 location 中的 uri

比如:

   location /proxy1 {
          proxy_pass http://localhost:8001;
   }

当访问http://localhost/proxy1/my_web/ ,将被代理为 http://localhost:8001/proxy1/my_web/。其最终代理url 生成规则为:proxy_pass的url + 访问地址的 uri,也就是 http://localhost:8001 + /proxy1/my_web/ 。

   location /proxy2/ {
          proxy_pass http://localhost:8001;
   }

http://localhost/proxy2/my_web/ ->  http://localhost:8001/proxy2/my_web/,这种情况最终代理url是由http://localhost:8001 + /proxy2/my_web/ 组装而成,效果和上一个例子相同。

2. proxy_pass 的url末尾带 uri,匹配到的location中的uri会被去除掉

   location /proxy3 {
          proxy_pass http://localhost:8001/;
   }

http://localhost/proxy3/my_web/ ->  http://localhost:8001//my_web/,http://localhost:8001/ + /my_web/,根据构造规则,端口号后产生两个斜杠,但此时是依然能正确访问的。

   location /proxy4/ {
          proxy_pass http://localhost:8001/;
   }

http://localhost/proxy4/my_web/ ->  http://localhost:8001/my_web/,http://localhost:8001/ + my_web/ 。

   location /proxy5 {
          proxy_pass http://localhost:8001/test;
   }

http://localhost/proxy5/my_web/ -> http://localhost:8001/test/my_web/,http://localhost:8001/test + /my_web/ 。

   location /proxy6/ {
          proxy_pass http://localhost:8001/test;
   }

http://localhost/proxy6/my_web/ ->  http://localhost:8001/testmy_web/,http://localhost:8001/test + my_web/,此情况容易产生 404错误,需要注意。

   location /proxy7 {
          proxy_pass http://localhost:8001/test/;
   }

http://localhost/proxy7/my_web/ ->  http://localhost:8001/test//my_web/,http://localhost:8001/test/ + /my_web/,此时类似例3 产生了两个斜杠。

   location /proxy8/ {
          proxy_pass http://localhost:8001/test/;
   }

http://localhost/proxy8/my_web/ ->   http://localhost:8001/test/my_web/,http://localhost:8001/test/ + my_web/

总的来说,当 proxy_pass 的url末尾带 uri 时,会先将访问网址的uri去掉 location 匹配的部分,然后拼装在 proxy_pass的 url 后面;当proxy_pass的 url 末尾不带uri时,会将访问网址的 uri 直接拼装在 proxy_pass的 url 后面。

刚开始在配置 proxy_pass时,被斜杠搞的晕头转向,一度觉得这是个玄学配置项。后来查看了一些资料和文档,反复做了一些实验之后,发现还是有迹可循的,这样一下子就清晰了。


F5

重定向和proxy_redirect

当上游 web服务器返回的是重定向(HTTP 状态码为 301 或 302)或刷新请求时,proxy_redirect可以修改其 HTTP 头部的location 或者 refresh 字段来满足我们的需要。

语法:

proxy_redirect default;

proxy_redirect off;

proxy_redirect redirect replacement;

默认:proxy_redirect default;

配置块:http, server, location


F5社区好文推荐: 理解Nginx之反向代理_java_04

图3 Nginx配置项proxy_redirect处理过程

比如proxy_redirect配置为proxy_redirect http://localhost:8000/two/ http://frontend/one/;

Location: http://localhost:8000/two/some/uri/ 将被修改为 Location: http://frontend/one/some/uri/”

当使用默认配置时,会根据当前location配置项和 proxy_pass 的配置重组来修改 location 的值。以下两种形式是等效的。

   location /one/ {
   proxy_pass     http://upstream:port/two/;
   proxy_redirect default;
   }
  
   location /one/ {
   proxy_pass     http://upstream:port/two/;
   proxy_redirect http://upstream:port/two/ /one/;;
   }

Nginx 资源缺失时的重定向:

当Nginx 服务器接收到的url 为 http://ip:port/my_web时,会先在html目录下寻找my_web文件,如果没有 my_web文件但是有名为my_web的目录,则会返回 301 重定向状态码,并且设置响应头字段 location 的值为 host_ip:server_port/my_web/ 。如果此时我们在 nginx 和服务器之间做了端口映射,那么 location 中的端口号对客户端来说是无效的,就会造成客户端无法访问重定向后的网址。此时则可以使用 proxy_redirect来修改 location 的内容。

下面我们来实验一下这个过程。

实验环境是centos容器,宿主机的 ip 为10.250.16.146,端口映射关系为 8080:80。

配置代码:

server {

        listen       80;

        server_name  localhost;

        location /proxy/ {

          proxy_pass http://localhost:8001/;

           proxy_redirect off;

           #proxy_redirect default;

      #proxy_redirect http://localhost:8001/ /proxy/;

      #proxy_redirect http://localhost:8001/ http://$http_port/proxy/; #使用 nginx 变量$http_port来获取请求HTTP的ip 和端口号

        }

        location / { 

         }

}

server {

        listen       8001;

        server_name  10.250.16.146:8080;

        location / {

        }

}

可上下滑动查看全部内容

如代码所示,我们实验了四种 proxy_redirect配置,其实验结果如下:

1. proxy_redirect off;

F5社区好文推荐: 理解Nginx之反向代理_java_05

2. proxy_redirect default;

F5社区好文推荐: 理解Nginx之反向代理_java_06

3. proxy_redirect http://localhost:8001/ /proxy/;

F5社区好文推荐: 理解Nginx之反向代理_java_07

4.proxy_redirect http://localhost:8001/  http://$http_port/proxy/;

F5社区好文推荐: 理解Nginx之反向代理_java_08

其中第四种能够达到我们的效果,第二和第三种效果是相同的。