lighttpd請求處理的過程:
1.服務器與客戶端建立連接后,連接進入CON_STATE_REQUEST_START狀態,服務器做一些標記,如連接開始的時間等。
2.連接進入CON_STATE_READ狀態,服務器從連接讀取HTTP頭并存放在con->requeset.request中。若一次調用沒能讀取全部數據,連接的狀態將繼續為READ,繼續等待剩下的數據可讀。
在對joblist進行處理的時候,依然會調用connecion_handle_read_state函數進行處理,函數中通過con->is_readable來判斷是否有數據可讀,如果沒有,則只是處理一下以前已經讀取的數據。
3.數據讀取完之后,連接進入CON_STATE_REQUEST_END狀態。
4.在REQUEST_END階段,調用http_request_parse函數解析request請求。
函數首先解析Request line,解析出來的結果存放在con->request.http_method, con->request.http_version和con->request.uri中(前兩個變量都是枚舉類型,后一個是個buffer)。
解析完request line后,開始分析header lines。找到一個header field name后,就和所有已經定義的field name比較,看看是哪個。確定之后,就將field name和value保存到con->request.headers中。request.headers是一個array類型變量,存放的是data_string類型數據。其中,data_string的key是filed name,value就是field的成員。
5.解析完之后判斷此次連接是否有POST數據,有則讀取POST數據,否則進入HANDLE_REQUEST狀態。
6.如果有POST數據要讀,連接進入READ_POST狀態。
READ_POST狀態的處理和READ狀態類似。
在connection_state_mechine函數中,這兩個switch分支一樣。在connection_handle_read_state函數中,前半部分讀取數據是一樣的,后面處理數據時才分開。
對于POST數據,由于數據可能很大,這時候可能會用到臨時文件來存儲。在程序中,作者對于小于64k的數據,直接存儲在buffer中,大于64k則存儲在臨時文件中。在向臨時文件寫數據時,每個臨時文件只寫1M的數據。數據大于1M就再打開一個臨時文件。
POST數據保存在con->requeset_content_queue,這是一個chunkqueue類型的成員變量,它是chunk結構體的鏈表:
~~~
typedef struct
{
chunk *first;
chunk *last;
/**
* 這個unused的chunk是一個棧的形式。
* 這個指針指向棧頂,而chunk中的next指針則將棧連 接起來。
*/
chunk *unused;
size_t unused_chunks;
array *tempdirs;
off_t bytes_in, bytes_out;
} chunkqueue;
~~~
unused成員是棧形式的鏈表,unused指向棧頂。它用來存儲不用的chunk結構體,如果需要chunk,則先從這個棧中查找有無空閑的。如果chunk不使用了,可以加到棧頂。這樣可以減少內存分配的時間,提高程序的效率。unused_chunks標記棧中有多少數據。
chunk的定義:
~~~
typedef struct chunk
{
enum { UNUSED_CHUNK, MEM_CHUNK, FILE_CHUNK } type;
/* 內存中的存儲塊或預讀緩存 */
buffer *mem;
/* either the storage of the mem-chunk or the read-ahead buffer */
struct
{
/*
* filechunk 文件塊
*/
buffer *name;/* name of the file */
off_t start;/* starting offset in the file */
off_t length;/* octets to send from the starting offset */
int fd;
struct
{
char *start;/* the start pointer of the mmap'ed area */
size_t length;/* size of the mmap'ed area */
off_t offset; /* start is <n> octet away from the start of the file */
} mmap;
int is_temp;/* file is temporary and will be
deleted if on cleanup */
} file;
off_t offset;/* octets sent from this chunk the size of the
* chunk is either -mem-chunk: mem->used - 1 file-chunk: file.length */
struct chunk *next;
} chunk;
~~~
chunk用來表述一塊存儲空間。這個存儲空間可能在內存中,也可能在文件中。
type成員標記這個塊是內存的還是文件的。
mem成員指向內存中的存儲空間(實際上是一個buffer)。
file結構體則表示在文件中的存儲空間(程序首先使用mmap函數將文件映射到內存中,mmap結構體的start成員保存映射到內存中的地址,于是對于文件的操作就可以像內存一樣)。
7.讀取完數據之后,進入HANDLE_REQUEST狀態,此時請求已經解析完畢,本狀態需要決定如何處理請求。
該狀態調用http_response_prepare函數,然后根據函數的返回值進行相應的處理(http_response_prepare函數定義在response.c文件中,函數中調用了很多plugins_call_handle_xxxx函數,插件系統的接口函數主要是在這個函數中調用,這個函數也是服務器和插件系統交互的地方)。
在http_response_prepare函數中,通過對url的解析,逐步的調用插件來處理。對url解析的結果存放在con->uri中:
~~~
typedef struct
{
buffer *scheme; //http , https and so on
buffer *authority; //user:password
buffer *path; //www.xxx.com/xxx/xxxx.html
buffer *path_raw; //www.xxx.com
buffer *query; //key1=data1&key2=data2
} request_uri;
~~~
uri的定義為:(scheme)://(authority)(path)?(query)#fragment。
如:
[http://user:passwd@www.google.com/pages/index.html?key1=data1&key2=data2#frag](http://user:passwd@www.google.com/pages/index.html?key1=data1&key2=data2#frag)
解析之后:
~~~
scheme = http
authority = user:passwd
path = www.google.com/pages/index.html
path_raw = 未進行解碼的path
query = key1=data1&key2=date2
~~~
注意,在瀏覽器向服務器發送url請求的時候,會對其中的保留字符和不安全字符進行編碼(參見RFC2396),比如漢字。編碼的形式是% HEX HEX,即一個%加兩個十六進制數。服務器在接到請求之后,要對這些編碼過的字符進行解碼。path_raw中保存的是還沒解碼的url,path保存的是已解碼的url。
對fragment服務器直接拋棄,因為fragment是瀏覽器使用的。
當解析出url中的path之后,服務器調用插件的plugins_call_handle_uri_raw函數,插件根據未解碼的url path進行處理。
如果沒有插件進行處理,服務器調用插件的plugins_call_handle_uri_clean函數,它根據解碼過的url path進行處理。
在這里,服務器根據解析出來的url地址直接將請求轉發給插件,而不需要自己對請求進行處理。
當請求仍然沒有被處理時,說明這個請求必須要在這被處理。服務器調用插件的plugins_call_handle_docroot函數對處理請求時的根目錄進行設置。對于不同種類的資源,可以設置不同的根目錄,提供一個虛擬服務器。接著,服務器根據根目錄和請求的url地址,拼接出資源在本機上對應的物理地址。比如,doc root = /abc/root, url path = /doc/index.html,得到的物理地址就是/abc/root/doc/index.html。然后服務器調用插件的plugins_call_handle_physical函數,根據得到的物理地址進行相應的處理。最后,服務器調用插件的plugins_call_handle_subrequest_start函數和plugins_call_handle_subrequest函數進行最后的處理。
8.連接進入CON_STATE_RESPONSE_START狀態,服務器準備給客戶端的response,包括準備response頭和寫數據。
參考:
[http://www.cnblogs.com/kernel_hcy/archive/2010/04/07/1706587.html](http://www.cnblogs.com/kernel_hcy/archive/2010/04/07/1706587.html)