1. 程式人生 > >開發一個使用upstream的示例模組

開發一個使用upstream的示例模組

Nginx配置檔案/usr/local/nginx/conf/nginx.conf配置如下:

#worker工作程序的使用者及使用者組
user  weijl;
#Nginx worker程序個數
worker_processes  1;

#error日誌的設定,預設logs/error.log error
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
error_log  logs/error.log  error;

#pid檔案的路徑
#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    #嵌入配置檔案mime.types
    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;

    #upstream test.proxy.com {
    #    #ip_hash;
    #    server 192.168.0.7;
    #    server 192.168.0.8;
    #}


    server {
        listen       127.0.0.1:1024;
        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;
        }

	#靜態圖片資源
	location /image/ {
            root /home/weijl/workspace/;
            autoindex on;
        }
	
	 #反向代理
        location /proxy_loc/ {
	    root html;
            proxy_set_header Host $host;
            proxy_pass http://test.proxy.com;
            #禁用快取
            proxy_buffering off;
	    proxy_set_header X-Real-IP $remote_addr; 
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	    client_max_body_size 100m;
        }

	#實現自己的HTTP模組
	location /test {
	    mytest;
	    #root html;

	    test_flag	on;

	    test_str apple;

	    test_str_array Content-Length;
	    test_str_array Content-Encoding;

	    test_keyval Content-Type image/png;
	    test_keyval Content-Type image/gif;
	    test_keyval Accept-Encoding gzip;

	    test_num 10;
		
	    test_size 2048k;
	
	    test_off 1g;

	    test_msec 1d;
	    test_sec 1d;

	    test_bufs 4 1024k;
	
   	    #test_enum apple;

	    test_bitmask good;

	    test_path /home/weijl/workspace/test/;
		
	    test_myconfig score 100;
	    
	    upstream_connect_timeout 60000;
	    upstream_send_timeout 60000;
	    upstream_read_timeout 60000;
	    #upstream_store_access all:rw;
	    upstream_buffering off;
	    upstream_bufs 8 4096;
	    upstream_buffer_size 4096;
	    upstream_busy_buffers_size 8192;
	    upstream_temp_file_write_size 8192;
	    upstream_max_temp_file_size 1024m;
	}

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

}


模組檔案程式碼如下:

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

extern ngx_module_t  ngx_http_mytest_module;
static ngx_str_t  ngx_http_proxy_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 char* ngx_conf_set_myconfig(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char* ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *conf);
static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r);
static ngx_int_t mytest_upstream_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);

//自定義配置項,用於自定義解析配置項
typedef struct
{
		ngx_str_t config_str;
		ngx_int_t	config_num;
}ngx_http_config_conf_t;

//儲存loc級別配置項的結構體
typedef struct
{
	ngx_str_t	my_str;
	ngx_int_t my_num;
	ngx_flag_t my_flag;
	size_t my_size;
	ngx_array_t* my_str_array;
	ngx_array_t* my_keyval;
	off_t my_off;
	ngx_msec_t my_msec;
	time_t my_sec;
	ngx_bufs_t	my_bufs;
	ngx_uint_t	my_enum_seq;
	ngx_uint_t 	my_bitmask;
	ngx_uint_t	my_access;
	ngx_path_t 	my_path;
	ngx_http_config_conf_t my_config;
	ngx_http_upstream_conf_t upstream;
}ngx_http_mytest_conf_t;

//mytest模組上下文
typedef struct
{
	ngx_http_status_t status;
	ngx_str_t backendServer;
}ngx_http_mytest_ctx_t;

static ngx_conf_enum_t	test_enums[] = {
		{ngx_string("apple"), 1},
		{ngx_string("banana"), 2},
		{ngx_string("orange"), 3},
		{ngx_null_string, 0}
};

static ngx_conf_bitmask_t test_bitmasks[] = {
		{ngx_string("good"), 0x0002},
		{ngx_string("better"), 0x0004},
		{ngx_string("best"), 0x0008},
		{ngx_null_string, 0}
};

//建立結構體用於儲存loc級別配置項
static void*  ngx_http_mytest_create_loc_conf(ngx_conf_t 	*cf)
{
		ngx_log_debug(NGX_LOG_DEBUG_HTTP, cf->log, 0, "建立結構體用於儲存loc級別配置項\n");
		ngx_http_mytest_conf_t	 *mycf;
		mycf = (ngx_http_mytest_conf_t *)ngx_pcalloc(cf->pool, sizeof(ngx_http_mytest_conf_t ));
		if(NULL == mycf)
		{
			ngx_log_error(NGX_LOG_ERR,cf->log, 0, "____weijl line=%d____\n", __LINE__);
			return NULL;
		}

		mycf->my_flag = NGX_CONF_UNSET;
		mycf->my_num = NGX_CONF_UNSET;
		mycf->my_str_array = NGX_CONF_UNSET_PTR;
		mycf->my_keyval = NULL;
		mycf->my_off = NGX_CONF_UNSET;
		mycf->my_msec = NGX_CONF_UNSET_MSEC;
		mycf->my_sec = NGX_CONF_UNSET;
		mycf->my_size = NGX_CONF_UNSET_SIZE;
		mycf->my_config.config_num = NGX_CONF_UNSET;
		mycf->upstream.connect_timeout = NGX_CONF_UNSET_MSEC;
		mycf->upstream.send_timeout = NGX_CONF_UNSET_MSEC;
		mycf->upstream.read_timeout = NGX_CONF_UNSET_MSEC;
		mycf->upstream.buffering = NGX_CONF_UNSET;
		mycf->upstream.buffer_size = NGX_CONF_UNSET_SIZE;
		mycf->upstream.busy_buffers_size = NGX_CONF_UNSET_SIZE;
		mycf->upstream.temp_file_write_size = NGX_CONF_UNSET_SIZE;
		mycf->upstream.max_temp_file_size = NGX_CONF_UNSET_SIZE;

		mycf->upstream.store_access = 600;

		mycf->upstream.hide_headers = NGX_CONF_UNSET_PTR;
		mycf->upstream.pass_headers = NGX_CONF_UNSET_PTR;

		//建立結構體的方法返回時,將建立的結構體傳遞給HTTP框架
		return mycf;
}

//請求包體接收完後回撥的函式
void ngx_http_mytest_body_handler(ngx_http_request_t *r)
{

}

//HTTP的NGX_HTTP_CONTENT_PHASE階段mytest模組介入處理http請求內容
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
		ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "HTTP的HTTP_CONTENT_PHASE階段模組介入處理http請求內容\n");
		ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "ngx_pagesize=%d\n",ngx_pagesize);
		//首先呼叫ngx_http_get_module_ctx巨集來獲取上下文結構體
		ngx_http_mytest_ctx_t* myctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);
		if(NULL == myctx)
		{
			//必須在當前請求的記憶體池r->pool中分配上下文結構體,這樣請求結束時結構體佔用的記憶體才會釋放
			myctx = ngx_pcalloc(r->pool, sizeof(ngx_http_mytest_ctx_t));
			if(NULL == myctx)
			{
				ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
				return NGX_ERROR;
			}
			//將剛分配的結構體設定到當前請求的上下文中
			ngx_http_set_ctx(r, myctx, ngx_http_mytest_module);
		}

		//必須時GET或者HEAD方法,否則返回405 Not Allowed
		if(!(r->method &(NGX_HTTP_GET | NGX_HTTP_HEAD)))
		{
			    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "weijl NGX_HTTP_NOT_ALLOWED");
				return NGX_HTTP_NOT_ALLOWED;
		}

		//對每個要使用upstream的請求,必須呼叫且只能呼叫一次ngx_http_upstream_create方法,它會初始化r->upstream成員
		if(ngx_http_upstream_create(r) != NGX_OK)
		{
			ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_http_upstream_create() failed\n");

			return NGX_ERROR;
		}

		//得打配置結構體ngx_http_mytest_conf_t
		ngx_http_mytest_conf_t* mycf = (ngx_http_mytest_conf_t*)ngx_http_get_module_loc_conf(r, ngx_http_mytest_module);
		ngx_http_upstream_t *u = r->upstream;
		//這裡用配置檔案中的結構體來賦給r->upstream->conf成員
		u->conf = &mycf->upstream;
		//決定轉發包體時使用的緩衝區
		u->buffering = mycf->upstream.buffering;

		//以下程式碼開始初始化resolved結構體,用來儲存上游伺服器地址
		u->resolved = (ngx_http_upstream_resolved_t*)ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_resolved_t));
		if(NULL == u->resolved )
		{
			ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "ngx_pcalloc resolved error. %s.", strerror(errno));

			return NGX_ERROR;
		}

		//這裡的上游伺服器就是s.taobao.com
		static struct sockaddr_in backendSockAddr;
		struct hostent *pHost = gethostbyname((char*)"s.taobao.com");
		if(NULL == pHost)
		{
			ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "gethostbyname fail. %s.", strerror(errno));

			return NGX_ERROR;
		}

		//訪問上游伺服器的80埠
		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);

		//將地址設定到resolved成員中
		u->resolved->sockaddr = (struct sockaddr *)&backendSockAddr;
		u->resolved->socklen = sizeof(struct sockaddr_in);
		u->resolved->naddrs = 1;
		u->resolved->port = backendSockAddr.sin_port;

		//設定3個必須實現的回撥方法
		u->create_request = mytest_upstream_create_request;
		u->process_header = mytest_upstream_status_line;
		u->finalize_request = mytest_upstream_finalize_request;

		//這裡必須將count成員加1
		r->main->count++;
		//啟動upstream
		ngx_http_upstream_init(r);

		//必須返回NGX_DONE
		return NGX_DONE;

		//以下注釋部分為構造包體直接傳送給客戶端,臨時註釋掉
		/*
		//丟棄請求中的包體
		ngx_int_t rc = ngx_http_discard_request_body(r);
		if(rc != NGX_OK)
		{
			 	ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "weijl rc=%d", rc);
				return rc;
		}

		//設定返回的Content_Type。注意,ngx_str_t有一個很方便的初始化巨集ngx_string,它可以把ngx_str_t的data和len成員都設定好
		ngx_str_t type = ngx_string("text/plain");
		//設定返回狀態碼
		r->headers_out.status = NGX_HTTP_OK;

		//傳送HTTP頭部
		rc = ngx_http_send_header(r);
		if(rc == NGX_ERROR || rc > NGX_OK || r->header_only)
		{
				ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "weijl rc=%d", rc);
				return rc;
		}

		//構造ngx_buf_t結構體準備傳送包體
		ngx_buf_t *b;
		b = ngx_palloc(r->pool, sizeof(ngx_buf_t));
		if(NULL == b)
		{
			    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "weijl b=NULL");
				return NGX_HTTP_INTERNAL_SERVER_ERROR;
		}

		u_char* filename = (u_char*)"/home/weijl/workspace/nginx-1.10.3/src/http/config";
		b->in_file = 1;
		b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
		b->file->fd = ngx_open_file(filename, NGX_FILE_RDONLY | NGX_FILE_NONBLOCK , NGX_FILE_OPEN, 0);
		b->file->log = r->connection->log;
		b->file->name.data = filename;
		b->file->name.len = strlen((const char*)filename);
		if(b->file->fd <= 0)
		{
				return NGX_HTTP_NOT_FOUND;
		}

		if(ngx_file_info(filename, &b->file->info) == NGX_FILE_ERROR)
		{
				return NGX_HTTP_INTERNAL_SERVER_ERROR;
		}

		//響應包是由包體內容的,需要設定Conten-Length長度
		r->headers_out.content_length_n = b->file->info.st_size;
		//設定Content-Type
		r->headers_out.content_type = type;

		b->file_pos = 0;
		b->file_last = b->file->info.st_size;
		//宣告這是最後一塊緩衝區
		b->last_buf =1;

		//構造傳送時的ngx_chain_t結構體
		ngx_chain_t out;
		out.buf = b;
		//設定next為NULL
		out.next = NULL;

		//清理檔案控制代碼
		////在請求結束時呼叫cln的handler方法清理資源
		ngx_pool_cleanup_t* cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_pool_cleanup_file_t));
		if(NULL == cln)
		{
				return NGX_ERROR;
		}
		//將Nginx提供的ngx_pool_cleanup_file函式設定為回撥方法
		cln->handler = ngx_pool_cleanup_file;
		//設定回撥方法的引數
		ngx_pool_cleanup_file_t *clnf = cln->data;

		clnf->fd = b->file->fd;
		clnf->name = b->file->name.data;
		clnf->log = r->pool->log;

		ngx_log_debug(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "weijl 構造包體完成,開始傳送包體\n");

		//最後一步為傳送包體,傳送結束後HTTP框架會呼叫ngx_http_finalize_request方法結束請求
		return ngx_http_output_filter(r, &out);*/
}

//沒有什麼工作必須在HTTP框架初始化時完成,不必實現ngx_http_module_t的8個回撥方法
static ngx_http_module_t ngx_http_mytest_module_ctx =
{
		NULL,//preconfiguration解析配置檔案前呼叫
		NULL,//postconfiguration完成配置檔案解析後呼叫

		NULL,//ceate_main_conf建立儲存全域性配置項的結構體
		NULL,//init_main_conf常用於初始化main級別的配置項

		NULL,//create_srv_conf建立儲存srv級別配置項的結構體
		NULL,//merge_srv_conf主要用於合併main級別和srv級別下的同名配置項

		ngx_http_mytest_create_loc_conf,//create_loc_conf建立用於儲存loc級別配置項的結構體
		ngx_http_mytest_merge_loc_conf//merge_loc_conf主要用於合併srv級別和loc級別下的同名配置項
};

//“mytest”配置項解析的回撥方法
static char *ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
		ngx_log_debug(NGX_LOG_DEBUG_HTTP, cf->log, 0, "mytest配置項解析的回撥方法\n");
		ngx_http_core_loc_conf_t  *clcf;
		clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

		/*HTTP框架在處理使用者請求進行到NGX_HTTP_CONTENT_PHASE階段時,如果請求的主機域名、URI與mytest
		 * 配置項所在的配置塊相匹配,就將呼叫ngx_http_mytest_handler方法處理這個請求*/
		clcf->handler = ngx_http_mytest_handler;

		return NGX_CONF_OK;
}

//mytest配置項的處理
static ngx_command_t  ngx_http_mytest_commands[] = {
		{ngx_string("mytest"),
		NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,
		ngx_http_mytest,//在出現配置項mytest時呼叫ngx_http_mytest解析
		NGX_HTTP_LOC_CONF_OFFSET,
		0,
		NULL},

		{ngx_string("test_flag"),
		NGX_HTTP_LOC_CONF | NGX_CONF_FLAG,
		ngx_conf_set_flag_slot,//在出現配置項test_flag時呼叫ngx_conf_set_flag_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_flag),
		NULL},

		{ngx_string("test_str"),
		NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_str_slot,//在出現配置項test_str時呼叫ngx_conf_set_str_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_str),
		NULL},

		{ngx_string("test_str_array"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_str_array_slot,//在出現配置項test_str_array時呼叫ngx_conf_set_str_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_str_array),
		NULL},

		{ngx_string("test_keyval"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
		ngx_conf_set_keyval_slot,//在出現配置項test_keyval時呼叫ngx_conf_set_keyval_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_keyval),
		NULL},

		{ngx_string("test_num"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_num_slot,//在出現配置項test_num時呼叫ngx_conf_set_num_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_num),
		NULL},

		{ngx_string("test_size"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_size_slot,//在出現配置項test_size時呼叫ngx_conf_set_size_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_size),
		NULL},

		{ngx_string("test_off"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_off_slot,//在出現配置項test_off時呼叫ngx_conf_set_off_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_off),
		NULL},

		{ngx_string("test_msec"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_msec_slot,//在出現配置項test_msec時呼叫ngx_conf_set_msec_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_msec),
		NULL},

		{ngx_string("test_sec"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_sec_slot,//在出現配置項test_sec時呼叫ngx_conf_set_sec_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_sec),
		NULL},

		{ngx_string("test_bufs"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
		ngx_conf_set_bufs_slot,//在出現配置項test_bufs時呼叫ngx_conf_set_bufs_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_bufs),
		NULL},

		{ngx_string("test_enum"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_enum_slot,//在出現配置項test_enum時呼叫ngx_conf_set_enum_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_enum_seq),
		test_enums},

		{ngx_string("test_bitmask"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_bitmask_slot,//在出現配置項test_bitmask時呼叫ngx_conf_set_bitmask_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_bitmask),
		test_bitmasks},

		{ngx_string("test_access"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
		ngx_conf_set_access_slot,//在出現配置項test_access時呼叫ngx_conf_set_access_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_access),
		NULL},

		{ngx_string("test_path"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
		ngx_conf_set_path_slot,//在出現配置項test_path時呼叫ngx_conf_set_path_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_path),
		NULL},

		{ngx_string("test_myconfig"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
		ngx_conf_set_myconfig,//在出現配置項test_myconfig時呼叫ngx_conf_set_myconfig解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, my_config),
		NULL},

		{ngx_string("upstream_connect_timeout"),
		NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
		ngx_conf_set_msec_slot,//在出現配置項upstream_connect_timeout時呼叫ngx_conf_set_msec_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t, upstream.connect_timeout),
		NULL},

		{ ngx_string("upstream_send_timeout"),
		NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
		ngx_conf_set_msec_slot,	//在出現配置項upstream_send_timeout時呼叫ngx_conf_set_msec_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t,upstream.send_timeout),
		NULL },

		{ ngx_string("upstream_read_timeout"),
	    NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
		ngx_conf_set_msec_slot,	//在出現配置項upstream_read_timeout時呼叫ngx_conf_set_msec_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t,upstream.read_timeout),
		NULL },

		{ ngx_string("upstream_store_access"),
		NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
		ngx_conf_set_access_slot,	//在出現配置項upstream_store_access時呼叫ngx_conf_set_access_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t,upstream.store_access),
		NULL },

		{ ngx_string("upstream_buffering"),
		NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
		ngx_conf_set_flag_slot,	//在出現配置項upstream_buffering時呼叫ngx_conf_set_num_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t,upstream.buffering),
		NULL },

		{ ngx_string("upstream_bufs"),
		NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2,
		ngx_conf_set_bufs_slot,	//在出現配置項upstream_buffering時呼叫ngx_conf_set_num_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t,upstream.bufs),
		NULL },

		{ ngx_string("upstream_buffer_size"),
		NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
		ngx_conf_set_size_slot,	//在出現配置項upstream_buffer_size時呼叫ngx_conf_set_size_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t,upstream.buffer_size),
		NULL },

		{ ngx_string("upstream_busy_buffers_size"),
		NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
		ngx_conf_set_size_slot,	//在出現配置項upstream_busy_buffers_size時呼叫ngx_conf_set_size_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t,upstream.busy_buffers_size),
		NULL },

		{ ngx_string("upstream_temp_file_write_size"),
		NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
		ngx_conf_set_size_slot,	//在出現配置項upstream_temp_file_write_size時呼叫ngx_conf_set_size_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t,upstream.temp_file_write_size),
		NULL },

		{ ngx_string("upstream_max_temp_file_size"),
		NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
		ngx_conf_set_size_slot,	//在出現配置項upstream_max_temp_file_size時呼叫ngx_conf_set_size_slot解析
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_mytest_conf_t,upstream.max_temp_file_size),
		NULL },

		//更多的配置項可以在這裡定義

		ngx_null_command
};

//定義mytest模組
ngx_module_t  ngx_http_mytest_module = {
    NGX_MODULE_V1,
    &ngx_http_mytest_module_ctx,             /* module context */
    ngx_http_mytest_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 char* ngx_conf_set_myconfig(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
		ngx_log_debug(NGX_LOG_DEBUG_HTTP, cf->log, 0, "自定義配置項處理方法\n");
		//注意,引數conf就是HTTP框架傳給使用者的在ngx_http_mytest_create_loc_conf回撥方法中分配的結構體ngx_http_mytest_conf_t
		ngx_http_mytest_conf_t *mycf = conf;

		/*cf->args是一個ngx_array_t佇列,它的成員都是ngx_str_t結構。我們用value指向ngx_array_t的elts的內容,其中value[1]
		 * 就是第一個引數,同理,value[2]是第二個引數*/
		ngx_str_t* value  = cf->args->elts;

		//ngx_array_t的nelts表示引數的個數
		if(cf->args->nelts > 1)
		{
				//直接賦值即可,ngx_str_t結構只是指標的傳遞
				mycf->my_config.config_str = value[1];
				ngx_log_debug(NGX_LOG_DEBUG_HTTP, cf->log, 0, "weijl config_str=%V", &mycf->my_config.config_str);
		}

		if(cf->args->nelts > 2)
		{
				//將字串形式的第二個引數轉為整型
				mycf->my_config.config_num = ngx_atoi(value[2].data, value[2].len);
				ngx_log_debug(NGX_LOG_DEBUG_HTTP, cf->log, 0, "weijl config_num=%d", mycf->my_config.config_num);
				//如果字串轉為整型失敗,將報"invalid number"錯誤,Nginx啟動失敗
				if(mycf->my_config.config_num == NGX_ERROR)
				{
						return "invalid number";
				}
		}

		//返回成功
		return NGX_CONF_OK;
}

//合併配置項
static char* ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *conf)
{
		ngx_log_debug(NGX_LOG_DEBUG_HTTP, cf->log, 0, "合併配置項\n");
		ngx_http_mytest_conf_t *prev = (ngx_http_mytest_conf_t*)parent;
		ngx_http_mytest_conf_t *this = (ngx_http_mytest_conf_t*)conf;
		//ngx_conf_merge_str_value(this->my_str, prev->my_str, "dedaultstr");

		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, &this->upstream, &prev->upstream, ngx_http_proxy_hide_headers, &hash) != NGX_OK)
		{
			ngx_log_error(NGX_LOG_ERR, cf->log, 0, "____weijl line=%d____\n", __LINE__);
			return NGX_CONF_ERROR;
		}

		return NGX_CONF_OK;
}

//構造發往上游伺服器的請求內容
static ngx_int_t mytest_upstream_create_request(ngx_http_request_t *r)
{
	/*發往baidu上游伺服器的請求很簡單,就是模仿正常的搜尋請求,以/search?=...的URI來發起請。*/
	static ngx_str_t backendQueryLine = ngx_string("GET /search?q=%V HTTP/1.1\r\nHost: s.taobao.com\r\nConnection: close\r\n\r\n");
	ngx_int_t queryLineLen = backendQueryLine.len + r->args.len-2;
	/*必須在記憶體池中申請記憶體,這有以下兩點好處:一個好處是,在網路情況不佳的情況下,向上遊伺服器傳送請求時,可能需要
	 * epoll多次排程send才能傳送完成,這時必須保證這段記憶體不會被釋放;另一個好處是,在結束請求時,這段記憶體會被自動釋放,
	 * 降低記憶體洩露的可能*/
	ngx_buf_t *b = ngx_create_temp_buf(r->pool, queryLineLen);
	if(NULL == b)
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
		return NGX_ERROR;
	}
	//last要指向請求的末尾
	b->last = b->pos + queryLineLen;

	//作用相當於snprintf,只是它支援ngx中的所有轉換格式
	ngx_snprintf(b->pos, queryLineLen, (char*)backendQueryLine.data, &r->args);
	/*r->upstream->request_bufs是一個ngx_chain_t結構,它包含著要傳送給上游伺服器的請求*/
	r->upstream->request_bufs = ngx_alloc_chain_link(r->pool);
	if(NULL == r->upstream->request_bufs)
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
		return NGX_ERROR;
	}

	//request->bufs在這裡只包含一個ngx_buf_t緩衝區
	r->upstream->request_bufs->buf = b;
	r->upstream->request_bufs->next = NULL;

	r->upstream->request_sent = 0;
	r->upstream->header_sent = 0;
	//header_hash不可以為0
	r->header_hash = 1;

	return NGX_OK;
}

static ngx_int_t mytest_upstream_status_line(ngx_http_request_t *r)
{
	size_t len;
	ngx_int_t rc;
	ngx_http_upstream_t *u;

	//上下文中才會儲存多次解析HTTP響應行的狀態,下面首先取出請求的上下文
	ngx_http_mytest_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_mytest_module);
	if(NULL == ctx)
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
		return NGX_ERROR;
	}

	u = r->upstream;

	/*HTTP框架提供的ngx_http_parse_status_line方法可以解析HTTP響應行,它的輸入就是收到的字元流和上下文
	 * 中的ngx_http_status_t結構*/
	rc = ngx_http_parse_status_line(r, &u->buffer, &ctx->status);
	//返回NGX_AGAIN時,表示還沒有解析出完整的HTTP響應行,需要接收更多的字元流再進行解析
	if(NGX_AGAIN == rc)
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
		return rc;
	}

	//返回NGX_ERROR時,表示沒有接收到合法的HTTP響應行
	if(NGX_ERROR == rc)
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent no valid HTTP/1.0 header");

		r->http_version = NGX_HTTP_VERSION_9;
		u->state->status = NGX_HTTP_OK;

		return NGX_OK;
	}

	/*以下表示在解析到完整的HTTP響應行時,會做一些簡單的賦值操作,將解析出的資訊設定到r->upstream->headers_in
	 * 結構體中。當upstream解析完所有的包頭時,會把headers_in中的成員設定到將要向下遊傳送的r->headers_out結構體
	 * 中,也就是說,現在使用者向headers_in中設定的資訊,最終都會發往下游客戶端。為什麼不直接設定r->headers_out而
	 * 要多此一舉呢?因為upstream希望能夠按照ngx_http_upstream_conf_t配置結構體中的hide_headers等成員對發往
	 * 下游的響應頭部做統一處理*/
	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_pnalloc(r->pool, len);
	if(u->headers_in.status_line.data == NULL)
	{
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
		return NGX_ERROR;
	}

	ngx_memcpy(u->headers_in.status_line.data, ctx->status.start, len);

	/*下一步將開始解析HTTP頭部。設定process_header回撥方法為mytest_upstream_process_header,之後再收到新的字元流就由
	 * mytest_upstream_process_header解析*/
	u->process_header = mytest_upstream_process_header;

	/*如果本次收到的字元流處理HTTP響應行外,還有多餘的字元,那麼將由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;

	/**這裡將upstream模組配置項ngx_http_upstream_main_conf_t取出來,目的只有一個,就是對將要轉發給下游客戶端的HTTP
	 * 響應頭部進行統一處理。該結構體中儲存了需要進行統一處理的HTTP頭部名稱和回撥方法*/
	umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module);

	//迴圈地解析所有的HTTP頭部
	for(; ;)
	{
		//HTTP框架提供了基礎性的ngx_http_parse_header_line方法,它用於解析HTTP頭部
		rc = ngx_http_parse_header_line(r, &r->upstream->buffer, 1);
		//返回NGX_OK時,表示解析出一行HTTP頭部
		if(NGX_OK == rc)
		{
			//向headers_in.headers這個ngx_list_t連結串列中新增HTTP頭部
			h = ngx_list_push(&r->upstream->headers_in.headers);
			if(NULL == h)
			{
				ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
				return NGX_ERROR;
			}

			//下面開始構造剛剛新增到headers連結串列中的HTTP頭部
			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;
			//必須在記憶體池中分配存放HTTP頭部的記憶體空間
			h->key.data = ngx_pnalloc(r->pool, h->key.len + 1 + h->value.len + 1 + h->key.len);
			if(h->key.data == NULL)
			{
				ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
				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);
			}

			//upstream模組會對一些HTTP頭部做特殊處理
			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)
			{
				ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
				return NGX_ERROR;
			}

			continue;
		}

		/*返回NGX_HTTP_PARSE_HEADER_DONE時,表示響應中所有的HTTP頭部都解析完畢,接下來再接收到的都將是HTTP包體*/
		if(NGX_HTTP_PARSE_HEADER_DONE == rc)
		{
			/*如果之前解析HTTP頭部時沒有發現server和data頭部,那麼下面會根據HTTP協議規範新增這個兩個頭部*/
			if(r->upstream->headers_in.server == NULL)
			{
				h = ngx_list_push(&r->upstream->headers_in.headers);
				if(NULL == h)
				{
					ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
					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(NULL == h)
				{
					ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
					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;
		}

		/*如果返回NGX_AGAIN,則表示狀態機還沒有解析到完整的HTTP頭部,此時要求upsream模組繼續接收新的位元組流,然後交由process_header回撥方法解析*/
		if(NGX_AGAIN == rc)
		{
			ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "____weijl line=%d____\n", __LINE__);
			return NGX_AGAIN;
		}

		//其他返回值都是非法的
		ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent invalid header");

		return NGX_HTTP_UPSTREAM_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");
}

編譯模組,安裝,然後測試驗證,在瀏覽器中輸入

localhost:1024/test?iphone

即可看到跳轉到了淘寶搜尋頁面。如圖:


注意:

在編寫除錯這個示例時,一開始有報錯資訊,日誌如下

除錯時推測應該是沒有設定上游伺服器埠號,最後在書上程式碼基礎上加了第193行的程式碼:

u->resolved->port = backendSockAddr.sin_port;


然後,測試驗證ok!