開發一個使用upstream的示例模組
阿新 • • 發佈:2019-02-06
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!