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; } return NGX_OK; }
真正的配置文件解析是在初始化cycle的时候处理实现的:
// core/ngx_cycle.c ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle) { void *rv; char **senv; ngx_uint_t i, n; ngx_log_t *log; ngx_time_t *tp; ngx_conf_t conf; ngx_pool_t *pool; ngx_cycle_t *cycle, **old; ngx_shm_zone_t *shm_zone, *oshm_zone; ngx_list_part_t *part, *opart; ngx_open_file_t *file; ngx_listening_t *ls, *nls; ngx_core_conf_t *ccf, *old_ccf; ngx_core_module_t *module; char hostname[NGX_MAXHOSTNAMELEN]; ngx_timezone_update(); /* force localtime update with a new timezone */ tp = ngx_timeofday(); tp->sec = 0; ngx_time_update(); log = old_cycle->log; pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log); if (pool == NULL) { return NULL; } pool->log = log; cycle = ngx_pcalloc(pool, sizeof(ngx_cycle_t)); if (cycle == NULL) { ngx_destroy_pool(pool); return NULL; } cycle->pool = pool; cycle->log = log; cycle->old_cycle = old_cycle; cycle->conf_prefix.len = old_cycle->conf_prefix.len; cycle->conf_prefix.data = ngx_pstrdup(pool, &old_cycle->conf_prefix); if (cycle->conf_prefix.data == NULL) { ngx_destroy_pool(pool); return NULL; } cycle->prefix.len = old_cycle->prefix.len; cycle->prefix.data = ngx_pstrdup(pool, &old_cycle->prefix); if (cycle->prefix.data == NULL) { ngx_destroy_pool(pool); return NULL; } cycle->conf_file.len = old_cycle->conf_file.len; cycle->conf_file.data = ngx_pnalloc(pool, old_cycle->conf_file.len + 1); if (cycle->conf_file.data == NULL) { ngx_destroy_pool(pool); return NULL; } ngx_cpystrn(cycle->conf_file.data, old_cycle->conf_file.data, old_cycle->conf_file.len + 1); cycle->conf_param.len = old_cycle->conf_param.len; cycle->conf_param.data = ngx_pstrdup(pool, &old_cycle->conf_param); if (cycle->conf_param.data == NULL) { ngx_destroy_pool(pool); return NULL; } n = old_cycle->paths.nelts ? old_cycle->paths.nelts : 10; if (ngx_array_init(&cycle->paths, pool, n, sizeof(ngx_path_t *)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } ngx_memzero(cycle->paths.elts, n * sizeof(ngx_path_t *)); if (ngx_array_init(&cycle->config_dump, pool, 1, sizeof(ngx_conf_dump_t)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } // 使用红黑树存放配置信息 ngx_rbtree_init(&cycle->config_dump_rbtree, &cycle->config_dump_sentinel, ngx_str_rbtree_insert_value); // 默认使用 20 个端口服务 if (old_cycle->open_files.part.nelts) { n = old_cycle->open_files.part.nelts; for (part = old_cycle->open_files.part.next; part; part = part->next) { n += part->nelts; } } else { n = 20; } // 每个监听端口使用一个 open_files 表示 if (ngx_list_init(&cycle->open_files, pool, n, sizeof(ngx_open_file_t)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } if (old_cycle->shared_memory.part.nelts) { n = old_cycle->shared_memory.part.nelts; for (part = old_cycle->shared_memory.part.next; part; part = part->next) { n += part->nelts; } } else { n = 1; } if (ngx_list_init(&cycle->shared_memory, pool, n, sizeof(ngx_shm_zone_t)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } n = old_cycle->listening.nelts ? old_cycle->listening.nelts : 10; if (ngx_array_init(&cycle->listening, pool, n, sizeof(ngx_listening_t)) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } ngx_memzero(cycle->listening.elts, n * sizeof(ngx_listening_t)); ngx_queue_init(&cycle->reusable_connections_queue); cycle->conf_ctx = ngx_pcalloc(pool, ngx_max_module * sizeof(void *)); if (cycle->conf_ctx == NULL) { ngx_destroy_pool(pool); return NULL; } // 获取当前机器的hostname if (gethostname(hostname, NGX_MAXHOSTNAMELEN) == -1) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "gethostname() failed"); ngx_destroy_pool(pool); return NULL; } /* on Linux gethostname() silently truncates name that does not fit */ hostname[NGX_MAXHOSTNAMELEN - 1] = '\0'; cycle->hostname.len = ngx_strlen(hostname); cycle->hostname.data = ngx_pnalloc(pool, cycle->hostname.len); if (cycle->hostname.data == NULL) { ngx_destroy_pool(pool); return NULL; } ngx_strlow(cycle->hostname.data, (u_char *) hostname, cycle->hostname.len); // 创建module内存空间 if (ngx_cycle_modules(cycle) != NGX_OK) { ngx_destroy_pool(pool); return NULL; } // 让各模块依次进行配置 for (i = 0; cycle->modules[i]; i++) { // NGINX_CORE_MODULE 可以进行配置文件处理 // 即几套顶级模块, http,events,mail,... if (cycle->modules[i]->type != NGX_CORE_MODULE) { continue; } module = cycle->modules[i]->ctx; // 调用各模块的 create_conf 实现配置加载 // 其中,以为的http模块会进行解析,其实却没有 if (module->create_conf) { rv = module->create_conf(cycle); if (rv == NULL) { ngx_destroy_pool(pool); return NULL; } cycle->conf_ctx[cycle->modules[i]->index] = rv; } } senv = environ; ngx_memzero(&conf, sizeof(ngx_conf_t)); /* STUB: init array ? */ conf.args = ngx_array_create(pool, 10, sizeof(ngx_str_t)); if (conf.args == NULL) { ngx_destroy_pool(pool); return NULL; } conf.temp_pool = ngx_create_pool(NGX_CYCLE_POOL_SIZE, log); if (conf.temp_pool == NULL) { ngx_destroy_pool(pool); return NULL; } conf.ctx = cycle->conf_ctx; conf.cycle = cycle; conf.pool = pool; conf.log = log; conf.module_type = NGX_CORE_MODULE; conf.cmd_type = NGX_MAIN_CONF; #if 0 log->log_level = NGX_LOG_DEBUG_ALL; #endif if (ngx_conf_param(&conf) != NGX_CONF_OK) { environ = senv; ngx_destroy_cycle_pools(&conf); return NULL; } if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) { environ = senv; ngx_destroy_cycle_pools(&conf); return NULL; } if (ngx_test_config && !ngx_quiet_mode) { ngx_log_stderr(0, "the configuration file %s syntax is ok", cycle->conf_file.data); } for (i = 0; cycle->modules[i]; i++) { if (cycle->modules[i]->type != NGX_CORE_MODULE) { continue; } module = cycle->modules[i]->ctx; if (module->init_conf) { if (module->init_conf(cycle, cycle->conf_ctx[cycle->modules[i]->index]) == NGX_CONF_ERROR) { environ = senv; ngx_destroy_cycle_pools(&conf); return NULL; } } } if (ngx_process == NGX_PROCESS_SIGNALLER) { return cycle; } ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); if (ngx_test_config) { if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) { goto failed; } } else if (!ngx_is_init_cycle(old_cycle)) { /* * we do not create the pid file in the first ngx_init_cycle() call * because we need to write the demonized process pid */ old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx, ngx_core_module); if (ccf->pid.len != old_ccf->pid.len || ngx_strcmp(ccf->pid.data, old_ccf->pid.data) != 0) { /* new pid file name */ if (ngx_create_pidfile(&ccf->pid, log) != NGX_OK) { goto failed; } ngx_delete_pidfile(old_cycle); } } if (ngx_test_lockfile(cycle->lock_file.data, log) != NGX_OK) { goto failed; } if (ngx_create_paths(cycle, ccf->user) != NGX_OK) { goto failed; } if (ngx_log_open_default(cycle) != NGX_OK) { goto failed; } /* open the new files */ part = &cycle->open_files.part; file = part->elts; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; file = part->elts; i = 0; } if (file[i].name.len == 0) { continue; } file[i].fd = ngx_open_file(file[i].name.data, NGX_FILE_APPEND, NGX_FILE_CREATE_OR_OPEN, NGX_FILE_DEFAULT_ACCESS); ngx_log_debug3(NGX_LOG_DEBUG_CORE, log, 0, "log: %p %d \"%s\"", &file[i], file[i].fd, file[i].name.data); if (file[i].fd == NGX_INVALID_FILE) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, ngx_open_file_n " \"%s\" failed", file[i].name.data); goto failed; } #if !(NGX_WIN32) if (fcntl(file[i].fd, F_SETFD, FD_CLOEXEC) == -1) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, "fcntl(FD_CLOEXEC) \"%s\" failed", file[i].name.data); goto failed; } #endif } cycle->log = &cycle->new_log; pool->log = &cycle->new_log; /* create shared memory */ part = &cycle->shared_memory.part; shm_zone = part->elts; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; shm_zone = part->elts; i = 0; } if (shm_zone[i].shm.size == 0) { ngx_log_error(NGX_LOG_EMERG, log, 0, "zero size shared memory zone \"%V\"", &shm_zone[i].shm.name); goto failed; } shm_zone[i].shm.log = cycle->log; opart = &old_cycle->shared_memory.part; oshm_zone = opart->elts; for (n = 0; /* void */ ; n++) { if (n >= opart->nelts) { if (opart->next == NULL) { break; } opart = opart->next; oshm_zone = opart->elts; n = 0; } if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) { continue; } if (ngx_strncmp(shm_zone[i].shm.name.data, oshm_zone[n].shm.name.data, shm_zone[i].shm.name.len) != 0) { continue; } if (shm_zone[i].tag == oshm_zone[n].tag && shm_zone[i].shm.size == oshm_zone[n].shm.size && !shm_zone[i].noreuse) { shm_zone[i].shm.addr = oshm_zone[n].shm.addr; #if (NGX_WIN32) shm_zone[i].shm.handle = oshm_zone[n].shm.handle; #endif if (shm_zone[i].init(&shm_zone[i], oshm_zone[n].data) != NGX_OK) { goto failed; } goto shm_zone_found; } break; } if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) { goto failed; } if (ngx_init_zone_pool(cycle, &shm_zone[i]) != NGX_OK) { goto failed; } if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) { goto failed; } shm_zone_found: continue; } /* handle the listening sockets */ if (old_cycle->listening.nelts) { ls = old_cycle->listening.elts; for (i = 0; i < old_cycle->listening.nelts; i++) { ls[i].remain = 0; } nls = cycle->listening.elts; for (n = 0; n < cycle->listening.nelts; n++) { for (i = 0; i < old_cycle->listening.nelts; i++) { if (ls[i].ignore) { continue; } if (ls[i].remain) { continue; } if (ls[i].type != nls[n].type) { continue; } if (ngx_cmp_sockaddr(nls[n].sockaddr, nls[n].socklen, ls[i].sockaddr, ls[i].socklen, 1) == NGX_OK) { nls[n].fd = ls[i].fd; nls[n].inherited = ls[i].inherited; nls[n].previous = &ls[i]; ls[i].remain = 1; if (ls[i].backlog != nls[n].backlog) { nls[n].listen = 1; } #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) /* * FreeBSD, except the most recent versions, * could not remove accept filter */ nls[n].deferred_accept = ls[i].deferred_accept; if (ls[i].accept_filter && nls[n].accept_filter) { if (ngx_strcmp(ls[i].accept_filter, nls[n].accept_filter) != 0) { nls[n].delete_deferred = 1; nls[n].add_deferred = 1; } } else if (ls[i].accept_filter) { nls[n].delete_deferred = 1; } else if (nls[n].accept_filter) { nls[n].add_deferred = 1; } #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) if (ls[i].deferred_accept && !nls[n].deferred_accept) { nls[n].delete_deferred = 1; } else if (ls[i].deferred_accept != nls[n].deferred_accept) { nls[n].add_deferred = 1; } #endif #if (NGX_HAVE_REUSEPORT) if (nls[n].reuseport && !ls[i].reuseport) { nls[n].add_reuseport = 1; } #endif break; } } if (nls[n].fd == (ngx_socket_t) -1) { nls[n].open = 1; #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) if (nls[n].accept_filter) { nls[n].add_deferred = 1; } #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) if (nls[n].deferred_accept) { nls[n].add_deferred = 1; } #endif } } } else { ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { ls[i].open = 1; #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) if (ls[i].accept_filter) { ls[i].add_deferred = 1; } #endif #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) if (ls[i].deferred_accept) { ls[i].add_deferred = 1; } #endif } } if (ngx_open_listening_sockets(cycle) != NGX_OK) { goto failed; } if (!ngx_test_config) { ngx_configure_listening_sockets(cycle); } /* commit the new cycle configuration */ if (!ngx_use_stderr) { (void) ngx_log_redirect_stderr(cycle); } pool->log = cycle->log; if (ngx_init_modules(cycle) != NGX_OK) { /* fatal */ exit(1); } /* close and delete stuff that lefts from an old cycle */ /* free the unnecessary shared memory */ opart = &old_cycle->shared_memory.part; oshm_zone = opart->elts; for (i = 0; /* void */ ; i++) { if (i >= opart->nelts) { if (opart->next == NULL) { goto old_shm_zone_done; } opart = opart->next; oshm_zone = opart->elts; i = 0; } part = &cycle->shared_memory.part; shm_zone = part->elts; for (n = 0; /* void */ ; n++) { if (n >= part->nelts) { if (part->next == NULL) { break; } part = part->next; shm_zone = part->elts; n = 0; } if (oshm_zone[i].shm.name.len != shm_zone[n].shm.name.len) { continue; } if (ngx_strncmp(oshm_zone[i].shm.name.data, shm_zone[n].shm.name.data, oshm_zone[i].shm.name.len) != 0) { continue; } if (oshm_zone[i].tag == shm_zone[n].tag && oshm_zone[i].shm.size == shm_zone[n].shm.size && !oshm_zone[i].noreuse) { goto live_shm_zone; } break; } ngx_shm_free(&oshm_zone[i].shm); live_shm_zone: continue; } old_shm_zone_done: /* close the unnecessary listening sockets */ ls = old_cycle->listening.elts; for (i = 0; i < old_cycle->listening.nelts; i++) { if (ls[i].remain || ls[i].fd == (ngx_socket_t) -1) { continue; } if (ngx_close_socket(ls[i].fd) == -1) { ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, ngx_close_socket_n " listening socket on %V failed", &ls[i].addr_text); } #if (NGX_HAVE_UNIX_DOMAIN) if (ls[i].sockaddr->sa_family == AF_UNIX) { u_char *name; name = ls[i].addr_text.data + sizeof("unix:") - 1; ngx_log_error(NGX_LOG_WARN, cycle->log, 0, "deleting socket %s", name); if (ngx_delete_file(name) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, ngx_delete_file_n " %s failed", name); } } #endif } /* close the unnecessary open files */ part = &old_cycle->open_files.part; file = part->elts; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; file = part->elts; i = 0; } if (file[i].fd == NGX_INVALID_FILE || file[i].fd == ngx_stderr) { continue; } if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, ngx_close_file_n " \"%s\" failed", file[i].name.data); } } ngx_destroy_pool(conf.temp_pool); if (ngx_process == NGX_PROCESS_MASTER || ngx_is_init_cycle(old_cycle)) { ngx_destroy_pool(old_cycle->pool); cycle->old_cycle = NULL; return cycle; } if (ngx_temp_pool == NULL) { ngx_temp_pool = ngx_create_pool(128, cycle->log); if (ngx_temp_pool == NULL) { ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, "could not create ngx_temp_pool"); exit(1); } n = 10; if (ngx_array_init(&ngx_old_cycles, ngx_temp_pool, n, sizeof(ngx_cycle_t *)) != NGX_OK) { exit(1); } ngx_memzero(ngx_old_cycles.elts, n * sizeof(ngx_cycle_t *)); ngx_cleaner_event.handler = ngx_clean_old_cycles; ngx_cleaner_event.log = cycle->log; ngx_cleaner_event.data = &dumb; dumb.fd = (ngx_socket_t) -1; } ngx_temp_pool->log = cycle->log; old = ngx_array_push(&ngx_old_cycles); if (old == NULL) { exit(1); } *old = old_cycle; if (!ngx_cleaner_event.timer_set) { ngx_add_timer(&ngx_cleaner_event, 30000); ngx_cleaner_event.timer_set = 1; } return cycle; failed: if (!ngx_is_init_cycle(old_cycle)) { old_ccf = (ngx_core_conf_t *) ngx_get_conf(old_cycle->conf_ctx, ngx_core_module); if (old_ccf->environment) { environ = old_ccf->environment; } } /* rollback the new cycle configuration */ part = &cycle->open_files.part; file = part->elts; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; file = part->elts; i = 0; } if (file[i].fd == NGX_INVALID_FILE || file[i].fd == ngx_stderr) { continue; } if (ngx_close_file(file[i].fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, ngx_close_file_n " \"%s\" failed", file[i].name.data); } } /* free the newly created shared memory */ part = &cycle->shared_memory.part; shm_zone = part->elts; for (i = 0; /* void */ ; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; shm_zone = part->elts; i = 0; } if (shm_zone[i].shm.addr == NULL) { continue; } opart = &old_cycle->shared_memory.part; oshm_zone = opart->elts; for (n = 0; /* void */ ; n++) { if (n >= opart->nelts) { if (opart->next == NULL) { break; } opart = opart->next; oshm_zone = opart->elts; n = 0; } if (shm_zone[i].shm.name.len != oshm_zone[n].shm.name.len) { continue; } if (ngx_strncmp(shm_zone[i].shm.name.data, oshm_zone[n].shm.name.data, shm_zone[i].shm.name.len) != 0) { continue; } if (shm_zone[i].tag == oshm_zone[n].tag && shm_zone[i].shm.size == oshm_zone[n].shm.size && !shm_zone[i].noreuse) { goto old_shm_zone_found; } break; } ngx_shm_free(&shm_zone[i].shm); old_shm_zone_found: continue; } if (ngx_test_config) { ngx_destroy_cycle_pools(&conf); return NULL; } ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { if (ls[i].fd == (ngx_socket_t) -1 || !ls[i].open) { continue; } if (ngx_close_socket(ls[i].fd) == -1) { ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, ngx_close_socket_n " %V failed", &ls[i].addr_text); } } ngx_destroy_cycle_pools(&conf); return NULL; }
nginx中设置了几个初始化的点,create_conf, init_conf, 供各模块实现各自的解析逻辑,以及使用一个全局的解析 ngx_conf_parse() 实现文件解析。
那么 http 作为独立的模块,其是否参与配置解析呢?我们看下其模块的配置即可:
// http 模块的配置简略 // http/ngx_http.c static ngx_core_module_t ngx_http_module_ctx = { ngx_string("http"), // 不做 create_conf 处理 NULL, NULL }; ngx_module_t ngx_http_module = { NGX_MODULE_V1, &ngx_http_module_ctx, /* module context */ ngx_http_commands, /* module directives */ NGX_CORE_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING };
真正的解析工作在 ngx_conf_file.c 文件中进行, 有ngx_conf_param(), ngx_conf_parse() 完成具体的解析。
// core/ngx_conf_file.c char * ngx_conf_param(ngx_conf_t *cf) { char *rv; ngx_str_t *param; ngx_buf_t b; ngx_conf_file_t conf_file; param = &cf->cycle->conf_param; if (param->len == 0) { return NGX_CONF_OK; } ngx_memzero(&conf_file, sizeof(ngx_conf_file_t)); ngx_memzero(&b, sizeof(ngx_buf_t)); b.start = param->data; b.pos = param->data; b.last = param->data + param->len; b.end = b.last; b.temporary = 1; conf_file.file.fd = NGX_INVALID_FILE; conf_file.file.name.data = NULL; conf_file.line = 0; cf->conf_file = &conf_file; cf->conf_file->buffer = &b; // 解析param信息,不解析配置文件 rv = ngx_conf_parse(cf, NULL); cf->conf_file = NULL; return rv; } char * ngx_conf_parse(ngx_conf_t *cf, ngx_str_t *filename) { char *rv; ngx_fd_t fd; ngx_int_t rc; ngx_buf_t buf; ngx_conf_file_t *prev, conf_file; enum { parse_file = 0, parse_block, parse_param } type; #if (NGX_SUPPRESS_WARN) fd = NGX_INVALID_FILE; prev = NULL; #endif // 如果给定配置文件,则解析 if (filename) { /* open configuration file */ // 打开配置文件 fd = ngx_open_file(filename->data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); if (fd == NGX_INVALID_FILE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, ngx_open_file_n " \"%s\" failed", filename->data); return NGX_CONF_ERROR; } prev = cf->conf_file; cf->conf_file = &conf_file; if (ngx_fd_info(fd, &cf->conf_file->file.info) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_EMERG, cf->log, ngx_errno, ngx_fd_info_n " \"%s\" failed", filename->data); } cf->conf_file->buffer = &buf; // NGX_CONF_BUFFER=1024 buf.start = ngx_alloc(NGX_CONF_BUFFER, cf->log); if (buf.start == NULL) { goto failed; } buf.pos = buf.start; buf.last = buf.start; buf.end = buf.last + NGX_CONF_BUFFER; buf.temporary = 1; // 初始化配置文件信息 cf->conf_file->file.fd = fd; cf->conf_file->file.name.len = filename->len; cf->conf_file->file.name.data = filename->data; cf->conf_file->file.offset = 0; cf->conf_file->file.log = cf->log; cf->conf_file->line = 1; type = parse_file; if (ngx_dump_config #if (NGX_DEBUG) || 1 #endif ) { if (ngx_conf_add_dump(cf, filename) != NGX_OK) { goto failed; } } else { cf->conf_file->dump = NULL; } } else if (cf->conf_file->file.fd != NGX_INVALID_FILE) { type = parse_block; } else { type = parse_param; } // 此处实现真正的解析操作 for ( ;; ) { // 重要1: 读取出一个个地token信息, 以pos, start等变量做标识起始 rc = ngx_conf_read_token(cf); /* * ngx_conf_read_token() may return * * NGX_ERROR there is error * NGX_OK the token terminated by ";" was found * NGX_CONF_BLOCK_START the token terminated by "{" was found * NGX_CONF_BLOCK_DONE the "}" was found * NGX_CONF_FILE_DONE the configuration file is done */ if (rc == NGX_ERROR) { goto done; } if (rc == NGX_CONF_BLOCK_DONE) { if (type != parse_block) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\""); goto failed; } goto done; } if (rc == NGX_CONF_FILE_DONE) { if (type == parse_block) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of file, expecting \"}\""); goto failed; } goto done; } if (rc == NGX_CONF_BLOCK_START) { if (type == parse_param) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "block directives are not supported " "in -g option"); goto failed; } } /* rc == NGX_OK || rc == NGX_CONF_BLOCK_START */ if (cf->handler) { /* * the custom handler, i.e., that is used in the http's * "types { ... }" directive */ if (rc == NGX_CONF_BLOCK_START) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"{\""); goto failed; } rv = (*cf->handler)(cf, NULL, cf->handler_conf); if (rv == NGX_CONF_OK) { continue; } if (rv == NGX_CONF_ERROR) { goto failed; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s", rv); goto failed; } // 重要2: 将解析出的变量进行细致处理 rc = ngx_conf_handler(cf, rc); if (rc == NGX_ERROR) { goto failed; } } failed: rc = NGX_ERROR; done: if (filename) { if (cf->conf_file->buffer->start) { ngx_free(cf->conf_file->buffer->start); } if (ngx_close_file(fd) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, ngx_close_file_n " %s failed", filename->data); rc = NGX_ERROR; } cf->conf_file = prev; } if (rc == NGX_ERROR) { return NGX_CONF_ERROR; } return NGX_CONF_OK; } // 读取一个个地 token 信息 static ngx_int_t ngx_conf_read_token(ngx_conf_t *cf) { u_char *start, ch, *src, *dst; off_t file_size; size_t len; ssize_t n, size; ngx_uint_t found, need_space, last_space, sharp_comment, variable; ngx_uint_t quoted, s_quoted, d_quoted, start_line; ngx_str_t *word; ngx_buf_t *b, *dump; found = 0; need_space = 0; last_space = 1; sharp_comment = 0; variable = 0; quoted = 0; s_quoted = 0; d_quoted = 0; cf->args->nelts = 0; b = cf->conf_file->buffer; dump = cf->conf_file->dump; start = b->pos; start_line = cf->conf_file->line; file_size = ngx_file_size(&cf->conf_file->file.info); for ( ;; ) { // last默认是最大buffer,pos默认为0,所以以下逻辑不一定走 if (b->pos >= b->last) { if (cf->conf_file->file.offset >= file_size) { if (cf->args->nelts > 0 || !last_space) { if (cf->conf_file->file.fd == NGX_INVALID_FILE) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of parameter, " "expecting \";\""); return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected end of file, " "expecting \";\" or \"}\""); return NGX_ERROR; } return NGX_CONF_FILE_DONE; } // 默认两值相等,len为0 len = b->pos - start; if (len == NGX_CONF_BUFFER) { cf->conf_file->line = start_line; if (d_quoted) { ch = '"'; } else if (s_quoted) { ch = '\''; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "too long parameter \"%*s...\" started", 10, start); return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "too long parameter, probably " "missing terminating \"%c\" character", ch); return NGX_ERROR; } if (len) { ngx_memmove(b->start, start, len); } // 计算剩余大小 size = (ssize_t) (file_size - cf->conf_file->file.offset); // 如果剩余大小超出当前缓冲的大小,则只能取当前缓冲剩余大小 if (size > b->end - (b->start + len)) { size = b->end - (b->start + len); } // 从配置文件中读取size大小的数据出来,放到buf中备用,相当于尽量一次全部读取 n = ngx_read_file(&cf->conf_file->file, b->start + len, size, cf->conf_file->file.offset); if (n == NGX_ERROR) { return NGX_ERROR; } if (n != size) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, ngx_read_file_n " returned " "only %z bytes instead of %z", n, size); return NGX_ERROR; } // 交换各偏移信息 b->pos = b->start + len; b->last = b->pos + n; start = b->start; if (dump) { dump->last = ngx_cpymem(dump->last, b->pos, size); } } // 从文件中读出一个字符用以判定,即如果假设文件已读取完,则相当于一个个字符取出判定 ch = *b->pos++; // 遇到换行,line++,注释标识重置 if (ch == LF) { cf->conf_file->line++; if (sharp_comment) { sharp_comment = 0; } } // 如果注释标识有效,则忽略当前段的字符,直到标识被清除 if (sharp_comment) { continue; } // 引号闭合处理 if (quoted) { quoted = 0; continue; } // 判断是否有新的语句开始,类似空格都可以 if (need_space) { if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { last_space = 1; need_space = 0; continue; } // 遇到分号,当前解析结束,即得到一个独立语句,如 root /www; if (ch == ';') { return NGX_OK; } // 遇到块也结束当前解析,交由子处理器处理 if (ch == '{') { return NGX_CONF_BLOCK_START; } if (ch == ')') { last_space = 1; need_space = 0; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"%c\"", ch); return NGX_ERROR; } } if (last_space) { // 新语句开始,行号暂存 start = b->pos - 1; start_line = cf->conf_file->line; // 忽略空格类字符 if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { continue; } switch (ch) { case ';': case '{': if (cf->args->nelts == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"%c\"", ch); return NGX_ERROR; } // 语句结束,nelts 到底是啥? if (ch == '{') { return NGX_CONF_BLOCK_START; } return NGX_OK; case '}': if (cf->args->nelts != 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unexpected \"}\""); return NGX_ERROR; } return NGX_CONF_BLOCK_DONE; case '#': sharp_comment = 1; continue; case '\\': // 转义符 quoted = 1; last_space = 0; continue; case '"': start++; d_quoted = 1; last_space = 0; continue; case '\'': start++; s_quoted = 1; last_space = 0; continue; case '$': // 变量解析 variable = 1; last_space = 0; continue; default: last_space = 0; } } else { // ${xxx} 变量 if (ch == '{' && variable) { continue; } variable = 0; if (ch == '\\') { quoted = 1; continue; } if (ch == '$') { variable = 1; continue; } // 引号处理 if (d_quoted) { if (ch == '"') { d_quoted = 0; need_space = 1; found = 1; } } else if (s_quoted) { if (ch == '\'') { s_quoted = 0; need_space = 1; found = 1; } } else if (ch == ' ' || ch == '\t' || ch == CR || ch == LF || ch == ';' || ch == '{') { last_space = 1; found = 1; // 完整解析一个语句 } if (found) { word = ngx_array_push(cf->args); if (word == NULL) { return NGX_ERROR; } word->data = ngx_pnalloc(cf->pool, b->pos - 1 - start + 1); if (word->data == NULL) { return NGX_ERROR; } // 将解析出的语句赋值到 args 中 for (dst = word->data, src = start, len = 0; src < b->pos - 1; len++) { if (*src == '\\') { switch (src[1]) { case '"': case '\'': case '\\': // 去除转义标识 src++; break; case 't': // 翻译转义 *dst++ = '\t'; src += 2; continue; case 'r': *dst++ = '\r'; src += 2; continue; case 'n': *dst++ = '\n'; src += 2; continue; } } *dst++ = *src++; } *dst = '\0'; word->len = len; if (ch == ';') { return NGX_OK; } if (ch == '{') { return NGX_CONF_BLOCK_START; } found = 0; } } } }
大体原理是:读取一个个token,然后进行依次翻译,分小语句,大block,… 类型依次处理。将解析的结果放入cf->args中。其解析结果如 location ~ /api/.*go { ,如 listen 80 81;
还差一个细节,就是读取到token之后,又是如何传递给各模块的呢?实际就是上面的重点2: ngx_conf_handler() 处理。
static ngx_int_t ngx_conf_handler(ngx_conf_t *cf, ngx_int_t last) { char *rv; void *conf, **confp; ngx_uint_t i, found; ngx_str_t *name; ngx_command_t *cmd; // 获取最后存入的配置变量名,elts是个神奇的变量,相当于数组末尾值, nelts 相当于数组长度 name = cf->args->elts; found = 0; // 遍历模块的 commands 列表,依次调用进行处理, 比如 root /www 处理 // location /api {..} 处理 for (i = 0; cf->cycle->modules[i]; i++) { cmd = cf->cycle->modules[i]->commands; if (cmd == NULL) { continue; } for ( /* void */ ; cmd->name.len; cmd++) { // 名字不同必然不负责解析 if (name->len != cmd->name.len) { continue; } if (ngx_strcmp(name->data, cmd->name.data) != 0) { continue; } found = 1; // 配置模块处理 if (cf->cycle->modules[i]->type != NGX_CONF_MODULE && cf->cycle->modules[i]->type != cf->module_type) { continue; } /* is the directive's location right ? */ // 变量类型判定 if (!(cmd->type & cf->cmd_type)) { continue; } if (!(cmd->type & NGX_CONF_BLOCK) && last != NGX_OK) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "directive \"%s\" is not terminated by \";\"", name->data); return NGX_ERROR; } if ((cmd->type & NGX_CONF_BLOCK) && last != NGX_CONF_BLOCK_START) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "directive \"%s\" has no opening \"{\"", name->data); return NGX_ERROR; } /* is the directive's argument count right ? */ // 参数个数匹配 if (!(cmd->type & NGX_CONF_ANY)) { if (cmd->type & NGX_CONF_FLAG) { if (cf->args->nelts != 2) { goto invalid; } } else if (cmd->type & NGX_CONF_1MORE) { if (cf->args->nelts < 2) { goto invalid; } } else if (cmd->type & NGX_CONF_2MORE) { if (cf->args->nelts < 3) { goto invalid; } } else if (cf->args->nelts > NGX_CONF_MAX_ARGS) { goto invalid; } else if (!(cmd->type & argument_number[cf->args->nelts - 1])) { goto invalid; } } /* set up the directive's configuration context */ conf = NULL; // 找不同的配置级别位置存储(这指针用得6啊) if (cmd->type & NGX_DIRECT_CONF) { conf = ((void **) cf->ctx)[cf->cycle->modules[i]->index]; } else if (cmd->type & NGX_MAIN_CONF) { conf = &(((void **) cf->ctx)[cf->cycle->modules[i]->index]); } else if (cf->ctx) { confp = *(void **) ((char *) cf->ctx + cmd->conf); if (confp) { conf = confp[cf->cycle->modules[i]->ctx_index]; } } // 调用各命令的set方法,处理配置语义 // 实际上就涉及到内嵌套解析问题了 // 同样不会很简单哦 rv = cmd->set(cf, cmd, conf); if (rv == NGX_CONF_OK) { return NGX_OK; } if (rv == NGX_CONF_ERROR) { return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"%s\" directive %s", name->data, rv); return NGX_ERROR; } } // 配置使用错误 if (found) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "\"%s\" directive is not allowed here", name->data); return NGX_ERROR; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "unknown directive \"%s\"", name->data); return NGX_ERROR; invalid: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid number of arguments in \"%s\" directive", name->data); return NGX_ERROR; }
将解析出的配置信息接入到各子模块之后,才算是真正意义上的解析成功,也才能被各子模块使用。
下面我们再来看个样例,子模块如何解析配置,http location ….
listen 80; 的解析比较简单些,有兴趣可查看:
static char * ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_http_core_srv_conf_t *cscf = conf; ngx_str_t *value, size; ngx_url_t u; ngx_uint_t n; ngx_http_listen_opt_t lsopt; cscf->listen = 1; value = cf->args->elts; ngx_memzero(&u, sizeof(ngx_url_t)); u.url = value[1]; u.listen = 1; u.default_port = 80; if (ngx_parse_url(cf->pool, &u) != NGX_OK) { if (u.err) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s in \"%V\" of the \"listen\" directive", u.err, &u.url); } return NGX_CONF_ERROR; } ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t)); lsopt.backlog = NGX_LISTEN_BACKLOG; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) lsopt.setfib = -1; #endif #if (NGX_HAVE_TCP_FASTOPEN) lsopt.fastopen = -1; #endif #if (NGX_HAVE_INET6) lsopt.ipv6only = 1; #endif // 更多参数解析,见官方文档说明 for (n = 2; n < cf->args->nelts; n++) { if (ngx_strcmp(value[n].data, "default_server") == 0 || ngx_strcmp(value[n].data, "default") == 0) { lsopt.default_server = 1; continue; } if (ngx_strcmp(value[n].data, "bind") == 0) { lsopt.set = 1; lsopt.bind = 1; continue; } #if (NGX_HAVE_SETFIB) if (ngx_strncmp(value[n].data, "setfib=", 7) == 0) { lsopt.setfib = ngx_atoi(value[n].data + 7, value[n].len - 7); lsopt.set = 1; lsopt.bind = 1; if (lsopt.setfib == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid setfib \"%V\"", &value[n]); return NGX_CONF_ERROR; } continue; } #endif #if (NGX_HAVE_TCP_FASTOPEN) if (ngx_strncmp(value[n].data, "fastopen=", 9) == 0) { lsopt.fastopen = ngx_atoi(value[n].data + 9, value[n].len - 9); lsopt.set = 1; lsopt.bind = 1; if (lsopt.fastopen == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid fastopen \"%V\"", &value[n]); return NGX_CONF_ERROR; } continue; } #endif if (ngx_strncmp(value[n].data, "backlog=", 8) == 0) { lsopt.backlog = ngx_atoi(value[n].data + 8, value[n].len - 8); lsopt.set = 1; lsopt.bind = 1; if (lsopt.backlog == NGX_ERROR || lsopt.backlog == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid backlog \"%V\"", &value[n]); return NGX_CONF_ERROR; } continue; } if (ngx_strncmp(value[n].data, "rcvbuf=", 7) == 0) { size.len = value[n].len - 7; size.data = value[n].data + 7; lsopt.rcvbuf = ngx_parse_size(&size); lsopt.set = 1; lsopt.bind = 1; if (lsopt.rcvbuf == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid rcvbuf \"%V\"", &value[n]); return NGX_CONF_ERROR; } continue; } if (ngx_strncmp(value[n].data, "sndbuf=", 7) == 0) { size.len = value[n].len - 7; size.data = value[n].data + 7; lsopt.sndbuf = ngx_parse_size(&size); lsopt.set = 1; lsopt.bind = 1; if (lsopt.sndbuf == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid sndbuf \"%V\"", &value[n]); return NGX_CONF_ERROR; } continue; } if (ngx_strncmp(value[n].data, "accept_filter=", 14) == 0) { #if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) lsopt.accept_filter = (char *) &value[n].data[14]; lsopt.set = 1; lsopt.bind = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "accept filters \"%V\" are not supported " "on this platform, ignored", &value[n]); #endif continue; } if (ngx_strcmp(value[n].data, "deferred") == 0) { #if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) lsopt.deferred_accept = 1; lsopt.set = 1; lsopt.bind = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the deferred accept is not supported " "on this platform, ignored"); #endif continue; } if (ngx_strncmp(value[n].data, "ipv6only=o", 10) == 0) { #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) if (ngx_strcmp(&value[n].data[10], "n") == 0) { lsopt.ipv6only = 1; } else if (ngx_strcmp(&value[n].data[10], "ff") == 0) { lsopt.ipv6only = 0; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid ipv6only flags \"%s\"", &value[n].data[9]); return NGX_CONF_ERROR; } lsopt.set = 1; lsopt.bind = 1; continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "ipv6only is not supported " "on this platform"); return NGX_CONF_ERROR; #endif } if (ngx_strcmp(value[n].data, "reuseport") == 0) { #if (NGX_HAVE_REUSEPORT) lsopt.reuseport = 1; lsopt.set = 1; lsopt.bind = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "reuseport is not supported " "on this platform, ignored"); #endif continue; } if (ngx_strcmp(value[n].data, "ssl") == 0) { #if (NGX_HTTP_SSL) lsopt.ssl = 1; continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the \"ssl\" parameter requires " "ngx_http_ssl_module"); return NGX_CONF_ERROR; #endif } if (ngx_strcmp(value[n].data, "http2") == 0) { #if (NGX_HTTP_V2) lsopt.http2 = 1; continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the \"http2\" parameter requires " "ngx_http_v2_module"); return NGX_CONF_ERROR; #endif } if (ngx_strcmp(value[n].data, "spdy") == 0) { ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "invalid parameter \"spdy\": " "ngx_http_spdy_module was superseded " "by ngx_http_v2_module"); continue; } if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[n].data[13], "on") == 0) { lsopt.so_keepalive = 1; } else if (ngx_strcmp(&value[n].data[13], "off") == 0) { lsopt.so_keepalive = 2; } else { #if (NGX_HAVE_KEEPALIVE_TUNABLE) u_char *p, *end; ngx_str_t s; end = value[n].data + value[n].len; s.data = value[n].data + 13; p = ngx_strlchr(s.data, end, ':'); if (p == NULL) { p = end; } if (p > s.data) { s.len = p - s.data; lsopt.tcp_keepidle = ngx_parse_time(&s, 1); if (lsopt.tcp_keepidle == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } s.data = (p < end) ? (p + 1) : end; p = ngx_strlchr(s.data, end, ':'); if (p == NULL) { p = end; } if (p > s.data) { s.len = p - s.data; lsopt.tcp_keepintvl = ngx_parse_time(&s, 1); if (lsopt.tcp_keepintvl == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } s.data = (p < end) ? (p + 1) : end; if (s.data < end) { s.len = end - s.data; lsopt.tcp_keepcnt = ngx_atoi(s.data, s.len); if (lsopt.tcp_keepcnt == NGX_ERROR) { goto invalid_so_keepalive; } } if (lsopt.tcp_keepidle == 0 && lsopt.tcp_keepintvl == 0 && lsopt.tcp_keepcnt == 0) { goto invalid_so_keepalive; } lsopt.so_keepalive = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "the \"so_keepalive\" parameter accepts " "only \"on\" or \"off\" on this platform"); return NGX_CONF_ERROR; #endif } lsopt.set = 1; lsopt.bind = 1; continue; #if (NGX_HAVE_KEEPALIVE_TUNABLE) invalid_so_keepalive: ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid so_keepalive value: \"%s\"", &value[n].data[13]); return NGX_CONF_ERROR; #endif } if (ngx_strcmp(value[n].data, "proxy_protocol") == 0) { lsopt.proxy_protocol = 1; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[n]); return NGX_CONF_ERROR; } // 将监听端口加入列表 for (n = 0; n < u.naddrs; n++) { lsopt.sockaddr = u.addrs[n].sockaddr; lsopt.socklen = u.addrs[n].socklen; lsopt.addr_text = u.addrs[n].name; lsopt.wildcard = ngx_inet_wildcard(lsopt.sockaddr); if (ngx_http_add_listen(cf, cscf, &lsopt) != NGX_OK) { return NGX_CONF_ERROR; } } return NGX_CONF_OK; }
View Code
location 的解析则肯定会复杂很多,因为它是一个块级的配置,内部将会有很多复杂的配置,想想这必然又涉及到递归解析了。
// location /xx {..} 的解析 static char * ngx_http_core_location(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) { char *rv; u_char *mod; size_t len; ngx_str_t *value, *name; ngx_uint_t i; ngx_conf_t save; ngx_http_module_t *module; ngx_http_conf_ctx_t *ctx, *pctx; ngx_http_core_loc_conf_t *clcf, *pclcf; ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t)); if (ctx == NULL) { return NGX_CONF_ERROR; } pctx = cf->ctx; ctx->main_conf = pctx->main_conf; ctx->srv_conf = pctx->srv_conf; ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module); if (ctx->loc_conf == NULL) { return NGX_CONF_ERROR; } // 嵌套解析 for (i = 0; cf->cycle->modules[i]; i++) { if (cf->cycle->modules[i]->type != NGX_HTTP_MODULE) { continue; } module = cf->cycle->modules[i]->ctx; if (module->create_loc_conf) { // 将解析的值放入 ctx 中,备用 ctx->loc_conf[cf->cycle->modules[i]->ctx_index] = module->create_loc_conf(cf); if (ctx->loc_conf[cf->cycle->modules[i]->ctx_index] == NULL) { return NGX_CONF_ERROR; } } } clcf = ctx->loc_conf[ngx_http_core_module.ctx_index]; clcf->loc_conf = ctx->loc_conf; value = cf->args->elts; if (cf->args->nelts == 3) { len = value[1].len; mod = value[1].data; name = &value[2]; // 疏松写法 // uri 模式匹配 if (len == 1 && mod[0] == '=') { clcf->name = *name; clcf->exact_match = 1; } else if (len == 2 && mod[0] == '^' && mod[1] == '~') { clcf->name = *name; clcf->noregex = 1; } else if (len == 1 && mod[0] == '~') { if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) { return NGX_CONF_ERROR; } } else if (len == 2 && mod[0] == '~' && mod[1] == '*') { if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) { return NGX_CONF_ERROR; } } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid location modifier \"%V\"", &value[1]); return NGX_CONF_ERROR; } } else { name = &value[1]; // 紧密写法 if (name->data[0] == '=') { clcf->name.len = name->len - 1; clcf->name.data = name->data + 1; clcf->exact_match = 1; } else if (name->data[0] == '^' && name->data[1] == '~') { clcf->name.len = name->len - 2; clcf->name.data = name->data + 2; clcf->noregex = 1; } else if (name->data[0] == '~') { name->len--; name->data++; if (name->data[0] == '*') { name->len--; name->data++; if (ngx_http_core_regex_location(cf, clcf, name, 1) != NGX_OK) { return NGX_CONF_ERROR; } } else { if (ngx_http_core_regex_location(cf, clcf, name, 0) != NGX_OK) { return NGX_CONF_ERROR; } } } else { clcf->name = *name; if (name->data[0] == '@') { clcf->named = 1; } } } pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index]; if (cf->cmd_type == NGX_HTTP_LOC_CONF) { /* nested location */ #if 0 clcf->prev_location = pclcf; #endif if (pclcf->exact_match) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "location \"%V\" cannot be inside " "the exact location \"%V\"", &clcf->name, &pclcf->name); return NGX_CONF_ERROR; } if (pclcf->named) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "location \"%V\" cannot be inside " "the named location \"%V\"", &clcf->name, &pclcf->name); return NGX_CONF_ERROR; } if (clcf->named) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "named location \"%V\" can be " "on the server level only", &clcf->name); return NGX_CONF_ERROR; } len = pclcf->name.len; #if (NGX_PCRE) if (clcf->regex == NULL && ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0) #else if (ngx_filename_cmp(clcf->name.data, pclcf->name.data, len) != 0) #endif { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "location \"%V\" is outside location \"%V\"", &clcf->name, &pclcf->name); return NGX_CONF_ERROR; } } // 添加到location列表中,备用 if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) { return NGX_CONF_ERROR; } save = *cf; cf->ctx = ctx; cf->cmd_type = NGX_HTTP_LOC_CONF; // 递归解析 rv = ngx_conf_parse(cf, NULL); *cf = save; return rv; }
同样,它会遍历其http模块下有哪些可以解析的命令,然后交其处理,然后再处理自身的逻辑。从而完成整个块的配置解析。
解析后的location信息添加到全局变更信息中, 这由 ngx_http_add_location() 完成并维护,实际上就是放到 loc_conf对应的模块变量下,以链表的形式保存起来,后续配置读取由其完成。
4. location配置的应用
上一节已经解析出location的各项配置了,那么它是如何运用到实际中呢?实际上,就是在需要的时候,从相应配置变量中取出来使用判定即可。
或者参考我之前的文章:正反向代理的实现: https://www.cnblogs.com/yougewe/p/13778108.html
欲知后事如何,且听下回分解。