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

源代码

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

/* -------------------------------------------------------------------------
//	文件名		:	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);
#endif

	char 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_string
	if (_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 CTinyHttp

int _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 socket
	WSADATA 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 this
	m_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

Tinyhttpd for Windows的更多相关文章

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

    前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.net/,源代码下载:https://sourceforge.ne ...

  2. Tinyhttpd for Windows(500多行)

    TinyHTTPd forWindows 前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.NET/,源代码下载:h ...

  3. Tinyhttpd精读解析

    首先,本人刚刚开始开源代码精读,写的不对的地方,大家轻拍,一起进步.本文是对Tinyhttpd的一次精读,大家每天都在用着http服务,很多人也一直活跃在上层,使用IIS.Apache等,大家是否想看 ...

  4. Windows server 2012 添加中文语言包(英文转为中文)(离线)

    Windows server 2012 添加中文语言包(英文转为中文)(离线) 相关资料: 公司环境:亚马孙aws虚拟机 英文版Windows2012 中文SQL Server2012安装包,需要安装 ...

  5. Windows Server 2012 NIC Teaming介绍及注意事项

    Windows Server 2012 NIC Teaming介绍及注意事项 转载自:http://www.it165.net/os/html/201303/4799.html Windows Ser ...

  6. C# 注册 Windows 热键

    闲扯: 前几日,一个朋友问我如何实现按 F1 键实现粘贴(Ctrl+V)功能,百度了一个方法,发给他,他看不懂(已经是 Boss 的曾经的码农),我就做了个Demo给他参考.今日得空,将 Demo 整 ...

  7. Windows 7上执行Cake 报错原因是Powershell 版本问题

    在Windows 7 SP1 电脑上执行Cake的的例子 http://cakebuild.net/docs/tutorials/getting-started ,运行./Build.ps1 报下面的 ...

  8. 在离线环境中发布.NET Core至Windows Server 2008

    在离线环境中发布.NET Core至Windows Server 2008 0x00 写在开始 之前一篇博客中写了在离线环境中使用.NET Core,之后一边学习一边写了一些页面作为测试,现在打算发布 ...

  9. Windows平台分布式架构实践 - 负载均衡

    概述 最近.NET的世界开始闹腾了,微软官方终于加入到了对.NET跨平台的支持,并且在不久的将来,我们在VS里面写的代码可能就可以通过Mono直接在Linux和Mac上运行.那么大家(开发者和企业)为 ...

随机推荐

  1. hdu1018 Big Number---N!的位数

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1018 题目大意: 求n阶乘的位数思路: N的阶乖的位数等于LOG10(N!)=LOG10(1)+.. ...

  2. Java 接口基础详解

    目录 Java接口示例 实现一个接口 接口实例 实现多个接口 方法签名重叠 接口变量 接口方法 接口默认方法 接口与继承 继承与默认方法 接口与多态性 在Java中,接口是一个抽象类型,有点类似于类, ...

  3. Python 字符串常见的27个操作

    有字符串 mystr = "hello world itcast and itcastcpp",以下是常见的操作: 1. mystr.find(str, start=0, end= ...

  4. .NET CORE 2.0之 httpcontext

    HttpContext  在之前的.NET framework 是一个非常常用且强大的类,在.NET CORE2.0中要像以前用是不太方便的了, 要是用sesson 首先需要在startup 的Con ...

  5. 洛谷P2042 [NOI2005]维护数列

    #include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring> #in ...

  6. bzoj 4542: [Hnoi2016]大数

    Description 小 B 有一个很大的数 S,长度达到了 N 位:这个数可以看成是一个串,它可能有前导 0,例如00009312345 小B还有一个素数P.现在,小 B 提出了 M 个询问,每个 ...

  7. 【USACO】奶牛抗议 树状数组+dp

    题目描述 约翰家的 N 头奶牛正在排队游行抗议.一些奶牛情绪激动,约翰测算下来,排在第 i 位的奶牛 的理智度为 A i ,数字可正可负. 约翰希望奶牛在抗议时保持理性,为此,他打算将这条队伍分割成几 ...

  8. [APIO2010]

    A.特别行动队 n<=1000000 看了数据范围和题目感觉就像是斜率优化,然后瞎推了一波式子,没想到A了. sij表示i+1到j的权值和. j比k优秀  $$fj+a*sij^{2}+b*si ...

  9. bzoj2005 NOI2010 方案统计

    2005: [Noi2010]能量采集 Time Limit: 10 Sec  Memory Limit: 552 MBSubmit: 4387  Solved: 2619[Submit][Statu ...

  10. BZOJ1095(动态点分治+堆)

    终于把这个坑填了.. 按重心分治建树,每个点存两个堆,第一个存的是这个点子树中的点到父重心的距离,第二个存的是子节点第一个堆的堆顶,同时有一个全局答案堆,存的是每个点第二个堆的最大值+次大值. 20亿 ...