手把手教你開發Nginx模塊


前面的哪些話

關於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下各模塊路徑,用冒號分隔,例如:
/root/Workspace/nginx-1.10.1/src/core:/root/Workspace/nginx-1.10.1/src/event:/root/Workspace/nginx-1.10.1/src/http:/root/Workspace/nginx-1.10.1/src/mail:/root/Workspace/nginx-1.10.1/src/stream:/root/Workspace/nginx-1.10.1/src/os/unix 

添加環境變量,創建C項目時自動作為-I選項
image
image

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第三方模塊的起式。

Upstream例子源碼

  • 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;

static void *mytest_create_loc_conf(ngx_conf_t *cf);
static char *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);
static void mytest_upstream_finalize_request(ngx_http_request_t *r,
		ngx_int_t rc);
static ngx_int_t mytest_handler(ngx_http_request_t *r);
static char *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
};

static void *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;
}

static char *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: www.google.com.hk\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;
	}
}

static void 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");
}

static char *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;
}

注意:《Nginx深入解析》的demo少了這句:“u->resolved->port = htons((in_port_t)80);”,否則報錯“2016/09/09 11:24:18 [error] 28352#0: *1 no port in upstream "", client: 127.0.0.1, server: localhost, request: "GET /mytest?q=test HTTP/1.1", host: "localhost"”

  • 編譯腳本
./configure --prefix=/usr/local/nginx --add-module=/root/Workspace/nginx-modules/ngx_http_mytest_module --with-debug
sudo make
sudo make install

安裝后即可到/usr/local/nginx下配置nginx.conf進行測試。

Subrequest例子源碼

  • 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>

typedef struct {
	ngx_str_t stock[6];
} mytest_ctx_t;

static ngx_int_t mytest_subrequest_post_handler(ngx_http_request_t *r, void *data, ngx_int_t rc);
static void mytest_post_handler(ngx_http_request_t *r);
static ngx_int_t mytest_handler(ngx_http_request_t *r);
static char *mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

static ngx_http_module_t mytest_conf = {
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL
};
static ngx_command_t mytest_commands[] = {
	{
		ngx_string("mytest"),
		NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_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_conf,
	mytest_commands,
	NGX_HTTP_MODULE,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NGX_MODULE_V1_PADDING
};

static ngx_int_t mytest_subrequest_post_handler(ngx_http_request_t *r,
		void *data, ngx_int_t rc) {
	ngx_http_request_t *pr = r->parent;

	mytest_ctx_t *myctx = ngx_http_get_module_ctx(pr, ngx_http_mytest_module);
	pr->headers_out.status = r->headers_out.status;
	if (r->headers_out.status == NGX_HTTP_OK) {
		int flag = 0;
		ngx_buf_t *pRecvBuf = &r->upstream->buffer;
		for (; pRecvBuf->pos != pRecvBuf->last; pRecvBuf->pos++) {
			if (*pRecvBuf->pos == ',' || *pRecvBuf->pos == '\"') {
				if (flag > 0) {
					myctx->stock[flag - 1].len = pRecvBuf->pos
							- myctx->stock[flag - 1].data;
				}
				flag++;
				myctx->stock[flag - 1].data = pRecvBuf->pos + 1;
			}
			if (flag > 6)
				break;
		}
	}
	pr->write_event_handler = mytest_post_handler;

	return NGX_OK;

}

static void mytest_post_handler(ngx_http_request_t *r){
	if(r->headers_out.status != NGX_HTTP_OK){
		ngx_http_finalize_request(r,r->headers_out.status);
		return;
	}

	mytest_ctx_t *myctx = ngx_http_get_module_ctx(r,ngx_http_mytest_module);
	ngx_str_t output_format = ngx_string("stock[%V],Today current price:%V, volumn:%V");
	int bodylen = output_format.len + myctx->stock[0].len + myctx->stock[1].len + myctx->stock[4].len - 6;
	r->headers_out.content_length_n = bodylen;

	ngx_buf_t *b = ngx_create_temp_buf(r->pool, bodylen);
	ngx_snprintf(b->pos,bodylen,(char *)output_format.data, &myctx->stock[0], &myctx->stock[1], &myctx->stock[4]);
	b->last = b->pos + bodylen;
	b->last_buf = 1;

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

	static ngx_str_t type = ngx_string("text/plain; charset=GBK");
	r->headers_out.content_type = type;
	r->headers_out.status = NGX_HTTP_OK;

	r->connection->buffered |= NGX_HTTP_WRITE_BUFFERED;
	ngx_int_t ret = ngx_http_send_header(r);
	ret = ngx_http_output_filter(r, &out);

	ngx_http_finalize_request(r,ret);
}

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);
	}

	ngx_http_post_subrequest_t *psr = ngx_pcalloc(r->pool, sizeof(ngx_http_post_subrequest_t));
	if(psr == NULL) return NGX_HTTP_INTERNAL_SERVER_ERROR;

	psr->handler = mytest_subrequest_post_handler;
	psr->data = myctx;

	ngx_str_t sub_prefix = ngx_string("/list=");
	ngx_str_t sub_location;
	sub_location.len = sub_prefix.len + r->args.len;
	sub_location.data = ngx_pcalloc(r->pool, sub_location.len);
	ngx_snprintf(sub_location.data, sub_location.len, "%V%V", &sub_prefix, &r->args);

	ngx_http_request_t *sr;
	ngx_int_t rc = ngx_http_subrequest(r, &sub_location, NULL, &sr, psr, NGX_HTTP_SUBREQUEST_IN_MEMORY);

	if(rc != NGX_OK) return NGX_ERROR;

	return NGX_DONE;
}

static char *mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
	ngx_http_core_loc_conf_t *clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
	clcf->handler = mytest_handler;
	return NGX_CONF_OK;
}

  • 編譯腳本
./configure --prefix=/usr/local/nginx --add-module=/root/Workspace/nginx-modules/ngx_http_mytest_module2 --with-debug
sudo make
sudo make install


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM