boa源码解析(1)-接收请求,发送html的流程
来源:互联网 发布:大数据挖掘系统 编辑:程序博客网 时间:2024/06/08 06:02
最近接触到boa,记录下来以便复习
本篇记录服务器接受请求,向客户端发送指定文件的流程
1.首先从boa.c的main函数开始
while ((c = getopt(argc, argv, "c:r:d")) != -1) { switch (c) { case 'c': if (server_root) free(server_root); server_root = strdup(optarg); if (!server_root) { perror("strdup (for server_root)"); exit(1); } break; case 'r': if (chdir(optarg) == -1) { log_error_time(); perror("chdir (to chroot)"); exit(1); } if (chroot(optarg) == -1) { log_error_time(); perror("chroot"); exit(1); } if (chdir("/") == -1) { log_error_time(); perror("chdir (after chroot)"); exit(1); } break; case 'd': do_fork = 0; break; default: fprintf(stderr, "Usage: %s [-c serverroot] [-r chroot] [-d]\n", argv[0]); exit(1); }刚开始会对传入的参数进行解析,例如-c /opt/app/,则server_root=/opt/app,server_root用于指定配置文件的所在位置,即boa.conf在/opt/app/目录下!!!
fixup_server_root(); read_config_files(); open_logs(); server_s = create_server_socket(); init_signals(); drop_privs(); create_common_env(); build_needs_escape();
fixup_server_root():该函数中最重要的语句是chdir(server_root),即跳转到配置文件所在的目录下,然后read_config_files,即读取配置文件!!!之后代码中很多参数的值都是从配置文件中获取的。
boa服务器创建socket连接,无非就是创建socket,将套接字设置为非阻塞,设置断开后端口可立即使用,绑定socket,设置监听。
init_signals:设置相应的信号及信号处理函数。
/* background ourself */ if (do_fork) { switch(fork()) { case -1: /* error */ perror("fork"); exit(1); break; case 0: /* child, success */ break; default: /* parent, success */ exit(0); break; } }守护进程:当终端被关闭后,利用该终端开启的相应的进程也会被关闭。守护进程就是为了冲破这种障碍。它从被执行的时候开始运转,知道整个系统关闭才退出(当然可以认为的杀死相应的守护进程)。如果想让某个进程不因为用户或中断或其他变化而影响,那么就必须把这个进程变成一个守护进程。
守护进程的步骤:
1.使用fork,创建子进程,父进程退出
2.调用setsid,摆脱其他进程的控制
3.改变当前目录为根目录
4.重设文件权限掩码
这里只做了第一步
程序总算是初始化完毕了。
接下来就是接收处理数据了。关键函数select_loop(server_s);boa会进入一个循环,不断地接收处理数据
/* any blocked req's move from request_ready to request_block */process_requests(server_s);if (!sigterm_flag && total_connections < (max_connections - 10)) { BOA_FD_SET(server_s, &block_read_fdset); /* server always set */}req_timeout.tv_sec = (request_ready ? 0 :(ka_timeout ? ka_timeout : REQUEST_TIMEOUT));req_timeout.tv_usec = 0l; /* reset timeout */if (select(max_fd + 1, &block_read_fdset, &block_write_fdset, NULL, (request_ready || request_block ? &req_timeout : NULL)) == -1) {/* what is the appropriate thing to do here on EBADF */if (errno == EINTR) continue; /* while(1) */else if (errno != EBADF) { DIE("select"); }}time(&t_time);if (FD_ISSET(server_s, &block_read_fdset)) pending_requests = 1;}
刚开始,pending_request为0。从上面的代码可以看到,程序首先会运行process_request,但因为pending_request=0,程序只是进去逛了一圈,什么事情也不会做。
程序会一直阻塞在select,直到有新的客户端连接进来。当有新的客户端连接到服务器后,即接收到http报文,select不再阻塞,pending_request将变成1,然后就会再次进入process_request中
if (pending_requests) { get_request(server_s);#ifdef ORIGINAL_BEHAVIOR pending_requests = 0;#endif }这时我们将会执行get_request来获取连接请求。在get_request中,首先执行accept,然后新建一个request
conn = new_request(); if (!conn) { close(fd); return; } conn->fd = fd; conn->status = READ_HEADER; conn->header_line = conn->client_stream; conn->time_last = current_time; conn->kacount = ka_max;初始状态为READ_HEADER。最后把conn加入request_ready队列中:enqueue(&request_ready, conn);
执行完get_request函数后,程序继续执行process_request
current = request_ready; while (current) { time(&t_time); if (current->buffer_end && /* there is data in the buffer */ current->status != DEAD && current->status != DONE) { retval = req_flush(current); /* * retval can be -2=error, -1=blocked, or bytes left */ if (retval == -2) { /* error */ current->status = DEAD; retval = 0; } else if (retval >= 0) { /* notice the >= which is different from below? Here, we may just be flushing headers. We don't want to return 0 because we are not DONE or DEAD */ retval = 1; } } else { switch (current->status) { case READ_HEADER: case ONE_CR: case ONE_LF: case TWO_CR: retval = read_header(current); break; case BODY_READ: retval = read_body(current); break; case BODY_WRITE: retval = write_body(current); break; case WRITE: retval = process_get(current); break; case PIPE_READ: retval = read_from_pipe(current); break; case PIPE_WRITE: retval = write_from_pipe(current); break; case DONE: /* a non-status that will terminate the request */ retval = req_flush(current); /* * retval can be -2=error, -1=blocked, or bytes left */ if (retval == -2) { /* error */ current->status = DEAD; retval = 0; } else if (retval > 0) { retval = 1; } break; case DEAD: retval = 0; current->buffer_end = 0; SQUASH_KA(current); break; default: retval = 0; fprintf(stderr, "Unknown status (%d), " "closing!\n", current->status); current->status = DEAD; break; } } if (sigterm_flag) SQUASH_KA(current); /* we put this here instead of after the switch so that * if we are on the last request, and get_request is successful, * current->next is valid! */ if (pending_requests) get_request(server_s); switch (retval) { case -1: /* request blocked */ trailer = current; current = current->next; block_request(trailer); break; case 0: /* request complete */ current->time_last = current_time; trailer = current; current = current->next; free_request(&request_ready, trailer); break; case 1: /* more to do */ current->time_last = current_time; current = current->next; break; default: log_error_time(); fprintf(stderr, "Unknown retval in process.c - " "Status: %d, retval: %d\n", current->status, retval); current = current->next; break; } }接下来开始循环处理刚才接收到的请求。刚开始由于没有数据,将会执行else中的内容,由于初始状态为READ_HEADER,程序将执行read_header,将会执行read读取报文,并返回1,由代码注释(more to do)我们也可以看出,我们只是获取了数据,并没有进行处理,然后current=current->next,如果只有一个求救的话,这时候current已经是NULL了,将会推出while循环,但数据并没有消失,仍有保存在request_ready队列中,因为request_ready还在,所以select(max_fd + 1, &block_read_fdset, &block_write_fdset, NULL,(request_ready || request_block ? &req_timeout : NULL))并不会一直阻塞,程序将再一次执行process_request函数,这时候current再次等于request_ready,处理刚才未处理的数据;这时候将再次执行read_header函数,不过不再是读取数据了,而是进行处理数据了。
while (check < (buffer + bytes)) { switch (req->status) { case READ_HEADER: if (*check == '\r') { req->status = ONE_CR; req->header_end = check; } else if (*check == '\n') { req->status = ONE_LF; req->header_end = check; } break; case ONE_CR: if (*check == '\n') req->status = ONE_LF; else if (*check != '\r') req->status = READ_HEADER; break; case ONE_LF: /* if here, we've found the end (for sure) of a header */ if (*check == '\r') /* could be end o headers */ req->status = TWO_CR; else if (*check == '\n') req->status = BODY_READ; else req->status = READ_HEADER; break; case TWO_CR: if (*check == '\n') req->status = BODY_READ; else if (*check != '\r') req->status = READ_HEADER; break; default: break; }#ifdef VERY_FASCIST_LOGGING log_error_time(); fprintf(stderr, "status, check: %d, %d\n", req->status, *check);#endif req->parse_pos++; /* update parse position */ check++; if (req->status == ONE_LF) { *req->header_end = '\0'; /* terminate string that begins at req->header_line */ if (req->logline) { if (process_option_line(req) == 0) { return 0; } } else { if (process_logline(req) == 0) return 0; if (req->simple) return process_header_end(req); } /* set header_line to point to beginning of new header */ req->header_line = check; } else if (req->status == BODY_READ) {#ifdef VERY_FASCIST_LOGGING int retval; log_error_time(); fprintf(stderr, "%s:%d -- got to body read.\n", __FILE__, __LINE__); retval = process_header_end(req);#else int retval = process_header_end(req);#endif /* process_header_end inits non-POST cgi's */ if (retval && req->method == M_POST) { /* for body_{read,write}, set header_line to start of data, and header_end to end of data */ req->header_line = check; req->header_end = req->client_stream + req->client_stream_pos; req->status = BODY_WRITE; /* so write it */ /* have to write first, or read will be confused * because of the special case where the * filesize is less than we have already read. */ /* As quoted from RFC1945: A valid Content-Length is required on all HTTP/1.0 POST requests. An HTTP/1.0 server should respond with a 400 (bad request) message if it cannot determine the length of the request message's content. */ if (req->content_length) { int content_length; content_length = boa_atoi(req->content_length); /* Is a content-length of 0 legal? */ if (content_length <= 0) { log_error_time(); fprintf(stderr, "Invalid Content-Length [%s] on POST!\n", req->content_length); send_r_bad_request(req); return 0; } if (single_post_limit && content_length > single_post_limit) { log_error_time(); fprintf(stderr, "Content-Length [%d] > SinglePostLimit [%d] on POST!\n", content_length, single_post_limit); send_r_bad_request(req); return 0; } req->filesize = content_length; req->filepos = 0; if (req->header_end - req->header_line > req->filesize) { req->header_end = req->header_line + req->filesize; } } else { log_error_time(); fprintf(stderr, "Unknown Content-Length POST!\n"); send_r_bad_request(req); return 0; } } /* either process_header_end failed or req->method != POST */ return retval; /* 0 - close it done, 1 - keep on ready */ } /* req->status == BODY_READ */ }首先,读取http第一行数据,即请求行。因为刚开始req->logfile为NULL,所有程序会执行process_logfile,该函数用于解析请求行数据。之后logfile不再为NULL,之后读取的请求头部都会执行process_option_file。请求头读取完毕后,http报文会空一行,也就是会有两个换行符,状态变更为BODY_READ,执行process_header_end。
int process_header_end(request * req){ if (!req->logline) { send_r_error(req); return 0; } /* Percent-decode request */ if (unescape_uri(req->request_uri, &(req->query_string)) == 0) { log_error_doc(req); fputs("Problem unescaping uri\n", stderr); send_r_bad_request(req); return 0; } /* clean pathname */ clean_pathname(req->request_uri); if (req->request_uri[0] != '/') { send_r_bad_request(req); return 0; } if (translate_uri(req) == 0) { /* unescape, parse uri */ SQUASH_KA(req); return 0; /* failure, close down */ } if (req->method == M_POST) { req->post_data_fd = create_temporary_file(1, NULL, 0); if (req->post_data_fd == 0) return(0); return(1); /* success */ } if (req->is_cgi) { return init_cgi(req); } req->status = WRITE; return init_get(req); /* get and head */}
该函数会对请求行的url进行解析。状态变更为write,执行init_get,读取要发送的文件数据。然后程序回到process_request,因为状态为WRITE,程序便会执行process_get,将文件发送到客户端。
阅读全文
0 0
- boa源码解析(1)-接收请求,发送html的流程
- HTTP发送请求和接收响应的整个流程
- nginx的请求接收流程(一)
- nginx的请求接收流程(二)
- Okhttp3源码(1)---同步异步请求流程解析
- Thrift源码系列----4.数据的解析与发送、接收
- 解析短信发送和接收流程
- tomcat源码解析(三)--请求过程之数据的接收
- RocketMQ源码解析:Message发送&接收
- java发送http请求,解析html返回的技术
- Android短彩信源码解析-短信发送流程(一)
- Android短彩信源码解析-短信发送流程(二)
- Android短彩信源码解析-短信发送流程(三)
- Java发送Http请求,解析html返回
- Java发送Http请求,解析html返回
- Java发送Http请求,解析html返回
- Java发送Http请求,解析html返回
- Java发送Http请求,解析html返回
- spring学习总结
- 矩阵乘法优化递归式
- 设计模式 — 工厂方法模式(Factory Method)
- java 二叉树三种遍历(递归非递归)
- 最近公共祖先(LCA)算法实现过程 【Tarjan离线+倍增在线+RMQ】
- boa源码解析(1)-接收请求,发送html的流程
- TCP/IP、Http、Socket、XMPP-从入门到深入
- JavaScript中的数组
- 学习C的一些笔记(一)
- ubuntu显卡驱动的下载和安装
- 你应该切换到Kotlin开发
- 单链表
- jpa findone 和 getone的区别
- c语言中,关于随机函数的使用详解