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中。
還差一個細節,就是讀取到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; // 獲取最後存入的配置變數名 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; }
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模組下有哪些可以解析的命令,然後交其處理,然後再處理自身的邏輯。從而完成整個塊的配置解析。
4. location配置的應用
上一節已經解析出location的各項配置了,那麼它是如何運用到實際中呢?實際上,就是在需要的時候,從相應配置變數中取出來使用判定即可。
或者參考我之前的文章:正反向代理的實現: https://www.cnblogs.com/yougewe/p/13778108.html
欲知後事如何,且聽下回分解。