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/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
http://blog.csdn.net/magictong/article/details/53201038
Tinyhttpd for Windows(学习型的项目,才500多行代码)的更多相关文章
- 安卓开发学习经历2--《第一行代码》coolweather项目SQL语句同一个“陷阱”掉两次 注意转义字符等特殊字符正确书写 关于Id字段自增加体会
今天,在运行<第一行代码>coolweather第二阶段代码,又一次报错,还是神奇地与昨天相似,提示,city_id字段不存在,这里我有两种理解,一种是sql语句出错了,另外一种是没有获取 ...
- Tinyhttpd for Windows
TinyHTTPd forWindows 前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.net/,源代码下载:h ...
- Tinyhttpd for Windows(500多行)
TinyHTTPd forWindows 前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.NET/,源代码下载:h ...
- git入门学习(一):github for windows上传本地项目到github
Git是目前最先进的分布式版本控制系统,作为一个程序员,我们需要掌握其用法.Github发布了Github for Windows 则大大降低了学习成本和使用难度,他甚至比SVN都简单. 一.首先在g ...
- Windows服务、批处理项目实战
一周一话题之三(Windows服务.批处理项目实战) -->目录导航 一. Windows服务 1. windows service介绍 2. 使用步骤 3. 项目实例--数据上传下载服务 ...
- vue学习(1) vue-cli 项目搭建
vue学习(1) vue-cli 项目搭建 一.windows环境 1. 下载node.js安装包 官网:https://nodejs.org/en/download/ 选择LTS下载 2. 安装 ...
- 学习Coding-iOS开源项目日志(五)
继续,接着前面第四篇<学习Coding-iOS开源项目日志(四)>讲解Coding-iOS开源项目. 前 言:作为初级程序员,想要提高自己的水平,其中一个有效的学习方法就是学习别人好的项目 ...
- 安卓开发学习历程1——《第一行代码》coolweather项目setOnItemClickListener函数,Sql语句修改对模拟app程序机影响
今天,将<第一行代码>最后实战的coolweather项目,认真做了一遍. 今晚,在书中第一阶段开发代码认眞在Android studio敲完,发现setOnItemClickListen ...
- Android Testing学习02 HelloTesting 项目建立与执行
Android Testing学习02 HelloTesting 项目建立与执行 Android测试,分为待测试的项目和测试项目,这两个项目会生成两个独立的apk,但是内部,它们会共享同一个进程. 下 ...
随机推荐
- [Node.js] Build microservices in Node.js with micro
micro is a small module that makes it easy to write high performance and asynchronous microservices ...
- swf loading 自身
stop(); import flash.net.URLRequest; import caurina.transitions.Tweener; loaderInfo.addEventListener ...
- Java-Maven项目引入UEditor图片上传组件jar包类库的5种方式
最近,硬是和百度的UEditor组件杠上了.自己的个人官网项目,很容易就搞定了,公司的项目,尼玛,各种问题.项目多了,环境复杂了,解决问题的方法也得不断调整. 项目用Maven管理jar包,用到了UE ...
- linux使用.rpm包安装mysql
一:下载mysql的.rpm安装包 点击链接查看下载教程:点击打开链接 二:创建目录,上传文件 创建mysql目录:# mkdir mysql 进入目录:# cd mysql 将下载好的MySQL-s ...
- 开源server软件
Java缓存server jmemcached http://www.oschina.net/p/jmemcached jmemcached 是一个Java版的 memcached 缓存server, ...
- BZOJ1415 聪聪与可可 - 期望dp
传送门 题目大意: 一张无向图上有一只猫和一只老鼠,猫先走,鼠后走.猫每次会向与其相邻的并且距离老鼠最近的点移动(若距离相等去编号较小的),如果移动一步后还没吃到老鼠,还可以再移动一步(算在一个时间内 ...
- 学习鸟哥的Linux私房菜笔记(4)——文件
一.检查文件 用ls -l以长模式查看文件的详细信息,包含当前目录的硬盘使用空间.文件类型.文件权限.硬连接数.文件拥有者.文件所属组.文件大小.更改时间.文件名称. 用file检查文件类型 由于li ...
- centos 软件安装的三种方式
Linux下面安装软件的常见方法: 1.yum 替你下载软件 替你安装 替你解决依赖关系 点外卖 缺少的东西 外卖解决 1).方便 简单2)没有办法深入修改 yum install -y tree 2 ...
- matlab 中使用 GPU 加速运算
为了提高大规模数据处理的能力,matlab 的 GPU 并行计算,本质上是在 cuda 的基础上开发的 wrapper,也就是说 matlab 目前只支持 NVIDIA 的显卡. 1. GPU 硬件支 ...
- 同一性(identical)
f(x)=x,表明 f(⋅) 为同一函数. A 与 B 具有 360° 的区别 A 向左转,再向右转 ⇒ A A 向左转,向左转,向后转 ⇒ A