part.4.2 反向代理

反向代理模块

proxy模块

proxy 模块接收客户端的请求是 http 协议,转发给上游的还是 http 协议。

http 反向代理流程
1-> 处理 content 阶段:proxy_pass 指令
2-> 判断 cache 是否命中,如果 cache 命中,则直接到15
3-> cache 未命中或者未开启 cache
4-> 根据指令生成发往上游的 http 头部及包体
5-> 这时请求中可能含有 body,但这时还没有接收 body,那么就需要根据 proxy_request_buffering 指令判断
6-> 如果配置为 proxy_request_buffering off,则直接进入8
7-> 如果配置为 proxy_request_buffering on,就读取请求的完整包体
8-> 根据负载均衡策略选择上游服务器
9-> 根据参数连接上游服务器
10-> 发送请求(边读包体边发)
11-> 接收响应头部
12-> 处理响应头部
13-> 判断指令 proxy_buffering 设置的值,如果为 off 则进入15
14-> 如果指令 proxy_buffering 设置的值为 on 则接收完整的响应包体
15-> 发送响应头部
16-> 发送响应包体(边读包体边发)
17-> 如果关闭 cache,则进入19
18-> 如果是打开 cache,则包体加入缓存
19-> 关闭或复用连接

proxy 模块中的 proxy_pass 指令

对上游服务使用 http/https 协议进行反向代理。默认编译进 nginx,通过 --without-http_proxy_module 禁用

Syntax: proxy_pass URL;
Default: --
Context: location, if in location, limit_except
URL 参数规则
  • URL 必须以 http:// 或者 https:// 开头,接下来是域名IPunix socket地址 或者 upstream的名字,前两者可以在域名或者 IP 后加端口。最后是可选的 URI
  • 当 URL 参数中携带 URI 与否,会导致发向上游请求的 URL 不同:
    • 不携带 URI,则将客户端请求中的 URL 直接转发给上游
      • location 后使用正则表达式、@ 名字时,应采用这种方式
    • 携带 URI,则对用户请求中的 URL 作如下操作:
      • 将 location 参数中匹配上的一段替换为该 URI
  • 该 URL 参数可以携带变量
  • 更复杂的 URL 替换,可以在 location 内的配置添加 rewrite break 语句
示例
#上游服务器配置:
server {
    listen 8011;
    default_type text/plain;
    return 200 '8011 server response.\n';
}

server {
    listen 8012;
    default_type text/plain;
    #client_body_in_single_buffer on;
    return 200 '8012 server response.uri: $rui\n';
}

#反向代理服务器配置(为了对比测试,所以有重复定义)
upstream proxyups {
    server 127.0.0.1:8012 weight=1;
}
server {
    server_name proxy.taohui.tech;
    error_log myerror.log info;
    access_log logs/upstream_access.log varups;
    
    location /a {
        proxy_pass http://proxyups/addurl;   #1
        proxy_pass http://proxyups;    #2
        #proxy_method POST;
        proxy_pass_request_headers off;
        #proxy_pass_request_body off;
        proxy_set_body 'hello world!';
        proxy_set_header name '';
        proxy_http_version 1.1;
        proxy_set_header Connection ""; 
    }
}

如果#1注释保留#2,访问此服务:
curl proxy.taohui.tech/a/b/c    #/a/b/c
如果#2注释保留#1,访问此服务:
curl proxy.taohui.tech/a/b/c    #/addurl/b/c

根据指令修改发往上游的请求

生成发往上游的请求行
Syntax: proxy_method method;
Default: --
Context: http, server, location

Syntax: proxy_http_version 1.0|1.1
Default: proxy_http_version 1.0
Context: http, server, location
生成发往上游的请求头部
#value的值为空字符串,则整个header都不会向上游发送
Syntax: proxy_set_header field value;
Default: proxy_set_header Host $proxy_host;
         proxy_set_header Connection close;
Context: http, server, location

Syntax: proxy_pass_request_headers on|off
Default: proxy_pass_request_headers on
Context: http, server, location

接收用户请求包体的方式

Syntax: proxy_request_buffering on|off
Default: proxy_request_buffering on;
Context: http, server, location

通过设置 proxy_request_buffering 来控制接收和转发的方式,如果设置为 on,则是接收完包体才会转发;如果设置为 off,则是边接收边转发。

当客户端网速较慢,上游服务并发处理能力低,高吞吐量场景等情况下适合设置为 on。因为设置为 on 后,非常依赖 nginx 的处理能力,而 nginx 处理能力本身处理能力非常的强。

设置为 off,能更及时的相应,降低 nginx 读写磁盘的消耗。但是有一点,一旦开始发送内容,则 proxy_next_upstream 功能失效(body 未接收完毕,已经开始转发内容,所以此功能失效)。

客户端包体的接收

当存在包体时,接收包体所分配的内存:

  • 若接收头部时已经接收完全部包体,则不分配;
  • 若剩余待接收包体的长度小于 client_body_buffer_size,则仅分配所需大小
  • 分配 client_body_buffer_size 大小内存接收包体
    • 关闭包体缓存时,该内存上内容及时发送给上游
    • 开启包体缓存时,该段大小内存用完时,写入临时文件,释放内存
Syntax: client_body_buffer_size size
Default: client_body_buffer_size 8k|16k
Context: http, server, location

#请求的 body 全部放在内存中,但是有很多依赖要求
Syntax: client_body_in_single_buffer on|off
Default: client_body_in_single_buffer off
Context: http, server, location
最大包体长度限制

仅对请求头部中含有 Content-Length 有效,超出最大长度后,返回413错误

Syntax: client_max_body_size size;
Default: client_max_body_size 1m;
Context: http, server, location
临时文件路径格式
Syntax: client_body_temp_path path [level1 [level2 [level3]]];
Default: client_body_temp_path client_body_temp;
Context: http, server, location

#包体存放在文件中,为定位问题而生的,生产环境禁用
#on -- 所有请求的 body 都写入文件中
#clean -- 所有请求的 body 写入文件中,请求完成即删除
#off -- 当请求小于设置的内存大小时,不存储文件中,即使存储到文件中也会在请求完成后删除
Syntax: client_body_in_file_only on | clean | off;
Default: client_body_in_file_only off;
Context: http, server, location
读取包体时的超时
#读取包体超出设置的时间时返回408错误
Syntax: client_body_timeout time
Default: client_body_timeout 60s
Context: http, server, location 

与上游服务建立连接

#tcp 连接超时时间,如果超时则返回502,如果返回502说明 nginx 没有与上游服务器建立成功连接
Syntax: proxy_connect_timeout time;
Default: proxy_connect_timeout 60s;
Context: http, server, location

#后跟错误码,可以在返回响应错误码时执行一些指令(如换一台上游服务器)
Syntax: proxy_next_upstream http_502 | ..;
Default: proxy_next_upstream error timeout;
Context: http, server, location
上游连接启动 TCP keepalive

TCP 的 keepalive,当数据传输完毕的时候,经过一段时间,没有数据传输,就发送一个探测包进行探测,看一看对端是否还在建立连接,然后返回一个探测包的应答。

TCP 层是由操作系统实现的,也就是说只要进程还在,只要客户端和服务器之间的网络是正常的,那么发探测包,探测包就会有应答。如果客户端的进程已经结束或者找不到客户端,则不会有探测包应答,这样就可以把这个连接关闭了。

TCP 的 keepalive 就是把一些不再使用的连接及时的关闭,来降低资源的浪费。

Syntax: proxy_socket_keepalive on | off;
Default: proxy_socket_keepalive off;
Context: http, server, location
上游连接启动 HTTP keepalive
Syntax: keepalive connections;
Default: --
Context: upstream

Syntax: keepalive_requests number;
Default: keepalive_requests 100;
Context: upstream
修改 TCP 连接中的 local address
Syntax: proxy_bind address [transparent] | off;
Default: --
Context: http, server, location

可以使用变量,如 proxy_bind $remote_addr; 可以使用不属于所在机器的 IP 地址,如 proxy_bind $remote_addr transparent

当客户端关闭连接时
#如果客户端连接失败,nginx 与上游服务的连接是否忽略这个错误,如果忽略(on),则会给上游服务增加一些压力
Syntax: proxy_ignore_client_abort on|off
Default: proxy_ignore_client_abort off
Context: http, server, location
向上游发送 HTTP 请求
Syntax: proxy_send_timeout time;
Default: proxy_send_timeout 60s;
Context: http, server, location

接收上游的响应

接收上游的http响应头部
#如果超出则会在日志中记录错误
#error.log: upstream send too big header
Syntax: proxy_buffer_size size;
Default: proxy_buffer_size 4k|8k;
Context: http, server, location
接收上游的HTTP包体
#这段内存能存放下 http 包体时,就不会向磁盘中写入包体
Syntax: proxy_buffers number size
Default: proxy_buffers 8 4k|8k
Context: http, server, location

#是否接收完整的响应包体,如果客户端的头部中有 X-Accel-Buffering,即可将指令设置为客户端传输的值(客户端传输的为yesno
Syntax: proxy_buffering on|off
Default: proxy_buffering on
Context: http, server, location

#写入磁盘中最大限制
Syntax: proxy_max_temp_file_size size;
Default: proxy_max_temp_file_size 1024m
Context: http, server, location

#一次向磁盘文件中写入的字节数
Syntax: proxy_temp_file_write_size size
Default: proxy_temp_file_write_size 8k|16k
Context: http, server, location

#设定存放临时文件的目录和子目录层级
Syntax: proxy_temp_path path [level1 [level2 [level3]]]
Default: proxy_temp_path proxy_temp
Context: http, server, location
及时转发包体
#虽然缓存所有的响应了,但是希望更及时的向客户端发送部分响应
Syntax: proxy_busy_buffers_size size;
Default: proxy_busy_buffers_size 8k|16k
Context: http, server, location
接收上游时网络速度相关指令
#两次之间超时时间
Syntax: proxy_read_timeout time;
Default: proxy_read_timeout 60s;
Context: https, server, location

#限制读取上游响应的速度
Syntax: proxy_limit_rate tate;
Default: proxy_limit_rate 0;
Context: http, server, location
上游包体的持久化
#缓存到本地硬盘的文件的权限
Syntax: proxy_store_access users:permissions ...;
Default: proxy_store_access user:rw;
Context: http, server, location

#直接把静态文件在本地硬盘创建并读取 
Syntax: proxy_store on|off|string
Default: proxy_store off;
Context: http, server, location
示例
upstream proxyups {
    server 127.0.0.1:8012 weight=1;
}
server {
    server_name store.taohui.tech;
    error_log logs/myerror.log debug;
    root /tmp;
    
    location / {
        proxy_pass http://proxyups;
        proxy_store on;
        proxy_store_access user:rw group:rw all:r;
    }
    listen 80; #managed by Certbot
}


curl store.taohui.tech/a.txt  #/tmp/a.txt文件生成,并且内容为a.txt保存的内容

处理上游的响应头部

禁用上游响应头部的功能
Syntax: proxy_ignore_headers field ...;
Default: --
Context: http, server, location

某些响应头部可以改变 nginx 的行为,使用 proxy_ignore_headers 可以禁止它们生效。

可以禁用功能的头部
  • X-Accel-Redirect -- 由上游服务指定在 nginx 内部重定向,控制请求的执行
  • X-Accel-Limit-Rate -- 由上游设置发往客户端的速度限制,等同 limit_rate 指令
  • X-Accel-Buffering -- 由上游控制是否缓存上游的响应
  • X-Accel-Charset -- 由上游控制 Content-Type 中的 `Charset
  • 缓存相关:
    • X-Accel-Expires -- 设置响应在 nginx 中的缓存时间,单位秒;@ 开头表示一天内某时刻
    • Expires -- 控制 nginx 缓存时间,优先级低于 X-Accel-Expires
    • Cache-Control -- 控制 nginx 缓存时间,优先级低于 X-Accel-Expires
    • Set-Cookie -- 响应中出现 Set-Cookie 则不缓存,可通过 proxy_ignore_headers 禁止生效
    • Vary -- 响应中出现 Vary:* 则不缓存,同样可禁止生效
转发上游的响应:proxy_hide_header 指令
Syntax: proxy_hide_header field;
Default: --
Context: http, server, location

proxy_hide_header 指令对应上游响应中的某些头部,设置不向客户端转发。默认不转发的响应头部有:

  • Date -- 由 ngx_http_header_filter_module 过滤模块填写,值为 nginx 发送响应头部时的时间
  • Server -- 有 ngx_http_header_filter_module 过滤模块填写,值为 nginx 版本
  • X-Pad -- 通常是 Apache 为避免浏览器 BUG 生成的头部,默认忽略
  • X-Accel -- 用于控制 nginx 行为的响应,不需要向客户端转发
proxy_pass_header 指令
Syntax: proxy_pass_header field;
Default: --
Context: http, server, location

对于已经被 proxy_hide_header 的头部,设置向上游转发

Syntax: proxy_cookie_domain off;
        proxy_cookie_domain domain replacement;
Default: proxy_cookie_domain off;
Context: http, server, location

Syntax: proxy_cookie_path off;
        proxy_cookie_path path replacement;
Default: proxy_cookie_path off;
Context: http, server, location 
修改返回的 Location 头部
Syntax: proxy_redirect default;
        proxy_redirect off;
        proxy_redirect redirect replacement;
Default: proxy_redirect default;
Context: http, server, location

上游出现失败时的容错方案

此处理方法的前提示没有向客户端发送任何内容。

Syntax:  proxy_next_upstream error | timeout | 
        invalid_header | http_500 | http_502 | http_503 |
        http_504 | http_403 | http_404 | http_429 |
        non_idempotent | off ...;
Default: proxy_next_upstream error timeout;
Context: http, server, location
参数解释
  • error -- 与上游建立连接,读取响应,发送请求等等这些过程中发生错误,都会使 error 生效。这些错误主要指网络错误。
  • timeout -- 超时
  • invalid_header -- 收到的上游服务 http header 不合法
  • http_ -- 后跟一个明确的响应状态码
  • non_diempotent -- 接收了禁用的 method 方式发来的请求
  • off -- 关闭这个功能
限制 proxy_next_upstream 的时间与次数
Syntax: proxy_next_upstream_timeout time
Default: proxy_next_upstream_timeout 0
Context: http, server, location

Syntax: proxy_next_upstream_tries number
Default: proxy_next_upstream_tries 0
Context: http, server, location
用 error_page 拦截上游失败响应

当上游响应的响应码大于等于300时,应将响应返回客户端还算按 error_page 指令处理

Syntax: proxy_intercept_errors on|off
Default: proxy_intercept_errors off
Context: http, server, location
Etag 头部

ETagHTTP 响应头是资源的特定版本的标识符。这可以让缓存更高效,并节省带宽,因为如果内容没有改变,web 服务器不需要发送完整的响应。而如果内容发生了变化,使用 ETag 有助于防止资源的同时更新相互覆盖(空中碰撞)。

如果给定 URL 中的资源更改,则一定要生成新的 Etag 值。因此 Etags 类似于指纹,也可能被某些服务器用于跟踪。比较 Etags 能快速确定此资源是否变化,但也可能被跟踪服务器永夂存留。

W/ (大小写敏感)表示使用弱验证器。弱验证器很容易生成,但不利于比较。强验证器是比较的理想选择,但很难有效地生成。相同资源的两个弱 Etag 值可能语义等同,但不是每个字节都相同。

Syntax: etag on | off
Default: etag on
Context: http, server, location

生成规则为:

ngx_sprintf(etag->value.data,"\"%xT-%xO\"",
            r->headers_out.last_modufied_time,
            r->headers_out.content_length_n)
If-None-Match 头部

If-None-Match 是一个条件式请求首部。对于 GET 和 HEAD 请求方法来说,当且仅当服务器上没有任何资源的 ETag 属性值与这个首部中列出的相匹配的时候,服务器端会才返回所请求的资源,响应码为200。对于其他方法来说,当且仅当最终确认没有已存在的资源的 ETag 属性值与这个首部中所列出的相匹配的时候,才会对请求进行相应的处理。

对于 GET 和 HEAD 方法来说,当验证失败的时候,服务器端必须返回响应码304(Not modified,未改变)。对于能够引发服务器状态改变的方法,则返回412(Precondition failed,前置条件失败)。需要注意的是,服务器端在生成状态码为304的响应的时候,必须同时生成以下会存在于对应的200响应中的首部: Cache-Control、 Content-LocationDateETagExpires 和 vary

ETag 属性之间的比较采用的是弱比较算法,即两个文件除了每个比特都相同外,内容一致也可以认为是相同的。例如,如果两个页面仅仅在页脚的生成时间有所不同,就可以认为二者是相同的当与 If-Modified-Since 一同使用的时候,If-None-Match 优先级更高(假如服务器支持的话)。以下是两个常见的应用场景:

  • 采用 GET 或 HEAD 方法,来更新拥有特定的 ETag 属性值的缓存。
  • 采用其他方法,尤其是 PUT,将 If-None-Match used 的值设置为 *,用来生成事先并不知道是否存在的文件,可以确保先前并没有进行过类似的上传操作,防止之前操作数据的丢失。这个问题属于更新丢失问题的一种。
If-Modified-Since 头部

If-Modified-Since 是一个条件式请求首部,服务器只在所请求的资源在给定的日期时间之后对内容进行过修改的情况下才会将资源返回,状态码为200。如果请求的资源从那时起未经修改,那么返回一个不带有消息主体的304响应,而在 Last- Modified 首部中会带有上次修改时间。不同于 IE-Unmodified-Since, If-Modified-Since 只可以用在 GET 或 HEAD 请求中。

当与 If-None-Match 一同出现时,它(If-Modified- Since)会被忽略掉,除非服务器不支持 If-None-Match

If-Match 头部

请求首部 If-Match 的使用表示这是一个条件请求。在请求方法为 GET 和 HEAD 的情况下,服务器仅在请求的资源满足此首部列出的 ETag 之一时才会返回资源。而对于 PUT 或其他非安全方法来说,只有在满足条件的情况下才可以将资源上传。

The comparison with the stored ETag 之间的比较使用的是强比较算法,即只有在每一个比特都相同的情况下,才可以认为两个文件是相同的。在 ETag 前面添加 W/ 前缀表示可以采用相对宽松的算法。

以下是两个常见的应用场景

  • For Get 和 HEAD 方法,搭配 Range 首部使用,可以用来保证新请求的范围与之前请求的范围是对同一份资源的请求。如果 ETag 无法匹配,那么需要返回416(Range Not Satisfiable,范围请求无法满足)响应。
  • 对于其他方法来说,尤其是 PUTIf-Match 首部可以用来避免更新丢失问题。它可以用来检测用户想要上传的不会覆盖获取原始资源之后做出的更新。如果请求的条件不满足,那么需要返回412(Precondition Failed,先决条件失败)响应。
If-Unmpdified-Since 头部

HTTP 协议中的 If-Unmodified-Since 消息头用于请求之中,使得当前请求成为条件式请求:只有当资源在指定的时间之后没有进行过修改的情况下,服务器才会返回请求的资源,或是接受 POST 或其他 non-safe 方法的请求。如果所请求的资源在指定的时间之后发生了修改,那么会返回412(Precondition Failed)错误。常见的应用场景有两种

与 non-safe 方法如 POST 搭配使用,可以用来优化并发控制,例如在某些 wiki 应用中的做法:假如在原始副本获取之后,服务器上所存储的文档已经被修改,那么对其作出的编辑会被拒绝提交。

与含有 If-Range 消息头的范围请求搭配使用,用来确保新的请求片段来自于未经修改的文档。

X-Accel-Expires 头部
Syntax: X-Accel-Expires [offseconds]
Default: X-Accel-Expires off

从上游服务定义缓存时间:0表示不缓存当前响应;@前缀表示缓存到当天的某个时间;

Vary 头部

Vary 是一个 HTTP 响应头部信息,它决定了对于未来的一个请求头,应该用一个缓存的回复(response)还是向源服务器请求一个新的回复。它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers)。

在响应状态码为 304 Not Modified 的响应中,也要设置 Vary 首部,而且要与相应的 200 OK 响应设置得一模一样。

  • Vary:*
    • 所有的请求都被视为唯一并且非缓存的,使用 Cache-Controlprivate,来实现则更适用,这样用于说明不存储该对象更加清晰
    • 若没有通过 proxy_ignore_headers 设置忽略,则不缓存响应
  • Vary:<header-name>,<header-name>,...
    • 逗号分隔的一系列 http 头部名称,用于确定缓存是否可用
Set-Cookie:<cookie-name>=<cookie-value>
Set-Cookie:<cookie-name>=<cookie-value>;Expires=<date>
Set-Cookie:<cookie-name>=<cookie-value>;Max-Age=<non-zero-digit>
Set-Cookie:<cookie-name>=<cookie-value>;Domain=<domain-value>
Set-Cookie:<cookie-name>=<cookie-value>;Path=<path-value>
Set-Cookie:<cookie-name>=<cookie-value>;Secure
Set-Cookie:<cookie-name>=<cookie-value>;HttpOnly

Set-Cookie:<cookie-name>=<cookie-value>;SameSite=Strict
Set-Cookie:<cookie-name>=<cookie-value>;SameSite=Lax

Set-Cookie:<cookie-name>=<cookie-value>;Domain=<domain-value>;Secure;HttpOnly

若 Set-Cookie 头部没有被 proxy_ignore_headers 设置忽略,则不对响应进行缓存

uwsgi、fastcgi、scgi指令的对照表


七层反向代理对照:构造请求内容
\uwsgi反向代理fastcgi反向代理scgi反向代理http反向代理
指定上游uwsgi_passfastcgi_passscgi_passproxy_pass
是否传递请求头部uwsgi_pass_request_headersfastcgi_pass_request_headersscgi_pass_request_headersproxy_pass_request_headers
是否传递请求包体uwsgi_pass_request_bodyfastcgi_pass_request_bodyscgi_pass_request_bodyproxy_pass_request_body
指定请求方法名proxy__method
指定请求协议proxy_http_version
增、改请求头部proxy_set_header
设置请求包体proxy_set_body
是否缓存请求包体uwsgi_request_bufferingfastcgi_request_bufferingscgi_request_bufferingproxy_request_buffering
七层反向代理对照:建立连接并发送请求
\uwsgi反向代理fastcgi反向代理scgi反向代理http反向代理
连接上游超时时间uwsgi_connect_timeoutfastcgi_connect_timeoutscgi_connect_timeoutproxy_connect_timeout
连接绑定地址uwsgi_bindfastcgi_bindscgi_bindproxy_bind
使用TCPkeepaliveuwsgi_socket_keepalivefastcgi_socket_keepalivescgi_socket_keepaliveproxy_socket_keepalive
忽略客户端关连接uwsgi_ignore_client_abortfastcgi_ignore_client_abortscgi_ignore_client_abortproxy_ignore_client_abort
设置HTTP头部用到的哈希表proxy_headers_hash_bucket_size
设置HTTP头部用到的哈希表proxy_headers_hash_max_size
发送请求超时时间uwsgi_send_timeoutscgi_send_timeoutfastcgi_send_timeoutproxy_send_timeout
七层反向代理对照:接收上游响应
\uwsgi反向代理fastcgi反向代理scgi反向代理http反向代理
是否缓存上游响应uwsgi_bufferingfastcgi_bufferingscgi_bufferingproxy_buffering
存放上游响应的目录uwsgi_temp_pathfastcgi_temp_pathscgi_temp_pathproxy_temp_path
写文件缓存大小uwsgi_temp_file_write_sizefastcgi_temp_file_write_sizescgi_temp_file_write_sizeproxy_temp_file_write_size
临时文件最大大小uwsgi_max_temp_file_sizefastcgi_max_temp_file_sizescgi_max_temp_file_sizeproxy_max_temp_file_size
接收响应头部缓存uwsgi_buffer_sizefastcgi_buffer_sizescgi_buffer_sizeproxy_buffer_size
缓存完成前转发包体uwsgi_busy_buffers_sizefastcgi_busy_buffers_sizescgi_busy_buffers_sizeproxy_busy_buffers_size
持久化包体文件uwsgi_storefastcgi_storescgi_storeproxy_store
设定包体文件权限uwsgi_store_accessfastcgi_store_accessscgi_store_accessproxy_store_access
读取响应超时时间uwsgi_read_timeoutfastcgi_read_timeoutscgi_read_timeoutproxy_read_timeout
读取响应限速uwsgi_limit_ratefastcgi_limit_ratescgi_limit_rateproxy_limit_rate
七层反向代理对照:转发响应
\uwsgi反向代理fastcgi反向代理scgi反向代理http反向代理
减少发向客户端的响应头部uwsgi_hide_headerfastcgi_hide_headerscgi_hide_headerproxy_hide_header
禁用响应头部功能uwsgi_ignore_headersfastcgi_ignore_headersscgi_ignore_headersproxy_ignore_headers
替换Set-Cookie头部中的域名proxy_cookie_domain
替换Set-Cookie头部中的URLproxy_cookie_path
修改重定向响应中Location的值proxy_redirect
传递头部到客户端uwsgi_pass_headerfastcgi_pass_headerscgi_pass_headerproxy_pass_header
出错时更换上游uwsgi_next_upstreamfastcgi_next_upstreamscgi_next_upstreamproxy_next_upstream
更换上游超时uwsgi_next_upstream_timeoutfastcgi_next_upstream_timeoutscgi_next_upstream_timeoutproxy_next_upstream_timeout
更换上游重试次数uwsgi_next_upstream_triesfastcgi_next_upstream_triesscgi_next_upstream_triesproxy_next_upstream_tries
拦截上游错误响应uwsgi_intercept_errorsfastcgi_intercept_errorsscgi_intercept_errorsproxy_intercept_errors
七层反向代理对照:SSL
\uwsgi反向代理fastcgi反向代理scgi反向代理http反向代理
配置用于上游通讯的证书uwsgi_ssl_certificateproxy_ssl_certificate
配置用于上游通讯的私钥uwsgi_ssl_certificate_keyproxy_ssl_certificate_key
指定安全套件uwsgi_ssl_ciphersproxy_ssl_ciphers
指定吊销证书CRL文件验证上游的证书uwsgi_ssl_crlproxy_ssl_crl
指定域名验证上游证书中的域名uwsgi_ssl_nameproxy_ssl_name
当私钥有密码时指定密码文件uwsgi_ssl_password_fileproxy_ssl_password_file
指定具体某个版本的协议uwsgi_ssl_protocolsproxy_ssl_protocols
传递SNI信息至上游uwsgi_ssl_server_nameproxy_ssl_server_name
是否重用SSL连接uwsgi_ssl_session_reuseproxy_ssl_session_reuse
验证上游服务的证书uwsgi_ssl_trusted_certificateproxy_ssl_trusted_certificate
是否验证上游服务的证书uwsgi_ssl_verifyproxy_ssl_verify
设置验证证书链的深度uwsgi_ssl_verify_depthproxy_ssl_verify_depth
七层反向代理对照:缓存类指令
\uwsgi反向代理fastcgi反向代理scgi反向代理http反向代理
指定共享内存uwsgi_cachefastcgi_cachescgi_cacheproxy_cache
缓存文件存放位置uwsgi_cache_pathfastcgi_cache_pathscgi_cache_pathproxy_cache_path
指定哪些请求不使用缓存uwsgi_cache_bypassfastcgi_cache_bypassscgi_cache_bypassproxy_cache_bypass
开启子请求更新旧缓存uwsgi_cache_background_updatefastcgi_cache_background_updatescgi_cache_background_updateproxy_cache_background_update
定义缓存关键字uwsgi_cache_keyfastcgi_cache_keyscgi_cache_keyproxy_cache_key
使用range协议的偏移uwsgi_cache_max_range_offsetfastcgi_cache_max_range_offsetscgi_cache_max_range_offsetproxy_cache_max_range_offset
缓存哪些请求方法uwsgi_cache_methodsfastcgi_cache_methodsscgi_cache_methodsproxy_cache_methods
多少请求后再缓存uwsgi_cache_min_usesfastcgi_cache_min_usesscgi_cache_min_usesproxy_cache_min_uses
缓存哪些响应及时长uwsgi_cache_validfastcgi_cache_validscgi_cache_validproxy_cache_valid
强制使用range协议uwsgi_force_rangesfastcgi_force_rangesscgi_force_rangesproxy_force_ranges
有陈旧内容使用304uwsgi_cache_revalidatefastcgi_cache_revalidatescgi_cache_revalidateproxy_cache_revalidate
返回陈旧的缓存内容uwsgi_cache_use_stalefastcgi_cache_use_stalescgi_cache_use_staleproxy_cache_use_stale
指定哪些响应不会写入缓存uwsgi_no_cachefastcgi_no_cachescgi_no_cacheproxy_no_cache
将HEAD方法转换为GET方法proxy_cache_convert_head
加锁减少回源请求uwsgi_cache_lockfastcgi_cache_lockscgi_cache_lockproxy_cache_lock
回源请求到达该超时时间后再放行uwsgi_cache_lock_agefastcgi_cache_lock_agescgi_cache_lock_ageproxy_cache_lock_age
等待请求的最长等待时间uwsgi_cache_lock_timeoutfastcgi_cache_lock_timeoutscgi_cache_lock_timeoutproxy_cache_lock_timeout
七层反向代理对照:独有配置
uwsgi反向代理fastcgi反向代理scgi反向代理http反向代理
uwsgi_modifier1
uwsgi_modifier2
uwsgi_paramfastcgi_paramscgi_param
fastcgi_index
fastcgi_cache_stderr

memcached反向代理

memcached 反向代理是 ngx_http_memcached_module 模块,默认编译进 nginx,通过 --without-http_memcached_module 禁用功能。

memcached 反向代理的功能:

  • 将 HTTP 请求转换为 memcached 协议中的 get 请求,转发请求至上游 memcached 服务
  • get 命令:get<key>*\r\n
  • 控制命令:<command name><key><flags><exptime><bytes>[noreply]\r\n
  • 通过设置 memecached_key 变量构造 key 键
memcached 指令
\memcached 反向代理http 反向代理
指定上游memcached_passproxy_bind
连接绑定地址memcached_bindproxy_bind
连接响应头部缓存memcached_buffer_sizeproxy_buffet_size
连接上游超时时间memcached_connect_timeoutproxy_connect_timeout
强制使用range协议memcached_force_rangesproxy_force_range
针对设置key时的flag,对相应flag添加gzip响应头部memcached_gzip_flag
出错时更换上游memcached_next_upstreamproxy_next_upstream
更换上游超时memcached_next_upstream_timeoutproxy_next_upstream_timeout
更换上游重试次数memcached_next_upstream_triesproxy_next_upstream_tries
读取响应超时时间memcached_read_timeoutproxy_read_timeout
发送请求超时时间memcached_send_timeoutproxy_send_timeout
使用TCPkeepalivememcached_socket_keepaliveproxy_socket_keepalive

stream四层反向代理的7个阶段及常用变量

在 osi 七层模型中的第四层做反向代理,第四层就是传输层。主要包括两个协议,一个是 tcp 协议,一个是 udp 协议。之前的七层应用层协议都是在 tcp 协议之上的。nginx 对所有的四层反向代理模块统称为 stream 模块。

stream模块处理请求的7个阶段

POST_ACCEPTrealip
PREACCESSlimit_conn
ACCESSaccess
SSLssl
PREREADssl_preread
CONTENTreturn, stream_proxy
LOGaccess_log
stream中的ssl
Syntax: stream {...}
Default: --
Context: main

Syntax: server {...}
Default: --
Context: stream

Syntax: listen address:port [ssl] [udp] [proxy_protocol] [backlog=number] [rcvbuf=size] [sndbuf=size] [bind] [ipv6only=on|off] [reuseport] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]];
Default: --
Context: server
传输层相关的变量
  • binary_remote_addr -- 客户端地址的整型格式,对于 IPv4 是4字节,对于 IPv6 是16字节
  • connection -- 递增的连接序号
  • remote_addr -- 客户端地址
  • remote_port -- 客户端端口
  • proxy_protocol_addr -- 若使用了 proxy protocol 协议则返回协议中的地址,否则返回空
  • proxy_protocol_port -- 若使用了 proxy protocol 协议则返回协议中的端口,否则返回空
  • protocol -- 传输层协议,值为 TCP 或者 UDP
  • server_addr -- 服务器端地址
  • server_port -- 服务器端端口
  • bytes_received -- 从客户端接收到的字节数
  • bytes_send -- 已经发送到客户端的字节数
  • status --
    • 200session 成功结束
    • 400: 客户端数据无法解析,例如 proxy protocol 坳协议的格式不正确
    • 403: 访问权限不足被拒绝,例如 access 模块限制了客户端 IP 地址
    • 500: 服务器内部代码错误
    • 502: 无法找到或者连接上游服务
    • 503: 上游服务不可用
nginx 系统变量
  • time_local -- 以本地时间标准输出的当前时间,例如 14/Nov2018:15:5:37+0800
  • time_iso8601 -- 使用 ISO8601 标准输出的当前时间,例如 2018-114T15:55:37+08:00
  • nginx_version -- nginx 版本号
  • pid -- 所属 worker 进程的进程 id
  • pipe -- 使用了管道则返回 ip,否则返回
  • hostname -- 所在服务器的主机名,与 hostname 命令输出一致
  • msee -- 197011日到现在的时间,单位为秒,小数点后精确到毫秒
content 阶段:return 模块
Syntax: return value
Default: --
Context: server

proxy_protocol 协议与 realip 模块

tcp 连接只能拿到这个连接的对端地址,如果客户端与 nginx 之间建立过 cdn 或者其他的负载均衡,反向代理等,那么这时的 tcp 的对端地址就不是客户端的真实地址了。我们也么有办法用 http 协议中的 header 头部去存放客户端的真实地址。这时就可以通过 proxy_protocol 协议在传输层的协议的头部增加了一段内容来存放客户端的真实 ip 地址,在 stream 模块中有一个叫 post_accept 阶段,这个阶段中有一个 realip 模块。它就负责通过 proxy_protocol 协议取出用户真实的 ip 地址。交给后续的模块使用。

proxy_protocol协议
  • v1 协议
    • PROXY TCP4 202.112.144.236 10.210.12.10 5678 80\r\n
    • PROXY TCP6 2001:da8:205::100 2400:89c0:2110:1::21 6324 80\r\n
    • PROXY UKNOWN\r\n
  • v2 协议
    • 12字节签名:\r\n\r\n\0\r\nQUIT\n
    • 4位协议版本号:2
    • 4位命令:0表示 LOCAL1表示 PROXYnginx 仅支持 PROXY
    • 4位地址族:1表示 IPV42表示 IPV6
    • 4位传输层协议:1表示 TCP2表示 UDPnginx 仅支持 TCP 协议
    • 2字节地址长度
读取 proxy_protocol 协议的超时控制
Syntax: proxy_protocol_timeout timeout
Default: proxy_protocol_timeout 30s
Context: stream, server
stream 处理 proxy_protocol 流程
1. 连接建立成功,是否携带 listen proxy_protocol?
2. 如果是,则进入3,如果否则进入9
3. 加入读定时器 proxy_protocol_timeout (默认30)
4. 读取107字节(proxy_protocol 最大长度)
5. 判断前12字节是否匹配V2协议的头部
6. 如果是,则进入8,如果否则进入7
7. 读取v1协议头部的真实IP地址,进入9
8. 读取v2协议头部的真实IP地址
9. 进入7个阶段的 stream 模块处理
post_accept 阶段:realip 模块

通过 proxy_protocol 协议取出客户端真实地址,并写入 remote_addr 及 remote_port 变量。同时使用 realip_remote_addr 和 realip_remote_port 保留 TCP 连接中获得的原始地址。

realip 模块全称为 ngx_stream_realip_module,通过 --with-stream_realip_module 启用功能

Syntax: set_real_ip_from address | CIDR | unix:;
Default: --
Context: stream, server

限制并发连接、限制IP、记录日志

PREACCESS 阶段的 limit_conn 模块

限制客户端的并发连接数。使用变量自定义限制依据,基于共享内存所以 worker 进程同时生效。limit_conn 模块全称为 ngx_stream_limit_conn_module,通过 --without-stream_limit_conn_module 禁用模块

Syntax: limit_conn_zone key zone=name:size
Default: --
Context: stream

Syntax: limit_conn zone number
Default: --
Context: stream, server

Syntax: limit_conn_log_level info | notice | warn | error
Default: limit_conn_log_level error
Context: stream, server
ACCESS 阶段的 access 模块

根据客户端地址(realip 模块可以修改地址)决定连接的访问权限。access 模块全称为 ngx_stream_access_module,通过 --without-stream_access_module 禁用模块。

Syntax: allow address | CIDR | unix: | all
Default: --
Context: stream, server

Syntax: deny address | CIDR | unix: | all
Default: --
Context: stream, server
log 阶段:stream_log 模块
Syntax: access_log path format [buffer=size] [gzip[=level]] [flush=time] [if=condition]
        access_log off
Default: access_log off
Context: stream, server

Syntax: log_format name [escape=default|json|none] string ...
Default: --
Context: stream

Syntax: open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time]
        open_log_file_cache off
Default: open_log_file_cache off
Context: stream, server

stream 四层反向代理处理 SSL 下游流量

stream 模块 TLS/SSL 应用场景:

客户端到 nginxnginx 到上游服务都是 TLS/SSL 协议;第二种是客户端到 nginx 是 TLS/SSL 协议,nginx 到上游服务是 TCP 协议;第三种是客户端到 nginx 是 TCP 协议,nginx 到上游服务是 TLS/SSL 协议。

stream 中的 ssl

使 stream 反向代理对下游支持 TLS/SSL 协议,ssl 模块全称为 ngx_stream_ssl_module,默认不编译进 nginx,通过 --with-stream_ssl_module 加入。

stream ssl 指令对比 http 模块:配置基本参数
\stream sslhttp ssl
配置服务器证书ssl_certificatessl_certificate
配置服务器证书私钥ssl_certificate_keyssl_certificate_key
指定安全套件ssl_ciphersssl_ciphers
指定吊销证书链CRLssl_crlssl_crl
密码交换DH算法参数ssl_dhparamssl_dhparam
指定密码交换时使用哪条椭圆曲线ssl_ecdh_curvessl_ecdh_curve
TLS握手的超时时间ssl_handshake_timeoutssl_handshake_timeout
指定私钥的密码文件ssl_password_filessl_password_file
是否启用服务器偏好的安全套件ssl_prefer_server_ciphersssl_prefer_server_ciphers
指定支持的TLS协议ssl_protocolsssl_protocols
stream ssl 指令对比 http 模块:验证客户端证书
\stream sslhttp ssl
是否验证客户端的证书ssl_verify_clientssl_verify_client
可信CA证书,用于验证客户端证书ssl_client_certificatessl_client_certificate
验证客户端证书是否可信ssl_trusted_certificatessl_trusted_certificate
验证客户端证书链的深度ssl_verify_depthssl_verify_depth
stream ssl 模块提供的变量
  • 安全套件
    • ssl_cipher: 本次通讯选用的安全套件,例如 ECDHE-RSA-AES128-GCM-SHA256
    • ssl_ciphers: 客户端支持的所有安全套件
    • ssl_protocol: 本次通讯选用的 TLS 版本,例如 TLSv1.2
    • ssl_curves: 客户端支持的椭圆曲线,例如 secp384rl:secp52lrl
  • 证书
    • ssl_client_raw_cert:原始客户端证书内容
    • ssl_client_escaped_cert:返回客户端证书做 urlencode 编码后的内容
    • ssl_client_cert:对客户端证书每一行内容前加 tab 制表符空白,增强可读性。
    • ssl_client_fingerprint:客户端证书的 SHA1 指纹
  • 证书结构化信息
    • ssl_server_name:通过 TLS 插件 SNI(Server Name Indication)获取到的服务域名
    • ssl_client_i_dn:依据 RFC2253 获取到证书 issuer dn 信息,例如: CN=...,o=...,L=...,C=...
    • ssl_clinet_i_dn_legacy:依据 RRC2253 获取到证书 issuer dn 信息,例如:/C=.../L=.../O=.../CN=...
    • ssl_client_s_dn: 依据RFC2253获取到证书 subject dn 信息,例如:CN=...,OU=...,L=...,ST=...,C=...
    • ssl_client_s_dn_legacy: 同样获取 subject dn 信息,格式为: /C=.../ST=.../L=.../O=.../OU=.../CN=...
  • 证书有效期
    • ssl_client_v_end:返回客户端证书的过期时间,例如 Dec 1 11:56:11 2028 GMT
    • ssl_client_v_remain:返回还有多少天客户端证书过期,例如针对上面的 ssl client v end 其值为 3649
    • ssl_client_v_start:客户端证书的颁发日期,例如 Dec 4 11:56:11 2018 GMT
  • 连接有效性
    • ssl_client_serial:返回连接上客户端证书的序列号,例如 8BE947674841BD44
    • ssl_early_data:在 TLS1.3 协议中使用了 early data 且握手未完成返回1,否则返回空字符串
    • ssl_client_verify: 如果验证失败为 FAILED:原因,如果没有验证证书则为 NONE,验证成功则为 SUCCESS
    • ssl_session_id: 已建立连接的 sessionid
    • ssl_session_reused: 如果 session 被复用(参考 session 缓存)则为r,否则为.

stream_preread 模块取出 SSL 关键信息

SSL_PREREAD 模块全称为 stream_ssl_preread_module,使用 --with-stream_ssl_preread_module 启用模块。解析下有 TLS 证书中信息,以变量方式赋能其他模块。

变量
  • $ssl_preread_protocol -- 客户端支持的 TLS 版本中最高的版本,例如 TLSv1.3
  • $ssl_preread_server_name -- 从 SNI 中获取到的服务器域名
  • $ssl_preread_alpn_protocols -- 通过 ALPN 中获取到的客户端建议使用的协议,例如 h2, http/1.1
preread 阶段:ssl_preread 模块
Syntax: preread_buffer_size size;
Default: preread_buffer_size 16k
Context: stream, server

Syntax: preread_timeout timeout
Default: preread_timeout 30s
Context: stream, server

SyntaxL ssl_preread on | off
Default: ssl_preread off
Context: stream, server

stream proxy 四层反向代理的用法

stream_proxy 模块全称 ngx_stream_proxy_module,默认在 nginx 中。提供 TCP/UDP 协议的反向代理;支持与上游的连接使用 TLS/SSL 协议;支持与上游的连接使用 proxy protocol 协议。

限制读取上游服务数据的速度
Syntax: proxy_download_rate rate;
Default: proxy_download_rate 0;
Context: stream, server

限制读取客户端数据的速度
Syntax: proxy_upload_rate rate;
Default: proxy_upload_rate 0;
Context: stream, server
stream 反向代理指令
\stream 反向代理http 反向代理
指定上游proxy_passproxy_pass
连接绑定地址proxy_bindproxy_bind
该值同时用于定义接收上游数据、接收下游数据的缓冲区大小proxy_buffer_sizeproxy_buffer_size
连接上游超时时间proxy_connect_timeoutproxy_connect_timeout
TCP连接使用proxy_protocol协议proxy_protocolproxy_protocol
出错时更换上游proxy_next_upstreamproxy_next_upstream
更换上游超时proxy_next_upstream_timeoutproxy_next_upstream_timeout
更换上游重试次数proxy_next_upstream_triesproxy_next_upstream_tries
读取响应超时时间proxy_timeoutproxy_read_timeout
发送请求超时时间proxy_timeoutproxy_send_timeout
使用TCPkeepaliveproxy_socket_keepaliveproxy_socket_keepalive
stream ssl 指令与 http proxy 模块对照表
\streamhttp 反向代理
是否对上游使用sslproxy_ssl
配置服务器证书proxy_ssl_certificateproxy_ssl_certificate
配置服务器证书私钥proxy_ssl_certificate_keyproxy_ssl_certificate_key
指定安全套件proxy_ssl_ciphersproxy_ssl_ciphers
指定吊销证书链 CRLproxy_ssl_crlproxy_ssl_crl
指定域名验证上游证书中域名proxy_ssl_nameproxy_ssl_name
当私钥有密码时指定密码文件proxy_ssl_password_fileproxy_ssl_password_file
指定具体某个版本的协议proxy_ssl_protocolsproxy_ssl_protocols
传递 SNI 信息至上游proxy_ssl_server_nameproxy_ssl_server_name
是否重用 SSL 连接proxy_ssl_session_reuseproxy_ssl_session_reuse
验证上游服务的证书proxy_ssl_trusted_certificateproxy_ssl_trusted_certificate
是否验证上游服务的证书proxy_ssl_verifyproxy_ssl_verify
设置验证证书链的深度proxy_ssl_verify_depthproxy_ssl_verify_depth

UDP 反向代理

客户端向 nginx 发送请求时,有一个原端口,客户端通过这个原端口向 nginx 发送请求,客户端也喜欢 nginx 返回响应是也是通过发送到这个原端口上,这个是通过 session 技术控制的。

客户端通过发送端口A向 nginx 发送了一个报文,nginx 通过 监听端口B 接收,然后 nginx 根据规则通过 发送端口C 向上游服务发送了这个报文,上游服务通过 监听端口D 接收。上游服务处理完成请求后,从上游服务的 D端口 向 nginx 的 端口C 发送了响应,然后 nginx 再通过 端口B 向客户端返回响应,客户端通过 端口A 接收响应。

#指定一次会话 session 中最多从客户端接收到多少报文就结束 session(1.15.7非稳定版本)
#    仅会话结束时才会记录 access 日志
#    同一个会话中,nginx 使用同一端口连接上游服务
#    设置为 0 表示不限制,每次请求都会记录 access 日志
Syntax: proxy_requests number
Default: proxy_request 0
Context: stream, server

#指定对应一个请求报文,上游应返回多少个响应报文
#     proxy_timeout 结合使用,控制上游服务是否不可用
Syntax: proxy_responses number
Default: --
Context: stream, server
示例
server {
    listen 4436 udp;
    proxy_pass localhost:9999;
    proxy_requests 1; #1
    proxy_responses 2;
    proxy_timeout 2s;
    access_log logs/udp_access.log udplog;
}

tcpdump -i lo port 9999 -A -s 0
python client.py 4436

发送三个请求,有三条日志。

如果修改#12位置的值为3,则发送3次请求,只有一条日志。