Tinyhttpd for Windows

Posted 朝闻道

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tinyhttpd for Windows相关的知识,希望对你有一定的参考价值。

TinyHTTPd forWindows

 

 

前言

TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.NET/,源代码下载:https://sourceforge.Net/projects/tinyhttpd/,因为是学习型的代码,已经有好多年没更新了,也没什么更新必要,整个代码才500多行,10多个函数,对于学习HTTP服务器的原理来说非常有帮助,把代码读一遍,再按照执行处理流程调试一下,基本上可以搞清楚Web服务器在收到静态页面请求和CGI请求的一些基本处理逻辑。源代码的注释我这里就不讲了,本身代码比较简单,而且网上这样的文章汗牛充栋,可以去后面的参考文档阅读下。

本文主要是将TinyHTTPd进行一些简单移植,使其可以在Windows上面运行调试,让只有Windows开发调试环境的小伙伴也能够学习学习。

 

 

修改明细

支持Windows部分

1、  Windows下的socket支持(微小改动,变量,宏调整等等)。

2、  接入基于Windows平台的一个微型线程池库(项目在用),对于每个新的http请求抛到线程池处理,源代码使用的是基于Linux的pthread。

3、  部分字符串比较函数修改为windows平台的对应支持函数,以及其它一些相应兼容性修改。

4、  CGI部分支持了Python脚本和Windows批处理脚本,其它的如果需要支持可以进行修改,另外,CGI部分目前实现比较粗糙,完全是为了体现一下CGI请求的原理(POST的CGI处理仅仅是把提交的数据返回给客户端显示)。

5、  CGI部分使用了匿名管道,匿名管道不支持异步读写数据,因此需要控制读写匿名管道的次数(建议仅读一次,并且CGI的返回字符长度不要超过2048字节)。

 

优化

1、  给客户端返回数据时,合并了需要发送的数据,使用send一次发送,而不是每次发送几个字符,不过这里没有进行send失败的错误处理,学习代码吧,如果是商用代码,send失败是需要重试的(当然,商用代码一般都是使用异步socket),这个地方之前作者分成多次发送的目的可能是为了体现网络数据传输的原理。

2、  合并了一些公用代码。

3、  代码里面直接写死了绑定80端口,如果需要由系统自动分配端口就把这句代码:u_short port = 80修改为u_short port = 0,绑死80端口是为了使用浏览器测试时比较方便。

 

bug修改

1、  cat函数里面使用fgets读取文件进行数据发送时,有可能发送不完整。

 

资源补充

1、  补充批处理的cgi支持和py脚本的cgi支持(都比较简单,需要注意的是py脚本支持需要本地安装python2.x的环境)。

 

 

测试情况

主页

URL:http://127.0.0.1/

 

 

其它静态页面

URL:http://127.0.0.1/detect.html

 

 

Python CGI

URL:http://127.0.0.1/cgipy?p.py

 

 

批处理 CGI

URL:http://127.0.0.1/cgibat?p.bat

 

 

POST CGI

URL:http://127.0.0.1/index.html

 

 

 

 

源代码

本来不想帖代码的,还是贴一点吧,工程下载请点这里

 

[cpp] view plain copy
 
 
  1. /* ------------------------------------------------------------------------- 
  2. //  文件名     :   tinyhttp.cpp 
  3. //  创建者     :   magictong 
  4. //  创建时间    :   2016/11/16 17:13:55 
  5. //  功能描述    :   support windows of tinyhttpd, use mutilthread... 
  6. // 
  7. //  $Id: $ 
  8. // -----------------------------------------------------------------------*/  
  9.   
  10. /* J. David\'s webserver */  
  11. /* This is a simple webserver. 
  12.  * Created November 1999 by J. David Blackstone. 
  13.  * CSE 4344 (Network concepts), Prof. Zeigler 
  14.  * University of Texas at Arlington 
  15.  */  
  16. /* This program compiles for Sparc Solaris 2.6. 
  17.  * To compile for Linux: 
  18.  *  1) Comment out the #include <pthread.h> line. 
  19.  *  2) Comment out the line that defines the variable newthread. 
  20.  *  3) Comment out the two lines that run pthread_create(). 
  21.  *  4) Uncomment the line that runs accept_request(). 
  22.  *  5) Remove -lsocket from the Makefile. 
  23.  */   
  24.   
  25. #include "stdafx.h"  
  26. #include "windowcgi.h"  
  27. #include "ThreadProc.h"  
  28.   
  29. #include <stdio.h>  
  30. #include <ctype.h>  
  31. #include <stdlib.h>  
  32. #include <string.h>  
  33. #include <sys/stat.h>  
  34. #include <sys/types.h>  
  35. #include <WinSock2.h>  
  36.   
  37. #pragma comment(lib, "wsock32.lib")  
  38. #pragma warning(disable : 4267)  
  39.   
  40. #define ISspace(x) isspace((int)(x))  
  41. #define SERVER_STRING "Server: tinyhttp /0.1.0\\r\\n"  
  42. // -------------------------------------------------------------------------  
  43.   
  44. // -------------------------------------------------------------------------  
  45. // 类名       : CTinyHttp  
  46. // 功能       :   
  47. // 附注       :   
  48. // -------------------------------------------------------------------------  
  49. class CTinyHttp  
  50. {  
  51. public:  
  52.     typedef struct tagSocketContext  
  53.     {  
  54.         SOCKET socket_Client;  
  55.         tagSocketContext() : socket_Client(-1) {}  
  56.     } SOCKET_CONTEXT, *PSOCKET_CONTEXT;  
  57.   
  58. /**********************************************************************/    
  59. /* A request has caused a call to accept() on the server port to  
  60.  * return.  Process the request appropriately.  
  61.  * Parameters: the socket connected to the client */    
  62. /**********************************************************************/    
  63. void accept_request(nilstruct&, SOCKET_CONTEXT& socket_context)  
  64. {  
  65.     printf("Tid[%u] accept_request\\n", (unsigned int)::GetCurrentThreadId());  
  66.   
  67. #ifdef _DEBUG  
  68.     // 测试是否可以并发  
  69.     ::Sleep(200);  
  70. #endif  
  71.   
  72.     char buf[1024] = {0};  
  73.     int numchars = 0;  
  74.     char method[255] = {0};  
  75.     char url[255] = {0};  
  76.     char path[512] = {0};  
  77.     int i = 0, j = 0;  
  78.     struct stat st;  
  79.     int cgi = 0;      /* becomes true if server decides this is a CGI program */  
  80.     char* query_string = NULL;  
  81.     SOCKET client = socket_context.socket_Client;  
  82.   
  83.     numchars = get_line(client, buf, sizeof(buf));  
  84.   
  85.     // 获取HTTP的请求方法名  
  86.     while (j < numchars && !ISspace(buf[j]) && (i < sizeof(method) - 1))  
  87.     {  
  88.         method[i] = buf[j];  
  89.         i++; j++;  
  90.     }  
  91.     method[i] = \'\\0\';  
  92.   
  93.     if (_stricmp(method, "GET") != 0 && _stricmp(method, "POST"))      // 只处理GET请求  
  94.     {  
  95.         if (numchars > 0)  
  96.         {  
  97.             discardheaders(client);  
  98.         }  
  99.   
  100.         unimplemented(client);  
  101.         closesocket(client);  
  102.         return;  
  103.     }  
  104.   
  105.     if (_stricmp(method, "POST") == 0)  
  106.         cgi = 1; // POST请求,当成CGI处理  
  107.   
  108.     // 获取到URL路径,存放到url字符数组里面  
  109.     i = 0;  
  110.     while (ISspace(buf[j]) && (j < sizeof(buf)))  
  111.     {  
  112.         j++;  
  113.     }  
  114.       
  115.     while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))  
  116.     {  
  117.         url[i] = buf[j];  
  118.         i++;  
  119.         j++;  
  120.     }  
  121.     url[i] = \'\\0\';  
  122.   
  123.     if (_stricmp(method, "GET") == 0)  
  124.     {  
  125.         query_string = url;  
  126.         while ((*query_string != \'?\') && (*query_string != \'\\0\'))  
  127.             query_string++;  
  128.   
  129.         if (*query_string == \'?\')  
  130.         {  
  131.             // URL带参数,当成CGI处理  
  132.             cgi = 1;  
  133.             *query_string = \'\\0\';  
  134.             query_string++;  
  135.         }  
  136.     }  
  137.   
  138.     sprintf_s(path, 512, "htdocs%s", url);  
  139.     if (path[strlen(path) - 1] == \'/\')  
  140.     {  
  141.         // 补齐  
  142.         strcat_s(path, 512, "index.html");  
  143.     }  
  144.       
  145.     if (stat(path, &st) == -1)  
  146.     {  
  147.         // 文件不存在  
  148.         if (numchars > 0)  
  149.         {  
  150.             discardheaders(client);  
  151.         }  
  152.   
  153.         not_found(client);  
  154.     }  
  155.     else  
  156.     {  
  157.         // 如果是文件夹则补齐  
  158.         if ((st.st_mode & S_IFMT) == S_IFDIR)  
  159.             strcat_s(path, 512, "/index.html");  
  160.   
  161.         if (st.st_mode & S_IEXEC)  
  162.             cgi = 1; // 具有可执行权限  
  163.   
  164.         if (!cgi)  
  165.         {  
  166.             serve_file(client, path);  
  167.         }  
  168.         else  
  169.         {  
  170.             execute_cgi(client, path, method, query_string);  
  171.         }  
  172.     }  
  173.   
  174.     closesocket(client);  
  175. }  
  176.   
  177. /**********************************************************************/  
  178. /* Execute a CGI script.  Will need to set environment variables as 
  179.  * appropriate. 
  180.  * Parameters: client socket descriptor 
  181.  *             path to the CGI script */  
  182. /**********************************************************************/  
  183. void execute_cgi(SOCKET client, const char *path, const char* method, const char* query_string)  
  184. {  
  185.     char buf[1024] = {0};  
  186.     int cgi_output[2] = {0};  
  187.     int cgi_input[2] = {0};  
  188.   
  189.     int i = 0;  
  190.     char c = 0;  
  191.     int numchars = 1;  
  192.     int content_length = -1;  
  193.   
  194.     buf[0] = \'A\'; buf[1] = \'\\0\';  
  195.     if (_stricmp(method, "GET") == 0)  
  196.     {  
  197.         discardheaders(client);  
  198.     }  
  199.     else    /* POST */  
  200.     {  
  201.         numchars = get_line(client, buf, sizeof(buf));  
  202.         while ((numchars > 0) && strcmp("\\n", buf))  
  203.         {  
  204.             buf[15] = \'\\0\';  
  205.             if (_stricmp(buf, "Content-Length:") == 0)  
  206.             {  
  207.                 content_length = atoi(&(buf[16]));  
  208.             }  
  209.   
  210.             numchars = get_line(client, buf, sizeof(buf));  
  211.         }  
  212.   
  213.         if (content_length == -1)  
  214.         {  
  215.             bad_request(client);  
  216.             return;  
  217.         }  
  218.     }  
  219.   
  220.     CWinCGI cgi;  
  221.     if (!cgi.Exec(path, query_string))  
  222.     {  
  223.         bad_request(client);  
  224.         return;  
  225.     }  
  226.   
  227.     //SOCKET client, const char *path, const char* method, const char* query_string  
  228.     if (_stricmp(method, "POST") == 0)  
  229.     {  
  230.         for (i = 0; i < content_length; i++)  
  231.         {  
  232.             recv(client, &c, 1, 0);  
  233.             cgi.Write((PBYTE)&c, 1);  
  234.         }  
  235.   
  236.         c = \'\\n\';  
  237.         cgi.Write((PBYTE)&c, 1);  
  238.     }  
  239.   
  240.     cgi.Wait();  
  241.     char outBuff[2048] = {0};  
  242.     cgi.Read((PBYTE)outBuff, 2047);  
  243.     send(client, outBuff, strlen(outBuff), 0);  
  244. }  
  245.   
  246. /**********************************************************************/  
  247. /* Put the entire contents of a file out on a socket.  This function 
  248.  * is named after the UNIX "cat" command, because it might have been 
  249.  * easier just to do something like pipe, fork, and exec("cat"). 
  250.  * Parameters: the client socket descriptor 
  251.  *             FILE pointer for the file to cat */  
  252. /**********************************************************************/  
  253. void cat(SOCKET client, FILE *resource)  
  254. {  
  255.     char buf[1024] = {0};  
  256.   
  257.     do   
  258.     {  
  259.         fgets(buf, sizeof(buf), resource);  
  260.         size_t len = strlen(buf);  
  261.         if (len > 0)  
  262.         {  
  263.             send(client, buf, len, 0);  
  264.         }  
  265.     } while (!feof(resource));  
  266. }  
  267.   
  268. /**********************************************************************/  
  269. /* Print out an error message with perror() (for system errors; based 
  270.  * on value of errno, which indicates system call errors) and exit the 
  271.  * program indicating an error. */  
  272. /**********************************************************************/  
  273. void error_die(const char *sc)  
  274. {  
  275.     perror(sc);  
  276.     exit(1);  
  277. }    
  278.     
  279. /**********************************************************************/  
  280. /* Get a line from a socket, whether the line ends in a newline, 
  281.  * carriage return, or a CRLF combination.  Terminates the string read 
  282.  * with a null character.  If no newline indicator is found before the 
  283.  * end of the buffer, the string is terminated with a null.  If any of 
  284.  * the above three line terminators is read, the last character of the 
  285.  * string will be a linefeed and the string will be terminated with a 
  286.  * null character. 
  287.  * Parameters: the socket descriptor 
  288.  *             the buffer to save the data in 
  289.  *             the size of the buffer 
  290.  * Returns: the number of bytes stored (excluding null) */  
  291. /**********************************************************************/  
  292. int get_line(SOCKET sock, char *buf, int size)  
  293. {  
  294.     int i = 0;  
  295.     char c = \'\\0\';  
  296.     int n;  
  297.   
  298.     while ((i < size - 1) && (c != \'\\n\'))  
  299.     {  
  300.         n = recv(sock, &c, 1, 0);  
  301.         /* DEBUG printf("%02X\\n", c); */  
  302.         if (n > 0)  
  303.         {  
  304.             if (c == \'\\r\')  
  305.             {  
  306.                 n = recv(sock, &c, 1, MSG_PEEK);  
  307.                 /* DEBUG printf("%02X\\n", c); */  
  308.                 if ((n > 0) && (c == \'\\n\'))  
  309.                 {  
  310.                     recv(sock, &c, 1, 0);  
  311.                 }  
  312.                 else  
  313.                 {  
  314.                     c = \'\\n\';  
  315.                 }  
  316.             }  
  317.             buf[i] = c;  
  318.             i++;  
  319.         }  
  320.         else  
  321.         {  
  322.             c = \'\\n\';  
  323.         }  
  324.     }  
  325.     buf[i] = \'\\0\';  
  326.   
  327.     return(i);  
  328. }    
  329.     
  330. /**********************************************************************/  
  331. /* Return the informational HTTP headers about a file. */  
  332. /* Parameters: the socket to print the headers on 
  333.  *             the name of the file */  
  334. /**********************************************************************/  
  335. void headers(SOCKET client, const char *filename)  
  336. {  
  337.     (void)filename;  
  338.   
  339.     char* pHeader = "HTTP/1.0 200 OK\\r\\n"\\  
  340.         SERVER_STRING \\  
  341.         "Content-Type: text/html\\r\\n\\r\\n";  
  342.   
  343.     send(client, pHeader, strlen(pHeader), 0);  
  344. }    
  345.     
  346. /**********************************************************************/  
  347. /* Give a client a 404 not found status message. */  
  348. /**********************************************************************/  
  349. void not_found(SOCKET client)    
  350. {  
  351.     char* pResponse = "HTTP/1.0 404 NOT FOUND\\r\\n"\\  
  352.         SERVER_STRING \\  
  353.         "Content-Type: text/html\\r\\n\\r\\n"\\  
  354.         "<HTML><TITLE>Not Found</TITLE>\\r\\n"\\  
  355.         "<BODY><P>The server could not fulfill\\r\\n"\\  
  356.         "your request because the resource specified\\r\\n"\\  
  357.         "is unavailable or nonexistent.\\r\\n"\\  
  358.         "</BODY></HTML>\\r\\n";  
  359.   
  360.     send(client, pResponse, strlen(pResponse), 0);  
  361. }  
  362.   
  363. /**********************************************************************/  
  364. /* Inform the client that the requested web method has not been 
  365.  * implemented. 
  366.  * Parameter: the client socket */  
  367. /**********************************************************************/  
  368. void unimplemented(SOCKET client)  
  369. {  
  370.     char* pResponse = "HTTP/1.0 501 Method Not Implemented\\r\\n"\\  
  371.         SERVER_STRING \\  
  372.         "Content-Type: text/html\\r\\n\\r\\n"\\  
  373.         "<HTML><HEAD><TITLE>Method Not Implemented\\r\\n"\\  
  374.         "</TITLE></HEAD>\\r\\n"\\  
  375.         "<BODY><P>HTTP request method not supported.</P>\\r\\n"\\  
  376.         "</BODY></HTML>\\r\\n";  
  377.   
  378.     send(client, pResponse, strlen(pResponse), 0);  
  379. }  
  380.   
  381. /**********************************************************************/  
  382. /* Inform the client that a CGI script could not be executed. 
  383.  * Parameter: the client socket descriptor. */  
  384. /**********************************************************************/  
  385. void cannot_execute(SOCKET client)  
  386. {  
  387.     char* pResponse = "HTTP/1.0 500 Internal Server Error\\r\\n"\\  
  388.         "Content-Type: text/html\\r\\n\\r\\n"\\  
  389.         "<P>Error prohibited CGI execution.</P>\\r\\n";  
  390.   
  391.     send(client, pResponse, strlen(pResponse), 0);  
  392. }  
  393.   
  394. /**********************************************************************/  
  395. /* Inform the client that a request it has made has a problem. 
  396.  * Parameters: client socket */  
  397. /**********************************************************************/  
  398. void bad_request(SOCKET client)  
  399. {  
  400.     char* pResponse = "HTTP/1.0 400 BAD REQUEST\\r\\n"\\  
  401.         "Content-Type: text/html\\r\\n\\r\\n"\\  
  402.         "<P>Your browser sent a bad request, such as a POST without a Content-Length.</P>\\r\\n";  
  403.   
  404.     send(client, pResponse, strlen(pResponse), 0);  
  405. }  
  406.   
  407. /**********************************************************************/  
  408. /* Send a regular file to the client.  Use headers, and report 
  409.  * errors to client if they occur. 
  410.  * Parameters: a pointer to a file structure produced from the socket 
  411.  *              file descriptor 
  412.  *             the name of the file to serve */  
  413. /**********************************************************************/  
  414. void serve_file(SOCKET client, const char *filename)  
  415. {  
  416.     FILE *resource = NULL;  
  417.     discardheaders(client);  
  418.   
  419.     fopen_s(&resource, filename, "r");  
  420.     if (resource == NULL)  
  421.     {  
  422.         not_found(client);  
  423.     }  
  424.     else    
  425.     {  
  426.         headers(client, filename);  
  427.         cat(client, resource);  
  428.     }  
  429.     fclose(resource);  
  430. }  
  431.   
  432. // -------------------------------------------------------------------------  
  433. // 函数       : discardheaders  
  434. // 功能       : 清除http头数据(从网络中全部读出来)  
  435. // 返回值  : void   
  436. // 参数       : SOCKET client  
  437. // 附注       :   
  438. // -------------------------------------------------------------------------  
  439. void discardheaders(SOCKET client)  
  440. {  
  441.     char buf[1024] = {0};  
  442.     int numchars = 1;  
  443.     while ((numchars > 0) && strcmp("\\n", buf))  /* read & discard headers */  
  444.     {  
  445.         numchars = get_line(client, buf, sizeof(buf));  
  446.     }  
  447. }  
  448.   
  449. /**********************************************************************/  
  450. /* This function starts the process of listening for web connections 
  451.  * on a specified port.  If the port is 0, then dynamically allocate a 
  452.  * port and modify the original port variable to reflect the actual 
  453.  * port. 
  454.  * Parameters: pointer to variable containing the port to connect on 
  455.  * Returns: the socket */  
  456. /**********************************************************************/  
  457. SOCKET startup(u_short* port)  
  458. {  
  459.     SOCKET httpd = 0;  
  460.     struct sockaddr_in name = {0};  
  461.   
  462.     httpd = socket(AF_INET, SOCK_STREAM, 0);  
  463.     if (httpd == INVALID_SOCKET)  
  464.     {  
  465.         error_die("startup socket");  
  466.     }  
  467.       
  468.     name.sin_family = AF_INET;  
  469.     name.sin_port = htons(*port);  
  470.     name.sin_addr.s_addr = inet_addr("127.0.0.1");  
  471.     if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)    
  472.     {  
  473.         error_die("startup bind");  
  474.     }  
  475.       
  476.     if (*port == 0)  /* if dynamically allocating a port */    
  477.     {  
  478.         int namelen = sizeof(name);  
  479.         if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)  
  480.         {  
  481.             error_die("getsockname");  
  482.         }  
  483.   
  484.         *port = ntohs(name.sin_port);  
  485.     }  
  486.   
  487.     if (listen(httpd, 5) < 0)  
  488.     {  
  489.         error_die("listen");  
  490.     }  
  491.   
  492.     return httpd;  
  493. }  
  494.   
  495. }; // End Class CTinyHttp  
  496.   
  497. int _tmain(int argc, _TCHAR* argv[])  
  498. {  
  499.     SOCKET server_sock = INVALID_SOCKET;  
  500.     //u_short port = 0;  
  501.     u_short port = 80;  
  502.     struct sockaddr_in client_name = {0};  
  503.     int client_name_len = sizeof(client_name);  
  504.     typedef CMultiTaskThreadPoolT<CTinyHttp, CTinyHttp::SOCKET_CONTEXT, nilstruct, 5, CComMultiThreadModel::AutoCriticalSection> CMultiTaskThreadPool;  
  505.     CTinyHttp tinyHttpSvr;  
  506.   
  507.     // init socket  
  508.     WSADATA wsaData = {0};  
  509.     WSAStartup(MAKEWORD(2, 2), &wsaData);  
  510.   
  511.     server_sock = tinyHttpSvr.startup(&port);  
  512.     printf("httpd running on port: %d\\n", port);  
  513.     CMultiTaskThreadPool m_threadpool(&tinyHttpSvr, &CTinyHttp::accept_request);  
  514.   
  515.     while (1)  
  516.     {  
  517.         CTinyHttp::SOCKET_CONTEXT socket_context;  
  518.         socket_context.socket_Client = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len);  
  519.         if (socket_context.socket_Client == INVALID_SOCKET)  
  520.         {  
  521.             tinyHttpSvr.error_die("accept");  
  522.         }  
  523.   
  524.         printf("Tid[%u] accetp new connect: %u\\n", (unsigned int)::GetCurrentThreadId(), (unsigned int)socket_context.socket_Client);  
  525.         m_threadpool.AddTask(socket_context);  
  526.     }  
  527.   
  528.     // can not to run this  
  529.     m_threadpool.EndTasks();  
  530.     closesocket(server_sock);  
  531.     WSACleanup();  
  532.     return 0;  
  533. }  
  534. // -------------------------------------------------------------------------  
  535. // $Log: $  

 

 

 

参考文档

[1] tinyhttp源码阅读(注释) http://www.cnblogs.com/oloroso/p/5459196.html

[2] 【源码剖析】tinyhttpd —— C 语言实现最简单的 HTTP 服务器http://blog.csdn.net/jcjc918/article/details/42129311

[3] Tinyhttp源码分析http://blog.csdn.net/yzhang6_10/article/details/51534409

[4] tinyhttpd源码详解http://blog.csdn.net/baiwfg2/article/details/45582723

[5] CGI介绍 http://www.jdon.com/idea/cgi.htm

 

http://blog.csdn.net/magictong/article/details/53201038

以上是关于Tinyhttpd for Windows的主要内容,如果未能解决你的问题,请参考以下文章

Tinyhttpd精读解析

C开源代码学习1 Tinyhttpd

升级tinyhttpd-0.1.0,让其支持网页显示图像

HTTP服务器的本质:tinyhttpd源码分析及拓展

Tinyhttpd 知识点

关于Tinyhttpd最全注释解析