学习nginx接口调用之摘录
一、參考鏈接:https://blog.csdn.net/marcky/article/details/6539387/
如果你正在從事nginx的模塊開發,可能需要獲取服務器端的ip地址。我在改進淘寶日志服務器的nginx模塊時,就碰到需要從request中讀取到接收這個請求的網卡ip。一開始,我試圖如下方式直接從請求中獲取地址:
struct sockaddr_in *sin = NULL; /* type of r is ngx_http_request_t */ sin = (struct sockaddr_in *)(r->connection->local_sockaddr);經過幾番嘗試都失敗了,讀取的值(sin)始終是空,也就是說這個字段(local_sockaddr)根本沒有被設置上ip相關的值。無奈之下查看一個請求的初始化過程的源碼后,發現這個字段根本就沒有被初始化。nginx為什么不幫我們做這件事情呢?難道需要我們自己通過getsockname函數來實現嗎?這也有點太坑爹了吧。最后向叔度(nginx專家@淘寶)請教后,得知:在使用如上代碼段獲取本地地址之前,先調用一下如下函數就可以獲取到了。
ngx_connection_local_sockaddr(r->connection, NULL, 0);查看ngx_connection_local_sockaddr函數源碼,可以看到這個函數的工作就是使用getsockname來獲取本地地址。在處理一個http請求的時候,很少會使用到本地地址,所以nginx為了節省這段空間,故默認不初始化本地地址,而是在具體需要的時候,再通過提供的接口去主動獲取,這樣一來節省了空間,又在需要的時候能夠快速的獲取。這是一個不錯的折中。
二、參考鏈接:https://www.cnblogs.com/lidabo/p/4177106.html
(此博主的其他系列文章也不錯,可以相繼查看)
上節說到nginx核心本身不會主動讀取請求體,這個工作是交給請求處理階段的模塊來做,但是nginx核心提供了ngx_http_read_client_request_body()接口來讀取請求體,另外還提供了一個丟棄請求體的接口-ngx_http_discard_request_body(),在請求執行的各個階段中,任何一個階段的模塊如果對請求體感興趣或者希望丟掉客戶端發過來的請求體,可以分別調用這兩個接口來完成。這兩個接口是nginx核心提供的處理請求體的標準接口,如果希望配置文件中一些請求體相關的指令(比如client_body_in_file_only,client_body_buffer_size等)能夠預期工作,以及能夠正常使用nginx內置的一些和請求體相關的變量(比如$request_body和$request_body_file),一般來說所有模塊都必須調用這些接口來完成相應操作,如果需要自定義接口來處理請求體,也應盡量兼容nginx默認的行為。
1,讀取請求體
?請求體的讀取一般發生在nginx的content handler中,一些nginx內置的模塊,比如proxy模塊,fastcgi模塊,uwsgi模塊等,這些模塊的行為必須將客戶端過來的請求體(如果有的話)以相應協議完整的轉發到后端服務進程,所有的這些模塊都是調用了ngx_http_read_client_request_body()接口來完成請求體讀取。值得注意的是這些模塊會把客戶端的請求體完整的讀取后才開始往后端轉發數據。
由于內存的限制,ngx_http_read_client_request_body()接口讀取的請求體會部分或者全部寫入一個臨時文件中,根據請求體的大小以及相關的指令配置,請求體可能完整放置在一塊連續內存中,也可能分別放置在兩塊不同內存中,還可能全部存在一個臨時文件中,最后還可能一部分在內存,剩余部分在臨時文件中。下面先介紹一下和這些不同存儲行為相關的指令:
client_body_buffer_size :?設置緩存請求體的buffer大小,默認為系統頁大小的2倍,當請求體的大小超過此大小時,nginx會把請求體寫入到臨時文件中。可以根據業務需求設置合適的大小,盡量避免磁盤io操作;
client_body_in_single_buffer:指示是否將請求體完整的存儲在一塊連續的內存中,默認為off,如果此指令被設置為on,則nginx會保證請求體在不大于client_body_buffer_size設置的值時,被存放在一塊連續的內存中,但超過大小時會被整個寫入一個臨時文件;
client_body_in_file_only:設置是否總是將請求體保存在臨時文件中,默認為off,當此指定被設置為on時,即使客戶端顯示指示了請求體長度為0時,nginx還是會為請求創建一個臨時文件。
接著介紹ngx_http_read_client_request_body()接口的實現,它的定義如下:
該接口有2個參數,第1個為指向請求結構的指針,第2個為一個函數指針,當請求體讀完時,它會被調用。之前也說到根據nginx現有行為,模塊邏輯會在請求體讀完后執行,這個回調函數一般就是模塊的邏輯處理函數。ngx_http_read_client_request_body()函數首先將參數r對應的主請求的引用加1,這樣做的目的和該接口被調用的上下文有關,一般而言,模塊是在content handler中調用此接口,一個典型的調用如下:
static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r) {...rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {return rc;}return NGX_DONE; }上面的代碼是在porxy模塊的content handler,ngx_http_proxy_handler()中調用了ngx_http_read_client_request_body()函數,其中ngx_http_upstream_init()被作為回調函數傳入進接口中,另外nginx中模塊的content handler調用的上下文如下:
ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) {...if (r->content_handler) {r->write_event_handler = ngx_http_request_empty_handler;ngx_http_finalize_request(r, r->content_handler(r));return NGX_OK;}... }上面的代碼中,content handler調用之后,它的返回值作為參數調用了ngx_http_finalize_request()函數,在請求體沒有被接收完全時,ngx_http_read_client_request_body()函數返回值為NGX_AGAIN,此時content handler,比如ngx_http_proxy_handler()會返回NGX_DONE,而NGX_DONE作為參數傳給ngx_http_finalize_request()函數會導致主請求的引用計數減1,所以正好抵消了ngx_http_read_client_request_body()函數開頭對主請求計數的加1。
接下來回到ngx_http_read_client_request_body()函數,它會檢查該請求的請求體是否已經被讀取或者被丟棄了,如果是的話,則直接調用回調函數并返回NGX_OK,這里實際上是為子請求檢查,子請求是nginx中的一個概念,nginx中可以在當前請求中發起另外一個或多個全新的子請求來訪問其他的location,關于子請求的具體介紹會在后面的章節作詳細分析,一般而言子請求不需要自己去讀取請求體。
函數接著調用ngx_http_test_expect()檢查客戶端是否發送了Expect: 100-continue頭,是的話則給客戶端回復"HTTP/1.1 100 Continue",根據http 1.1協議,客戶端可以發送一個Expect頭來向服務器表明期望發送請求體,服務器如果允許客戶端發送請求體,則會回復"HTTP/1.1 100 Continue",客戶端收到時,才會開始發送請求體.
接著繼續為接收請求體做準備工作,分配一個ngx_http_request_body_t結構,并保存在r->request_body,這個結構用來保存請求體讀取過程用到的緩存引用,臨時文件引用,剩余請求體大小等信息,它的定義如下。
做好準備工作之后,函數開始檢查請求是否帶有content_length頭,如果沒有該頭或者客戶端發送了一個值為0的content_length頭,表明沒有請求體,這時直接調用回調函數并返回NGX_OK即可。當然如果client_body_in_file_only指令被設置為on,且content_length為0時,該函數在調用回調函數之前,會創建一個空的臨時文件。
進入到函數下半部分,表明客戶端請求確實表明了要發送請求體,該函數會先檢查是否在讀取請求頭時預讀了請求體,這里的檢查是通過判斷保存請求頭的緩存(r->header_in)中是否還有未處理的數據。如果有預讀數據,則分配一個ngx_buf_t結構,并將r->header_in中的預讀數據保存在其中,并且如果r->header_in中還有剩余空間,并且能夠容下剩余未讀取的請求體,這些空間將被繼續使用,而不用分配新的緩存,當然甚至如果請求體已經被整個預讀了,則不需要繼續處理了,此時調用回調函數后返回。
如果沒有預讀數據或者預讀不完整,該函數會分配一塊新的內存(除非r->header_in還有足夠的剩余空間),另外如果request_body_in_single_buf指令被設置為no,則預讀的數據會被拷貝進新開辟的內存塊中,真正讀取請求體的操作是在ngx_http_do_read_client_request_body()函數,該函數循環的讀取請求體并保存在緩存中,如果緩存被寫滿了,其中的數據會被清空并寫回到臨時文件中。當然這里有可能不能一次將數據讀到,該函數會掛載讀事件并設置讀事件handler為ngx_http_read_client_request_body_handler,另外nginx核心對兩次請求體的讀事件之間也做了超時設置,client_body_timeout指令可以設置這個超時時間,默認為60s,如果下次讀事件超時了,nginx會返回408給客戶端。
最終讀完請求體后,ngx_http_do_read_client_request_body()會根據配置,將請求體調整到預期的位置(內存或者文件),所有情況下請求體都可以從r->request_body的bufs鏈表得到,該鏈表最多可能有2個節點,每個節點為一個buffer,但是這個buffer的內容可能是保存在內存中,也可能是保存在磁盤文件中。另外$request_body變量只在當請求體已經被讀取并且是全部保存在內存中,才能取得相應的數據。
三、參考鏈接:https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/3838053.html
?【Nginx】請求上下文
??上下文與全異步web服務器的關系
請求上下文指在一個請求的處理過程中,把一些關鍵的信息保存下來的類似struct這樣的結構體。每個http模塊都可以有自己的上下文結構體,一般都是在剛開始處理請求時在內存池上分配它,之后當經由epoll、http框架再次調用到http模塊的處理方法時,這個http模塊可以由請求上下文結構體中獲取信息。請求結束時就會銷毀該請求的內存池,自然也就銷毀了上下文結構體。
Nginx是全異步處理的web服務器,http模塊可能會多次反復處理同一個請求,所以必須定義上下文結構體來保存處理過程的中間狀態。Nginx框架不會維護這個上下文,只能由這個請求自己保存著上下文結構體。
??使用http上下文
之所以使用上下文, 是因為Nginx是一個全異步處理的Web服務器, 1個請求并不會在epoll的一次調度中處理完成, 甚至會在成千上萬的HTTP模塊后才能完成請求的處理。
既然上下文是針對某請求的某模塊的, 那么可以根據前文中, 存儲某模塊存儲create_main_conf函數返回的結構體的方式了解到, 其實所有模塊在每個請求中都對應了數組的一個元素, 所以可以根據數組下標來找到對應模塊的數據.
??? 使用上下文的方式就是調用下面這兩個宏:
#define ngx_http_get_module_ctx(r, module) (r)->ctx[module.ctx_index]; #define ngx_http_set_ctx(r, c, module) r->ctx[module.ctx_index] = c;??下面提供一個例子:
//首先建立上下文的結構體 typedef struct {ngx_uint_t ben_count; }ngx_ben_test_ctx_t;//當請求第一次進入ben_test模塊處理時,我們需要創建一個ngx_ben_test_ctx_t結構體,并設置到請求中去 static ngx_int_t ngx_http_ben_test(ngx_http_request_t *r) {//首先調用ngx_http_get_module_ctx獲取此模塊的上下文結構體(可以預料,既然這是第一次進入此模塊處理,這個上下文應該就是NULL的)//傳入的第一個參數是請求對象,第二個參數是模塊對象ngx_ben_test_ctx_t *ctx = ngx_http_get_module_ctx(r, ngx_http_ben_test_module);if (ctx == NULL) {//必須在內存池中申請對象,這樣才能保證在一個請求結束和這些資源都被釋放ctx = ngx_palloc(r->pool, sizeof(ngx_ben_test_ctx_t));if (ctx == NULL) return NGX_ERROR;//把上下文結構體存起來ngx_http_set_ctx(r, ctx, ngx_http_ben_test_module);}//接下來就可以使用這個上下文了... } //如果Nginx多次回調ben_test模塊的相應方法,那么每次使用宏得到上下文,繼而修改再保存,這樣,上下文就被正常使用起來了。下面看一下請求結構體中的一個變量就明白了
struct ngx_http_request_s {...void **ctx;... }與我們上面提到的也確實是相符合的。總結
以上是生活随笔為你收集整理的学习nginx接口调用之摘录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习“用dlopen,dlsym加载动态
- 下一篇: 最全的http头部信息分析(转载)