当前位置 博文首页 > 等你归去来:Nginx(六):配置解析之location解析

    等你归去来:Nginx(六):配置解析之location解析

    作者:等你归去来 时间:2021-01-19 12:06

      nginx成为非常流行的代理服务软件,最根本的原因也许是在于其强悍性能。但还有一些必要的条件,比如功能的完整,配置的易用,能够解决各种各样的实际需求问题,这些是一个好的软件的必备特性。

      那么,今天我们就来看看nginx配置的部分原则和解析原理吧。我们只做location部分的细节解析,但其他配置道理基本相通,推一及二即可。

     

    1. nginx配置的基本原则

      nginx是支持高度配置化的,那么也许就会涉及许多部分的配置,要如何协调好这些配置,是个问题。比如是否将配置定义一个个独立的文件,或者其他。

      然而,nginx使用一个统一的配置文件,管理起了所有的配置工作。即 nginx.conf, 其默认位置是 $NGINX_HOME/nginx.conf, 在这个主配置文件中,又可以包含其他任意多的配置文件,从而达到统一管理的作用。

      其默认配置nginx.conf如下:

        
    #user  nobody;
    worker_processes  1;
    
    #error_log  logs/error.log;
    #error_log  logs/error.log  notice;
    #error_log  logs/error.log  info;
    
    #pid        logs/nginx.pid;
    
    
    events {
        worker_connections  1024;
    }
    
    
    http {
        include       mime.types;
        default_type  application/octet-stream;
    
        #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        #                  '$status $body_bytes_sent "$http_referer" '
        #                  '"$http_user_agent" "$http_x_forwarded_for"';
    
        #access_log  logs/access.log  main;
    
        sendfile        on;
        #tcp_nopush     on;
    
        #keepalive_timeout  0;
        keepalive_timeout  65;
    
        #gzip  on;
    
        server {
            listen       80;
            server_name  localhost;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
            location / {
                root   html;
                index  index.html index.htm;
            }
    
            #error_page  404              /404.html;
    
            # redirect server error pages to the static page /50x.html
            #
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
    
            # proxy the PHP scripts to Apache listening on 127.0.0.1:80
            #
            #location ~ \.php$ {
            #    proxy_pass   http://127.0.0.1;
            #}
    
            # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
            #
            #location ~ \.php$ {
            #    root           html;
            #    fastcgi_pass   127.0.0.1:9000;
            #    fastcgi_index  index.php;
            #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
            #    include        fastcgi_params;
            #}
    
            # deny access to .htaccess files, if Apache's document root
            # concurs with nginx's one
            #
            #location ~ /\.ht {
            #    deny  all;
            #}
        }
    
    
        # another virtual host using mix of IP-, name-, and port-based configuration
        #
        #server {
        #    listen       8000;
        #    listen       somename:8080;
        #    server_name  somename  alias  another.alias;
    
        #    location / {
        #        root   html;
        #        index  index.html index.htm;
        #    }
        #}
    
    
        # HTTPS server
        #
        #server {
        #    listen       443 ssl;
        #    server_name  localhost;
    
        #    ssl_certificate      cert.pem;
        #    ssl_certificate_key  cert.key;
    
        #    ssl_session_cache    shared:SSL:1m;
        #    ssl_session_timeout  5m;
    
        #    ssl_ciphers  HIGH:!aNULL:!MD5;
        #    ssl_prefer_server_ciphers  on;
    
        #    location / {
        #        root   html;
        #        index  index.html index.htm;
        #    }
        #}
    
    }

      这很明显不是什么标准语言语法,但其同样遵从一定的准则,从而让用户更易于理解配置。如:

        1. 每一个细配置项,都使用一个';'作为结束标识;
        2. '#' 代表该行被注释(这几乎是linux默认标准);
        3. 使用'{}'表示一个配置块,'{}'中代表其子项配置;
        4. 使用include可以包含其他文件的配置,相当于将其中的配置项copy到当前位置,该包含路径可以是相对路径也可以是绝对路径;

      因此,基本上,你只要按照这个标准来做配置,至少语法 是不会有误了。但是,具体应该如何配置呢?实际上这要依赖于某类操作的具体实现,比如 location 的 配置, user 的配置,都是有各自的含义的。如果想要具体了解各细节配置,则必须要查询官网的配置定义了。请参考: https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/

      总体上来说,nginx有几个顶级配置: 

        events – 连接类的配置,比如最大连接数配置
        http – 重头戏,http模块配置,所有的代理服务http服务都在其子配置下
        mail – 邮件配置
        stream – TCP、UDP 底层通信协议配置,功能与http模块相仿
      一般地我们接接触最多的应该就是http配置了,至于其他功能,没有实践就没有发言权,略去不说。

      

    2. location的配置用例

      location的本义是用于定位一个http请求匹配情况,用于确定某个路径的请求应该如何做转换处理。它是属于 http 模块下的 server 模块下的一个选项配置。即 nginx -> http -> server -> location {..} 是其配置体现。它拥有相当多的配置项,因为做反向代理或其他服务器时,往往都可以通过这个配置,将功能完成。

      如下几个配置项,可供参考:

    http {
        upstream backend {
            ip_hash;
            server backend1.example.com weight=5;
            server backend2.example.com;
            server 192.0.0.1 backup;
        }
        server {
            root /www/data;
            # 路径相等处理,优先级最高
            location = / {
                #...
            }
            # 根路径配置,优先级最低
            location / {
                root /data/www;
            }
            # 带前缀的配置,优先级其次
            location /images/ {
                root /data;
            }
            # 正则匹配的配置,优先级较高
            location ~ \.(gif|jpg|png)$ {
                root /data/images;
            }
            # 正则取反配置
            location ^~ \.(php)$ {
                root /data/other;
            }
            # 反向代理的配置,将请求转换给后端服务
            # 如果backend是一个 upstream 配置,则做为一个负载均衡器使用
            location /api1 {
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_pass http://backend;
            }
            location /api2 {
                proxy_pass http://www.abc.com;
            }
            # 路径重写
            location /users/ {
                rewrite ^/users/(.*)$ /show?user=$1 break;
            }
            # 带状态码的返回配置
            location /wrong/url {
                return 404;
                open_file_cache_errors off;
            }
            location /permanently/moved/url {
                return 301 http://www.example.com/moved/here;
            }
            # 响应内容替换
            location / {
                sub_filter     'href="http://127.0.0.1:8080/'    'href="https://$host/';
                sub_filter     'img src="http://127.0.0.1:8080/' 'img src="https://$host/';
                sub_filter_once on;
            }
        }
    }

      更多内容可查阅官网: https://docs.nginx.com/nginx/admin-guide/web-server/web-server/

      总体上来说,location提供了可以配置如何查找本地文件,以及可以配置如何转发请求到其他服务器的方式。其中,还有很多附加的设置各种需求变量的实现,以辅助我们实现一些正常请求提供的内容。配置比较多,到真正使用时,按需配置即可。一般也是一次配置,永久使用,不会太费事。

     

    3. location配置的解析

      nginx有自己的一套配置方法,那么这些配置好了的语句,如何应用到具体的服务上呢?自然是需要先进行解析,然后放置到对应的内存空间变量中,然后在需要的时候进行读取判定,以及转换了。大体思路如此,但如何解析配置却并非易事。因为我们的配置是无数现有配置的任意组合,如何有效的放置到可理解的位置,应该需要单独的数据结构设计,以及解析步骤。实际上,这也相当于是一个简单的编译器或解析器,它需要将文本解析为认识的东西。

      下面我们就一起来看看nginx都是如何解析这些配置的吧!(这自然是在启动时完成的工作)

    // 首先,nginx会解析启动行命令,这里面可以指定配置文件
    // core/nginx.c 
    static ngx_int_t
    ngx_get_options(int argc, char *const *argv)
    {
        u_char     *p;
        ngx_int_t   i;
    
        for (i = 1; i < argc; i++) {
    
            p = (u_char *) argv[i];
    
            if (*p++ != '-') {
                ngx_log_stderr(0, "invalid option: \"%s\"", argv[i]);
                return NGX_ERROR;
            }
    
            while (*p) {
    
                switch (*p++) {
    
                case '?':
                case 'h':
                    ngx_show_version = 1;
                    ngx_show_help = 1;
                    break;
    
                case 'v':
                    ngx_show_version = 1;
                    break;
    
                case 'V':
                    ngx_show_version = 1;
                    ngx_show_configure = 1;
                    break;
                // -t 测试配置文件有效性
                case 't':
                    ngx_test_config = 1;
                    break;
    
                case 'T':
                    ngx_test_config = 1;
                    ngx_dump_config = 1;
                    break;
    
                case 'q':
                    ngx_quiet_mode = 1;
                    break;
    
                case 'p':
                    if (*p) {
                        ngx_prefix = p;
                        goto next;
                    }
    
                    if (argv[++i]) {
                        ngx_prefix = (u_char *) argv[i];
                        goto next;
                    }
    
                    ngx_log_stderr(0, "option \"-p\" requires directory name");
                    return NGX_ERROR;
                // -c nginx.conf 指定nginx配置文件路径
                case 'c':
                    if (*p) {
                        ngx_conf_file = p;
                        goto next;
                    }
    
                    if (argv[++i]) {
                        ngx_conf_file = (u_char *) argv[i];
                        goto next;
                    }
    
                    ngx_log_stderr(0, "option \"-c\" requires file name");
                    return NGX_ERROR;
    
                case 'g':
                    if (*p) {
                        ngx_conf_params = p;
                        goto next;
                    }
    
                    if (argv[++i]) {
                        ngx_conf_params = (u_char *) argv[i];
                        goto next;
                    }
    
                    ngx_log_stderr(0, "option \"-g\" requires parameter");
                    return NGX_ERROR;
                // -s (stop|quit|reopen|reload) 向现有运行的nginx进程发起控制命令
                case 's':
                    // 紧贴式给出命令: -sstop, -sreload
                    if (*p) {
                        ngx_signal = (char *) p;
    
                    } else if (argv[++i]) {
                        ngx_signal = argv[i];
    
                    } else {
                        ngx_log_stderr(0, "option \"-s\" requires parameter");
                        return NGX_ERROR;
                    }
    
                    if (ngx_strcmp(ngx_signal, "stop") == 0
                        || ngx_strcmp(ngx_signal, "quit") == 0
                        || ngx_strcmp(ngx_signal, "reopen") == 0
                        || ngx_strcmp(ngx_signal, "reload") == 0)
                    {
                        ngx_process = NGX_PROCESS_SIGNALLER;
                        goto next;
                    }
    
                    ngx_log_stderr(0, "invalid option: \"-s %s\"", ngx_signal);
                    return NGX_ERROR;
    
                default:
                    ngx_log_stderr(0, "invalid option: \"%c\"", *(p - 1));
                    return NGX_ERROR;
                }
            }
    
        next:
    
            continue;
        }
    
        return NGX_OK;
    }

      以上,是对命令行参数的简单解析,解析出来的变量放入到各全局变量中:ngx_show_version|ngx_show_help|ngx_show_configure|ngx_test_config|ngx_dump_config|ngx_quiet_mode|ngx_prefix|ngx_conf_file|ngx_conf_params|ngx_signal.

      以上,最重要的是两个参数:-c -s, 用于指定配置文件和操作现有nginx进程。当然,对于配置解析,自然最重要的是 -c 命令了。但对于一些没有指定的配置值,则使用系统的默认值。其处理如下:

    // core/nginx.c
    static ngx_int_t
    ngx_process_options(ngx_cycle_t *cycle)
    {
        u_char  *p;
        size_t   len;
    
        if (ngx_prefix) {
            len = ngx_strlen(ngx_prefix);
            p = ngx_prefix;
    
            if (len && !ngx_path_separator(p[len - 1])) {
                p = ngx_pnalloc(cycle->pool, len + 1);
                if (p == NULL) {
                    return NGX_ERROR;
                }
    
                ngx_memcpy(p, ngx_prefix, len);
                p[len++] = '/';
            }
    
            cycle->conf_prefix.len = len;
            cycle->conf_prefix.data = p;
            cycle->prefix.len = len;
            cycle->prefix.data = p;
    
        } else {
    
    #ifndef NGX_PREFIX
    
            p = ngx_pnalloc(cycle->pool, NGX_MAX_PATH);
            if (p == NULL) {
                return NGX_ERROR;
            }
    
            if (ngx_getcwd(p, NGX_MAX_PATH) == 0) {
                ngx_log_stderr(ngx_errno, "[emerg]: " ngx_getcwd_n " failed");
                return NGX_ERROR;
            }
    
            len = ngx_strlen(p);
    
            p[len++] = '/';
    
            cycle->conf_prefix.len = len;
            cycle->conf_prefix.data = p;
            cycle->prefix.len = len;
            cycle->prefix.data = p;
    
    #else
    
    #ifdef NGX_CONF_PREFIX
            // 默认路径前缀: conf/
            ngx_str_set(&cycle->conf_prefix, NGX_CONF_PREFIX);
    #else
            ngx_str_set(&cycle->conf_prefix, NGX_PREFIX);
    #endif
            ngx_str_set(&cycle->prefix, NGX_PREFIX);
    
    #endif
        }
    
        if (ngx_conf_file) {
            cycle->conf_file.len = ngx_strlen(ngx_conf_file);
            cycle->conf_file.data = ngx_conf_file;
    
        } else {
            // 默认配置文件: conf/nginx.conf
            ngx_str_set(&cycle->conf_file, NGX_CONF_PATH);
        }
    
        if (ngx_conf_full_name(cycle, &cycle->conf_file, 0) != NGX_OK) {
            return NGX_ERROR;
        }
    
        for (p = cycle->conf_file.data + cycle->conf_file.len - 1;
             p > cycle->conf_file.data;
             p--)
        {
            if (ngx_path_separator(*p)) {
                cycle->conf_prefix.len = p - cycle->conf_file.data + 1;
                cycle->conf_prefix.data = cycle->conf_file.data;
                break;
            }
        }
    
        if (ngx_conf_params) {
            cycle->conf_param.len = ngx_strlen(ngx_conf_params);
            cycle->conf_param.data = ngx_conf_params;
        }
    
        if (ngx_test_config) {
            cycle->log->log_level = NGX_LOG_INFO;
        }
    
        
    
    下一篇:没有了