Lighttpd啟動時完成了一系列初始化操作后,就進入了一個包含11個狀態的有限狀態機中。
每個連接都是一個connection實例(con),狀態的切換取決于con->state。
lighttpd經過初步處理后將con的基本信息初始化,而插件對事件的處理就是針對con進行的,它拿到con后按照業務需要進行相應處理,然后再交還給lighttpd,lighttpd根據con中的信息完成響應。
狀態定義如下:
~~~
typedef enum
{
CON_STATE_CONNECT, //connect 連接開始
CON_STATE_REQUEST_START, //reqstart 開始讀取請求
CON_STATE_READ, //read 讀取并解析請求
CON_STATE_REQUEST_END, //reqend 讀取請求結束
CON_STATE_READ_POST, //readpost 讀取post數據
CON_STATE_HANDLE_REQUEST, //handelreq 處理請求
CON_STATE_RESPONSE_START, //respstart 開始回復
CON_STATE_WRITE, //write 回復寫數據
CON_STATE_RESPONSE_END, //respend 回復結束
CON_STATE_ERROR, //error 出錯
CON_STATE_CLOSE //close 連接關閉
} connection_state_t;
~~~
下面就是lighttpd的狀態機:

在每個連接中都會保存這樣一個狀態機,用以表示當前連接的狀態。
在連接建立以后,在connections.c/connection_accpet()函數中,lighttpd調用connection_set_state()函數,將新建立的連接的狀態設置為CON_STATE_REQUEST_START。在這個狀態中,lighttpd記錄連接建立的時間等信息。
整個狀態機的核心函數是connections.c/ connection_state_machine()函數。
函數的主體部分刪減之后如下:
~~~
int connection_state_machine(server * srv, connection * con)
{
int done = 0, r;
while (done == 0)
{
size_t ostate = con -> state;
int b;
//根據當前狀態機的狀態進行相應的處理和狀態轉換。
switch (con->state)
{
case CON_STATE_REQUEST_START: /* transient */
//do something
case CON_STATE_REQUEST_END: /* transient */
//do something
case CON_STATE_HANDLE_REQUEST:
//do something
case CON_STATE_RESPONSE_START:
//do something
case CON_STATE_RESPONSE_END: /* transient */
//do something
case CON_STATE_CONNECT:
//do something
case CON_STATE_CLOSE:
//do something
case CON_STATE_READ_POST:
//do something
case CON_STATE_READ:
//do something
case CON_STATE_WRITE:
//do something
case CON_STATE_ERROR: /* transient */
//do something
default:
//do something
break;
}//end of switch(con -> state) ...
if (done == -1)
{
done = 0;
}
else if (ostate == con->state)
{
done = 1;
}
}
/* something else */
/* 將fd加入到fdevent系統中,等待IO事件。
* 當有數據可讀的時候,在main函數中,lighttpd調用這個fd對應的handle函數,
* 這里就是connection_handle_fdevent()函數。
* 這個函數一開始將連接加入到了joblist(作業隊列)中。
*/
switch (con->state)
{
case CON_STATE_READ_POST:
case CON_STATE_READ:
case CON_STATE_CLOSE:
fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);
break;
case CON_STATE_WRITE:
/* request write-fdevent only if we really need it
* - if we have data to write
* - if the socket is not writable yet
*/
if (!chunkqueue_is_empty(con->write_queue) &&
(con->is_writable == 0)&& (con->traffic_limit_reached == 0))
{
fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
}
else
{
fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
}
break;
default:
fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
break;
}
return 0;
}
~~~
這個函數首先根據當前的狀態進入對應的switch分支執行相應的動作,然后根據情況進入下一個狀態。
跳出switch語句之后,如果連接的狀態沒有改變,說明連接讀寫數據還沒有結束,但是需要等待IO事件,這時跳出循環,等待IO事件。
如果在處理的過程中不需要等待IO事件,那么在while循環中,連接將被處理完畢并關閉。
在我們的main函數中,之前討論過,在一個while循環中,處理超時,處理IO時間,之后有下面這段代碼:
~~~
for (ndx = 0; ndx < srv->joblist->used; ndx++) {
connection *con = srv->joblist->ptr[ndx];
handler_t r;
connection_state_machine(srv, con);
switch(r = plugins_call_handle_joblist(srv, con)) {
case HANDLER_FINISHED:
case HANDLER_GO_ON:
break;
default:
log_error_write(srv, __FILE__, __LINE__, "d", r);
break;
}
con->in_joblist = 0;
}
~~~
這段代碼對joblist中的所有連接依次調用connection_state_machine()函數進行處理。
下面說明下各狀態的主要內容:
~~~
CON_STATE_CONNECT
清除待讀取隊列中的數據-chunkqueue_reset(con->read_queue);
置con->request_count = 0。(本次連接還未處理過請求)
CON_STATE_REQUEST_START /*transient */
記錄事件起始時間;
con->request_count++(一次長連接最多可以處理的請求數量是有限制的);
轉移到CON_STATE_READ狀態。
CON_STATE_READ和CON_STATE_READ_POST
connection_handle_read_state(srv,con);
CON_STATE_REQUEST_END /*transient */
http_request_parse(srv, con);
解析請求,若是POST請求則轉移到CON_STATE_READ_POST狀態,
否則轉移到CON_STATE_HANDLE_REQUEST狀態。
CON_STATE_HANDLE_REQUEST
http_response_prepare(srv, con);
函數中調用
handle_uri_raw;
handle_uri_clean;
handle_docroot;
handle_physical;
handle_subrequest_start;
handle_subrequest。
如果函數返回了HANDLER_FINISHED,且con->mode!=DIRECT(事件已經被我們的業務插件接管),
則直接進入CON_STATE_RESPONSE_START。
否則lighttpd會做一些處理后再進入CON_STATE_RESPONSE_START狀態。
如果函數返回了HANDLER_WAIT_FOR_FD或
HANDLER_WAIT_FOR_EVENT,
狀態依舊會停留在CON_STATE_HANDLE_REQUEST,等待事件或數據。
如果函數返回了HANDLER_ERROR,進入到CON_STATE_ERROR狀態。
CON_STATE_RESPONSE_START
connection_handle_write_prepare(srv,con);
CON_STATE_WRITE
connection_handle_write(srv,con);
CON_STATE_RESPONSE_END
調用插件的handle_request_done接口。
如果是長連接,重新回到CON_STATE_REQUEST_START;否則調用插件的handle_connection_close接口。
執行connection_close(srv, con);和connection_reset(srv, con);將連接關閉。
CON_STATE_ERROR /* transient */
調用插件handle_request_done;
調用插件handle_connection_close;
執行connection_close將連接關閉。
CON_STATE_CLOSE
connection_close(srv, con);將連接關閉。
~~~
以上是狀態機的概況。
總覽了狀態機,我們知道狀態機會針對相應的階段對事件進行處理,那么狀態機是如何處理這些事件的?
事實上,對于事件的處理,一部分是由lighttpd完成的,而一部分是由插件完成的。插件中那些負責事件處理的接口分布在某幾個狀態中。我們只需在插件的各個階段完成指定工作并返回相應的返回值,就可以促使狀態機完成狀態切換,完成事件的整套處理流程,并最終由lighttpd完成事件的響應。
在插件中,我們可以編寫代碼來注冊lighttpd提供的回調接口,lighttpd在初始化階段、狀態機執行階段、退出階段會分別調用這些回調函數,完成插件的實例化,初始化,連接重置,事件處理,插件釋放等功能。
要了解lighttpd對插件的調用方式,需要明白一個概念:事件接管。
對于每個事件,都有一個mode字段(con->mode)。該字段的定義:
~~~
typedef enum { DIRECT, EXTERNAL } connection_type;
~~~
連接對象有一個字段mode用來標識該連接是最初由服務器accept產生的客戶端連接還是插件產生的其他輔助連接,當mode=DIRECT時表示對應連接由lighttpd服務器accept產生,mode!=DIRECT時表示對應連接是由插件產生的。
事件(con)初始化時mode是DIRECT;connection_reset(srv,con);
lighttpd在大部分流程中會在入口檢查到mode != DIRECT時直接返回GO_ON。即:此事件由用戶插件接管,lighttpd不參與。
用戶編寫的插件應通過將mode置為插件自身的ID達到接管的作用。插件ID是在插件加載時由插件的加載順序確定的,是插件的唯一標識。
用戶編寫插件在每個接口的一開始應該判斷mode是否等于自身的ID,若相等才能繼續執行,否則直接退出,返回GO_ON。
了解了以上概念之后,我們就可以理解lighttpd對插件的調用方式了:
在lighttpd需要調用插件某一個階段的接口函數時,會對所有插件注冊在該處的接口順序調用,順序與插件加載順序相同。例如:調用uri_raw接口,會先調用A插件的mod_A_uri_raw,然后調用B插件的mod_B_uri_raw,直到將所有已加載插件這個位置的接口全部調用完成。但實際處理這次事件通常只有一個插件,即插件ID與mode相同的那個插件。
因此,假設在CON_STATE_HANDLE_REQUEST狀態,lighttpd調用了插件的handle_uri_raw接口,但是我們有多個插件,每個插件都注冊了handle_uri_raw這個接口,lighttpd也能辨別出要使用哪個插件。
如果插件在處理事件的過程中,想讓lighttpd接管,還需要把mode置為DIRECT才行。
以上是lighttpd狀態機和插件的總覽概況。