• Tinyhttpd for Windows(学习型的项目,才500多行代码)


    前言

    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 "  
    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 ", (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] = '';  
    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] = '';  
    122.   
    123.     if (_stricmp(method, "GET") == 0)  
    124.     {  
    125.         query_string = url;  
    126.         while ((*query_string != '?') && (*query_string != ''))  
    127.             query_string++;  
    128.   
    129.         if (*query_string == '?')  
    130.         {  
    131.             // URL带参数,当成CGI处理  
    132.             cgi = 1;  
    133.             *query_string = '';  
    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] = '';  
    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(" ", buf))  
    203.         {  
    204.             buf[15] = '';  
    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 = ' ';  
    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 = '';  
    296.     int n;  
    297.   
    298.     while ((i < size - 1) && (c != ' '))  
    299.     {  
    300.         n = recv(sock, &c, 1, 0);  
    301.         /* DEBUG printf("%02X ", c); */  
    302.         if (n > 0)  
    303.         {  
    304.             if (c == ' ')  
    305.             {  
    306.                 n = recv(sock, &c, 1, MSG_PEEK);  
    307.                 /* DEBUG printf("%02X ", c); */  
    308.                 if ((n > 0) && (c == ' '))  
    309.                 {  
    310.                     recv(sock, &c, 1, 0);  
    311.                 }  
    312.                 else  
    313.                 {  
    314.                     c = ' ';  
    315.                 }  
    316.             }  
    317.             buf[i] = c;  
    318.             i++;  
    319.         }  
    320.         else  
    321.         {  
    322.             c = ' ';  
    323.         }  
    324.     }  
    325.     buf[i] = '';  
    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 "  
    340.         SERVER_STRING   
    341.         "Content-Type: text/html ";  
    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 "  
    352.         SERVER_STRING   
    353.         "Content-Type: text/html "  
    354.         "<HTML><TITLE>Not Found</TITLE> "  
    355.         "<BODY><P>The server could not fulfill "  
    356.         "your request because the resource specified "  
    357.         "is unavailable or nonexistent. "  
    358.         "</BODY></HTML> ";  
    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 "  
    371.         SERVER_STRING   
    372.         "Content-Type: text/html "  
    373.         "<HTML><HEAD><TITLE>Method Not Implemented "  
    374.         "</TITLE></HEAD> "  
    375.         "<BODY><P>HTTP request method not supported.</P> "  
    376.         "</BODY></HTML> ";  
    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 "  
    388.         "Content-Type: text/html "  
    389.         "<P>Error prohibited CGI execution.</P> ";  
    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 "  
    401.         "Content-Type: text/html "  
    402.         "<P>Your browser sent a bad request, such as a POST without a Content-Length.</P> ";  
    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(" ", 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 ", 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 ", (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

  • 相关阅读:
    微信网页授权功能来获取用户信息(昵称或头像)之php实现
    你人生的那口井挖好了吗?
    java项目打jar包
    Oracle 客户端配置笔记
    资源管理右键卡住的问题
    Java Web 项目学习(二) 开发注册功能
    Java Web 项目学习(三) 过滤敏感词 前缀树 反射 类加载
    Java Web 项目学习(二) 检查登录状态
    Java Web 项目学习(二)账号设置
    Java Web 项目学习(二) 显示登录信息
  • 原文地址:https://www.cnblogs.com/findumars/p/7123512.html
Copyright © 2020-2023  润新知