BOA是一个单进程的web服务器,支持CGI交互,浏览器每次发送一个POAT请求,BOA会对应fork一个CGI进程,数据发送完成后,CGI进程退出,尽管存在这样的进程创建和销毁的开销,但BOA仍然是一款轻量级的web服务器,适用于需要较为简单的web页面访问。下面对BOA的主要部分进行代码分析,包含GET和POST两种方法。
GET和POST对客户端请求数据的解析过程都差不多,GET主要的函数为init_get,而POST主要为init_cgi
1 GET
1.1 GET调用过程源码分析
if (request_block)
fdset_update();
process_requests(server_s);
read_header(current);
read()//读取客户端发来的数据到req->client_stream
//解析请求行,获取http版本和请求方法,URL
process_logline()
process_option_line()//解析每一行请求头并保存属性值
process_header_end()
unescape_uri()//如果请求行中有query_string,则解析
clean_pathname()//确保URL中路径分隔符是'/'
translate_uri()//解析出req->pathname,绝对路径
req->status = WRITE;
init_get(req);//获取请求的文件并发送
//如果在init_get中已经发送完,则req->status = DONE;
//process_get就不会执行了
process_get() //WRITE 状态机
调用系统write()发送get的文件
free_request //释放这次请求
release_mmap(req->mmap_entry_var);
munmap()
close() //关闭fd
init_get(req)
//pathname是路径,一般会返回index.html
//如果是文件,直接打开文件
open(req->pathname, O_RDONLY);
fstat(data_fd, &statbuf);//获取文件信息
if (S_ISDIR(statbuf.st_mode))//路径
data_fd = get_dir(req, &statbuf);//获取index.html
req->filesize = statbuf.st_size;
//mmap映射文件
req->mmap_entry_var = find_mmap(data_fd, &statbuf);
req->data_mem = req->mmap_entry_var->mmap;
send_r_request_ok(req);//copy响应行和头到req->buffer
//copy响应体,也就是get的文件内容
memcpy(req->buffer + req->buffer_end, req->data_mem, bytes);
req_flush(req); //发送响应行,响应头,响应体
1.2 GET调用过程内存数据传输图
2 POST
BOA针对POST方法会fork一个子进程进行CGI的输出,父子进程通过pipe进行通信,程序主要调用过程如下所示
2.1 POST代码执行过程
process_requests
read_header(current);//READ_HEADER状态机
process_header_end()
unescape_uri()//如果请求行中有query_string,则解析
clean_pathname()//确保URL中路径分隔符是'/'
translate_uri()//解析出req->pathname,绝对路径
//创建临时文件
req->post_data_fd = create_temporary_file(1, NULL, 0);
req->status = BODY_WRITE;
req->header_line //执行请求体内容的开始
req->header_end //执行请求体内容的结束
write_body(current);//BODY_WRITE状态机
write(req->post_data_fd)//向临时文件中发送client的请求体
req->header_line = req->header_end = req->buffer;
init_cgi(req);
req->status = PIPE_READ;
req->header_line = req->header_end =
(req->buffer + BUFFER_SIZE / 2);
read_from_pipe(current);//PIPE_READ状态机,从pipe中读取cgi写入的数据
bytes_read = read(req->data_fd,req->header_end)
header_end += bytes_read
req->status = PIPE_WRITE;
process_cgi_header(req);
send_r_request_ok(req);//响应行和头写入req->buffer
dest = req->buffer + req->buffer_end;
howmuch = req->header_end - req->header_line;
memmove(dest, req->header_line, howmuch);
req->buffer_end += howmuch;
req->header_line = req->buffer + req->buffer_end;
req->header_end = req->header_line;
req_flush(req);//发送响应行、头、体
write_from_pipe //PIPE_WRITE状态机
write()
return 0;
init_cgi(req);
pipe(pipes)
child_pid = fork();
child
close(pipes[0]);
dup2(pipes[1], STDOUT_FILENO)
close(pipes[1]);
//从标准输入中读取临时文件的内容,即client的请求体
dup2(req->post_data_fd, STDIN_FILENO);
close(req->post_data_fd);
execve(req->pathname, aargv, req->cgi_env);//执行cgi
parent
close(req->post_data_fd);
eq->post_data_fd = 0;
close(pipes[1]);
req->data_fd = pipes[0];//可以从pipe中读取cgi写入的响应体
req->status = PIPE_READ;
2.2 POST程序执行过程中内存中数据流图