从零手写实现 nginx-25-directive map 条件判断指令

打印 上一主题 下一主题

主题 574|帖子 574|积分 1722

前言

大家好,我是老马。很高兴遇到你。
我们为 java 开发者实现了 java 版本的 nginx
   https://github.com/houbb/nginx4j
  假如你想知道 servlet 如那边理的,可以参考我的另一个项目:
   手写从零实现浅易版 tomcat minicat
  手写 nginx 系列

假如你对 nginx 原理感爱好,可以阅读:
从零手写实现 nginx-01-为什么不能有 java 版本的 nginx?
从零手写实现 nginx-02-nginx 的核心能力
从零手写实现 nginx-03-nginx 基于 Netty 实现
从零手写实现 nginx-04-基于 netty http 出入参优化处理
从零手写实现 nginx-05-MIME范例(Multipurpose Internet Mail Extensions,多用途互联网邮件扩展范例)
从零手写实现 nginx-06-文件夹自动索引
从零手写实现 nginx-07-大文件下载
从零手写实现 nginx-08-范围查询
从零手写实现 nginx-09-文件压缩
从零手写实现 nginx-10-sendfile 零拷贝
从零手写实现 nginx-11-file+range 合并
从零手写实现 nginx-12-keep-alive 毗连复用
从零手写实现 nginx-13-nginx.conf 配置文件介绍
从零手写实现 nginx-14-nginx.conf 和 hocon 格式有关系吗?
从零手写实现 nginx-15-nginx.conf 如何通过 java 剖析处理?
从零手写实现 nginx-16-nginx 支持配置多个 server
从零手写实现 nginx-17-nginx 默认配置优化
从零手写实现 nginx-18-nginx 请求头+相应头操作
从零手写实现 nginx-19-nginx cors
从零手写实现 nginx-20-nginx 占位符 placeholder
从零手写实现 nginx-21-nginx modules 模块信息概览
从零手写实现 nginx-22-nginx modules 分模块加载优化
从零手写实现 nginx-23-nginx cookie 的操作处理
从零手写实现 nginx-24-nginx IF 指令
从零手写实现 nginx-25-nginx map 指令
前言

大家好,我是老马。
这一节我们将配置的加载,拆分为差别的模块加载处理,便于后续拓展。
if

具体介绍一下 nginx 的 map 指令

Nginx 的 map 指令是一个强大的工具,用于根据变量的值来设置另一个变量的值。
它可以用于许多场景,比如基于请求的某些特征来动态设置变量,从而影响后续的处理逻辑。
以下是关于 map 指令的具体介绍:
语法和根本用法

map 指令的根本语法如下:
  1. map $variable_to_test $variable_to_set {
  2.     default value;
  3.     key value;
  4.     ...
  5. }
复制代码


  • $variable_to_test:要测试的变量。
  • $variable_to_set:要设置的变量。
  • default:假如没有找到匹配的键,则利用默认值。
  • key value:键值对,根据 $variable_to_test 的值来设置 $variable_to_set。
示例

假设我们想根据请求的主机名设置一个变量,进而用这个变量来决定后续的举动。
可以这样利用 map 指令:
  1. http {
  2.     map $http_host $backend_server {
  3.         default         backend1.example.com;
  4.         "www.example.com" backend2.example.com;
  5.         "api.example.com" backend3.example.com;
  6.     }
  7.     server {
  8.         listen 80;
  9.         server_name example.com;
  10.         location / {
  11.             proxy_pass http://$backend_server;
  12.         }
  13.     }
  14. }
复制代码
在这个例子中:


  • 根据 $http_host 的值(请求头中的主机名),将 $backend_server 变量设置为差别的后端服务器。
  • 假如主机名是 www.example.com,则 $backend_server 设置为 backend2.example.com。
  • 假如主机名是 api.example.com,则 $backend_server 设置为 backend3.example.com。
  • 假如主机名不匹配任何键,则利用默认值 backend1.example.com。
复杂匹配

map 指令支持更复杂的匹配模式,包罗正则表达式。
示例如下:
  1. http {
  2.     map $request_uri $file_extension {
  3.         "~*\.jpg$"  image;
  4.         "~*\.png$"  image;
  5.         "~*\.css$"  stylesheet;
  6.         "~*\.js$"   javascript;
  7.         default     other;
  8.     }
  9.     server {
  10.         listen 80;
  11.         server_name example.com;
  12.         location / {
  13.             set $content_type $file_extension;
  14.             # 在此可以根据 $content_type 变量进行不同的处理
  15.         }
  16.     }
  17. }
复制代码
在这个例子中:


  • 根据请求的 URI,将 $file_extension 变量设置为差别的值。
  • 假如 URI 以 .jpg 或 .png 末端,则设置为 image。
  • 假如 URI 以 .css 末端,则设置为 stylesheet。
  • 假如 URI 以 .js 末端,则设置为 javascript。
  • 假如 URI 不匹配任何模式,则利用默认值 other。
注意事项



  • map 指令必须放在 http 块中,不能直接放在 server 或 location 块中。
  • 在 map 指令中利用的变量必须在之前已经界说或已经存在。
  • map 指令中键的匹配是按次序举行的,匹配到第一个符合条件的键时就会克制匹配。
实际应用

map 指令可以用于许多实际应用场景,比如:


  • 根据客户端 IP 设置访问限制或调解访问策略。
  • 根据 User-Agent 头设置差别的相应头。
  • 动态调解缓存策略。
  • 根据请求路径或参数动态选择后端服务器。
通过 map 指令,Nginx 的配置变得更加机动和强大,可以根据实际需要举行复杂的条件判断和变量设置。
为什么 nginx 中需要 map 指令

在 Nginx 配置中,map 指令用于根据某个变量的值来动态设置另一个变量的值。这在许多情况下都非常有用,尤其是在需要根据请求的差别条件(如 URL、IP 地址、请求头等)来执行差别的配置或举动时。以下是一些具体的利用场景和map指令的具体表明:
利用场景


  • 动态配置

    • 通过map指令,可以根据请求的特定条件(例如,客户端 IP 地址、请求路径、请求头等)来设置差别的 Nginx 配置项。
    • 例如,可以根据访问路径设置差别的后端服务器、差别的缓存策略或差别的访问控制策略。

  • 简化配置

    • map指令可以简化复杂的条件判断逻辑,避免在配置文件中编写大量的if指令。
    • 通过集中管理映射规则,可以使配置文件更清楚、更易于维护。

  • 负载均衡

    • 可以根据请求的属性(如 User-Agent 或 Cookie)将请求分配到差别的后端服务器,实现更机动的负载均衡策略。

map 指令的语法和用法

map指令的根本语法如下:
  1. map $variable_to_map $result_variable {
  2.     default value;  # 设置默认值
  3.     condition1 value1;  # 条件1 对应的值
  4.     condition2 value2;  # 条件2 对应的值
  5.     ...
  6. }
复制代码


  • $variable_to_map:要根据其值举行映射的变量。
  • $result_variable:映射结果存储到的变量。
  • default value:假如没有匹配的条件,利用的默认值。
  • condition value:条件和值的对,满足条件时将值赋给$result_variable。
示例

假设我们需要根据差别的主机名来设置差别的后端服务器:
  1. http {
  2.     map $host $backend {
  3.         default web1.example.com;
  4.         host1.example.com web2.example.com;
  5.         host2.example.com web3.example.com;
  6.     }
  7.     server {
  8.         listen 80;
  9.         location / {
  10.             proxy_pass http://$backend;
  11.         }
  12.     }
  13. }
复制代码
在这个示例中:


  • 根据请求的主机名($host),将 $backend 变量设置为差别的后端服务器。
  • 默认情况下,$backend 会被设置为 web1.example.com。
  • 假如请求的主机名是 host1.example.com,$backend 会被设置为 web2.example.com。
  • 假如请求的主机名是 host2.example.com,$backend 会被设置为 web3.example.com。
结论

map指令在 Nginx 中是一个强大的工具,可以根据请求的条件动态设置变量,从而实现更机动和可维护的配置。
通过合理利用map指令,可以简化配置文件,增强 Nginx 的功能,使其可以大概更好地顺应各种复杂的应用场景。
java 实现

配置的剖析

我们以一个比较全的配置为例
  1. http {
  2.     # 定义一个 map 指令,根据请求的主机名设置后端服务器
  3.     map $host $backend {
  4.         default web1.example.com;
  5.         host1.example.com web2.example.com;
  6.         host2.example.com web3.example.com;
  7.     }
  8.     # 定义另一个 map 指令,根据用户代理设置变量
  9.     map $http_user_agent $mobile {
  10.         default 0;
  11.         "~*iphone|android" 1;
  12.     }
  13.     # others
  14. }
复制代码
配置加载

直接放在 http 的全局配置中,剖析如下:
  1. /**
  2. * @since 0.22.0
  3. * @author 老马啸西风
  4. */
  5. public class NginxUserMapConfigLoadFile implements INginxUserMapConfigLoad {
  6.     //conf
  7.     @Override
  8.     public NginxUserMapConfig load() {
  9.         Map<String, String> mapping = new HashMap<>();
  10.         NginxUserMapConfig config = new NginxUserMapConfig();
  11.         List<String> values = mapBlock.getValues();
  12.         if(values.size() != 2) {
  13.             throw new Nginx4jException("map 指令的 values 必须为 2,形如 map $key1 $key2");
  14.         }
  15.         config.setPlaceholderMatchKey(values.get(0));
  16.         config.setPlaceholderTargetKey(values.get(1));
  17.         Collection<NgxEntry> entryList = mapBlock.getEntries();
  18.         if(CollectionUtil.isEmpty(entryList)) {
  19.             throw new Nginx4jException("map 指令的映射关系不可为空,可以配置 default xxx");
  20.         }
  21.         for(NgxEntry entry : entryList) {
  22.             if(entry instanceof NgxParam) {
  23.                 NgxParam ngxParam = (NgxParam) entry;
  24.                 String name = ngxParam.getName();
  25.                 String value = ngxParam.getValue();
  26.                 // 对比
  27.                 if("default".equals(name)) {
  28.                     config.setDefaultVal(value);
  29.                 } else {
  30.                     mapping.put(name, value);
  31.                 }
  32.             }
  33.         }
  34.         config.setMapping(mapping);
  35.         return config;
  36.     }
  37. }
复制代码
map 指令的实现

目前实现简单的,在 dispatch 前触发 map 指令。
  1. /**
  2. * @since 0.22.0
  3. * @author 老马啸西风
  4. */
  5. public class NginxMapDirectiveDefault implements NginxMapDirective {
  6.     private static final Log logger = LogFactory.getLog(NginxMapDirectiveDefault.class);
  7.     @Override
  8.     public void map(NginxRequestDispatchContext context) {
  9.         Map<String, Object> placeholderMap = context.getPlaceholderMap();
  10.         List<NginxUserMapConfig> mapConfigList = context.getNginxConfig().getNginxUserConfig().getMapConfigs();
  11.         if(CollectionUtil.isEmpty(mapConfigList)) {
  12.             // 忽略
  13.             logger.info("mapConfigList 为空,忽略处理 map 指令");
  14.             return;
  15.         }
  16.         for(NginxUserMapConfig mapConfig : mapConfigList) {
  17.             processMap(mapConfig, placeholderMap);
  18.         }
  19.     }
  20.     protected void processMap(NginxUserMapConfig mapConfig,
  21.                               Map<String, Object> placeholderMap) {
  22.         //1. key
  23.         String matchKey = mapConfig.getPlaceholderMatchKey();
  24.         String matchValue = (String) placeholderMap.get(matchKey);
  25.         String targetKey = mapConfig.getPlaceholderTargetKey();
  26.         // 遍历
  27.         for(Map.Entry<String, String> mapEntry : mapConfig.getMapping().entrySet()) {
  28.             if(matchValue == null) {
  29.                 logger.info("matchValue is null, ignore match");
  30.                 break;
  31.             }
  32.             String key = mapEntry.getKey();
  33.             String value = mapEntry.getValue();
  34.             if(key.equals(matchValue)) {
  35.                 // fast-return
  36.                 placeholderMap.put(targetKey, value);
  37.                 logger.info("命中相等 {}={}, {}={}", matchKey, matchValue, targetKey, value);
  38.                 return;
  39.             } else if(matchValue.matches(key)) {
  40.                 placeholderMap.put(targetKey, value);
  41.                 logger.info("命中正则 {}={}, {}={}", matchKey, matchValue, targetKey, value);
  42.                 return;
  43.             }
  44.         }
  45.         // 默认值
  46.         placeholderMap.put(targetKey, mapConfig.getDefaultVal());
  47.         logger.info("命中默认值 {}={}", targetKey, mapConfig.getDefaultVal());
  48.     }
  49. }
复制代码
测试验证

直接本地启用访问 http://192.168.1.13:8080/
日志:
  1. 信息: 命中默认值 $backend=web1.example.com
  2. 信息: 命中默认值 $mobile=0
复制代码
小结

map 指令是 Nginx 中一个强大的工具,用于根据请求属性动态设置变量。
通过合理利用 map 指令,可以简化配置,提高性能和机动性。
利用 Java 库 nginxparser 可以动态剖析和处理 Nginx 配置文件,进一步增强配置管理的自动化和机动性。
我们后续思量继承学习下 rewrite try_files 等指令。
我是老马,期待与你的下次重逢。
开源地址

为了便于大家学习,已经将 nginx 开源
   https://github.com/houbb/nginx4j

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦应逍遥

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表