共计 8778 个字符,预计需要花费 22 分钟才能阅读完成。
1 Background
http://nginx.org/en/docs/http/ngx_http_stub_status_module.html
ngx_http_stub_status_module 是一个 Nginx 的内置 HTTP 模块,该模块可以提供 Nginx 的状态信息。默认情况下这个模块是不被编译进来的,所以在编译 Nginx 时要指定加载该模块:
--with-http_stub_status_module
为什么拿它做例子?因为它也是个足够短小精悍的模块,是一个典型 handler 模块。那么以后我们讲解模块的过程,都是:
- 简要的介绍
- 使用的实例
- 指令介绍
- 源码分析
2 Simple example
location /nginx_status { | |
stub_status on; | |
access_log off; access_log /usr/local/nginx/logs/status.log; # 日志 | |
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 connectionsserver 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 headerwriting
– nginx reads request body, processes request, or writes response to a clientwaiting
– 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. | |
*/ | |
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:
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); | |
} |
:
