1) map 指令是由 'ngx_http_map_module 模块'提供的,'默认'情况下nginx 会'安装'该模块
2) map 的主要作用是'创建自定义变量',通过使用 nginx 的'内置'变量,去'匹配'某些特定规则;
备注:只有'result_var'引用的时候,才会寻找这个'map'
(1)总述
说明: string["已有变量"]可以是'变量(动态提供了无限可能)',也可以是'字符串'
意外之喜: map的'源变量'可以是'组合形式'
map '${var1}${var2}' $result {
...
}
对比:if'只能'使用'某一个变量'判断
① 理解新变量的值取决于多个源变量场景
细节: '源变量'必须是'已经存在的',不然'怎么做'适配
源变量: 即 map指令 后面的'那个string(已有变量)'
② map块何时被执行
(2)源值的形式
① 源值是裸字符串,不包含特殊字符串
源值如果是'裸字符串',则'不区分'大小写
② 源值是裸值,包含特殊字符
③ 源值以~和~*开头,只做正则表达式匹配
说明:最经常遇到的就是'正则域名'中需要'\.'转义
④ 源值以~和~*开头,正则表达式匹配后补获
说明: 这种方式,一次创建'多个'变量
1)方式1
2)方式2 重点掌握
(3) 结果值形式
高级: 结果值是'$uri'形式
(4)特殊参数
① default value
map $source_var target_var {
case "1";
default "";
}
+++++++ "等价形式" +++++++
map $source_var target_var {
case "1";
}
② hostnames指令
应用场景: $http_host、$host变量
1)案例
2)优先级小结
③ include
细节点: 注意include'文件'的形式和'相对路径'
④ volatile
(5)应用场景
++++++++++++ 'map的应用场景' ++++++++++++
1) 想象'编程语言'中的'switch { case: }'场景
2) 或者if {} else if {} else {}场景,这里都可以'转换'为map
3) map与'nginx log_format if=' 做'debug'判断
4) iframe嵌套,通过'map $http_referer $result'判断,另外通过改变'$result'做例外判断
5) nginx配置解决'低版本 Chrome浏览器'SameSite跨域'兼容性'问题
源变量必须是'已有变量'
强调:和'内置变量'才能玩出'花样'
说明:'string'一般我们会用'nginx的内置变量'来代替,不会直接写'裸值',便于'动态'
补充:已知'set、map、正则命名补获(?<name>.*)'三种方式来'设置变量'
二 案例讲解
1、所有的'案例场景'都是基于'变量',基于'请求处理过程'来说明
2、了解哪些'指令'可以使用'哪些变量'
备注: limit_req_zone $variable
3、map 是'基于变量'才玩出花活
补充: '变量'与'dynamic'动态关联起来了,不用'reload'了
① 查询参数
map $args $foo {
default 0;
debug 1;
}
变量解读:$args 是nginx'内置[inner]'变量,$foo是我们自定义的'普通[common]'变量
附加:$args 这个变量等于'请求行中(GET请求)的参数',例如foo=123&bar=wzj;
效果:如果 $args '匹配到 debug' 那么 $foo 的值会'被设为 1 '
ps: 如果 $args 一个都'匹配不到' $foo 就是'default 定义的值',在这里就是 0
大白话:类似于一个'if/else'判断,设置'变量值'
② nginx开启websocket代理功能
http {
...
# 是否是'websocket'
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
location /v1/kind {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# proxy_set_header Connection "upgrade"; -->对比二者的区别?
...
}
}
websocket协议为什么HTTP Upgrade的时候需要Connection: upgrade ?
WebSocket为什么需要Connection和Upgrade两个header?
③ 灰度发布
说明; nginx 实现灰度发布的方式有'多种',大多是基于'请求信息 [uuid、ip、请求头]'等标示的
思考: nginx是否能基于'第三方平台'的响应信息,捕获'然后分析'做判断,继续下一步请求?
1、前端: '兰绿 [灰度]'发布,切换不同的'版本'
技术点: root '指令的值'可以携带'变量'
推荐: $http_gray 或 $http_fronted 这种'自定义'请求头
引申: 前端如何'全局'加自定义请求头
2、后端: '兰绿 [灰度]'发布,切换不同的'版本'
技术点: proxy_paas '指令的值'可以携带变量
推荐: $COOKIE_k8s_flag 这种'大小写'结合的方式
upstream upstream_k8s {
server ip:port;
...
}
upstream upstream_default {
server ip:port
}
# zone是存储区域
limit_req_zone $client_real_ip zone=A:100m rate=1000r/s
limit_conn_zone $client_real_ip zone=B:100m;
# 变量必须是nginx的内置变量吗? -->'非必须'
map $COOKIE_k8s_flag $k8s {
# 等待匹配的字符串可以不加引号
k8s1 upstream_k8s;
# 说明:upstream_default和upstream_k8s与前面的映射
deafult upstream_default;
}
location /k8s {
limit_req zone=A burst=10000 nodelay;
limit_conn B 1000;
# 注意引入方式
proxy_pass https://${k8s};
...
}
背景: 我们知道'$remote_addr'是TCP'直连'的ip,'不是'客户端的真实ip
说明: 该案例尝试从'XFF'头中获取客户端的'真实ip',需要每层proxy代理'都透传'
++++++++++++++++ "本文只简单罗列关键配置" ++++++++++++++++
map $http_x_forwarded_for $client_ip {
default $remote_addr;
~^(?<first_ip>[^,]+),?.*$ $first_ip;
# ~^(?:\d+.\d+.\d+.\d+)(?:,|$) $1;
}
map $client_ip $result {
1 normal_upstream;
# 验证白名单列表 --> 只允许这几个'特定ip的用户'来访问B版本
1.1.1.1 gray_upstream;
2.2.2.2 gray_upstream;
}
location /api {
proxy_pass https://$result;
}
核心点: 通过'正则'获取到XFF头的第一个'ip'
④ 跨域名访问
推荐: 把'CORS'抽离到cors.conf的配置'文件'中
# 这些配置可以写在 http{} 或者 server{} 都是'支持'的
add_header Access-Control-Allow-Origin "http://www.wzj.com";
add_header Access-Control-Allow-Methods "POST, GET, PUT, OPTIONS, DELETE";
add_header Access-Control-Max-Age "3600";
add_header Access-Control-Allow-Headers "Origin, X-Requested-With, Content-Type, Accept";
1) 上面的配置'只允许' http://www.wzj.com 跨域访问
2) 如果要支持'所有域名'都可以跨域调用该站, 不过'不推荐'这样做,因为'不安全'
add_header Access-Control-Allow-Origin "*";
++++++++++++++"不想允许所有,但是又需要允许多个域名,那么就需要用到 map"++++++++++++++
需求: 使用 map 来实现允许'多个域名跨域'访问的问题
map $http_origin $corsHost {
default 0;
"~http://www.wzj.com" http://www.wzj.com;
"~http://m.haibakeji.com" http://harbor.wzj.com;
"~http://nginx.wzj.com" http://nginx.wzj.com;
}
server{
listen 80;
server_name www.wzj.com;
root /nginx;
location /
{
add_header Access-Control-Allow-Origin $corsHost;
...
}
}
⑤ map相关调试
需求:
1) 使用源变量'通常是 nginx 内置变量'匹配一些规则,创建自定义变量
2) 然后在页面输出. 这通常在'调试'的时候非常有用
http {
map $uri $match {
# 说明:default可以'省略'
~^/www/(.*) http://www.wzj.com/;
}
server {
listen 8080;
server_name harbor.wzj.com;
location /www {
default_type text/plain;
echo uri: $uri;
echo match: $match;
echo capture: $1;
echo new: $match$1;
}
细节: openresty自带'echo'模块 或者 nginx '源码编译'
⑥ nginx通过map 删除 HttpOnly、Secure
# 删除Cookie中的HttpOnly
map $sent_http_set_cookie $remove_cookie {
~*(?<CK>.+)HttpOnly $CK;
}
server {
listen 80;
server_name *.wzj.com;
location / {
add_header Set-Cookie $remove_cookie;
proxy_pass http://xxxxx;
}
}
+++++++++++++++++++++++++ "拆解分析" +++++++++++++++++++++++++
1、按照Cookie规范,HttpOnly是'最后一个字符串'
2、通过在map中使用正则表达式提取'除HttpOnly之前'的值,然后使用'add_header'复写到Cookie中
3、$sent_http_set_cookie 为 nginx '预定义'内置变量,获取后端的'Set-Cookies'响应头
4、处理 Cookie 的 Secure 也'是一样'
本质: nginx拦截'Set-Cookie'响应头,对其进行'二次'加工
补充: 也可以通过'proxy_cookie_flags'来解决这里不再'赘述'了
⑦ 利用 $http_user_agent做不同客户端的适配
补充: '不同'客户端版本,同一客户端的'不同'版本
关注点:chrome的'版本'跟SameSite的关系 --> 'Chrome/81.0.4044.138'
nginx配置解决Chrome浏览器SameSite跨域问题 Chrome同站策略
nginx 为chrome客户端请求加SameSite=None;Secure
⑧ map指令的阶段问题
⑨ 典型的应用场景
+++++++++++++++++ "后续再遇到"会慢慢补充 +++++++++++++++++
1) 特殊'版本'的agent才允许访问
2)nginx自身通过XFF获取'真实ip' -->创建其它'新变量'
3) 'websocket'配置
4)CORS '跨域'