自己动手写 HTTP Server

来源:互联网 发布:分光计实验数据 编辑:程序博客网 时间:2024/05/08 12:43

自己动手写 HTTP Server

  • 作者:heiyeluren
  • 博客:http://blog.csdn.net/heiyeshuwu
  • 代码:http://code.google.com/p/heiyeluren/downloads
  • 时间: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请求的流程:

HTTP Server process

大致就是 客户端的浏览器(IE、Firefox、Opera、Lynx、Curl、Robot ...) 访问到Web服务器的端口(一般是80),然后端口接受到请求后开始解析请求,如果请求不正确或者非法则直接返回指定的错误代码(可以参考 RFC 1945),如果请求合法,那么就查找相应用户请求的文件,把文件读取后发送给客户端,关闭连接,请求结束。

【 实现 】

贴部分tmhttpd核心代码来描述这个过程:

view plaincopy to clipboardprint?
  1. /** 
  2.  
  3.  * 初始化服务器端的Socket连接,等待连接,连接成功后fork新进程处理 
  4.  
  5.  * 
  6.  
  7.  */  
  8.   
  9. static void InitServerListen( unsigned int port, unsigned int max_client ){  
  10.   
  11.     int serversock, clientsock;  
  12.   
  13.     struct sockaddr_in server_addr, client_addr;  
  14.   
  15.     char currtime[32];  
  16.   
  17.   
  18.   
  19.     /* Create the TCP socket */  
  20.   
  21.     if ((serversock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){  
  22.   
  23.         die("Failed to create socket");  
  24.   
  25.     }  
  26.   
  27.   
  28.   
  29.     /* Construct the server sockaddr_in structure */  
  30.   
  31.     memset(&server_addr, 0, sizeof(server_addr));       /* Clear struct */  
  32.   
  33.     server_addr.sin_family = AF_INET;                  /* Internet/IP */  
  34.   
  35.     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   /* Incoming addr */  
  36.   
  37.     server_addr.sin_port = htons(port);          /* server port */  
  38.   
  39.   
  40.   
  41.     /* Bind the server socket */  
  42.   
  43.     if (bind(serversock, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0){  
  44.   
  45.         die("Failed to bind the server socket");  
  46.   
  47.     }  
  48.   
  49.     /* Listen on the server socket */  
  50.   
  51.     if (listen(serversock, max_client) < 0){  
  52.   
  53.         die("Failed to listen on server socket");  
  54.   
  55.     }  
  56.   
  57.   
  58.   
  59.     /* Print listening message */  
  60.   
  61.     getdate(currtime);  
  62.   
  63.     fprintf(stdout, "[%s] Start server listening at port %d .../n", currtime, port);  
  64.   
  65.     fprintf(stdout, "[%s] Waiting client connection .../n", currtime);  
  66.   
  67.   
  68.   
  69.   
  70.   
  71.     /* Run until cancelled */  
  72.   
  73.     while (1){  
  74.   
  75.         unsigned int clientlen = sizeof(client_addr);  
  76.   
  77.         memset(currtime, 0, sizeof(currtime));  
  78.   
  79.         getdate(currtime);  
  80.   
  81.   
  82.   
  83.         /* Wait for client connection */  
  84.   
  85.         if ((clientsock = accept(serversock, (struct sockaddr *) &client_addr, &clientlen)) < 0){  
  86.   
  87.             die("Failed to accept client connection");  
  88.   
  89.         }  
  90.   
  91.   
  92.   
  93.         /* Use child process new connection */  
  94.   
  95.         if ( fork() == 0 ){  
  96.   
  97.             HandleClient(clientsock, client_addr);  
  98.   
  99.         } else {  
  100.   
  101.             wait(NULL);   
  102.   
  103.         }  
  104.   
  105.   
  106.   
  107.         /* Not use close socket connection */  
  108.   
  109.         close(clientsock);  
  110.   
  111.     }  
  112.   
  113.   
  114.   
  115. }  
  116.   
  117.   
  118.   
  119.   
  120.   
  121.   
  122.   
  123. /** 
  124.  
  125.  * 获取一个连接,读取连接客户端发送的请求数据,把请求数据叫给请求解析函数进行解析 
  126.  
  127.  * 
  128.  
  129.  */  
  130.   
  131. static void HandleClient( int client_sock, struct sockaddr_in client_addr ){  
  132.   
  133.     char buf[REQUEST_MAX_SIZE];  
  134.   
  135.   
  136.   
  137.     if ( read(client_sock, buf, REQUEST_MAX_SIZE) < 0){  
  138.   
  139.         SendError( client_sock, 500, "Internal Server Error""""Client request not success." );  
  140.   
  141.         die("read sock");  
  142.   
  143.     }  
  144.   
  145.     ParseRequest( client_sock, client_addr, buf );  
  146.   
  147.   
  148.   
  149.     close(client_sock);  
  150.   
  151.     exit(0);  
  152.   
  153. }  
  154.   
  155.   
  156.   
  157.   
  158.   
  159. /** 
  160.  
  161.  * 解析一个请求,解析出GET/HEAD方法,需要请求的文件,协议版本等,构造成结构体提交给处理函数 
  162.  
  163.  * 
  164.  
  165.  */  
  166.   
  167. static int ParseRequest( int client_sock, struct sockaddr_in client_addr, char *req ){  
  168.   
  169.     char **buf, **method_buf, **query_buf, currtime[32], cwd[1024], tmp_path[1536], pathinfo[512], path[256], file[256], log[1024];  
  170.   
  171.     int line_total, method_total, query_total, i;  
  172.   
  173.     struct st_request_info request_info;  
  174.   
  175.   
  176.   
  177.     /* Split client request */  
  178.   
  179.     getdate(currtime);  
  180.   
  181.     explode(req, '/n', &buf, &line_total);  
  182.   
  183.   
  184.   
  185.     /* Print log message  */  
  186.   
  187.     memset(log, 0, sizeof(log));  
  188.   
  189.     sprintf(log, "[%s] %s %s/n", currtime, inet_ntoa(client_addr.sin_addr), buf[0]);  
  190.   
  191.     WriteLog(log);  
  192.   
  193.   
  194.   
  195.     /* Check request is empty */  
  196.   
  197.     if (strcmp(buf[0], "/n") == 0 || strcmp(buf[0], "/r/n") == 0){  
  198.   
  199.         SendError( client_sock, 400, "Bad Request""""Can't parse request." );  
  200.   
  201.     }  
  202.   
  203.   
  204.   
  205.     /* Check method is implement */  
  206.   
  207.     explode(buf[0], ' ', &method_buf, &method_total);  
  208.   
  209.     if ( strcmp( strtolower(method_buf[0]), "get") != 0 &&  strcmp( strtolower(method_buf[0]), "head") != 0 ){  
  210.   
  211.         SendError( client_sock, 501, "Not Implemented""""That method is not implemented." );  
  212.   
  213.     }  
  214.   
  215.     explode(method_buf[1], '?', &query_buf, &query_total);  
  216.   
  217.   
  218.   
  219.     /* Make request data */   
  220.   
  221.     getcwd(cwd, sizeof(cwd));  
  222.   
  223.     strcpy(pathinfo, query_buf[0]);  
  224.   
  225.     substr(query_buf[0], 0, strrpos(pathinfo, '/')+1, path);  
  226.   
  227.     substr(query_buf[0], strrpos(pathinfo, '/')+1, 0, file);  
  228.   
  229.       
  230.   
  231.     /* Pad request struct */  
  232.   
  233.     memset(&request_info, 0, sizeof(request_info));  
  234.   
  235.     strcat(cwd, pathinfo);  
  236.   
  237.   
  238.   
  239.     request_info.method         = method_buf[0];  
  240.   
  241.     request_info.pathinfo       = pathinfo;  
  242.   
  243.     request_info.query          = (query_total == 2 ? query_buf[1] : "");  
  244.   
  245.     request_info.protocal       = strtolower(method_buf[2]);  
  246.   
  247.     request_info.path           = path;  
  248.   
  249.     request_info.file           = file;  
  250.   
  251.     request_info.physical_path  = cwd;  
  252.   
  253.   
  254.   
  255.     /* Is a directory pad default index page */  
  256.   
  257.     memset(tmp_path, 0, sizeof(tmp_path));  
  258.   
  259.     strcpy(tmp_path, cwd);  
  260.   
  261.     if ( is_dir(tmp_path) ){  
  262.   
  263.         strcat(tmp_path, g_dir_index);  
  264.   
  265.         if ( file_exists(tmp_path) ){  
  266.   
  267.             request_info.physical_path = tmp_path;  
  268.   
  269.         }  
  270.   
  271.     }  
  272.   
  273.       
  274.   
  275.     /* Debug message */  
  276.   
  277.     if ( g_is_debug ){  
  278.   
  279.         fprintf(stderr, "[ Request ]/n");  
  280.   
  281.         for(i=0; i<line_total; i++){  
  282.   
  283.             fprintf(stderr, "%s/n", buf[i]);  
  284.   
  285.         }  
  286.   
  287.     }  
  288.   
  289.   
  290.   
  291.     /* Process client request */  
  292.   
  293.     ProcRequest( client_sock, client_addr, request_info );  
  294.   
  295.   
  296.   
  297.     return 0;  
  298.   
  299. }  
  300.   
  301.   
  302.   
  303.   
  304.   
  305. /** 
  306.  
  307.  * 处理函数按照解析出来的请求内容进行数据返回,返回文件/目录列表或者提示错误 
  308.  
  309.  * 
  310.  
  311.  */  
  312.   
  313. static int ProcRequest( int client_sock, struct sockaddr_in client_addr, struct st_request_info request_info ){  
  314.   
  315.     char buf[128];  
  316.   
  317.   
  318.   
  319.     /* File is exist or has access permission */  
  320.   
  321.     if ( !file_exists( request_info.physical_path ) ){  
  322.   
  323.         memset(buf, 0, sizeof(buf));  
  324.   
  325.         sprintf(buf, "File %s not found.", request_info.pathinfo);  
  326.   
  327.         SendError( client_sock, 404, "Not Found""", buf);  
  328.   
  329.     }  
  330.   
  331.     if ( access(request_info.physical_path, R_OK) != 0 ){  
  332.   
  333.         memset(buf, 0, sizeof(buf));  
  334.   
  335.         sprintf(buf, "File %s is protected.", request_info.pathinfo);  
  336.   
  337.         SendError( client_sock, 403, "Forbidden""", buf);  
  338.   
  339.     }  
  340.   
  341.   
  342.   
  343.     /* Check target is regular file or directory */  
  344.   
  345.     if ( is_file(request_info.physical_path) == 1 ){  
  346.   
  347.         SendFile( client_sock,  request_info.physical_path, request_info.pathinfo );  
  348.   
  349.   
  350.   
  351.     } else if ( is_dir(request_info.physical_path) == 1 ){   
  352.   
  353.         /* Is a directory choose browse dir list */  
  354.   
  355.         if ( g_is_browse ){  
  356.   
  357.             SendDirectory( client_sock, request_info.physical_path, request_info.pathinfo );  
  358.   
  359.   
  360.   
  361.         } else {  
  362.   
  363.             memset(buf, 0, sizeof(buf));  
  364.   
  365.             sprintf(buf, "File %s is protected.", request_info.pathinfo);  
  366.   
  367.             SendError( client_sock, 403, "Forbidden""", buf);  
  368.   
  369.         }  
  370.   
  371.   
  372.   
  373.     } else {  
  374.   
  375.         memset(buf, 0, sizeof(buf));  
  376.   
  377.         sprintf(buf, "File %s is protected.", request_info.pathinfo);  
  378.   
  379.         SendError( client_sock, 403, "Forbidden""", buf);       
  380.   
  381.     }  
  382.   
  383.   
  384.   
  385.     return 0;  
  386.   
  387. }  

  主要核心的函数就是这四个,如果要了解其他函数,包括字符串处理,HTTP头信息发送,错误发送,读取文件,遍历目录等等请下载源码回去研究。源码下载地址:http://heiyeluren.googlecode.com/files/tmhttpd-1.0.0_alpha.tar.gz

 

【 结束 】

其实还有很多功能能够增加的,比如性能,采用 select 或者 epool 肯定要比单纯的fork性能更好,另外增加处理动态内容,比如CGI,能够接受POST请求,能够作为一个反向代理存在,能够具有缓存功能,能够缓存请求 过的文件到内存中,能够具有插件扩展功能等等,都是需要进一步提升的。

参考文档:  

  • RFC 1945: http://www.w3.org/Protocols/rfc1945/rfc1945
  • RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616.html