这里就不细述了,代码很简单.
其实现的功能比较若,可以做一个参考.
因为其通过文件的权限位来判断是否是一个CGI脚本,所以在权限位不对的情况下会判断不正确.例如我将这个目录放置在NTFS分区,所有的文件都有可执行权限,会导致将index.html文件当做CGI脚本.

注释后的文件在这里下载http://files.cnblogs.com/files/oloroso/tinyhttpd-0.1.0.tar.7z

/* 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 <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>#define ISspace(x) isspace((int)(x))#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"// 接受请求,并处理
void accept_request(int clientsockfd);
// 无法处理请求,回写400到client
void bad_request(int);
// 将文件内容发送到client
void cat(int client, FILE* fp);
// 不可以执行CGI程序,回写500到client
void cannot_execute(int);
// 输出错误信息
void error_die(const char *);
// 执行cgi程序
void execute_cgi(int, const char *, const char *, const char *);
// 从fd中读取一行,并将读取的'\r\n'和'\r'转换为'\n'
int get_line(int fd, char* buf, int bufsize);
// 返回200 OK给客户端
void headers(int clientsockfd, const char * filename);
// 返回404状态给客户端
void not_found(int);
// 处理客户端文件请求,发送404或200+文件内容到client
void serve_file(int client, const char* filename);
// 开启soket监听
int startup(u_short *);
// 返回501状态给客户端
void unimplemented(int);/**********************************************************************/
/* 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(int client)
{char    buf[1024];int     numchars;char    method[255];    // 方法(请求类型)char    url[255];       // 请求的资源URLchar    path[512];      // 请求资源的本地路径size_t i, j;struct stat st;int cgi = 0;        /* becomes true if server decides this is a CGI program *//* 为真时,表示服务器需调用一个CGI程序 */char *query_string = NULL;// 1. 从客户端连接请求数据包中读取一行numchars = get_line(client, buf, sizeof(buf));// 2. 从读取的数据中提取出请求类型i = 0; j = 0;while (!ISspace(buf[j]) && (i < sizeof(method) - 1)){method[i] = buf[j];i++; j++;}method[i] = '\0';// 3. 判断请求类型if (strcasecmp(method, "GET") && strcasecmp(method, "POST")){unimplemented(client);  // 不支持请求类型,向客户端返回501return;}if (strcasecmp(method, "POST") == 0){cgi = 1;    // 如果是POST请求,调用cgi程序}// 4. 提取请求的资源URLi = 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 (strcasecmp(method, "GET") == 0){query_string = url; // 为GET请求,查询语句为url// 如果查询语句中含义'?',查询语句为'?'字符后面部分while ((*query_string != '?') && (*query_string != '\0')){query_string++;}if (*query_string == '?'){cgi = 1;*query_string = '\0';   // 从'?'处截断,前半截为urlquery_string++;}}// 5. 生成本地路径sprintf(path, "htdocs%s", url);if (path[strlen(path) - 1] == '/'){strcat(path, "index.html"); //如果请求url是以'/'结尾,则指定为该目录下的index.html文件}// 6. 判断请求资源的状态if (stat(path, &st) == -1) {// 从client中读取,直到遇到两个换行(起始行startline和首部header之间间隔)while ((numchars > 0) && strcmp("\n", buf)){  /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));}not_found(client);  // 501}else{// 请求的是个目录,指定为该目录下的index.html文件if ((st.st_mode & S_IFMT) == S_IFDIR){strcat(path, "/index.html");}// 请求的是一个可执行文件,作为CGI程序if ((st.st_mode & S_IXUSR) ||(st.st_mode & S_IXGRP) ||(st.st_mode & S_IXOTH)    ){cgi = 1;}// 判断是执行一个CGI程序还是返回一个文件内容给客户端if (!cgi){serve_file(client, path); // }else{execute_cgi(client, path, method, query_string);}}close(client);
}/**********************************************************************/
/* Inform the client that a request it has made has a problem.* Parameters: client socket */
/**********************************************************************/
void bad_request(int client)
{char buf[1024];// 向client回写400状态sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "Content-type: text/html\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "<P>Your browser sent a bad request, ");send(client, buf, sizeof(buf), 0);sprintf(buf, "such as a POST without a Content-Length.\r\n");send(client, buf, sizeof(buf), 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(int client, FILE *resource)
{char buf[1024];// 从resource中读取一行fgets(buf, sizeof(buf), resource);// 将文件中的内容发送到客户端while (!feof(resource)){send(client, buf, strlen(buf), 0);fgets(buf, sizeof(buf), resource);}
}/**********************************************************************/
/* Inform the client that a CGI script could not be executed.* Parameter: the client socket descriptor. */
/**********************************************************************/
void cannot_execute(int client)
{char buf[1024];// 向客户端回写500状态,不可以执行CGI程序sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "Content-type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<P>Error prohibited CGI execution.\r\n");send(client, buf, strlen(buf), 0);
}/**********************************************************************/
/* 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);    // 退出程序
}/********************************************************************//** @brief   执行一个CGI脚本.可能需要设置适当的环境变量.* @prama   client  客户端socket文件描述符* @prama   path    CGI 脚本路径* @prama   method  请求类型* @prama   query_string    查询语句**********************************************************************/
void execute_cgi(int client, const char *path,const char *method, const char *query_string)
{char    buf[1024];int     cgi_output[2];  // CGI程序输出管道int     cgi_input[2];   // CGI程序输入管道pid_t   pid;int     status;int     i;char    c;int     numchars = 1;int     content_length = -1;buf[0] = 'A'; buf[1] = '\0';if (strcasecmp(method, "GET") == 0){// 从client中读取,直到遇到两个换行while ((numchars > 0) && strcmp("\n", buf)){  /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));}}else    /* POST */{// 获取请求主体的长度numchars = get_line(client, buf, sizeof(buf));while ((numchars > 0) && strcmp("\n", buf)){buf[15] = '\0';if (strcasecmp(buf, "Content-Length:") == 0){content_length = atoi(&(buf[16]));}numchars = get_line(client, buf, sizeof(buf));}if (content_length == -1) {bad_request(client);return;}}// 向client回写200 OKsprintf(buf, "HTTP/1.0 200 OK\r\n");send(client, buf, strlen(buf), 0);// 创建两个匿名管道if (pipe(cgi_output) < 0) {cannot_execute(client);return;}if (pipe(cgi_input) < 0) {cannot_execute(client);return;}// 创建子进程,去执行CGI程序if ( (pid = fork()) < 0 ) {cannot_execute(client);return;}if (pid == 0)  /* child: CGI script */{char meth_env[255];char query_env[255];char length_env[255];dup2(cgi_output[1], 1); //复制cgi_output[1](读端)到子进程的标准输出dup2(cgi_input[0], 0);  //复制cgi_input[0](写端)到子进程的标准输入close(cgi_output[0]);   //关闭多余文件描述符close(cgi_input[1]);sprintf(meth_env, "REQUEST_METHOD=%s", method);putenv(meth_env);   // 添加一个环境变量if (strcasecmp(method, "GET") == 0) {sprintf(query_env, "QUERY_STRING=%s", query_string);putenv(query_env);}else {   /* POST */sprintf(length_env, "CONTENT_LENGTH=%d", content_length);putenv(length_env);}// 执行CGI程序execl(path, path, NULL);exit(0);    // 子进程退出}else {    /* parent */close(cgi_output[1]);   // 关闭cgi_output读端close(cgi_input[0]);    // 关闭cgi_input写端if (strcasecmp(method, "POST") == 0){// 请求类型为POST的时候,将POST数据包的主体entity-body部分// 通过cgi_input[1](写端)写入到CGI的标准输入for (i = 0; i < content_length; i++) {recv(client, &c, 1, 0);write(cgi_input[1], &c, 1);}}// 读取CGI的标准输出,发送到客户端while (read(cgi_output[0], &c, 1) > 0){send(client, &c, 1, 0);}// 关闭多余文件描述符close(cgi_output[0]);close(cgi_input[1]);// 等待子进程结束waitpid(pid, &status, 0);}
}/**********************************************************************/
/* 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(int sock, char *buf, int size)
{int     i = 0;char    c = '\0';int     n;while ((i < size - 1) && (c != '\n')){// 从sock中读取一个字节n = recv(sock, &c, 1, 0);/* DEBUG printf("%02X\n", c); */if (n > 0){// 将 \r\n 或 \r 转换为'\n'if (c == '\r'){// 读到了'\r'就再预读一个字节n = recv(sock, &c, 1, MSG_PEEK);/* DEBUG printf("%02X\n", c); */// 如果读取到的是'\n',就读取,否则c='\n'if ((n > 0) && (c == '\n'))recv(sock, &c, 1, 0);elsec = '\n';}// 读取数据放入bufbuf[i] = c;i++;}else{c = '\n';}}buf[i] = '\0';// 返回写入buf的字节数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(int client, const char *filename)
{char buf[1024];(void)filename;  /* could use filename to determine file type */// 向客户端回写200应答strcpy(buf, "HTTP/1.0 200 OK\r\n");send(client, buf, strlen(buf), 0);strcpy(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);strcpy(buf, "\r\n");send(client, buf, strlen(buf), 0);
}/**********************************************************************/
/* Give a client a 404 not found status message. */
/**********************************************************************/
void not_found(int client)
{char buf[1024];// 向client回写404状态sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>The server could not fulfill\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "your request because the resource specified\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "is unavailable or nonexistent.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 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(int client, const char *filename)
{FILE *resource = NULL;int numchars = 1;char buf[1024];buf[0] = 'A'; buf[1] = '\0';// 从client中读取,直到遇到两个换行(起始行start line和首部header的间隔)while ((numchars > 0) && strcmp("\n", buf)){  /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));}// resource = fopen(filename, "r");if (resource == NULL)not_found(client);  // 404错误else{headers(client, filename);  // 向客户端回写200 OKcat(client, resource);      // 将resource指向文件中的内容写入client}fclose(resource);
}/**********************************************************************/
/* 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 */
/**********************************************************************/
int startup(u_short *port)
{int httpd = 0;struct sockaddr_in name;// 创建一个sockethttpd = socket(PF_INET, SOCK_STREAM, 0);if (httpd == -1){error_die("socket");}// 填写绑定地址memset(&name, 0, sizeof(name));name.sin_family = AF_INET;name.sin_port = htons(*port);name.sin_addr.s_addr = htonl(INADDR_ANY);// 绑定socket到指定地址if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0){error_die("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);
}/**********************************************************************/
/* Inform the client that the requested web method has not been* implemented.* Parameter: the client socket */
/**********************************************************************/
void unimplemented(int client)
{char buf[1024];// 向client回写501状态sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</TITLE></HEAD>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 0);
}/**********************************************************************/int main(void)
{int server_sock = -1;u_short port    = 0;    // 使用随机端口int client_sock = -1;struct sockaddr_in client_name;int client_name_len = sizeof(client_name);pthread_t newthread;// 启动服务,监听,等待连接server_sock = startup(&port);printf("httpd running on port %d\n", port);while (1){// 接受请求client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);if (client_sock == -1)error_die("accept");// 创建一个线程来处理请求/* accept_request(client_sock); */if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)perror("pthread_create");}// 关闭close(server_sock);return(0);
}

转载于:https://www.cnblogs.com/oloroso/p/5459196.html

tinyhttp源码阅读(注释)相关推荐

  1. mybatis源码阅读

    说下mybatis执行一个sql语句的流程 执行语句,事务等SqlSession都交给了excutor,excutor又委托给statementHandler SimpleExecutor:每执行一次 ...

  2. gin context和官方context_gin 源码阅读(二) 路由和路由组

    " 上一篇讲的是gin 框架的启动原理,今天来讲一下 gin 路由的实现. 1 用法 还是老样子,先从使用方式开始: func main() { r := gin.Default() r.G ...

  3. koa源码阅读之koa-compose/application.js

    koa源码阅读之koa-compose/application.js koa-Compose 为了理解方便特地把注释也粘进来 //这英语.我也来翻译一波 //大概就是把所有的中间件组合返回一个完整大块 ...

  4. TiDB 源码阅读系列文章(六)Select 语句概览

    在先前的 TiDB 源码阅读系列文章(四) 中,我们介绍了 Insert 语句,想必大家已经了解了 TiDB 是如何写入数据,本篇文章介绍一下 Select 语句是如何执行.相比 Insert,Sel ...

  5. 代码分析:NASM源码阅读笔记

    NASM源码阅读笔记 NASM(Netwide Assembler)的使用文档和代码间的注释相当齐全,这给阅读源码 提供了很大的方便.按作者的说法,这是一个模块化的,可重用的x86汇编器, 而且能够被 ...

  6. 超像素SLIC算法源码阅读

    超像素SLIC算法源码阅读 超像素SLIC算法源码阅读 SLIC简介 源码阅读 实验结果 其他超像素算法对比 超像素SLIC算法源码阅读 SLIC简介 SLIC的全称Simple Linear Ite ...

  7. SpringMVC源码阅读系列汇总

    1.前言 1.1 导入 SpringMVC是基于Servlet和Spring框架设计的Web框架,做JavaWeb的同学应该都知道 本文基于Spring4.3.7源码分析,(不要被图片欺骗了,手动滑稽 ...

  8. 【NLP】NLP实战篇之bert源码阅读(run_classifier)

    本文主要会阅读bert源码 (https://github.com/google-research/bert )中run_classifier.py文件,已完成modeling.py.optimiza ...

  9. DotText源码阅读(7) --Pingback/TrackBack

    DotText源码阅读(7) --Pingback/TrackBack 博客这种服务的区别于论坛和所谓文集网站,很大程度上我认为是由于pingback/trackback的存在,使得博客这种自媒体有可 ...

最新文章

  1. 源代码阅读工具Source-Navigator 在ubuntu 9.04下的安装与问题解决
  2. GPS系统误差的主要来源
  3. 推荐小课1:推荐、推荐系统是什么?有什么价值?
  4. android 的webview解析
  5. 【scala初学】scala 控制 for while match if
  6. 吉他谱——寂寞是因为思念谁
  7. python 堆栈溢出_IAR堆栈溢出的问题
  8. Unstated浅析
  9. java中怎么表示数组中的某个值_java中如何高效判断数组中是否包含某个特定的值...
  10. 2017.4.5 java中static关键字
  11. 2014年6月计算机二级c语言答案,2014年计算机二级C语言真题及答案(4)
  12. Linux CentOS 7 Apache Tomcat 7 安装与配置
  13. HTML计算机代码元素
  14. php微信获取openid_PHP微信网页授权获取OPENID
  15. matlab求条件概率密度_数值优化方法—迭代法amp;终止条件
  16. linux snoop抓包命令,snoop抓包简介
  17. Unity3D游戏开发之自由视角状态下的角色控制
  18. 复制计算机软件,一键复制粘贴工具
  19. PC上阅读电子书的软件:Sumatra PDF和calibre
  20. Unity2018新功能之2D Animation2D动画

热门文章

  1. 【Python】异常捕获
  2. libcoredb.class.php,ThinkPHP/Lib/Core/Db.class.php中pdo处理逻辑似乎不完善,导致config中pdo配置失效...
  3. Java入门算法(排序篇)丨蓄力计划
  4. python学习-知识点回顾(Python3的安装,编译器、一些关键知识点、数据类型、数据类型转换、运算符优先级)
  5. HDFS中常用的shell命令总结
  6. html 行内超出隐藏,css如何设置文字不换行超出隐藏?
  7. MySQL百一题库_「灵魂拷问」MySQL面试高频一百问(工程师方向)
  8. stm32 复位到内部bootloader
  9. 移动办公系统 服务器地址,安卓系统移动办公服务器地址
  10. linux panic 构造_Linux Wireless架构总结