解剖Nginx·模組開發篇(5)解讀內建非預設模組 ngx_http_stub_status_module

鍾超發表於2012-06-03

解剖Nginx·模組開發篇(5)解讀內建非預設模組 ngx_http_stub_status_module

  • 作者:柳大·Poechant(鍾超)
  • 郵箱:zhongchao.ustc#gmail.com(# -> @)
  • 部落格:Blog.CSDN.net/Poechant
  • 日期:June 3nd, 2012

1 Background

ngx_http_stub_status_module 是一個 Nginx 的內建 HTTP 模組,該模組可以提供 Nginx 的狀態資訊。預設情況下這個模組是不被編譯進來的,所以在編譯 Nginx 時要指定載入該模組:

--with-http_stub_status_module

當然了,如果你是重新編譯,僅僅-s reload是不夠的,可能需要用到平滑升級:《高效能Web伺服器Nginx的配置與部署研究(14)平滑升級你的Nginx》

為什麼拿它做例子?因為它也是個足夠短小精悍的模組,是一個典型 handler 模組。那麼以後我們講解模組的過程,都是:

  1. 簡要的介紹
  2. 使用的例項
  3. 指令介紹
  4. 原始碼分析
    1. 先上原始碼
    2. 分析

2 Simple example

location /nginx_status {
  # copied from http://blog.kovyrin.net/2006/04/29/monitoring-nginx-with-rrdtool/
  stub_status on;
  access_log   off;
  allow SOME.IP.ADD.RESS;
  deny all;
}

我們假設你是在本機上實驗,並且開啟的是 80 埠,那麼在瀏覽器中輸入:

http://localhost/nginx_status

會看到這樣的資訊:

Active connections: 291
server accepts handled requests
  16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106

其含義很容易理解:

  • 第一行
    • 當前的活躍連線數:291
  • 第二行
    • 伺服器已接受的連線數:16630948(accepted connection #)
    • 伺服器已處理的連線數:16630948(handled connection #)
    • 伺服器已處理的請求:31070465(可以算出,平均每個連線有 1.8 個請求)(handled connection #)
  • 第三行
    • Reading – Nginx 讀取的請求頭次數為 6;
    • Writting – Nginx 讀取請求體、處理請求併傳送響應給客戶端的次數為 179;
    • Waiting – 當前活動的長連線數:106。

Nginx 官方的解釋如下:

  • active connections– number of all open connections
  • server accepts handled requests– nginx accepted 16630948 connections, handled 16630948 connections (no one was closed just it was accepted), and handles 31070465 requests (1.8 requests per connection)
  • reading– nginx reads request header
  • writing– nginx reads request body, processes request, or writes response to a client
  • waiting– keep-alive connections, actually it is active - (reading + writing)

3 Directives

這個模組中的唯一一個指令,是:

stub_status
  • 語法:stub_status on
  • 作用域:location
  • 功能:統計這個 location 的資訊。

4 Source analysis

先看完整程式碼:

/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) Nginx, Inc.
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


static char *ngx_http_set_status(ngx_conf_t *cf, ngx_command_t *cmd,
                                 void *conf);

static ngx_command_t  ngx_http_status_commands[] = {

    { ngx_string("stub_status"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_http_set_status,
      0,
      0,
      NULL },

      ngx_null_command
};



static ngx_http_module_t  ngx_http_stub_status_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};


ngx_module_t  ngx_http_stub_status_module = {
    NGX_MODULE_V1,
    &ngx_http_stub_status_module_ctx,      /* module context */
    ngx_http_status_commands,              /* module directives */
    NGX_HTTP_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
};


static ngx_int_t ngx_http_status_handler(ngx_http_request_t *r)
{
    size_t             size;
    ngx_int_t          rc;
    ngx_buf_t         *b;
    ngx_chain_t        out;
    ngx_atomic_int_t   ap, hn, ac, rq, rd, wr;

    if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    rc = ngx_http_discard_request_body(r);

    if (rc != NGX_OK) {
        return rc;
    }

    ngx_str_set(&r->headers_out.content_type, "text/plain");

    if (r->method == NGX_HTTP_HEAD) {
        r->headers_out.status = NGX_HTTP_OK;

        rc = ngx_http_send_header(r);

        if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
            return rc;
        }
    }

    size = sizeof("Active connections:  \n") + NGX_ATOMIC_T_LEN
           + sizeof("server accepts handled requests\n") - 1
           + 6 + 3 * NGX_ATOMIC_T_LEN
           + sizeof("Reading:  Writing:  Waiting:  \n") + 3 * NGX_ATOMIC_T_LEN;

    b = ngx_create_temp_buf(r->pool, size);
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    out.buf = b;
    out.next = NULL;

    ap = *ngx_stat_accepted;
    hn = *ngx_stat_handled;
    ac = *ngx_stat_active;
    rq = *ngx_stat_requests;
    rd = *ngx_stat_reading;
    wr = *ngx_stat_writing;

    b->last = ngx_sprintf(b->last, "Active connections: %uA \n", ac);

    b->last = ngx_cpymem(b->last, "server accepts handled requests\n",
                         sizeof("server accepts handled requests\n") - 1);

    b->last = ngx_sprintf(b->last, " %uA %uA %uA \n", ap, hn, rq);

    b->last = ngx_sprintf(b->last, "Reading: %uA Writing: %uA Waiting: %uA \n",
                          rd, wr, ac - (rd + wr));

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = b->last - b->pos;

    b->last_buf = 1;

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    return ngx_http_output_filter(r, &out);
}


static char *ngx_http_set_status(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_status_handler;

    return NGX_CONF_OK;
}

的確夠短小精悍吧?關鍵在於 Nginx 提供的模組擴充套件方式比較好,讓你可以少寫一些程式碼(NDK 可以讓你寫的更少,這是後話)。

4.1 模組定義 ngx_http_stub_status_module

ngx_module_t  ngx_http_stub_status_module = {
    NGX_MODULE_V1,
    &ngx_http_stub_status_module_ctx,      /* module context */
    ngx_http_status_commands,              /* module directives */
    NGX_HTTP_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_http_hello_world_module 並無本質區別。

4.2 命令集定義 ngx_http_status_commands

static ngx_command_t  ngx_http_status_commands[] = {

    { ngx_string("stub_status"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_http_set_status,
      0,
      0,
      NULL },

      ngx_null_command
};

命令集定義如上,得到如下資訊:

  • name:stub_status
  • type:server conf、location conf、conf flag,其中最後一個比較陌生,相似的取值有:
    • #define NGX_CONF_ARGS_NUMBER 0x000000ff
    • #define NGX_CONF_BLOCK 0x00000100
    • #define NGX_CONF_FLAG 0x00000200
    • #define NGX_CONF_ANY 0x00000400
    • #define NGX_CONF_1MORE 0x00000800
    • #define NGX_CONF_2MORE 0x00001000
    • #define NGX_CONF_MULTI 0x00002000
  • set:ngx_http_set_status

下面解釋下一些 types:

4.2.1 NGX_CONF_XXX

以下巨集定義來自 ngx_conf_file.h:

#define NGX_CONF_NOARGS      0x00000001 // 命令不接受引數
#define NGX_CONF_TAKE1       0x00000002 // 命令攜帶1個引數
#define NGX_CONF_TAKE2       0x00000004 // 命令攜帶2個引數
#define NGX_CONF_TAKE3       0x00000008 // 命令攜帶3個引數
#define NGX_CONF_TAKE4       0x00000010 // 命令攜帶4個引數
#define NGX_CONF_TAKE5       0x00000020 // 命令攜帶5個引數
#define NGX_CONF_TAKE6       0x00000040 // 命令攜帶6個引數
#define NGX_CONF_TAKE7       0x00000080 // 命令攜帶7個引數

#define NGX_CONF_TAKE12      (NGX_CONF_TAKE1|NGX_CONF_TAKE2) // 命令攜帶1個或2個引數
#define NGX_CONF_TAKE13      (NGX_CONF_TAKE1|NGX_CONF_TAKE3) // 命令攜帶1個或3個引數
#define NGX_CONF_TAKE23      (NGX_CONF_TAKE2|NGX_CONF_TAKE3) // 命令攜帶2個或3個引數
#define NGX_CONF_TAKE123     (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3) // 命令攜帶1個、2個或3個引數
#define NGX_CONF_TAKE1234    (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3|NGX_CONF_TAKE4) // 命令攜帶1個、2個、3個或4個引數

#define NGX_CONF_ARGS_NUMBER 0x000000ff // 命令
#define NGX_CONF_BLOCK       0x00000100 // 塊域,後面跟 {…},比如 server {...}
#define NGX_CONF_FLAG        0x00000200 // 命令接受“on|off”引數
#define NGX_CONF_ANY         0x00000400
#define NGX_CONF_1MORE       0x00000800 // 命令攜帶至少1個引數
#define NGX_CONF_2MORE       0x00001000 // 命令攜帶至少2個引數
#define NGX_CONF_MULTI       0x00002000 // 命令攜帶多個引數

4.3 上下文定義 ngx_http_stub_status_module_ctx

static ngx_http_module_t  ngx_http_stub_status_module_ctx = {
    NULL,                                  /* preconfiguration */
    NULL,                                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};

這個都是 NULL,夠簡單,無話可說了⋯⋯

4.4 命令設定函式 ngx_http_set_status

static char *ngx_http_set_status(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {

    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_status_handler;

    return NGX_CONF_OK;
}

和 ngx_http_hello_world_module 對比下:

static char* ngx_http_hello_world(ngx_conf_t* cf, ngx_command_t* cmd, void* conf) {

    ngx_http_core_loc_conf_t* clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
    clcf->handler = ngx_http_hello_world_handler;

    ngx_conf_set_str_slot(cf, cmd, conf);

    return NGX_CONF_OK;
}

唯一的區別,就是 ngx_http_hello_world_module 多了一句 ngx_conf_set_str_slot。這個先留做一個問題,後面會介紹,暫時與關鍵主題無關。

4.5 命令處理函式 ngx_http_status_handler

static ngx_int_t ngx_http_status_handler(ngx_http_request_t *r)
{
    size_t             size;
    ngx_int_t          rc;
    ngx_buf_t         *b;
    ngx_chain_t        out;
    ngx_atomic_int_t   ap, hn, ac, rq, rd, wr;

這個模組要求接受的請求類是 GET、HEAD,其他型別的請求會被拒絕。

    if (r->method != NGX_HTTP_GET && r->method != NGX_HTTP_HEAD) {
        return NGX_HTTP_NOT_ALLOWED;
    }

放棄請求體,因為這個模組用不上。

    rc = ngx_http_discard_request_body(r);

    if (rc != NGX_OK) {
        return rc;
    }

如果請求是 HEAD 型別的,則直接設定響應頭的 content_type、status 欄位,併傳送響應頭。

    ngx_str_set(&r->headers_out.content_type, "text/plain");

    if (r->method == NGX_HTTP_HEAD) {
        r->headers_out.status = NGX_HTTP_OK;

        rc = ngx_http_send_header(r);

        if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
            return rc;
        }
    }

建立一個緩衝區,向緩衝區寫入我們上面在瀏覽器中看到的東西。

    size = sizeof("Active connections:  \n") + NGX_ATOMIC_T_LEN
           + sizeof("server accepts handled requests\n") - 1
           + 6 + 3 * NGX_ATOMIC_T_LEN
           + sizeof("Reading:  Writing:  Waiting:  \n") + 3 * NGX_ATOMIC_T_LEN;

    b = ngx_create_temp_buf(r->pool, size);
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    out.buf = b;
    out.next = NULL;

    ap = *ngx_stat_accepted;
    hn = *ngx_stat_handled;
    ac = *ngx_stat_active;
    rq = *ngx_stat_requests;
    rd = *ngx_stat_reading;
    wr = *ngx_stat_writing;

    // 封裝了 sprintf
    b->last = ngx_sprintf(b->last, "Active connections: %uA \n", ac);

    // 封裝了 memcpy
    b->last = ngx_cpymem(b->last, "server accepts handled requests\n",
                         sizeof("server accepts handled requests\n") - 1);

    b->last = ngx_sprintf(b->last, " %uA %uA %uA \n", ap, hn, rq);

    b->last = ngx_sprintf(b->last, "Reading: %uA Writing: %uA Waiting: %uA \n",
                          rd, wr, ac - (rd + wr));

緩衝區寫完了。然後設定下響應頭的 status、content_length_n(還記得嗎?b->last - b->pos 剛好是緩衝區的第二個區域,是已寫入資料部分。)

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = b->last - b->pos;

    b->last_buf = 1;

傳送響應頭。

    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

filter。

    return ngx_http_output_filter(r, &out);
}

5 Reference

  1. http://wiki.nginx.org/HttpStubStatusModule
  2. http://blog.csdn.net/lengzijian/article/details/7356064
  3. http://www.codinglabs.org/html/intro-of-nginx-module-development.html

6 本節留下的問題:

  • ngx_conf_set_str_slot:你可以先去了解下,或者等待在後面的博文中看到 :)

-

轉載請註明來自柳大的CSDN部落格:Blog.CSDN.net/Poechant

-

相關文章