开发一个使用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!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值