1. 程式人生 > >nginx處理post請求(http響應頭部的收發)

nginx處理post請求(http響應頭部的收發)

        上一篇文章分析了nginx如何傳送來自客戶端的請求資料到後端伺服器, 本篇文章開始將分析nginx如何接收來自後端伺服器的響應。nginx接收來自後端伺服器的響應分為兩個過程,一個是接收來自後端伺服器的http響應頭部, 另一個是接收來自後端伺服器的響應包體。

        有必要在最前面說明,也是很重要的幾點。(1)nginx在收到來自客戶端的全部請求資料後,採用邊傳送請求資料到後端伺服器,一邊接收來自後端伺服器的響應, 收發同時進行,是一種全雙工模式。也就是說,nginx要與後端伺服器開始通訊,必須接收到了來自客戶端的所有請求資料,把壓力都集中在nginx這端,跟後端伺服器沒關係;(2)nginx傳送請求資料與接收來自後端伺服器的響應可以同時進行,是一種全雙工方式;(3)nginx接收到了後端伺服器的響應,什麼時候向客戶端轉發響應呢? 對於http響應頭部,nginx只有在接收到了所有來自後端伺服器的響應頭部後,才會轉發給客戶端。對於http響應包體,則nginx邊接收後端伺服器的響應包體,邊發給客戶端。

        本篇文章先來看下nginx接收後端伺服器http響應頭部的過程,下一篇將詳細分析接收包體的過程。

一、接收響應頭部的啟動

        nginx在與後端伺服器建立 tcp連線時,已經把讀事件的回撥設定為了ngx_http_upstream_handler, 並把負載均衡的讀事件回撥設定為:ngx_http_upstream_process_header。

//與後端伺服器建立連線,並註冊讀寫事件的回撥
static void ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
	//設定讀寫事件的回撥
    c->write->handler = ngx_http_upstream_handler;
    c->read->handler = ngx_http_upstream_handler;

	//設定upstream機制的讀寫事件回撥
    u->write_event_handler = ngx_http_upstream_send_request_handler;
    u->read_event_handler = ngx_http_upstream_process_header;
}
        因此,如果nginx傳送完部分請求資料給後端伺服器時,如果接受到了來自後端服務的響應,則讀事件的回撥ngx_http_upstream_handler會被觸發,進而呼叫負載均衡模組的讀事件回撥:ngx_http_upstream_process_header, 開始接http響應頭部。當然,如果一次不能接收完來自後端伺服器的所有響應頭部,則讀事件會再次被觸發,繼續重複接收這個過程。
//事件模組的讀寫回調,事件被觸發時會呼叫負載均衡模組對應的讀寫回調
static void ngx_http_upstream_handler(ngx_event_t *ev)
{
    if (ev->write) 
	{
		//向後端伺服器傳送資料
        u->write_event_handler(r, u);
    }
	else 
	{
		//接收後端伺服器的響應,此時回撥為ngx_http_upstream_process_header
        u->read_event_handler(r, u);
    }
}
二、接收響應頭部分析

        1、接收快取區的開闢

        nginx要接收來自後端伺服器的響應頭部,肯定要有一個空間來存放這些響應頭部。 這個緩衝區就是ngx_http_upstream_t結構中的buffer成員。這是一個固定的空間,即用來接收來自後端伺服器的http響應頭部,也接收來自後端伺服器的響應包體。 當然,響應頭部存放到最前面,響應包體緊跟響應頭部存放。以此同時也初始化u->headers_in連結串列,這個連結串列是用於存放解析buffer中的所有響應頭部的結果。因為buffer中存放的是直接來自後端伺服器的http響應頭部,這是一種factcgi格式的報文,解析fastcgi報文後,將得到的響應頭部儲存到連結串列中。

//nginx接收來自上游伺服器的響應頭部
static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
	//如果沒有開闢接收來自上游伺服器響應包頭緩衝區,則開闢
    if (u->buffer.start == NULL)
	{
        u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size);
        u->buffer.pos = u->buffer.start;
        u->buffer.last = u->buffer.start;
        u->buffer.end = u->buffer.start + u->conf->buffer_size;
        u->buffer.temporary = 1;

		//初始化連結串列,這個連結串列是存放解析buffer後的響應頭部
        ngx_list_init(&u->headers_in.headers, r->pool, 8, sizeof(ngx_table_elt_t));
    }
}
        2、接收響應頭部

        緩衝區空間開闢後,接下來就是迴圈接收來自後端伺服器的響應頭部。為什麼要迴圈接收呢? 因為一次操作有可能不能夠從核心緩衝區中接收到所有的響應頭部到應用層緩衝區,因此需要迴圈接收。當然了,如果核心緩衝區中還沒有接收到所有來自後端伺服器的響應頭部,那迴圈接收也沒有用,此時需要把讀事件重新註冊到epoll紅黑樹中,以便下次讀事件觸發時,可以繼續接收響應頭部。

//nginx接收來自上游伺服器的響應頭部
static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
	//迴圈接收來自後端伺服器的響應頭部,並使用狀態機解析
    for ( ;; )
	{
        n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last);

		//表示還需要再次接收來自上游伺服器的響應,重新註冊讀事件到epoll中
        if (n == NGX_AGAIN) 
		{
            ngx_handle_read_event(c->read, 0);
            return;
        }

		//更新接收緩衝區
        u->buffer.last += n;

		//呼叫http模組的方法,解析響應頭部
		//fastcgi為: ngx_http_fastcgi_process_header
        rc = u->process_header(r);

		//表示包頭還沒有完全接收到
        if (rc == NGX_AGAIN) 
		{
            continue;
        }
    }
}
        3、狀態機解析響應頭部
        對於接收到的來自後端伺服器的響應頭部,儲存到了u->buffer中。這是一個fastcgi格式的響應報文,需要從fastcgi格式的報文中提取出http響應頭部,因此需要經過狀態機處理,從而解析出http響應頭部。還記得nginx傳送請求資料給後端伺服器的過程嗎? 請求資料是一種fastcgi的報文格式,同樣的nginx接收後端服務的響應也應該是一種fastcgi報文格式。

        看下nginx接收到的fastcgi響應報文格式;


        從圖中可以看出,每一個http響應包頭前面都加上了fastcgi頭部,而每一個http響應包頭都有可能由多條http響應頭部組成。我們暫且稱每一個fastcgi頭部 + http響應包頭為一個組,那怎麼解析這些fastcgi格式的響應報文呢? nginx使用掃描法,掃描每一組。每一個組使用兩個狀態機,一個狀態機解析這個組內的fastcgi頭部, 另一個狀態機解析這個組內的http響應包頭,從而獲取到每一個http響應頭部。

//解析來自後端伺服器發來的響應頭部,從fastcgi格式轉為nginx格式。
//採用兩個狀態機,一個解析fastcgi頭部,一個解析fastcgi響應包頭
static ngx_int_t ngx_http_fastcgi_process_header(ngx_http_request_t *r)
{
	//迴圈解析每一個組(fastcgi頭部 + 多個http響應頭部組成的資料)。因為每次從核心接收到的資料有可能包含多個組
	for ( ;; ) 
	{
		//解析fastcgi的頭部
        if (f->state < ngx_http_fastcgi_st_data)
		{
            f->pos = u->buffer.pos;
            f->last = u->buffer.last;
			
			//使用狀態解析fastcgi的頭部
            rc = ngx_http_fastcgi_process_record(r, f);
            u->buffer.pos = f->pos;
            u->buffer.last = f->last;
        }

		//這個迴圈用於解析這個組內的每一個http響應頭部。因此該組內的http響應包頭可能由多個http響應頭部組成。
		//而ngx_http_parse_header_line狀態機每次只能解析一個http響應頭部,因此需要迴圈解析
        for ( ;; )
		{
			//使用狀態機解析來自後端伺服器發來的http響應頭部
            rc = ngx_http_parse_header_line(r, &u->buffer, 1);
            if (rc == NGX_OK) 
			{
				//解析某個http響應頭部成功後,儲存到陣列連結串列中
                /* a header line has been parsed successfully */
                h = ngx_list_push(&u->headers_in.headers);

				//儲存http響應頭部
				ngx_memcpy(h->key.data, r->header_name_start, h->key.len);
				//儲存http響應頭部的值
				ngx_memcpy(h->value.data, r->header_start, h->value.len);
                h->hash = r->header_hash;

				//如果是負責均衡模組支援的頭部,則把常用頭部的指標指向陣列連結串列中相應位置
                hh = ngx_hash_find(&umcf->headers_in_hash, h->hash,
                                   h->lowcase_key, h->key.len);
                hh->handler(r, h, hh->offset);
            }

			//解析完成所有的響應頭部,則儲存響應狀態碼
            if (rc == NGX_HTTP_PARSE_HEADER_DONE) 
			{
                if (u->headers_in.status) 
				{
					//後端伺服器返回了狀態碼,則優先使用後端服務的響應狀態碼
                } 
				else if (u->headers_in.location) 
				{
					//後端伺服器返回了location,則需要給客戶端返回302重定向
                }
				else 
				{
					//否則nginx構造200 0k返回給客戶端
                    u->headers_in.status_n = 200;
                    ngx_str_set(&u->headers_in.status_line, "200 OK");
                }
            }
        }
    }
}
        ngx_http_fastcgi_process_header這個解析函式, 即便拋開一些細枝末節,還得到了這麼長的程式碼。上面的程式碼整體上就是解析的框架流程,註釋也很清楚了,結合上面的這張圖,仔細分析應該都能看得懂。需要注意的是,當解析成功某個http響應頭部時,會臨時把這個響應頭部儲存到u->headers_in連結串列中。這個連結串列只是一個過渡,最終還是需要拷貝到ngx_http_request_s這個http請求中的headers_out成員中,因此最終是把headers_out連結串列中的資料傳送給客戶端瀏覽器。為什麼要多此一舉呢? 直接把解析後的http響應頭部儲存到ngx_http_request_s這個http請求中的headers_out成員連結串列中不就可以了,省得在做一次拷貝操作。我想還是分層思想的作用吧! ngx_http_request_s這個http請求中的headers_out成員連結串列是nginx與客戶端瀏覽器直接維護的資料結構, 而u->headers_in連結串列則是nginx與後端伺服器之間維護的資料結構 ,兩者是不同物件之間的互動,寧願多花一點記憶體資源,分開對待才不會耦合在一起。

        如果nginx接收到了所有的http響應頭部,則nginx構造響應碼,用於給客戶端傳送響應狀態,例如200 ok等。後端伺服器是不會返回http 200 ok這樣的響應行的,需要nginx自己構造給客戶端的響應碼。

        另外需要注意的時,使用狀態機進行解析時,如果資料還沒接受完整,則一次解析是解析不完的,需要反覆多次進入狀態機進行解析。 此時fastcgi的上下文結構ngx_http_fastcgi_ctx_t會記錄當前解析到緩衝區中的哪個位置,以及解析到了哪種狀態。下次解析時,可以從該緩衝區對應位置開始解析上一次的狀態。

//使用狀態機解析收到後端服務的fastcgi格式的響應頭部。解析後的內容存放到f的相應欄位
//
static ngx_int_t ngx_http_fastcgi_process_record(ngx_http_request_t *r,
    																ngx_http_fastcgi_ctx_t *f)
{
	//遍歷緩衝區中的每一個位元組,使用狀態機進行解析。
    for (p = f->pos; p < f->last; p++) 
	{
		switch (state) 
		{
	        case ngx_http_fastcgi_st_version:			//解析版本
				break
			case ngx_http_fastcgi_st_type:				//解析型別
				break
			..........
			case ngx_http_fastcgi_st_data:				//解析包體資料
				break
		}
	}
	
	//緩衝區中的內容還不夠組成一個fastcgi的頭部大小,則記錄當前解析的狀態。下一次從這個狀態開始繼續解析
    f->state = state;
}
        使用狀態機解析fastcgi頭部很簡單,解析後的內容儲存到了fastcgi的上下文結構ngx_http_fastcgi_ctx_t相應成員中,例如記錄響應包頭的長度等資訊。fastcgi協議內容就不在本篇文章範疇了,讀者不是很清楚的話可以找相應的資源去了解fastcgi協議。

        另一個狀態機機ngx_http_parse_header_line,用來解析每一個http響應頭部。函式內容也比較長,這裡也就不在貼程式碼了,這裡提下內部實現過程。函式內部迴圈的遍歷每一個字元,從而提取出鍵值對key,value,並把key,value儲存到http結構ngx_http_request_s中的4個成員中;

//http請求結構
struct ngx_http_request_s 
{
    u_char   * header_name_start; //http頭部名,例如:content-length:40,則為content-length
    u_char   * header_name_end;	  //頭部名的結束位置
	
    u_char   * header_start;	  //http頭部值開始位置,例如:content-length:40,則為40
    u_char   * header_end;		  //頭部值的結束位置
}
        在nginx接收到來自客戶端的請求頭部這一篇文章中也使用到這個狀態機進行解析http請求頭部, 讀者也可以參考這篇文章進行分析。 雖然解析過程比較長,但很容易看懂的,粗劣看下這個程式碼, 細節的程式碼還是留給讀者自己分析。
//使用狀態機獲取http請求頭部中的每一個鍵值對,underscores_in_headers表示key是否支援下劃線
//該函式一次只解析一個http請求頭部,因此需要反覆呼叫
ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t allow_underscores)
{
	//變數每一個字元進行接
	for (p = b->pos; p < b->last; p++) 
	{
	
	}
}
三、將解析後的http響應頭部儲存到http請求結構中的響應頭部連結串列

        使用狀態機解析完http響應頭部後,儲存到了u->headers_in連結串列中。這個連結串列只是一個過渡,最終還是需要拷貝到ngx_http_request_s這個http請求中的headers_out成員中。這個headers_out連結串列中的資料才是最終要發給客戶端的響應頭部。回到ngx_http_upstream_process_header函式進行分析, 在解析完所有的來自後端服務的響應頭部後,會呼叫ngx_http_upstream_process_headers函式將響應頭部從u->headers_in連結串列拷貝到請求結構的headers_out連結串列。

//將已經解析後的來自上游伺服器的響應頭部設定到headers_out成員中
static ngx_int_t ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
	//遍歷來自後端伺服器響應的http頭部
    for (i = 0; /* void */; i++) 
	{
		//該http響應頭部存在不需要發給客戶端的響應頭部雜湊表中,則跳過
        if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, h[i].lowcase_key, h[i].key.len))
        {
            continue;
        }
		//該http響應頭部也是負載均衡模組支援的響應頭部,則使用負載均衡模組這個頭部對應的拷貝回撥
        hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash, h[i].lowcase_key, h[i].key.len);
        if (hh)
		{
            if (hh->copy_handler(r, &h[i], hh->conf) != NGX_OK) 
			{
                return NGX_DONE;
            }
            continue;
        }
		//直接儲存到http請求結構中的響應頭部連結串列
        ngx_http_upstream_copy_header_line(r, &h[i], 0);
    }
}

四、傳送http響應頭部

        不管是上游網速優先還是下游網速優先, 都需要把http響應頭部發發給客戶端瀏覽器,這是一個公共的操作。傳送http響應包體前,必須先發送http響應頭部。傳送http響應頭部其實沒有什麼特殊的地方,直接呼叫過濾器模組就可以把http響應頭部發送給客戶端瀏覽器了。過濾器模組在前面的文章已經分析過了,這裡就不在重複敘述了。那什麼時候nginx會觸發http響應頭部給客戶端呢? 答案是在nginx在接收到了全部的來自後端伺服器的響應頭部後,才會觸發向客戶端瀏覽器傳送http響應頭部這個操作。如果nginx只接收到了部分的來自後端伺服器的響應頭部,是不會觸發這個邏輯的。

//nginx接收來自上游伺服器的響應頭部
static void ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
	//subrequest_in_memory值為0,表示需要轉發來自上游伺服器的響應到客戶端
    if (!r->subrequest_in_memory)
	{
        ngx_http_upstream_send_response(r, u);
        return;
    }
}

static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
	//呼叫過濾器模組傳送響應頭部給客戶端, 此時一定是接收完全部來自後端伺服器的響應頭部
    ngx_http_send_header(r);
}
        到此為止,nginx如何接收來自後端伺服器的響應頭部, 以及把響應頭部發給下游客戶端已經分析完成了,下一篇文章將分析nginx如何接收來自後端伺服器的響應包體。

相關推薦

nginx處理post請求(http響應頭部收發)

        上一篇文章分析了nginx如何傳送來自客戶端的請求資料到後端伺服器, 本篇文章開始將分析nginx如何接收來自後端伺服器的響應。nginx接收來自後端伺服器的響應分為兩個過程,一個是接收來自後端伺服器的http響應頭部, 另一個是接收來自後端伺服器的響應包體

nginx處理post請求之資料轉發

        上一篇文章分析了nginx在處理post請求時,如何啟動upstream這個負載均衡模組。它是一個http框架,由它來排程具體的http模組,例如fastcgi, proxyd反向代理等,這些模組負責將來自客戶端 的請求包頭,請求包體轉為與後端伺服器通訊的格

multer處理post請求的代碼演示

multer app col -- RR expr use con send let express = require(‘express‘); let multer = require(‘multer‘); let mObj = multer({dest:__di

HttpClient中post請求http、https示例

HttpClient中post請求http、https示例 【轉自https://www.cnblogs.com/Mr-Rocker/p/6229652.html】 點選加入QQ群 【】HttpClient 是 Apache Jakarta Common 下的子專案,可以

java後臺發起上傳檔案的post請求(http和https)

分享一下我的偶像大神的人工智慧教程!http://blog.csdn.net/jiangjunshow 也歡迎轉載我的文章,轉載請註明出處 https://blog.csdn.net/aabbyyz 一、http post 對於檔案上傳,客戶端通常就是頁

Lua獲取NginxPost請求資料並寫入Redis

1.環境安裝 1.lnmp.conf 設定 Enable_Nginx_Lua='y'。然後按通常情況安裝。 2.lnmp ./addons.sh安裝redis,如果連線遠端redis伺服器不用裝。 3.安裝lua ubuntu安裝lua apt-get install lua

java後臺發起上傳檔案的post請求 http和https

一、http post 對於檔案上傳,客戶端通常就是頁面,在頁面裡實現上傳檔案不是什麼難事,寫個form,加上enctype = "multipart/form-data",在寫個接收的就可以了,沒什麼難的。如: <!DOCTYPE 

【Java工具七】java使用HttpClient的post請求http、https示例

package com.xxx.utils; import com.google.gson.Gson; import org.apache.commons.codec.binary.Base64; import org.apache.commons.httpclien

nodejs伺服器——post請求響應

資料夾結構: 資料庫結構: html結構: <form action="/login.do" method="post"> 使用者名稱:<input name="userName" type="text"> 密 碼:<i

使用express搭建了框架後,用multer處理post請求傳的檔案或圖片注意的問題

node中處理post請求,只有body-parser是不夠的,當上傳的是圖片或者是檔案時,就要用multer來處理 注意的問題: 1.如果在app.js中引入multer,如法根據請求的路由分別處理,所以要在路由配置中,根據需要使用multer 首先引入

Nginxpost請求的優化

在nginx配置檔案中設定: client_body_buffer_size 256k(足夠大); client_body_in_single_buffer on; 這樣才能在r->request_body->bufs->buf裡存放完整的request_body,要不然,就依賴資

nginx檢視post請求日誌

在http段加上 log_format access '$remote_addr - $remote_user [$time_local] "$request" $status $body_byte

koa2利用bodyparser中介軟體處理post請求(五)

上篇的原生寫法不利用開發,我們使用已經有的外掛koa-bodyparser來實現接收並解析post請求1》安裝中介軟體使用npm進行安裝,需要注意的是我們這裡要用–save,因為它在生產環境中需要使用。 npm install --save [email prote

get請求post請求響應報文

GET /07_WEB_HTTP/index.html HTTP/1.1 Accept: application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg

post 請求400,頭部資訊不對

專案ajax 請求需要頭部資訊為    headers:{'Content-Type': 'application/json'}  ,傳入的是 json ,然後請求的時候一直報400 錯誤,原因是後臺要要接收的是字串形式,把傳入的資料轉換一下 JSON.stringify(d

nginx 設定post請求

由於想在nginx日誌上看到post請求的資訊,做了如下的設定: 首先使用python模擬post請求,發現返回405錯誤,通過查資料,找到通過如下配置可以解決此問題 error_page   405 =200 $uri; 在nginx上新增後重啟,再次請求,可以放回資料,

Apache、IIS、Nginx等絕大多數web服務器,都不允許靜態文件響應POST請求,否則會返回“HTTP/1.1 405 Method not allowed”錯誤。

.com rewrite requested gin pos 2.0 $2 127.0.0.1 page   例1:用Linux下的curl命令發送POST請求給Apache服務器上的HTML靜態頁 [root@new-host ~]# curl -d 1=1 http:/

Apache、IIS、Nginx等絕大多數web服務器,都不允許靜態文件響應POST請求

ebr ons auth red stat getmethod eas scope decode 最近調用一個接口,發現httppost請求目標網站會出現405 狀態碼,原因為 Apache、IIS、Nginx等絕大多數web服務器,都不允許靜態文件響應POST請求 所以

深入Nginx之《HTTP請求報文與HTTP響應報文》

分組 value align 後端服務 請求超時 odin cep 問題 ati HTTP請求報文 這個很有必要了解,好歹我們得知道Nginx在提供HTTP服務時,客戶端都會傳些什麽。HTTP請求中客戶端傳送的內容稱為HTTP請求報文。 1、請求行包含: 請

spring boot 常見http get ,post請求引數處理

 在定義一個Rest介面時通常會利用GET、POST、PUT、DELETE來實現資料的增刪改查;這幾種方式有的需要傳遞引數,後臺開發人員必須對接收到的引數進行引數驗證來確保程式的健壯性 GET 一般用於查詢資料,採用明文進行傳輸,一般用來獲取一些無關使用者資訊的資料 POST