原理
lighttpd的事件处理是由一个状态机完成的。
- connect
waiting for a connection - reqstart
init the read-idle timer - read
read http-request-header from network - reqend
parse request - readpost
read http-request-content from network - handlereq
handle the request internally (might result in sub-requests) - respstart
prepare response header - write
write response-header + content to network - respend
cleanup environment, log request - error
reset connection (incl. close()) - close
close connection (handle lingering close)
其中的绿线代表的是一个简单的GET请求的状态变化线路;而灰线表示的是POST请求。图中的状态并不都是稳定的状态,有的是个瞬时状态;也就是说一个外部事件会触发若干状态的转换。
数据处理流程
当客户端发起一个请求时,lighttpd为它建立一个新的连接,同时置该连接的状态为CON_STATE_REQUEST_START。
connection *connection_accept(server *srv, server_socket *srv_socket) {
/* accept everything */
/* search an empty place */
int cnt;
sock_addr cnt_addr;
socklen_t cnt_len;
/* accept it and register the fd */
/**
* check if we can still open a new connections
*
* see #1216
*/
if (srv->conns->used >= srv->max_conns) {
return NULL;
}
cnt_len = sizeof(cnt_addr);
if (-1 == (cnt = accept(srv_socket->fd, (struct sockaddr *) &cnt_addr, &cnt_len))) {
switch (errno) {
case EAGAIN:
#if EWOULDBLOCK != EAGAIN
case EWOULDBLOCK:
#endif
case EINTR:
/* we were stopped _before_ we had a connection */
case ECONNABORTED: /* this is a FreeBSD thingy */
/* we were stopped _after_ we had a connection */
break;
case EMFILE:
/* out of fds */
break;
default:
log_error_write(srv, __FILE__, __LINE__, "ssd", "accept failed:", strerror(errno), errno);
}
return NULL;
} else {
connection *con;
srv->cur_fds++;
/* ok, we have the connection, register it */
#if 0
log_error_write(srv, __FILE__, __LINE__, "sd",
"appected()", cnt);
#endif
srv->con_opened++;
con = connections_get_new_connection(srv);
con->fd = cnt;
con->fde_ndx = -1;
#if 0
gettimeofday(&(con->start_tv), NULL);
#endif
fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con);
connection_set_state(srv, con, CON_STATE_REQUEST_START);
con->connection_start = srv->cur_ts;
con->dst_addr = cnt_addr;
buffer_copy_string(con->dst_addr_buf, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
con->srv_socket = srv_socket;
if (-1 == (fdevent_fcntl_set(srv->ev, con->fd))) {
log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
return NULL;
}
#ifdef USE_OPENSSL
/* connect FD to SSL */
if (srv_socket->is_ssl) {
if (NULL == (con->ssl = SSL_new(srv_socket->ssl_ctx))) {
log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
SSL_set_accept_state(con->ssl);
con->conf.is_ssl=1;
if (1 != (SSL_set_fd(con->ssl, cnt))) {
log_error_write(srv, __FILE__, __LINE__, "ss", "SSL:",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
}
#endif
return con;
}
}
connection_accept新建一个连接后流程回到函数network_server_handle_fdevent(network.c文件)
for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) {
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_STATE_REQUEST_START
状态reqstart
- case CON_STATE_REQUEST_START: /* transient */
if (srv->srvconf.log_state_handling) {
log_error_write(srv, __FILE__, __LINE__, "sds",
"state for fd", con->fd, connection_get_state(con->state));
} - con->request_start = srv->cur_ts;
con->read_idle_ts = srv->cur_ts; - con->request_count++;
con->loops_per_request = 0; - connection_set_state(srv, con, CON_STATE_READ);
- /* patch con->conf.is_ssl if the connection is a ssl-socket already */
- #ifdef USE_OPENSSL
con->conf.is_ssl = srv_sock->is_ssl;
#endif - break;
该状态下主要做些初始化的准备工作。
需要注意的是这里将connetion中的两个字段保存为当前时间,request_start和read_idle_ts, 前者存放的是接收连接的时间, 后者用于超时判断,lighttpd设置了一个每一秒一次的定时器, 每次定时器到时就依次轮询所有的连接, 判断是否超时, 而判断的依据就是拿当前的时间 - connection的read_idle_ts字段, 看看是否超时:
// 如果当前时间与read_idle_ts之差大于max_read_idle, 超时
if (srv->cur_ts - con->read_idle_ts > con->conf.max_read_idle) {
/* time - out */
connection_set_state(srv, con, CON_STATE_ERROR);
changed = 1;
}
这些该保存的数据都保存完毕之后, 状态机进入下一个状态,CON_STATE_READ, 也就是开始接收数据。
状态read
case CON_STATE_READ:
if (srv->srvconf.log_state_handling) {
log_error_write(srv, __FILE__, __LINE__, "sds",
"state for fd", con->fd, connection_get_state(con->state));
}
connection_handle_read_state(srv, con);
break;
看看connection_handle_read_state都做了些什么。
static int connection_handle_read_state(server *srv, connection *con) {
connection_state_t ostate = con->state;
chunk *c, *last_chunk;
off_t last_offset;
chunkqueue *cq = con->read_queue;
chunkqueue *dst_cq = con->request_content_queue;
int is_closed = 0; /* the connection got closed, if we don't have a complete header, -> error */
if (con->is_readable) {
con->read_idle_ts = srv->cur_ts;
switch(connection_handle_read(srv, con)) {
case -1:
return -1;
case -2:
is_closed = 1;
break;
default:
break;
}
}
static int connection_handle_read(server *srv, connection *con) {
int len;
buffer *b;
int toread;
if (con->conf.is_ssl) { /*启用了SSL*/
return connection_handle_read_ssl(srv, con);
}
#if defined(__WIN32)
b = chunkqueue_get_append_buffer(con->read_queue);
buffer_prepare_copy(b, 4 * 1024);
len = recv(con->fd, b->ptr, b->size - 1, 0);
#else
if (ioctl(con->fd, FIONREAD, &toread) || toread == 0) { /* 有多少数据待读*/
b = chunkqueue_get_append_buffer(con->read_queue);
buffer_prepare_copy(b, 4 * 1024);
} else {
if (toread > MAX_READ_LIMIT) toread = MAX_READ_LIMIT;
b = chunkqueue_get_append_buffer(con->read_queue);
buffer_prepare_copy(b, toread + 1);
}
len = read(con->fd, b->ptr, b->size - 1);
#endif
if (len < 0) {
con->is_readable = 0;
if (errno == EAGAIN) return 0;/*当前没有数据可读*/
if (errno == EINTR) { /* 被信号中断*/
/* we have been interrupted before we could read */
con->is_readable = 1;
return 0;
}
if (errno != ECONNRESET) {
/* expected for keep-alive */
log_error_write(srv, __FILE__, __LINE__, "ssd", "connection closed - read failed: ", strerror(errno), errno);
}
connection_set_state(srv, con, CON_STATE_ERROR);
return -1;
} else if (len == 0) {
con->is_readable = 0;
/* the other end close the connection -> KEEP-ALIVE */
/* pipelining */
return -2; /* 连接被对端关闭,返回-2*/
} else if ((size_t)len < b->size - 1) {
/* we got less then expected, wait for the next fd-event */
con->is_readable = 0;
}
b->used = len;
b->ptr[b->used++] = '/0';
con->bytes_read += len;
#if 0
dump_packet(b->ptr, len);
#endif
return 0;
}
read返回-1并不一定是没有数据可读;函数最后在读到的数据末尾添加一个字符串结束符('/0')。
继续回到函数connection_handle_read_state中, 看接下来的代码:
// 这一段循环代码用于更新read chunk队列,没有使用的chunk都归入未使用chunk链中
/* the last chunk might be empty */
for (c = cq->first; c;) {
if (cq->first == c && c->mem->used == 0) {
// 如果第一个chunk是空的并且没有使用过
/* the first node is empty */
/* and it is empty, move it to unused */
// 则chunk队列的第一个chunk为下一个chunk
cq->first = c->next;
// 第一个chunk为NULL, 那么最后一个chunk为NULL
if (cq->first == NULL)
cq->last = NULL;
// 更新chunk队列中对于未使用chunk的记录
c->next = cq->unused;
cq->unused = c;
cq->unused_chunks++;
// 重新指向第一个chunk
c = cq->first;
} else if (c->next && c->next->mem->used == 0) {
chunk *fc;
// 如果下一个chunk存在而且未使用过
/* next node is the last one */
/* and it is empty, move it to unused */
// 将这个chunk从队列中分离出去, 同时fc指向这个未使用的chunk
fc = c->next;
c->next = fc->next;
// 将这个未使用的chunk(fc所指)保存到未使用chunk链中
fc->next = cq->unused;
cq->unused = fc;
cq->unused_chunks++;
/* the last node was empty */
// 如果c的下一个chunk是空的, 那么chunk队列的最后一个chunk就是c了
if (c->next == NULL) {
cq->last = c;
}
// 继续往下走
c = c->next;
} else {
// 继续往下走
c = c->next;
}
}
每个connection结构体中, 有一个read_queue成员, 该成员是chunkqueue类型的, 一个connection读入的数据都会保存在这个成员中。
switch(ostate) {
case CON_STATE_READ:
/* if there is a /r/n/r/n in the chunkqueue
* /r/n/r/n用来判断一个请求数据包的结束
* scan the chunk-queue twice
* 1. to find the /r/n/r/n
* 2. to copy the header-packet
*
*/
last_chunk = NULL;
last_offset = 0;
for循环用来寻找/r/n/r/n,有可能在一个chunk里,也有可能跨越chunk。
for (c = cq->first; !last_chunk && c; c = c->next) {
buffer b;
size_t i;
b.ptr = c->mem->ptr + c->offset;
b.used = c->mem->used - c->offset;
for (i = 0; !last_chunk && i < b.used; i++) {
char ch = b.ptr[i];
size_t have_chars = 0;
switch (ch) {
case '/r':
/* we have to do a 4 char lookup */
have_chars = b.used - i - 1;
if (have_chars >= 4) {
/* all chars are in this buffer */
if (0 == strncmp(b.ptr + i, "/r/n/r/n", 4)) {
/* found */
last_chunk = c;
last_offset = i + 4;
break;
}
} else {
chunk *lookahead_chunk = c->next;
size_t missing_chars;
/* looks like the following chars are not in the same chunk */
missing_chars = 4 - have_chars;
if (lookahead_chunk && lookahead_chunk->type == MEM_CHUNK) {
/* is the chunk long enough to contain the other chars ? */
if (lookahead_chunk->mem->used > missing_chars) {
if (0 == strncmp(b.ptr + i, "/r/n/r/n", have_chars) &&
0 == strncmp(lookahead_chunk->mem->ptr, "/r/n/r/n" + have_chars, missing_chars)) {
last_chunk = lookahead_chunk;
last_offset = missing_chars;
break;
}
} else {
/* a splited /r /n */
break;
}
}
}
break;
}
}
}
从first开始到last之间的chunk里的数据都是该次请求的头部(对于POST等可能有其它数据,这里先提取头部出来保存到con->request.request)。
/* found */
if (last_chunk) {
buffer_reset(con->request.request);
for (c = cq->first; c; c = c->next) {
buffer b;
b.ptr = c->mem->ptr + c->offset;
b.used = c->mem->used - c->offset;
if (c == last_chunk) {
b.used = last_offset + 1;
}
buffer_append_string_buffer(con->request.request, &b);
if (c == last_chunk) {
c->offset += last_offset;
break;
} else {
/* the whole packet was copied */
c->offset = c->mem->used - 1;
}
}
connection_set_state(srv, con, CON_STATE_REQUEST_END);
} else if (chunkqueue_length(cq) > 64 * 1024) {
log_error_write(srv, __FILE__, __LINE__, "s", "oversized request-header -> sending Status 414");
con->http_status = 414; /* Request-URI too large */
con->keep_alive = 0;
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
}
break;
/* the connection got closed and we didn't got enough data to leave one of the READ states
* the only way is to leave here */
if (is_closed && ostate == con->state) {
connection_set_state(srv, con, CON_STATE_ERROR);
}
chunkqueue_remove_finished_chunks(cq); /*该请求数据已经读取,从队列中删除*/
return 0;
状态reqend
case CON_STATE_REQUEST_END: /* transient */
if (srv->srvconf.log_state_handling) {
log_error_write(srv, __FILE__, __LINE__, "sds",
"state for fd", con->fd, connection_get_state(con->state));
}
if (http_request_parse(srv, con)) {
/* we have to read some data from the POST request */
connection_set_state(srv, con, CON_STATE_READ_POST);
break;
}
connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST);
break;
这个状态的工作是对读到的request头部解析,然后进入请求处理状态CON_STATE_HANDLE_REQUEST;当然对于POST请求的话,http_request_parse会返回1,从而进入CON_STATE_READ_POST。
看看http_request_parse函数:
首先解析的是请求行(Request-Line)
Request-Line =Method SP Request-URL SP HTTP-Version CRLF
int http_request_parse(server *srv, connection *con) {
char *uri = NULL, *proto = NULL, *method = NULL, con_length_set;
int is_key = 1, key_len = 0, is_ws_after_key = 0, in_folding;
char *value = NULL, *key = NULL;
enum { HTTP_CONNECTION_UNSET, HTTP_CONNECTION_KEEPALIVE, HTTP_CONNECTION_CLOSE } keep_alive_set = HTTP_CONNECTION_UNSET;
int line = 0;
int request_line_stage = 0;
size_t i, first;
int done = 0;
/*
* Request: "^(GET|POST|HEAD) ([^ ]+(//?[^ ]+|)) (HTTP/1//.[01])$"
* Option : "^([-a-zA-Z]+): (.+)$"
* End : "^$"
*/
if (con->conf.log_request_header) {
log_error_write(srv, __FILE__, __LINE__, "sdsdSb",
"fd:", con->fd,
"request-len:", con->request.request->used,
"/n", con->request.request);
}
if (con->request_count > 1 &&
con->request.request->ptr[0] == '/r' &&
con->request.request->ptr[1] == '/n') {
/* we are in keep-alive and might get /r/n after a previous POST request.*/
buffer_copy_string_len(con->parse_request, con->request.request->ptr + 2, con->request.request->used - 1 - 2);
} else {
/* fill the local request buffer */
buffer_copy_string_buffer(con->parse_request, con->request.request);
}
keep_alive_set = 0;
con_length_set = 0;
/* parse the first line of the request
*
* should be:
*
* /r/n
* */
for (i = 0, first = 0; i < con->parse_request->used && line == 0; i++) {
char *cur = con->parse_request->ptr + i;
switch(*cur) {
case '/r': /*到达request-line的结束标记处*/
if (con->parse_request->ptr[i+1] == '/n') {
http_method_t r;
char *nuri = NULL;
size_t j;
/* /r/n -> /0/0 */
con->parse_request->ptr[i] = '/0';
con->parse_request->ptr[i+1] = '/0';
buffer_copy_string_len(con->request.request_line, con->parse_request->ptr, i);
if (request_line_stage != 2) { /*解析结果不完整*/
con->http_status = 400;
con->response.keep_alive = 0;
con->keep_alive = 0;
if (srv->srvconf.log_request_header_on_error) {
log_error_write(srv, __FILE__, __LINE__, "s", "incomplete request line -> 400");
log_error_write(srv, __FILE__, __LINE__, "Sb",
"request-header:/n",
con->request.request);
}
return 0;
}
得到正确的解析结果
proto = con->parse_request->ptr + first;
把这3个字段用字符串结束符分割开来
*(uri - 1) = '/0';
*(proto - 1) = '/0';
/* we got the first one :) 方法解析*/
if (-1 == (r = get_http_method_key(method))) {
con->http_status = 501;
con->response.keep_alive = 0;
con->keep_alive = 0;