<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Nginx(六):配置解析之location解析

          共 69905字,需瀏覽 140分鐘

           ·

          2021-01-24 04:28

          走過(guò)路過(guò)不要錯(cuò)過(guò)

          點(diǎn)擊藍(lán)字關(guān)注我們


          nginx成為非常流行的代理服務(wù)軟件,最根本的原因也許是在于其強(qiáng)悍性能。但還有一些必要的條件,比如功能的完整,配置的易用,能夠解決各種各樣的實(shí)際需求問(wèn)題,這些是一個(gè)好的軟件的必備特性。

          那么,今天我們就來(lái)看看nginx配置的部分原則和解析原理吧。我們只做location部分的細(xì)節(jié)解析,但其他配置道理基本相通,推一及二即可。

          1:nginx配置的基本原則

          nginx是支持高度配置化的,那么也許就會(huì)涉及許多部分的配置,要如何協(xié)調(diào)好這些配置,是個(gè)問(wèn)題。比如是否將配置定義一個(gè)個(gè)獨(dú)立的文件,或者其他。

          然而,nginx使用一個(gè)統(tǒng)一的配置文件,管理起了所有的配置工作。即 nginx.conf, 其默認(rèn)位置是 $NGINX_HOME/nginx.conf, 在這個(gè)主配置文件中,又可以包含其他任意多的配置文件,從而達(dá)到統(tǒng)一管理的作用。

          其默認(rèn)配置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; # } #}
          }

          這很明顯不是什么標(biāo)準(zhǔn)語(yǔ)言語(yǔ)法,但其同樣遵從一定的準(zhǔn)則,從而讓用戶(hù)更易于理解配置。如:

              1. 每一個(gè)細(xì)配置項(xiàng),都使用一個(gè)';'作為結(jié)束標(biāo)識(shí);
              2. '#' 代表該行被注釋?zhuān)ㄟ@幾乎是linux默認(rèn)標(biāo)準(zhǔn));
              3. 使用'{}'表示一個(gè)配置塊,'{}'中代表其子項(xiàng)配置;
              4. 使用include可以包含其他文件的配置,相當(dāng)于將其中的配置項(xiàng)copy到當(dāng)前位置,該包含路徑可以是相對(duì)路徑也可以是絕對(duì)路徑;

          因此,基本上,你只要按照這個(gè)標(biāo)準(zhǔn)來(lái)做配置,至少語(yǔ)法 是不會(huì)有誤了。但是,具體應(yīng)該如何配置呢?實(shí)際上這要依賴(lài)于某類(lèi)操作的具體實(shí)現(xiàn),比如 location 的 配置, user 的配置,都是有各自的含義的。如果想要具體了解各細(xì)節(jié)配置,則必須要查詢(xún)官網(wǎng)的配置定義了。請(qǐng)參考:?https://docs.nginx.com/nginx/admin-guide/basic-functionality/managing-configuration-files/

          總體上來(lái)說(shuō),nginx有幾個(gè)頂級(jí)配置:?

              events – 連接類(lèi)的配置,比如最大連接數(shù)配置
              http – 重頭戲,http模塊配置,所有的代理服務(wù)http服務(wù)都在其子配置下
              mail – 郵件配置
              stream – TCP、UDP 底層通信協(xié)議配置,功能與http模塊相仿
          一般地我們接接觸最多的應(yīng)該就是http配置了,至于其他功能,沒(méi)有實(shí)踐就沒(méi)有發(fā)言權(quán),略去不說(shuō)。

          2. location的配置用例

          location的本義是用于定位一個(gè)http請(qǐng)求匹配情況,用于確定某個(gè)路徑的請(qǐng)求應(yīng)該如何做轉(zhuǎn)換處理。它是屬于 http 模塊下的 server 模塊下的一個(gè)選項(xiàng)配置。即 nginx -> http -> server -> location {..} 是其配置體現(xiàn)。它擁有相當(dāng)多的配置項(xiàng),因?yàn)樽龇聪虼砘蚱渌?wù)器時(shí),往往都可以通過(guò)這個(gè)配置,將功能完成。

          如下幾個(gè)配置項(xiàng),可供參考:

          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;        # 路徑相等處理,優(yōu)先級(jí)最高        location = / {            #...        }        # 根路徑配置,優(yōu)先級(jí)最低        location / {            root /data/www;        }        # 帶前綴的配置,優(yōu)先級(jí)其次        location /images/ {            root /data;        }        # 正則匹配的配置,優(yōu)先級(jí)較高        location ~ \.(gif|jpg|png)$ {            root /data/images;        }        # 正則取反配置        location ^~ \.(php)$ {            root /data/other;        }        # 反向代理的配置,將請(qǐng)求轉(zhuǎn)換給后端服務(wù)        # 如果backend是一個(gè) upstream 配置,則做為一個(gè)負(fù)載均衡器使用        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;        }        # 路徑重寫(xiě)        location /users/ {            rewrite ^/users/(.*)$ /show?user=$1 break;        }        # 帶狀態(tài)碼的返回配置        location /wrong/url {            return 404;            open_file_cache_errors off;        }        location /permanently/moved/url {            return 301 http://www.example.com/moved/here;        }        # 響應(yīng)內(nèi)容替換        location / {            sub_filter     'href="https://$host/';            sub_filter     'img src="http://127.0.0.1:8080/' 'img src="https://$host/';            sub_filter_once on;        }    }}

          更多內(nèi)容可查閱官網(wǎng): https://docs.nginx.com/nginx/admin-guide/web-server/web-server/

          總體上來(lái)說(shuō),location提供了可以配置如何查找本地文件,以及可以配置如何轉(zhuǎn)發(fā)請(qǐng)求到其他服務(wù)器的方式。其中,還有很多附加的設(shè)置各種需求變量的實(shí)現(xiàn),以輔助我們實(shí)現(xiàn)一些正常請(qǐng)求提供的內(nèi)容。配置比較多,到真正使用時(shí),按需配置即可。一般也是一次配置,永久使用,不會(huì)太費(fèi)事。

          3. location配置的解析

          nginx有自己的一套配置方法,那么這些配置好了的語(yǔ)句,如何應(yīng)用到具體的服務(wù)上呢?自然是需要先進(jìn)行解析,然后放置到對(duì)應(yīng)的內(nèi)存空間變量中,然后在需要的時(shí)候進(jìn)行讀取判定,以及轉(zhuǎn)換了。大體思路如此,但如何解析配置卻并非易事。因?yàn)槲覀兊呐渲檬菬o(wú)數(shù)現(xiàn)有配置的任意組合,如何有效的放置到可理解的位置,應(yīng)該需要單獨(dú)的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì),以及解析步驟。實(shí)際上,這也相當(dāng)于是一個(gè)簡(jiǎn)單的編譯器或解析器,它需要將文本解析為認(rèn)識(shí)的東西。

          下面我們就一起來(lái)看看nginx都是如何解析這些配置的吧!(這自然是在啟動(dòng)時(shí)完成的工作)

          // 首先,nginx會(huì)解析啟動(dòng)行命令,這里面可以指定配置文件// core/nginx.c static ngx_int_tngx_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 測(cè)試配置文件有效性 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) 向現(xiàn)有運(yùn)行的nginx進(jìn)程發(fā)起控制命令 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;}

          以上,是對(duì)命令行參數(shù)的簡(jiǎn)單解析,解析出來(lái)的變量放入到各全局變量中: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.

          以上,最重要的是兩個(gè)參數(shù):-c -s, 用于指定配置文件和操作現(xiàn)有nginx進(jìn)程。當(dāng)然,對(duì)于配置解析,自然最重要的是 -c 命令了。但對(duì)于一些沒(méi)有指定的配置值,則使用系統(tǒng)的默認(rèn)值。其處理如下:

          // core/nginx.cstatic ngx_int_tngx_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 // 默認(rèn)路徑前綴: 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 { // 默認(rèn)配置文件: 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的時(shí)候處理實(shí)現(xiàn)的:

          // core/ngx_cycle.cngx_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; } // 使用紅黑樹(shù)存放配置信息 ngx_rbtree_init(&cycle->config_dump_rbtree, &cycle->config_dump_sentinel, ngx_str_rbtree_insert_value); // 默認(rèn)使用 20 個(gè)端口服務(wù) 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; } // 每個(gè)監(jiān)聽(tīng)端口使用一個(gè) 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; }
          // 獲取當(dāng)前機(jī)器的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);
          // 創(chuàng)建module內(nèi)存空間 if (ngx_cycle_modules(cycle) != NGX_OK) { ngx_destroy_pool(pool); return NULL; }
          // 讓各模塊依次進(jìn)行配置 for (i = 0; cycle->modules[i]; i++) { // NGINX_CORE_MODULE 可以進(jìn)行配置文件處理 // 即幾套頂級(jí)模塊, http,events,mail,... if (cycle->modules[i]->type != NGX_CORE_MODULE) { continue; }
          module = cycle->modules[i]->ctx; // 調(diào)用各模塊的 create_conf 實(shí)現(xiàn)配置加載 // 其中,以為的http模塊會(huì)進(jìn)行解析,其實(shí)卻沒(méi)有 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中設(shè)置了幾個(gè)初始化的點(diǎn),create_conf, init_conf, 供各模塊實(shí)現(xiàn)各自的解析邏輯,以及使用一個(gè)全局的解析? ngx_conf_parse()?實(shí)現(xiàn)文件解析。

          那么 http 作為獨(dú)立的模塊,其是否參與配置解析呢?我們看下其模塊的配置即可:

          // http 模塊的配置簡(jiǎn)略// http/ngx_http.cstatic 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 文件中進(jìn)行,?有ngx_conf_param(), ngx_conf_parse()?完成具體的解析。

          // core/ngx_conf_file.cchar *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 */ // 打開(kāi)配置文件 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; }
          // 此處實(shí)現(xiàn)真正的解析操作 for ( ;; ) { // 重要1: 讀取出一個(gè)個(gè)地token信息, 以pos, start等變量做標(biāo)識(shí)起始 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: 將解析出的變量進(jìn)行細(xì)致處理 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;}
          // 讀取一個(gè)個(gè)地 token 信息static ngx_int_tngx_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默認(rèn)是最大buffer,pos默認(rèn)為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; } // 默認(rèn)兩值相等,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); } // 計(jì)算剩余大小 size = (ssize_t) (file_size - cf->conf_file->file.offset); // 如果剩余大小超出當(dāng)前緩沖的大小,則只能取當(dāng)前緩沖剩余大小 if (size > b->end - (b->start + len)) { size = b->end - (b->start + len); } // 從配置文件中讀取size大小的數(shù)據(jù)出來(lái),放到buf中備用,相當(dāng)于盡量一次全部讀取 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); } } // 從文件中讀出一個(gè)字符用以判定,即如果假設(shè)文件已讀取完,則相當(dāng)于一個(gè)個(gè)字符取出判定 ch = *b->pos++; // 遇到換行,line++,注釋標(biāo)識(shí)重置 if (ch == LF) { cf->conf_file->line++;
          if (sharp_comment) { sharp_comment = 0; } } // 如果注釋標(biāo)識(shí)有效,則忽略當(dāng)前段的字符,直到標(biāo)識(shí)被清除 if (sharp_comment) { continue; } // 引號(hào)閉合處理 if (quoted) { quoted = 0; continue; } // 判斷是否有新的語(yǔ)句開(kāi)始,類(lèi)似空格都可以 if (need_space) { if (ch == ' ' || ch == '\t' || ch == CR || ch == LF) { last_space = 1; need_space = 0; continue; } // 遇到分號(hào),當(dāng)前解析結(jié)束,即得到一個(gè)獨(dú)立語(yǔ)句,如 root /www; if (ch == ';') { return NGX_OK; } // 遇到塊也結(jié)束當(dāng)前解析,交由子處理器處理 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) { // 新語(yǔ)句開(kāi)始,行號(hào)暫存 start = b->pos - 1; start_line = cf->conf_file->line; // 忽略空格類(lèi)字符 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; } // 語(yǔ)句結(jié)束,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 '\\': // 轉(zhuǎn)義符 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; } // 引號(hào)處理 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; // 完整解析一個(gè)語(yǔ)句 }
          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; } // 將解析出的語(yǔ)句賦值到 args 中 for (dst = word->data, src = start, len = 0; src < b->pos - 1; len++) { if (*src == '\\') { switch (src[1]) { case '"': case '\'': case '\\': // 去除轉(zhuǎn)義標(biāo)識(shí) src++; break;
          case 't': // 翻譯轉(zhuǎn)義 *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; } } }}


          大體原理是:讀取一個(gè)個(gè)token,然后進(jìn)行依次翻譯,分小語(yǔ)句,大block,... 類(lèi)型依次處理。將解析的結(jié)果放入cf->args中。其解析結(jié)果如 location ~ /api/.*go { ,如 listen 80 81;

          還差一個(gè)細(xì)節(jié),就是讀取到token之后,又是如何傳遞給各模塊的呢?實(shí)際就是上面的重點(diǎn)2:ngx_conf_handler()?處理。

          static ngx_int_tngx_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 列表,依次調(diào)用進(jìn)行處理, 比如 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++) { // 名字不同必然不負(fù)責(zé)解析 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 ? */ // 變量類(lèi)型判定 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 ? */ // 參數(shù)個(gè)數(shù)匹配 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; // 找不同的配置級(jí)別位置存儲(chǔ)(這指針用得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]; } } // 調(diào)用各命令的set方法,處理配置語(yǔ)義 // 實(shí)際上就涉及到內(nèi)嵌套解析問(wèn)題了 // 同樣不會(huì)很簡(jiǎn)單哦 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; } } // 配置使用錯(cuò)誤 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;}

          將解析出的配置信息接入到各子模塊之后,才算是真正意義上的解析成功,也才能被各子模塊使用。

          下面我們?cè)賮?lái)看個(gè)樣例,子模塊如何解析配置,http location ....

          listen 80;?的解析比較簡(jiǎn)單些,有興趣可查看:

          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 // 更多參數(shù)解析,見(jiàn)官方文檔說(shuō)明 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; } // 將監(jiān)聽(tīng)端口加入列表 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 的解析則肯定會(huì)復(fù)雜很多,因?yàn)樗且粋€(gè)塊級(jí)的配置,內(nèi)部將會(huì)有很多復(fù)雜的配置,想想這必然又涉及到遞歸解析了。

          // 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]; // 疏松寫(xiě)法 // 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]; // 緊密寫(xiě)法 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;}

          同樣,它會(huì)遍歷其http模塊下有哪些可以解析的命令,然后交其處理,然后再處理自身的邏輯。從而完成整個(gè)塊的配置解析。

          4. location配置的應(yīng)用

          上一節(jié)已經(jīng)解析出location的各項(xiàng)配置了,那么它是如何運(yùn)用到實(shí)際中呢?實(shí)際上,就是在需要的時(shí)候,從相應(yīng)配置變量中取出來(lái)使用判定即可。




          往期精彩推薦



          騰訊、阿里、滴滴后臺(tái)面試題匯總總結(jié) — (含答案)

          面試:史上最全多線程面試題 !

          最新阿里內(nèi)推Java后端面試題

          JVM難學(xué)?那是因?yàn)槟銢](méi)認(rèn)真看完這篇文章


          END


          關(guān)注作者微信公眾號(hào) —《JAVA爛豬皮》


          了解更多java后端架構(gòu)知識(shí)以及最新面試寶典


          你點(diǎn)的每個(gè)好看,我都認(rèn)真當(dāng)成了


          看完本文記得給作者點(diǎn)贊+在看哦~~~大家的支持,是作者源源不斷出文的動(dòng)力


          作者:等你歸去來(lái)

          出處:https://www.cnblogs.com/yougewe/p/14289846.html

          瀏覽 31
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美精品久久久久久久久91 | 日本三级在线观看中文字串 | 天天综合~91入口 | 爆操91| TS人妖系列亚洲一区二区 |