状态机之response
最后更新于:2022-04-01 15:57:19
在CON_STATE_RESPONSE_START状态中,服务器开始准备给客户端的response:
~~~
case CON_STATE_RESPONSE_START:
/*
* the decision is done
* - create the HTTP-Response-Header
*
*/
if (srv->srvconf.log_state_handling) {
log_error_write(srv, __FILE__, __LINE__, "sds",
"state for fd", con->fd, connection_get_state(con->state));
}
if (-1 == connection_handle_write_prepare(srv, con)) {
connection_set_state(srv, con, CON_STATE_ERROR);
break;
}
connection_set_state(srv, con, CON_STATE_WRITE);
break;
~~~
可以看到,该状态主要调用了connection_handle_write_prepare函数,它根据客户端请求的method来设置response的headers(其实就是设置“Content-Length”的值)。
connection_handle_write_prepare函数的执行内容:
1.该函数首先判断连接的模式(mode)是否为DIRECT,如果是,说明连接没有经过插件处理,是由服务器自身处理的。
2.接着判断连接的请求method,如果是OPTION,则设置Allow的值,同时清空write_queue,因为没有数据需要返回。设置con->file_finished为1,表示不需要给客户端发送文件。
3.比较http_status的值,如果为204,205,304,说明服务器不需要给客户端返回文件,仅返回 response中headers及其之前的部分,设置con->file_finished为1。
4.判断file_finished的值。如果值为1,说明不需要给客户端返回文件数据。对于1xx,204和 304状态,将Content-Length设置为空值。如果method是HEAD,那么服务器可能需要返回一些数据,这时候要设置对应的 Content-Length。如果file_finished的值为0,那么要设置一下keep_alive的值。
5.最后,调用http_response_write_header将 headers写入write_queue,等待返回给客户端。如果一切顺利,状态机进入CON_STATE_WRITE状态。
下面是CON_STATE_WRITE分支的操作:
~~~
case CON_STATE_WRITE:
if (srv->srvconf.log_state_handling) {
log_error_write(srv, __FILE__, __LINE__, "sds",
"state for fd", con->fd, connection_get_state(con->state));
}
/* only try to write if we have something in the queue */
if (!chunkqueue_is_empty(con->write_queue)) {
#if 0
log_error_write(srv, __FILE__, __LINE__, "dsd",
con->fd,
"packets to write:",
con->write_queue->used);
#endif
}
if (!chunkqueue_is_empty(con->write_queue) && con->is_writable) {
if (-1 == connection_handle_write(srv, con)) {
log_error_write(srv, __FILE__, __LINE__, "ds",
con->fd,
"handle write failed.");
connection_set_state(srv, con, CON_STATE_ERROR);
} else if (con->state == CON_STATE_WRITE) {
con->write_request_ts = srv->cur_ts;
}
}
break;
~~~
由于数据可能不会一次写完,所以在CON_STATE_WRITE状态中,首先判断write_queue是否为空,即有没有数据需要发送。同时判断连接是否可写。如果有数据且可写,那么调用connection_handle_write发送数据。如果没有数据可写或者连接不可写,那么跳出switch(con->state)这个语句。
由于状态机状态没有发生改变,switch后面的if语句使得服务器退出了大while循环,进入循环后面的小switch(con->state)语句(如果连接的状态没有改变,说明连接读写数据还没有结束,但是需要等待IO事件)。在这里,进入 CON_STATE_WRITE分支,如果有数据可写且连接可写且没有达到流量限制,那么在fdevent中注册这个连接,等IO结束后继续写数据,否则,删除这个连接。
当有数据可写且连接可写时,进入connection_handle_write函数,下面看看该函数的作用:
1.首先调用network_write_chunkqueue函数,将write_queue中的数据写回给客户端。函数network_write_chunkqueue首先判断当前连接的流量是否超过了限制,如果是,则不发送任何数据,直接将连接加到作业列表(joblist)中,让其他连接发送数据。如果没有超限,那么首先设置TCP_CORK选项。这个选项可以将多个write调用的数据一起发送, 提高发送效率。
2.接下来调用srv->network_backend_wirte()函数真正的写数据。这个函数的定义有多个,在network_*.c文件中。服务器在network.c的network_init函数中会根据当前的运行环境设置不同的值。传统的IO方法先read再write,需要4次数据拷贝(从磁盘到内核缓冲区,从内核缓冲区到用户缓冲区,从用户缓冲区到网络接口的内核缓冲区,最后,从网络接口的内核缓冲区到网络设备缓冲区),为提高服务器的效率,不同的OS会提供一些特定的方法来减少拷贝的次数(直接IO),提高发送文件的速度,lighttpd根据不同的OS去调用特定的接口来实现network_backend_wirte()函数。这些实现大同小异,以network_write.c中的实现为例:
函数的主体是个大循环,遍历所有的chunk。
如果chunk的类型是 MEM_CHUNK,那么这个chunk中的数据是在内存中的,直接调用write或者windows下的send函数发送数据。
如果是 FILE_CHUNK类型,说明这个chunk表示的是一个文件,那么如果运行环境有mmap函数,就使用mmap映射文件并发送,否则就read再write。
如果这个chunk发送完了,则继续发送下一个chunk。
如果没有发送完(chunk_finished=0),退出循环,接着退出了这个函数。
3.服务器返回到network_write_chunkqueue中,做一些统计工作,再一次检查该连接的流量是否超限。
4.最后服务器返回到connection_handle_write中。
如果network_write_chunkqueue返回-1,表示服务器出错。状态机进 入CON_STATE_ERROR。
如果返回-2,则客户端关闭连接,状态机也进入CON_STATE_ERROR。
返回0表示发送完毕,进入下一个状态。
返回1说明数据没有发送完,标记is_wirtable为0。
5.从connection_handler_write函数返回后,如果数据没有发送完毕,那么状态机还在CON_STATE_WRITE状态,接着连接被加到fdevent系统中,等待下一次数据发送。重复上述过程直到发送完毕或出错。
6.如果数据发送完毕,状态机进入CON_STATE_RESPONSE_END状态。
在状态CON_STATE_RESPONSE_END中:
1.服务器首先调用 plugins_call_handle_request_done通知所有插件连接处理完毕。
2.判断是否保持连接,如果保持,将状态机设置为 CON_STATE_REQUEST_START。如果不保持,先调用plugins_call_handle_connection_close通知所有插件连接关闭,然后关闭连接。
3.重置con,清除前一次请求的数据。
至此,请求处理结束。