自己动手写 HTTP Server
-
作者:heiyeluren
-
时间:2008-06-22
【 原理 】 一般来说,HTTP Server 也是我们常说的Web服务器,大名鼎鼎的 Apache,还是微软的 IIS (Internet Information Server),开源领域的有 Lighttpd 和最近风头正劲的 Nginx 都是典型的Web服务器。最近想想能不能做一个Web服务器,就提供简单的功能,但是速度很快,能够作为一个专门处理 HTML/CSS/JS/Image 的Web服务器,那样能够让静态资源文件迅速的被访问到,如果有反向代理功能就更帅了,当然了,要是有Cache功能啥的,并且能够编写自定义插件(扩 展)就很完美了。。。YY中。。。
基于这个思想,我就花十天时间使用标准C写了一个千行代码小型的HTTP Server,当然目前还不具有反向代理和扩展功能,只是能够简单的支持 HTML/CSS/JS/Image 静态资源文件的访问。HTTP Server 的名字叫做 tmhttpd - TieMa (Tiny&Mini) HTTP Server,小巧,代码少,执行速度快,目前具有的功能包括:
- Support GET/HEAD method
- The common MIME types.
- Support Self custom default index page
- Directory listings.
- Support access log
- Support Self custom port and max clients
- Use fork mode accept new conntion
- Support daemon type work
- more ..
目前已经发布了 tmhttpd-1.0.0_alpha 版本,包括for Unix/Linux (在 Ubuntu/Fedoar/FreeBSD 下编译通过) 和 在cygwin环境下编译的 for Windows 版本,下面地址有下载:
- Unix/Linux: http://heiyeluren.googlecode.com/files/tmhttpd-1.0.0_alpha.tar.gz
- Windows: http://heiyeluren.googlecode.com/files/tmhttpd-1.0.0_alpha-win32.zip
下面大致来聊聊怎么写一个 HTTP Server,先看看一个HTTP请求的流程:
大致就是 客户端的浏览器(IE、Firefox、Opera、Lynx、Curl、Robot ...) 访问到Web服务器的端口(一般是80),然后端口接受到请求后开始解析请求,如果请求不正确或者非法则直接返回指定的错误代码(可以参考 RFC 1945),如果请求合法,那么就查找相应用户请求的文件,把文件读取后发送给客户端,关闭连接,请求结束。
【 实现 】
贴部分tmhttpd核心代码来描述这个过程:
- /**
- * 初始化服务器端的Socket连接,等待连接,连接成功后fork新进程处理
- *
- */
- static void InitServerListen( unsigned int port, unsigned int max_client ){
- int serversock, clientsock;
- struct sockaddr_in server_addr, client_addr;
- char currtime[32];
- /* Create the TCP socket */
- if ((serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){
- die("Failed to create socket");
- }
- /* Construct the server sockaddr_in structure */
- memset(&server_addr, 0, sizeof(server_addr)); /* Clear struct */
- server_addr.sin_family = AF_INET; /* Internet/IP */
- server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* Incoming addr */
- server_addr.sin_port = htons(port); /* server port */
- /* Bind the server socket */
- if (bind(serversock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0){
- die("Failed to bind the server socket");
- }
- /* Listen on the server socket */
- if (listen(serversock, max_client) < 0){
- die("Failed to listen on server socket");
- }
- /* Print listening message */
- getdate(currtime);
- fprintf(stdout, "[%s] Start server listening at port %d .../n", currtime, port);
- fprintf(stdout, "[%s] Waiting client connection .../n", currtime);
- /* Run until cancelled */
- while (1){
- unsigned int clientlen = sizeof(client_addr);
- memset(currtime, 0, sizeof(currtime));
- getdate(currtime);
- /* Wait for client connection */
- if ((clientsock = accept(serversock, (struct sockaddr *) &client_addr, &clientlen)) < 0){
- die("Failed to accept client connection");
- }
- /* Use child process new connection */
- if ( fork() == 0 ){
- HandleClient(clientsock, client_addr);
- } else {
- wait(NULL);
- }
- /* Not use close socket connection */
- close(clientsock);
- }
- }
- /**
- * 获取一个连接,读取连接客户端发送的请求数据,把请求数据叫给请求解析函数进行解析
- *
- */
- static void HandleClient( int client_sock, struct sockaddr_in client_addr ){
- char buf[REQUEST_MAX_SIZE];
- if ( read(client_sock, buf, REQUEST_MAX_SIZE) < 0){
- SendError( client_sock, 500, "Internal Server Error", "", "Client request not success." );
- die("read sock");
- }
- ParseRequest( client_sock, client_addr, buf );
- close(client_sock);
- exit(0);
- }
- /**
- * 解析一个请求,解析出GET/HEAD方法,需要请求的文件,协议版本等,构造成结构体提交给处理函数
- *
- */
- static int ParseRequest( int client_sock, struct sockaddr_in client_addr, char *req ){
- char **buf, **method_buf, **query_buf, currtime[32], cwd[1024], tmp_path[1536], pathinfo[512], path[256], file[256], log[1024];
- int line_total, method_total, query_total, i;
- struct st_request_info request_info;
- /* Split client request */
- getdate(currtime);
- explode(req, '/n', &buf, &line_total);
- /* Print log message */
- memset(log, 0, sizeof(log));
- sprintf(log, "[%s] %s %s/n", currtime, inet_ntoa(client_addr.sin_addr), buf[0]);
- WriteLog(log);
- /* Check request is empty */
- if (strcmp(buf[0], "/n") == 0 || strcmp(buf[0], "/r/n") == 0){
- SendError( client_sock, 400, "Bad Request", "", "Can't parse request." );
- }
- /* Check method is implement */
- explode(buf[0], ' ', &method_buf, &method_total);
- if ( strcmp( strtolower(method_buf[0]), "get") != 0 && strcmp( strtolower(method_buf[0]), "head") != 0 ){
- SendError( client_sock, 501, "Not Implemented", "", "That method is not implemented." );
- }
- explode(method_buf[1], '?', &query_buf, &query_total);
- /* Make request data */
- getcwd(cwd, sizeof(cwd));
- strcpy(pathinfo, query_buf[0]);
- substr(query_buf[0], 0, strrpos(pathinfo, '/')+1, path);
- substr(query_buf[0], strrpos(pathinfo, '/')+1, 0, file);
- /* Pad request struct */
- memset(&request_info, 0, sizeof(request_info));
- strcat(cwd, pathinfo);
- request_info.method = method_buf[0];
- request_info.pathinfo = pathinfo;
- request_info.query = (query_total == 2 ? query_buf[1] : "");
- request_info.protocal = strtolower(method_buf[2]);
- request_info.path = path;
- request_info.file = file;
- request_info.physical_path = cwd;
- /* Is a directory pad default index page */
- memset(tmp_path, 0, sizeof(tmp_path));
- strcpy(tmp_path, cwd);
- if ( is_dir(tmp_path) ){
- strcat(tmp_path, g_dir_index);
- if ( file_exists(tmp_path) ){
- request_info.physical_path = tmp_path;
- }
- }
- /* Debug message */
- if ( g_is_debug ){
- fprintf(stderr, "[ Request ]/n");
- for(i=0; i<line_total; i++){
- fprintf(stderr, "%s/n", buf[i]);
- }
- }
- /* Process client request */
- ProcRequest( client_sock, client_addr, request_info );
- return 0;
- }
- /**
- * 处理函数按照解析出来的请求内容进行数据返回,返回文件/目录列表或者提示错误
- *
- */
- static int ProcRequest( int client_sock, struct sockaddr_in client_addr, struct st_request_info request_info ){
- char buf[128];
- /* File is exist or has access permission */
- if ( !file_exists( request_info.physical_path ) ){
- memset(buf, 0, sizeof(buf));
- sprintf(buf, "File %s not found.", request_info.pathinfo);
- SendError( client_sock, 404, "Not Found", "", buf);
- }
- if ( access(request_info.physical_path, R_OK) != 0 ){
- memset(buf, 0, sizeof(buf));
- sprintf(buf, "File %s is protected.", request_info.pathinfo);
- SendError( client_sock, 403, "Forbidden", "", buf);
- }
- /* Check target is regular file or directory */
- if ( is_file(request_info.physical_path) == 1 ){
- SendFile( client_sock, request_info.physical_path, request_info.pathinfo );
- } else if ( is_dir(request_info.physical_path) == 1 ){
- /* Is a directory choose browse dir list */
- if ( g_is_browse ){
- SendDirectory( client_sock, request_info.physical_path, request_info.pathinfo );
- } else {
- memset(buf, 0, sizeof(buf));
- sprintf(buf, "File %s is protected.", request_info.pathinfo);
- SendError( client_sock, 403, "Forbidden", "", buf);
- }
- } else {
- memset(buf, 0, sizeof(buf));
- sprintf(buf, "File %s is protected.", request_info.pathinfo);
- SendError( client_sock, 403, "Forbidden", "", buf);
- }
- return 0;
- }
/** * 初始化服务器端的Socket连接,等待连接,连接成功后fork新进程处理 * */ static void InitServerListen( unsigned int port, unsigned int max_client ){ int serversock, clientsock; struct sockaddr_in server_addr, client_addr; char currtime[32]; /* Create the TCP socket */ if ((serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){ die("Failed to create socket"); } /* Construct the server sockaddr_in structure */ memset(&server_addr, 0, sizeof(server_addr)); /* Clear struct */ server_addr.sin_family = AF_INET; /* Internet/IP */ server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /* Incoming addr */ server_addr.sin_port = htons(port); /* server port */ /* Bind the server socket */ if (bind(serversock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0){ die("Failed to bind the server socket"); } /* Listen on the server socket */ if (listen(serversock, max_client) < 0){ die("Failed to listen on server socket"); } /* Print listening message */ getdate(currtime); fprintf(stdout, "[%s] Start server listening at port %d .../n", currtime, port); fprintf(stdout, "[%s] Waiting client connection .../n", currtime); /* Run until cancelled */ while (1){ unsigned int clientlen = sizeof(client_addr); memset(currtime, 0, sizeof(currtime)); getdate(currtime); /* Wait for client connection */ if ((clientsock = accept(serversock, (struct sockaddr *) &client_addr, &clientlen)) < 0){ die("Failed to accept client connection"); } /* Use child process new connection */ if ( fork() == 0 ){ HandleClient(clientsock, client_addr); } else { wait(NULL); } /* Not use close socket connection */ close(clientsock); } } /** * 获取一个连接,读取连接客户端发送的请求数据,把请求数据叫给请求解析函数进行解析 * */ static void HandleClient( int client_sock, struct sockaddr_in client_addr ){ char buf[REQUEST_MAX_SIZE]; if ( read(client_sock, buf, REQUEST_MAX_SIZE) < 0){ SendError( client_sock, 500, "Internal Server Error", "", "Client request not success." ); die("read sock"); } ParseRequest( client_sock, client_addr, buf ); close(client_sock); exit(0); } /** * 解析一个请求,解析出GET/HEAD方法,需要请求的文件,协议版本等,构造成结构体提交给处理函数 * */ static int ParseRequest( int client_sock, struct sockaddr_in client_addr, char *req ){ char **buf, **method_buf, **query_buf, currtime[32], cwd[1024], tmp_path[1536], pathinfo[512], path[256], file[256], log[1024]; int line_total, method_total, query_total, i; struct st_request_info request_info; /* Split client request */ getdate(currtime); explode(req, '/n', &buf, &line_total); /* Print log message */ memset(log, 0, sizeof(log)); sprintf(log, "[%s] %s %s/n", currtime, inet_ntoa(client_addr.sin_addr), buf[0]); WriteLog(log); /* Check request is empty */ if (strcmp(buf[0], "/n") == 0 || strcmp(buf[0], "/r/n") == 0){ SendError( client_sock, 400, "Bad Request", "", "Can't parse request." ); } /* Check method is implement */ explode(buf[0], ' ', &method_buf, &method_total); if ( strcmp( strtolower(method_buf[0]), "get") != 0 && strcmp( strtolower(method_buf[0]), "head") != 0 ){ SendError( client_sock, 501, "Not Implemented", "", "That method is not implemented." ); } explode(method_buf[1], '?', &query_buf, &query_total); /* Make request data */ getcwd(cwd, sizeof(cwd)); strcpy(pathinfo, query_buf[0]); substr(query_buf[0], 0, strrpos(pathinfo, '/')+1, path); substr(query_buf[0], strrpos(pathinfo, '/')+1, 0, file); /* Pad request struct */ memset(&request_info, 0, sizeof(request_info)); strcat(cwd, pathinfo); request_info.method = method_buf[0]; request_info.pathinfo = pathinfo; request_info.query = (query_total == 2 ? query_buf[1] : ""); request_info.protocal = strtolower(method_buf[2]); request_info.path = path; request_info.file = file; request_info.physical_path = cwd; /* Is a directory pad default index page */ memset(tmp_path, 0, sizeof(tmp_path)); strcpy(tmp_path, cwd); if ( is_dir(tmp_path) ){ strcat(tmp_path, g_dir_index); if ( file_exists(tmp_path) ){ request_info.physical_path = tmp_path; } } /* Debug message */ if ( g_is_debug ){ fprintf(stderr, "[ Request ]/n"); for(i=0; i<line_total; i++){ fprintf(stderr, "%s/n", buf[i]); } } /* Process client request */ ProcRequest( client_sock, client_addr, request_info ); return 0; } /** * 处理函数按照解析出来的请求内容进行数据返回,返回文件/目录列表或者提示错误 * */ static int ProcRequest( int client_sock, struct sockaddr_in client_addr, struct st_request_info request_info ){ char buf[128]; /* File is exist or has access permission */ if ( !file_exists( request_info.physical_path ) ){ memset(buf, 0, sizeof(buf)); sprintf(buf, "File %s not found.", request_info.pathinfo); SendError( client_sock, 404, "Not Found", "", buf); } if ( access(request_info.physical_path, R_OK) != 0 ){ memset(buf, 0, sizeof(buf)); sprintf(buf, "File %s is protected.", request_info.pathinfo); SendError( client_sock, 403, "Forbidden", "", buf); } /* Check target is regular file or directory */ if ( is_file(request_info.physical_path) == 1 ){ SendFile( client_sock, request_info.physical_path, request_info.pathinfo ); } else if ( is_dir(request_info.physical_path) == 1 ){ /* Is a directory choose browse dir list */ if ( g_is_browse ){ SendDirectory( client_sock, request_info.physical_path, request_info.pathinfo ); } else { memset(buf, 0, sizeof(buf)); sprintf(buf, "File %s is protected.", request_info.pathinfo); SendError( client_sock, 403, "Forbidden", "", buf); } } else { memset(buf, 0, sizeof(buf)); sprintf(buf, "File %s is protected.", request_info.pathinfo); SendError( client_sock, 403, "Forbidden", "", buf); } return 0; }
主要核心的函数就是这四个,如果要了解其他函数,包括字符串处理,HTTP头信息发送,错误发送,读取文件,遍历目录等等请下载源码回去研究。源码下载地址:http://heiyeluren.googlecode.com/files/tmhttpd-1.0.0_alpha.tar.gz
【 结束 】
其实还有很多功能能够增加的,比如性能,采用 select 或者 epool 肯定要比单纯的fork性能更好,另外增加处理动态内容,比如CGI,能够接受POST请求,能够作为一个反向代理存在,能够具有缓存功能,能够缓存请求 过的文件到内存中,能够具有插件扩展功能等等,都是需要进一步提升的。
参考文档: