梦应逍遥 发表于 2024-7-12 17:24:02

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

前言

大家好,我是老马。很高兴遇到你。
我们为 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 指令的根本语法如下:
map $variable_to_test $variable_to_set {
    default value;
    key value;
    ...
}

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

假设我们想根据请求的主机名设置一个变量,进而用这个变量来决定后续的举动。
可以这样利用 map 指令:
http {
    map $http_host $backend_server {
      default         backend1.example.com;
      "www.example.com" backend2.example.com;
      "api.example.com" backend3.example.com;
    }

    server {
      listen 80;
      server_name example.com;

      location / {
            proxy_pass http://$backend_server;
      }
    }
} 在这个例子中:


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

map 指令支持更复杂的匹配模式,包罗正则表达式。
示例如下:
http {
    map $request_uri $file_extension {
      "~*\.jpg$"image;
      "~*\.png$"image;
      "~*\.css$"stylesheet;
      "~*\.js$"   javascript;
      default   other;
    }

    server {
      listen 80;
      server_name example.com;

      location / {
            set $content_type $file_extension;
            # 在此可以根据 $content_type 变量进行不同的处理
      }
    }
} 在这个例子中:


[*]根据请求的 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指令的根本语法如下:
map $variable_to_map $result_variable {
    default value;# 设置默认值
    condition1 value1;# 条件1 对应的值
    condition2 value2;# 条件2 对应的值
    ...
}

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

假设我们需要根据差别的主机名来设置差别的后端服务器:
http {
    map $host $backend {
      default web1.example.com;
      host1.example.com web2.example.com;
      host2.example.com web3.example.com;
    }

    server {
      listen 80;

      location / {
            proxy_pass http://$backend;
      }
    }
} 在这个示例中:


[*]根据请求的主机名($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 实现

配置的剖析

我们以一个比较全的配置为例
http {
    # 定义一个 map 指令,根据请求的主机名设置后端服务器
    map $host $backend {
      default web1.example.com;
      host1.example.com web2.example.com;
      host2.example.com web3.example.com;
    }

    # 定义另一个 map 指令,根据用户代理设置变量
    map $http_user_agent $mobile {
      default 0;
      "~*iphone|android" 1;
    }

    # others
} 配置加载

直接放在 http 的全局配置中,剖析如下:
/**
* @since 0.22.0
* @author 老马啸西风
*/
public class NginxUserMapConfigLoadFile implements INginxUserMapConfigLoad {

    //conf

    @Override
    public NginxUserMapConfig load() {
      Map<String, String> mapping = new HashMap<>();

      NginxUserMapConfig config = new NginxUserMapConfig();

      List<String> values = mapBlock.getValues();
      if(values.size() != 2) {
            throw new Nginx4jException("map 指令的 values 必须为 2,形如 map $key1 $key2");
      }
      config.setPlaceholderMatchKey(values.get(0));
      config.setPlaceholderTargetKey(values.get(1));

      Collection<NgxEntry> entryList = mapBlock.getEntries();
      if(CollectionUtil.isEmpty(entryList)) {
            throw new Nginx4jException("map 指令的映射关系不可为空,可以配置 default xxx");
      }

      for(NgxEntry entry : entryList) {
            if(entry instanceof NgxParam) {
                NgxParam ngxParam = (NgxParam) entry;
                String name = ngxParam.getName();
                String value = ngxParam.getValue();

                // 对比
                if("default".equals(name)) {
                  config.setDefaultVal(value);
                } else {
                  mapping.put(name, value);
                }
            }
      }

      config.setMapping(mapping);
      return config;
    }

} map 指令的实现

目前实现简单的,在 dispatch 前触发 map 指令。
/**
* @since 0.22.0
* @author 老马啸西风
*/
public class NginxMapDirectiveDefault implements NginxMapDirective {

    private static final Log logger = LogFactory.getLog(NginxMapDirectiveDefault.class);

    @Override
    public void map(NginxRequestDispatchContext context) {
      Map<String, Object> placeholderMap = context.getPlaceholderMap();
      List<NginxUserMapConfig> mapConfigList = context.getNginxConfig().getNginxUserConfig().getMapConfigs();
      if(CollectionUtil.isEmpty(mapConfigList)) {
            // 忽略
            logger.info("mapConfigList 为空,忽略处理 map 指令");
            return;
      }

      for(NginxUserMapConfig mapConfig : mapConfigList) {
            processMap(mapConfig, placeholderMap);
      }
    }

    protected void processMap(NginxUserMapConfig mapConfig,
                              Map<String, Object> placeholderMap) {
      //1. key
      String matchKey = mapConfig.getPlaceholderMatchKey();
      String matchValue = (String) placeholderMap.get(matchKey);

      String targetKey = mapConfig.getPlaceholderTargetKey();

      // 遍历
      for(Map.Entry<String, String> mapEntry : mapConfig.getMapping().entrySet()) {
            if(matchValue == null) {
                logger.info("matchValue is null, ignore match");
                break;
            }

            String key = mapEntry.getKey();
            String value = mapEntry.getValue();
            if(key.equals(matchValue)) {
                // fast-return
                placeholderMap.put(targetKey, value);
                logger.info("命中相等 {}={}, {}={}", matchKey, matchValue, targetKey, value);
                return;
            } else if(matchValue.matches(key)) {
                placeholderMap.put(targetKey, value);
                logger.info("命中正则 {}={}, {}={}", matchKey, matchValue, targetKey, value);
                return;
            }
      }

      // 默认值
      placeholderMap.put(targetKey, mapConfig.getDefaultVal());
      logger.info("命中默认值 {}={}", targetKey, mapConfig.getDefaultVal());
    }

} 测试验证

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

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

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 从零手写实现 nginx-25-directive map 条件判断指令