Tinyhttpd for Windows

article/2025/10/29 9:13:58

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请求抛到线程池处理,源代码使用的是基于linuxpthread

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

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

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的环境)。

测试情况

主页

URLhttp://127.0.0.1/


其它静态页面

URLhttp://127.0.0.1/detect.html


Python CGI

URLhttp://127.0.0.1/cgipy?p.py


批处理 CGI

URLhttp://127.0.0.1/cgibat?p.bat


POST CGI

URLhttp://127.0.0.1/index.html



源代码

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

/* -------------------------------------------------------------------------
//	文件名		:	tinyhttp.cpp
//	创建者		:	magictong
//	创建时间	:	2016/11/16 17:13:55
//	功能描述	:	support windows of tinyhttpd, use mutilthread...
//
//	$Id: $
// -----------------------------------------------------------------------*//* J. David's webserver */
/* This is a simple webserver.* Created November 1999 by J. David Blackstone.* CSE 4344 (Network concepts), Prof. Zeigler* University of Texas at Arlington*/
/* This program compiles for Sparc Solaris 2.6.* To compile for Linux:*  1) Comment out the #include <pthread.h> line.*  2) Comment out the line that defines the variable newthread.*  3) Comment out the two lines that run pthread_create().*  4) Uncomment the line that runs accept_request().*  5) Remove -lsocket from the Makefile.*/ #include "stdafx.h"
#include "windowcgi.h"
#include "ThreadProc.h"#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <WinSock2.h>#pragma comment(lib, "wsock32.lib")
#pragma warning(disable : 4267)#define ISspace(x) isspace((int)(x))
#define SERVER_STRING "Server: tinyhttp /0.1.0\r\n"
// -------------------------------------------------------------------------// -------------------------------------------------------------------------
// 类名		: CTinyHttp
// 功能		: 
// 附注		: 
// -------------------------------------------------------------------------
class CTinyHttp
{
public:typedef struct tagSocketContext{SOCKET socket_Client;tagSocketContext() : socket_Client(-1) {}} SOCKET_CONTEXT, *PSOCKET_CONTEXT;/**********************************************************************/  
/* A request has caused a call to accept() on the server port to * return.  Process the request appropriately. * Parameters: the socket connected to the client */  
/**********************************************************************/  
void accept_request(nilstruct&, SOCKET_CONTEXT& socket_context)
{printf("Tid[%u] accept_request\n", (unsigned int)::GetCurrentThreadId());#ifdef _DEBUG// 测试是否可以并发::Sleep(200);
#endifchar buf[1024] = {0};int numchars = 0;char method[255] = {0};char url[255] = {0};char path[512] = {0};int i = 0, j = 0;struct stat st;int cgi = 0;      /* becomes true if server decides this is a CGI program */char* query_string = NULL;SOCKET client = socket_context.socket_Client;numchars = get_line(client, buf, sizeof(buf));// 获取HTTP的请求方法名while (j < numchars && !ISspace(buf[j]) && (i < sizeof(method) - 1)){method[i] = buf[j];i++; j++;}method[i] = '\0';if (_stricmp(method, "GET") != 0 && _stricmp(method, "POST"))      // 只处理GET请求{if (numchars > 0){discardheaders(client);}unimplemented(client);closesocket(client);return;}if (_stricmp(method, "POST") == 0)cgi = 1; // POST请求,当成CGI处理// 获取到URL路径,存放到url字符数组里面i = 0;while (ISspace(buf[j]) && (j < sizeof(buf))){j++;}while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))){url[i] = buf[j];i++;j++;}url[i] = '\0';if (_stricmp(method, "GET") == 0){query_string = url;while ((*query_string != '?') && (*query_string != '\0'))query_string++;if (*query_string == '?'){// URL带参数,当成CGI处理cgi = 1;*query_string = '\0';query_string++;}}sprintf_s(path, 512, "htdocs%s", url);if (path[strlen(path) - 1] == '/'){// 补齐strcat_s(path, 512, "index.html");}if (stat(path, &st) == -1){// 文件不存在if (numchars > 0){discardheaders(client);}not_found(client);}else{// 如果是文件夹则补齐if ((st.st_mode & S_IFMT) == S_IFDIR)strcat_s(path, 512, "/index.html");if (st.st_mode & S_IEXEC)cgi = 1; // 具有可执行权限if (!cgi){serve_file(client, path);}else{execute_cgi(client, path, method, query_string);}}closesocket(client);
}/**********************************************************************/
/* Execute a CGI script.  Will need to set environment variables as* appropriate.* Parameters: client socket descriptor*             path to the CGI script */
/**********************************************************************/
void execute_cgi(SOCKET client, const char *path, const char* method, const char* query_string)
{char buf[1024] = {0};int cgi_output[2] = {0};int cgi_input[2] = {0};int i = 0;char c = 0;int numchars = 1;int content_length = -1;buf[0] = 'A'; buf[1] = '\0';if (_stricmp(method, "GET") == 0){discardheaders(client);}else    /* POST */{numchars = get_line(client, buf, sizeof(buf));while ((numchars > 0) && strcmp("\n", buf)){buf[15] = '\0';if (_stricmp(buf, "Content-Length:") == 0){content_length = atoi(&(buf[16]));}numchars = get_line(client, buf, sizeof(buf));}if (content_length == -1){bad_request(client);return;}}CWinCGI cgi;if (!cgi.Exec(path, query_string)){bad_request(client);return;}//SOCKET client, const char *path, const char* method, const char* query_stringif (_stricmp(method, "POST") == 0){for (i = 0; i < content_length; i++){recv(client, &c, 1, 0);cgi.Write((PBYTE)&c, 1);}c = '\n';cgi.Write((PBYTE)&c, 1);}cgi.Wait();char outBuff[2048] = {0};cgi.Read((PBYTE)outBuff, 2047);send(client, outBuff, strlen(outBuff), 0);
}/**********************************************************************/
/* Put the entire contents of a file out on a socket.  This function* is named after the UNIX "cat" command, because it might have been* easier just to do something like pipe, fork, and exec("cat").* Parameters: the client socket descriptor*             FILE pointer for the file to cat */
/**********************************************************************/
void cat(SOCKET client, FILE *resource)
{char buf[1024] = {0};do {fgets(buf, sizeof(buf), resource);size_t len = strlen(buf);if (len > 0){send(client, buf, len, 0);}} while (!feof(resource));
}/**********************************************************************/
/* Print out an error message with perror() (for system errors; based* on value of errno, which indicates system call errors) and exit the* program indicating an error. */
/**********************************************************************/
void error_die(const char *sc)
{perror(sc);exit(1);
}  /**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,* carriage return, or a CRLF combination.  Terminates the string read* with a null character.  If no newline indicator is found before the* end of the buffer, the string is terminated with a null.  If any of* the above three line terminators is read, the last character of the* string will be a linefeed and the string will be terminated with a* null character.* Parameters: the socket descriptor*             the buffer to save the data in*             the size of the buffer* Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
int get_line(SOCKET sock, char *buf, int size)
{int i = 0;char c = '\0';int n;while ((i < size - 1) && (c != '\n')){n = recv(sock, &c, 1, 0);/* DEBUG printf("%02X\n", c); */if (n > 0){if (c == '\r'){n = recv(sock, &c, 1, MSG_PEEK);/* DEBUG printf("%02X\n", c); */if ((n > 0) && (c == '\n')){recv(sock, &c, 1, 0);}else{c = '\n';}}buf[i] = c;i++;}else{c = '\n';}}buf[i] = '\0';return(i);
}  /**********************************************************************/
/* Return the informational HTTP headers about a file. */
/* Parameters: the socket to print the headers on*             the name of the file */
/**********************************************************************/
void headers(SOCKET client, const char *filename)
{(void)filename;char* pHeader = "HTTP/1.0 200 OK\r\n"\SERVER_STRING \"Content-Type: text/html\r\n\r\n";send(client, pHeader, strlen(pHeader), 0);
}  /**********************************************************************/
/* Give a client a 404 not found status message. */
/**********************************************************************/
void not_found(SOCKET client)  
{char* pResponse = "HTTP/1.0 404 NOT FOUND\r\n"\SERVER_STRING \"Content-Type: text/html\r\n\r\n"\"<HTML><TITLE>Not Found</TITLE>\r\n"\"<BODY><P>The server could not fulfill\r\n"\"your request because the resource specified\r\n"\"is unavailable or nonexistent.\r\n"\"</BODY></HTML>\r\n";send(client, pResponse, strlen(pResponse), 0);
}/**********************************************************************/
/* Inform the client that the requested web method has not been* implemented.* Parameter: the client socket */
/**********************************************************************/
void unimplemented(SOCKET client)
{char* pResponse = "HTTP/1.0 501 Method Not Implemented\r\n"\SERVER_STRING \"Content-Type: text/html\r\n\r\n"\"<HTML><HEAD><TITLE>Method Not Implemented\r\n"\"</TITLE></HEAD>\r\n"\"<BODY><P>HTTP request method not supported.</P>\r\n"\"</BODY></HTML>\r\n";send(client, pResponse, strlen(pResponse), 0);
}/**********************************************************************/
/* Inform the client that a CGI script could not be executed.* Parameter: the client socket descriptor. */
/**********************************************************************/
void cannot_execute(SOCKET client)
{char* pResponse = "HTTP/1.0 500 Internal Server Error\r\n"\"Content-Type: text/html\r\n\r\n"\"<P>Error prohibited CGI execution.</P>\r\n";send(client, pResponse, strlen(pResponse), 0);
}/**********************************************************************/
/* Inform the client that a request it has made has a problem.* Parameters: client socket */
/**********************************************************************/
void bad_request(SOCKET client)
{char* pResponse = "HTTP/1.0 400 BAD REQUEST\r\n"\"Content-Type: text/html\r\n\r\n"\"<P>Your browser sent a bad request, such as a POST without a Content-Length.</P>\r\n";send(client, pResponse, strlen(pResponse), 0);
}/**********************************************************************/
/* Send a regular file to the client.  Use headers, and report* errors to client if they occur.* Parameters: a pointer to a file structure produced from the socket*              file descriptor*             the name of the file to serve */
/**********************************************************************/
void serve_file(SOCKET client, const char *filename)
{FILE *resource = NULL;discardheaders(client);fopen_s(&resource, filename, "r");if (resource == NULL){not_found(client);}else  {headers(client, filename);cat(client, resource);}fclose(resource);
}// -------------------------------------------------------------------------
// 函数		: discardheaders
// 功能		: 清除http头数据(从网络中全部读出来)
// 返回值	: void 
// 参数		: SOCKET client
// 附注		: 
// -------------------------------------------------------------------------
void discardheaders(SOCKET client)
{char buf[1024] = {0};int numchars = 1;while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */{numchars = get_line(client, buf, sizeof(buf));}
}/**********************************************************************/
/* This function starts the process of listening for web connections* on a specified port.  If the port is 0, then dynamically allocate a* port and modify the original port variable to reflect the actual* port.* Parameters: pointer to variable containing the port to connect on* Returns: the socket */
/**********************************************************************/
SOCKET startup(u_short* port)
{SOCKET httpd = 0;struct sockaddr_in name = {0};httpd = socket(AF_INET, SOCK_STREAM, 0);if (httpd == INVALID_SOCKET){error_die("startup socket");}name.sin_family = AF_INET;name.sin_port = htons(*port);name.sin_addr.s_addr = inet_addr("127.0.0.1");if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)  {error_die("startup bind");}if (*port == 0)  /* if dynamically allocating a port */  {int namelen = sizeof(name);if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1){error_die("getsockname");}*port = ntohs(name.sin_port);}if (listen(httpd, 5) < 0){error_die("listen");}return httpd;
}}; // End Class CTinyHttpint _tmain(int argc, _TCHAR* argv[])
{SOCKET server_sock = INVALID_SOCKET;//u_short port = 0;u_short port = 80;struct sockaddr_in client_name = {0};int client_name_len = sizeof(client_name);typedef CMultiTaskThreadPoolT<CTinyHttp, CTinyHttp::SOCKET_CONTEXT, nilstruct, 5, CComMultiThreadModel::AutoCriticalSection> CMultiTaskThreadPool;CTinyHttp tinyHttpSvr;// init socketWSADATA wsaData = {0};WSAStartup(MAKEWORD(2, 2), &wsaData);server_sock = tinyHttpSvr.startup(&port);printf("httpd running on port: %d\n", port);CMultiTaskThreadPool m_threadpool(&tinyHttpSvr, &CTinyHttp::accept_request);while (1){CTinyHttp::SOCKET_CONTEXT socket_context;socket_context.socket_Client = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len);if (socket_context.socket_Client == INVALID_SOCKET){tinyHttpSvr.error_die("accept");}printf("Tid[%u] accetp new connect: %u\n", (unsigned int)::GetCurrentThreadId(), (unsigned int)socket_context.socket_Client);m_threadpool.AddTask(socket_context);}// can not to run thism_threadpool.EndTasks();closesocket(server_sock);WSACleanup();return 0;
}
// -------------------------------------------------------------------------
// $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://chatgpt.dhexx.cn/article/re6UeZks.shtml

相关文章

windows安装http测试服务

祝您身体健康&#xff0c;前程似锦&#xff0c;小弟期待文章对您有帮助&#xff0c;也期待您的打赏: 目录 1. 下载 HttpMockServer工具 2. windows10 安装jdk1.8 3. jdk按照上面配置完环境变量后打开cmd&#xff0c;运行 4. 准备测试的响应数据 text.txt,内容如下: 5. 会弹…

C++ winhttp 实现文件下载器

本篇内容讲述 C winHttp 实现下载器的简单 demo&#xff0c;使用了 WinHttpOpen、WinHttpConnect、WinHttpOpenRequest、WinHttpSendRequest、WinHttpReceiveResponse、WinHttpQueryDataAvailable、WinHttpReadData、WinHttpCloseHandle 等函数。 版权声明&#xff1a;本文为C…

WinHTTP教程

最近有些忙&#xff0c;也没更新BLOG&#xff0c;这几天在捣鼓一个小玩意要用到WinHTTP API&#xff0c;发现资料很少&#xff0c;而且大都是些MFC封装的例子&#xff0c;看得我是一个头几个大。就把自己关于WinHTTP的学习总结了一下&#xff0c;仅供参考&#xff0c;各人理解可…

WinHttp c++ 介绍及应用

一、HTTP协议介绍 http协议的底层协议是TCP协议。TCP协议是基于数据流的传输方式。其又叫做“超文本传输协议”&#xff0c;为什么呢&#xff0c;因为它是将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器&#xff0c;通过因特网传送万维网文档的数据传送协议。 1…

WinHTTP

记录WinHTTP学习过程 一、什么是WinHTTP&#xff1f; WinHTTP的全称是Microsoft Windows HTTP Services&#xff0c; 它提供给开发者一个HTTP客户端应用程序接口(API)&#xff0c;通过这种API借助HTTP协议给其他的HTTP服务器发送请求。 二、WinHTTP访问流程 如上图&#xff0c;…

Java递归算法计算5的阶乘

递归 package com.etime.test019; //计算5的阶乘&#xff1b; public class Test15 {public static void main(String[] args) {//调用test1方法&#xff0c;且只调用一次int i test1(5);System.out.println(i);}//定义一个int类型返回值的静态方法public static int test1(i…

Java算法递归与递推

Java算法----递归与递推 递推实现递推思想递归实现递归思想递归实现递推思想递推实现递归思想四种方法的特点思维拓展 问题&#xff1a;给你一个整数n&#xff0c;如果n是奇数&#xff0c;就进行运算nn*31&#xff0c;如果n是偶数&#xff0c;就进行运算nn/2&#xff0c;直到n等…

Java开发 | 数据结构和算法之——递归算法

著名的Pascal之父——Nicklaus Wirth(沃斯)让他获得图灵奖的一句话就是他提出的著名公式:“程序=数据结构+算法”,这个公式对计算机科学的影响类似于爱因斯坦的质能方程在物理界的影响。 因此可以看出来数据结构和算法在我们开发程序中有多么的重要了,下面我们来简单认识…

java中递归算法的理解

Coding多了&#xff0c;递归算法是非常常见的&#xff0c;最近我一直在做树形结构的封装&#xff0c;所以更加的离不开递归算法。所以今天就简单说一下这个递归算法&#xff0c;用java实现一个非常经典的递归实例。 递归算法&#xff0c;其实说白了&#xff0c;就是程序的自身调…

【递归】java递归算法及替代方法

文章目录 菜单树递归&#xff08;树根往子节点递归&#xff09;需求&#xff1a; 取所有level小于2的节点 ( 返回结果为普通list格式) 为list格式的数据设置children&#xff08;非递归&#xff09;需求&#xff1a; 数据库查出来的原始list 如果有children就设置 使用循环代替…

Java递归

1.递归的概念&#xff1a; 一个方法在执行过程中调用自身, 就称为 "递归". 示例&#xff1a; //递归求n的阶乘 public static void main(String[] args) {int n 5;int ret factor(n);System.out.println("ret " ret); }public static int factor(int…

java递归算法(一)——详解以及几个经典示例

什么是递归 递归就是一个程序或函数在其中定义或说明有之间或者间接调用自身的一种方法&#xff0c;它通常把一个大型复杂的问题层层转化为一个原问题相似的规模较小的问题来求解&#xff0c;递归策略只需要少量的程序就可以描述出解题过程所需要的多次重复计算&#xff0c;大…

Java 递归算法

一、概述&#xff1a; Java递归&#xff1a;简单说就是函数自身直接或间接调用函数的本身。 二、应用场景&#xff1a; 若&#xff1a;一个功能在被重复使用&#xff0c;并每次使用时&#xff0c;参与运算的结果和上一次调用有关&#xff0c;这时就可以使用递归来解决这个问题…

递归与递归算法实例(java实现)

一、递归介绍 递归算法&#xff08;英语&#xff1a;recursion algorithm&#xff09;在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。绝大 多数编程语言支持函数的自调用&#xff0c;在这些语言中函数可以通过调用自身来进行递归。 定义&#xff1…

Java的递归算法

递归算法设计的基本思想是&#xff1a;对于一个复杂的问题&#xff0c;把原问题分解为若干个相对简单类同的子问题&#xff0c;继续下去直到子问题简单到能够直接求解&#xff0c;也就是说到了递推的出口&#xff0c;这样原问题就有递推得解。 关键要抓住的是&#xff1a; &…

java递归算法实现

Coding多了&#xff0c;递归算法是非常常见的&#xff0c;最近我一直在做树形结构的封装&#xff0c;所以更加的离不开递归算法。所以今天就简单说一下这个递归算法&#xff0c;用java实现一个非常经典的递归实例。 递归算法&#xff0c;其实说白了&#xff0c;就是程序的自身调…

【Java】递归算法

文章目录 什么是递归&#xff1f;递归求阶乘递归求解斐波那契数列猴子吃桃问题 什么是递归&#xff1f; 程序 调用自身 的编程技巧成为 递归&#xff08;recursion&#xff09;。 递归算法是一种直接或间接调用、定义自身的函数或方法的算法&#xff0c;也就是调用自身。 递归…

Java递归算法

递归在程序语言中就是方法本身自己调用自己&#xff0c;而递归思想是算法的重要思想之一&#xff0c;就是利用递归来实现解决问题的算法。 递归也分为直接递归和间接递归。 那么什么叫直接递归什么又叫间接递归呢&#xff1f; //直接递归调用 function(){...function();... …

散布矩阵(scatter_matrix)及相关系数(correlation coefficients)实例分析

在进行机器学习建模之前&#xff0c;需要对数据进行分析&#xff0c;判断各特征(属性&#xff0c;维度)的数据分布及其之间的关系成为十分必要的环节&#xff0c;本文利用Pandas和Numpy的散布矩阵函数及相关系数函数对数据集特征及其关系进行实例分析。 散布矩阵(scatter_matri…

Probability And Statistics——CorrelationsCovariance

Skew&#xff08;偏度&#xff09; 在概率论和统计学中&#xff0c;偏度衡量实数随机变量概率分布的不对称性。偏度的值可以为正&#xff0c;可以为负或者甚至是无法定义。在数量上&#xff0c;偏度为负&#xff08;负偏态&#xff09;就意味着在概率密度函数左侧的尾部比右侧的…