Nginx map module 语法和 websocket 反代的行为细节

https://nginx.org/en/docs/http/ngx_http_map_module.html

基本用法

语法: map $var1 $var2 { map list }
默认值:
配置段: http(可以存在多个 map 区域)
map 指定了两个变量,其顺序和映射表中的列对应,这里的变量 var1 是 Nginx 自带变量,var2 则是自定义变量名,Nginx 自带变量表可以在 https://nginx.org/en/docs/varindex.html 查到。
map 的大括号内则是映射表,映射表由两列组成,匹配模式(string)和需要设定的值(variable)。
在 map 块里的参数指定了源变量值和结果值的对应关系。

  • 匹配模式可以是一个简单的字符串或者正则表达式,使用正则表达式要用(‘~’)。
  • 一个正则表达式如果以 “~” 开头,表示这个正则表达式对大小写敏感。以 “~*”开头,表示这个正则表达式对大小写不敏感。
  • 不使用正则的时候,默认使用大小写不敏感匹配。
  • 如果源变量值包含特殊字符如~,则要以\来转义。

一个简单的例子:

效果为:这个区块指定了两个变量进行关联: $http_user_agent$is_curl"~curl"即是匹配模式,如果 User-Agent 变量包含 curl(正则大小写敏感),则将 $is_curl变量设为1,默认为0,这个变量是全局有效的,比如,可以在 log_format 中直接使用变量 $is_curl, 也可以用于其他 location 的 if 判断。

复杂匹配逻辑

在复杂情景下,大括号内可以使用以下几个参数:

  • default (value): 默认值,当不指定时,默认值为空字符串
  • hostnames: 允许使用通配进行前缀或后缀匹配,如example.* 1;,这个参数必须写在映射列表的最前面
  • include (file): nginx 的标准 include 语法,类似include conf.d/*的效果,等同于直接将文件内容完整替换到此处
  • volatile: 指定不允许被缓存的值

当满足多个匹配条件时,匹配的顺序是:

  1. 不带通配的字符串完全匹配
  2. 最长前缀匹配的字符串,如 *.example.com
  3. 最长后缀匹配的字符串,辱 mail.*
  4. 第一个匹配的正则
  5. default 指定的值

不能在 map 块的映射表里使用 Nginx 的引用命名捕获或位置捕获变量(即类似 $1 的变量,这个用法常用于 rewrite)。如 ~^/someURI/(.*) /locationxxxx/$1; 这样会报错 nginx: [emerg] unknown variable。

实践:反代 Websocket

情景:为模板渲染方便,使用类似以下格式的 proxy.conf 配置文件,upstrem 在其他位置另行指定,满足在 proxy.conf 完全不进行修改时,满足 upstream 为 websocket 和 http 的情景。主要困难在于 Nginx 反代 websocket 时需要主动添加 Upgrade 头(HAProxy 不需要作出任何特殊修改,直接使用 http 反代的配置文件可以正常工作),而向一般服务器发送多余的 header 可能引起 400 bad request 等问题,因此需要做出兼容处理。两年前在 https://www.starduster.me/2016/08/23/brief-talk-of-websocket/ 曾经讨论过 websocket 的协议设计、特点和反代的配置,将这个问题重新拿出来讨论一下。

已知 websocket 连接发起时完整请求头形如:

Websocket 的实现方式是通过发送 Upgrade 头,但是 Upgrade 和 Connection 都是逐跳(hop-by-hop)的 header,即只在一跳内有效而不会被传递到源站,这个 header 在正常情况下经过代理的时候会被去掉,使用 map 指令可以根据客户端是否有 Upgrade:websocket 来判断是否需要向后发送 Upgrade 和 Connection。

查 Nginx 变量表,知所有 http header 都是 Nginx 内置变量,如 Upgrade 头的数据可以直接使用 $http_upgrade 变量取得,

 

验证的时候根据抓包分析发现一个问题,先前的文章中的配置是有一定问题的,Nginx 会对所有没有 Connection 头的请求向后加上 Connection:close 头,我尝试显示的指定 Connection: keep-alive 时,Nginx 依旧向 upstream 发送了 Connection: close

尝试改为

此时判断的逻辑就变为:判断客户端是否传递了Upgrade: websocket,如果有,则传递 Connection 头并原样传递 Upgrade 头,反之默认为空,不发送 Connection 头,做出这个一修改后 websocket 和 http 的行为依旧正常,但是抓包显示 Nginx 已经不会显示的向 upstream 发送 Connection: close ,这个配置的兼容性有待讨论。

PS, Nginx 作为反代时,ngx_http_upstream_module 在 upstream 区块中的keepalive参数表示的是维持的空闲连接数,注意不要望文生义,


本文链接:https://www.starduster.me/2018/08/20/nginx-map-module-%e8%af%ad%e6%b3%95%e5%92%8c-websocket-%e5%8f%8d%e4%bb%a3%e7%9a%84%e8%a1%8c%e4%b8%ba%e7%bb%86%e8%8a%82/
本站基于 Creactive Commons BY-NC-SA 4.0 License 允许并欢迎您在注明来源和非商业使用前提下自由地对本文进行复制、分享或基于本文进行创作。
请注意:受限于笔者水平,本站内容可能存在主观臆断或事实错误,文中信息也可能因时间推移而不再准确,在此提醒读者结合自身判断谨慎地采纳。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据