状态机之请求处理
最后更新于:2022-04-01 15:57:17
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)