关于Nginx模块开发的博客资料,网上很多,很多。但是,每篇博客都只提要点,无法"step by step"照着做,对于初次接触Nginx开发的同学,只能像只盲目的蚂蚁瞎燥急!该篇文章没有太多技术深度,只是一步一步说明白Nginx模块的开发过程。
开发环境搭建工欲善其事,必先利其器。个人推荐Eclipse CDT 作为IDE,原因很简单,代码提示与补全功能很全,完胜Codeblock这类...相信与否,试过就知道。
在Ubuntu下搭建开发环境:
安装GCC编译器
apt-get install build-essential
安装pcre/openssl/zlib开发库
apt-get install libpcre3-dev apt-get install libssl-dev apt-get install libzip-dev 必需安装nginx核心模块依赖的pcre,openssl,zilib开发库
安装JRE/Eclipse CDT
apt-get install openjdk-8-jre wget http://ftp.yz.yamagata-u.ac.jp/pub/eclipse//technology/epp/downloads/release/neon/R/eclipse-cpp-neon-R-linux-gtk-x86_64.tar.gz && tzr -xzvf eclipse-cpp-neon-R-linux-gtk-x86_64.tar.gz
下载nginx源码
wget http://nginx.org/download/nginx-1.10.1.tar.gz && tar -xzvf nginx-1.10.1.tar.gz
配置CDT Build Environment
添加变量,值Nginx src下各模块路径,用冒号分隔,例如:
添加环境变量,创建C项目时自动作为-I选项
Nginx模块编译流程
Nginx使用configure脚本分析环境,自动生成objs结果。哪么configure如何编译第三方模块?答案是--add-module指定第三方模块目录,并将目录存为$ngx_addon_dir环境变量。执行$ngx_addon_dir/config脚本,读取模块配置。在config中的环境变量分为2种:小写的本地环境变量,大写的全局环境变量。例如:
ngx_addon_name=ngx_http_mytest_module HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_moudle.c" CORE_LIBS="$CORE_LIBS -lpcre"HTTP_MODULES中的ngx_http_mytest_module就是NGX_ADDON_SRCS中源码(如果有多个,都要写上)ngx_http_mytest_module.c中定义的ngx_module_t类型的全局变量。
可见,第三方模块的入口点就是ngx_module_t类型全局变量,该变量又关联ngx_http_module_t类型static变量,与ngx_command_t类型static数组。
在ngx_http_module_t中定义上下文配置nginx.conf解析的回调方法。
在ngx_command_t中定义配置项处理的set回调方法。
Nginx的全部操作都是异步的。在上述的方法中根据需要又会使用其他handler方法。
以上可以看成Nginx第三方模块的起式。
config
ngx_addon_name=ngx_http_mytest_module HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module" NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
源代码
#include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #include <ngx_stream.h> typedef struct { ngx_http_upstream_conf_t upstream; } mytest_conf_t; typedef struct { ngx_http_status_t status; ngx_str_t backendServer; } mytest_ctx_t; staticvoid *mytest_create_loc_conf(ngx_conf_t *cf); staticchar *mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r); static ngx_int_t mytest_upstream_process_status_line(ngx_http_request_t *r); static ngx_int_t mytest_upstream_process_header(ngx_http_request_t *r); staticvoid mytest_upstream_finalize_request(ngx_http_request_t *r, ngx_int_t rc); static ngx_int_t mytest_handler(ngx_http_request_t *r); staticchar *mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static ngx_http_module_t mytest_ctx = { NULL, NULL, NULL, NULL, NULL, NULL, mytest_create_loc_conf, mytest_merge_loc_conf }; static ngx_command_t mytest_commands[] = { { ngx_string("mytest"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS, mytest, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, ngx_null_command }; ngx_module_t ngx_http_mytest_module = { NGX_MODULE_V1, &mytest_ctx, mytest_commands, NGX_HTTP_MODULE, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NGX_MODULE_V1_PADDING }; static ngx_str_t mytest_upstream_hide_headers[] = { ngx_string("Date"), ngx_string("Server"), ngx_string("X-Pad"), ngx_string("X-Accel-Expires"), ngx_string("X-Accel-Redirect"), ngx_string("X-Accel-Limit-Rate"), ngx_string("X-Accel-Buffering"), ngx_string("X-Accel-Charset"), ngx_null_string }; staticvoid *mytest_create_loc_conf(ngx_conf_t *cf){ mytest_conf_t *mycf; mycf = (mytest_conf_t *)ngx_pcalloc(cf->pool, sizeof(mytest_conf_t)); if(mycf == NULL){ return NULL; } mycf->upstream.connect_timeout = 60000; mycf->upstream.send_timeout = 60000; mycf->upstream.read_timeout = 60000; mycf->upstream.store_access = 0600; mycf->upstream.buffering = 0; mycf->upstream.bufs.num = 8; mycf->upstream.bufs.size = ngx_pagesize; mycf->upstream.buffer_size = ngx_pagesize; mycf->upstream.busy_buffers_size = 2 * ngx_pagesize; mycf->upstream.temp_file_write_size = 2 * ngx_pagesize; mycf->upstream.max_temp_file_size = 1024 * 1024 *1024; mycf->upstream.hide_headers = NGX_CONF_UNSET_PTR; mycf->upstream.pass_headers = NGX_CONF_UNSET_PTR; return mycf; } staticchar *mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child){ mytest_conf_t *prev = (mytest_conf_t *)parent; mytest_conf_t *conf = (mytest_conf_t *)child; ngx_hash_init_t hash; hash.max_size = 100; hash.bucket_size = 1024; hash.name = "proxy_headers_hash"; if(ngx_http_upstream_hide_headers_hash(cf,&conf->upstream, &prev->upstream,mytest_upstream_hide_headers,&hash)!=NGX_OK){ return NGX_CONF_ERROR; } return NGX_CONF_OK; } static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r){ static ngx_str_t backendQueryLine = ngx_string("GET /search?q=%V HTTP/1.1\r\nHost: \r\nConnection: close\r\n\r\n"); ngx_int_t queryLineLen = backendQueryLine.len + r->args.len - 2; ngx_buf_t *b = ngx_create_temp_buf(r->pool, queryLineLen); if(b == NULL) return NGX_ERROR; b->last = b->pos + queryLineLen; ngx_snprintf(b->pos, queryLineLen, (char *)backendQueryLine.data, &r->args); r->upstream->request_bufs = ngx_alloc_chain_link(r->pool); if(r->upstream->request_bufs == NULL) return NGX_ERROR; r->upstream->request_bufs->buf = b; r->upstream->request_bufs->next = NULL; r->upstream->request_sent = 0; r->upstream->header_sent = 0; r->header_hash = 1; return NGX_OK; } static ngx_int_t mytest_upstream_process_status_line(ngx_http_request_t *r){ size_t len; ngx_int_t rc; ngx_http_upstream_t *u; mytest_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module); if(ctx == NULL) return NGX_ERROR; u = r->upstream; rc = ngx_http_parse_status_line(r, &u->buffer, &ctx->status); if(rc == NGX_AGAIN) return rc; if(rc == NGX_ERROR){ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent to valid HTTP/1.0 header"); r->http_version = NGX_HTTP_VERSION_9; u->state->status = NGX_HTTP_OK; return NGX_OK; } if(u->state){ u->state->status = ctx->status.code; } u->headers_in.status_n = ctx->status.code; len = ctx->status.end - ctx->status.start; u->headers_in.status_line.len = len; u->headers_in.status_line.data = ngx_pcalloc(r->pool, len); if(u->headers_in.status_line.data == NULL) return NGX_ERROR; ngx_memcpy(u->headers_in.status_line.data, ctx->status.start, len); u->process_header = mytest_upstream_process_header; return mytest_upstream_process_header(r); } static ngx_int_t mytest_upstream_process_header(ngx_http_request_t *r){ ngx_int_t rc; ngx_table_elt_t *h; ngx_http_upstream_header_t *hh; ngx_http_upstream_main_conf_t *umcf; umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); for(;;){ rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1); if(rc == NGX_OK){ h = ngx_list_push(&r->upstream->headers_in.headers); if(h == NULL) return NGX_ERROR; h->hash = r->header_hash; h->key.len = r->header_name_end - r->header_name_start; h->value.len = r->header_end - r->header_start; h->key.data = ngx_pcalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len); if(h->key.data == NULL) return NGX_ERROR; h->value.data = h->key.data + h->key.len + 1; h->lowcase_key = h->key.data + h->key.len + 1 + h->value.len + 1; ngx_memcpy(h->key.data, r->header_name_start, h->key.len); h->key.data[h->key.len]='\0'; ngx_memcpy(h->value.data, r->header_start, h->value.len); h->value.data[h->value.len] = '\0'; if(h->key.len == r->lowcase_index){ ngx_memcpy(h->lowcase_key, r->lowcase_header, h->key.len); }else{ ngx_strlow(h->lowcase_key, h->key.data, h->key.len); } hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); if(hh && hh->handler(r, h, hh->offset)!=NGX_OK) return NGX_ERROR; continue; } if(rc == NGX_HTTP_PARSE_HEADER_DONE){ if(r->upstream->headers_in.server == NULL){ h = ngx_list_push(&r->upstream->headers_in.headers); if(h == NULL) return NGX_ERROR; h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash(ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r'); ngx_str_set(&h->key, "Server"); ngx_str_null(&h->value); h->lowcase_key = (u_char *)"server"; } if(r->upstream->headers_in.date == NULL){ h = ngx_list_push(&r->upstream->headers_in.headers); if(h == NULL) return NGX_ERROR; h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e'); ngx_str_set(&h->key, "Date"); ngx_str_null(&h->value); h->lowcase_key = (u_char *)"date"; } return NGX_OK; } if(rc == NGX_AGAIN) return NGX_AGAIN; ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header"); return NGX_HTTP_UPSTREAM_FT_INVALID_HEADER; } } staticvoid mytest_upstream_finalize_request(ngx_http_request_t *r, ngx_int_t rc){ ngx_log_error(NGX_LOG_DEBUG, r->connection->log, 0, "mytest_upstream_finalize_request"); } staticchar *mytest(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 = mytest_handler; return NGX_CONF_OK; } static ngx_int_t mytest_handler(ngx_http_request_t *r){ mytest_ctx_t *myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module); if(myctx == NULL){ myctx = ngx_pcalloc(r->pool, sizeof(mytest_ctx_t)); if(myctx == NULL) return NGX_ERROR; ngx_http_set_ctx(r, myctx, ngx_http_mytest_module); } if(ngx_http_upstream_create(r)!=NGX_OK){ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_upstream_create() failed"); return NGX_ERROR; } mytest_conf_t *mycf = (mytest_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_mytest_module); ngx_http_upstream_t *u = r->upstream; u->conf = &mycf->upstream; u->buffering = mycf->upstream.buffering; u->resolved = (ngx_http_upstream_resolved_t *) ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t)); if(u->resolved == NULL){ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pcalloc resolved error. %s", strerror(errno)); return NGX_ERROR; } static struct sockaddr_in backendSockAddr; struct hostent *pHost = gethostbyname((char *)"www.google.com.hk"); if(pHost == NULL){ ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "gethostbyname fail. %s", strerror(errno)); return NGX_ERROR; } backendSockAddr.sin_family = AF_INET; backendSockAddr.sin_port = htons((in_port_t)80); char *pDmsIP = inet_ntoa(*(struct in_addr *)(pHost->h_addr_list[0])); backendSockAddr.sin_addr.s_addr = inet_addr(pDmsIP); myctx->backendServer.data = (u_char *)pDmsIP; myctx->backendServer.len = strlen(pDmsIP); u->resolved->sockaddr = (struct sockaddr *)&backendSockAddr; u->resolved->port = htons((in_port_t)80); u->resolved->socklen = sizeof(struct sockaddr_in); u->resolved->naddrs = 1; u->create_request = mytest_upstream_create_request; u->process_header = mytest_upstream_process_status_line; u->finalize_request = mytest_upstream_finalize_request; r->main->count++; ngx_http_upstream_init(r); return NGX_DONE; }