tinyhttpd-0.1.0_hacking
/****************************************************************************
*
* tinyhttpd-0.1.0_hacking
*
* 1.这是tinyhttpd-0.1.0版本中httpd.c(主程序)的源码,源码不到500行(除去注释).
* 2.通过分析、阅读该源码,可以一窥web服务器的大致工作机制.
* 3.知识量:
* 1.C语言;
* 2.Unix或类Unix系统编程;
* 3.微量的http协议(请求行、消息头、实体内容);
* 4.如何阅读别人的代码( 从main函数开始 :) );
* 4.tinyhttpd-0.1.0 文件结构如下:
* .
* |-- Makefile -------->makefile 文件
* |-- README -------->说明文档
* |-- htdocs -------->程序会到该文件夹下找对应html、cgi文件
* | |-- README -------->说明文档
* | |-- check.cgi -------->cgi 程序
* | |-- color.cgi ----^
* | `-- index.html -------->默认的 web 首页文件
* |-- httpd.c -------->你接下来要阅读的文件
* `-- simpleclient.c -------->没发现该文件有任何用处 @_@
* 5.如何阅读该文档:
* 1.linux下使用vi/vim配和ctags,windows下使用Source Insight,当然你也
* 可以用其他文本编辑器看.
* 2.先找到main函数,然后就可以开始阅读了,遇到对应的函数,就去看对应的
* 函数.
* 3.对于有些函数,本人没有添加注释,或者说本人觉得没必要.
* 4.祝您好运. :)
*
* 6.tinyhttpd-0.1.0版本下载url: http://sourceforge.net/projects/tinyhttpd/
*
* 如果您对本文有任何意见、提议,可以发邮件至zengjf42@163.com,会尽快回复.
* 本文的最终解释权归本人(曾剑锋)所有,仅供学习、讨论.
*
* 2015-3-1 阴 深圳 尚观 Var
*
***************************************************************************/ /* 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);
void bad_request(int);
void cat(int, FILE *);
void cannot_execute(int);
void error_die(const char *);
void execute_cgi(int, const char *, const char *, const char *);
int get_line(int, char *, int);
void headers(int, const char *);
void not_found(int);
void serve_file(int, const char *);
int startup(u_short *);
void unimplemented(int); /**
* accept_request 函数说明:
* 1.获取请求方式,目前只支持GET、POST请求;
* 2.在本程序中所有的POST请求、带参数的GET请求都都被定义为访问cgi程序;
* 3.从带参数的GET请求中分离出请求参数;
* 4.如果没有指定需要访问的文件,使用index.html文件作为默认访问文件;
* 5.检查需要访问的文件是否存在,以及其是否具有对应的权限;
* 6.根据是否是cgi程序访问,来执行对应的任务.
*/
void accept_request(int client)
{
/**
* 局部变量说明:
* 1.buf : buffer缩写,主要用于暂存从socket中读出来的数据;
* 2.numchars : 用于保存每次从socket中读到的字符的个数;
* 3.method : 用于保存请求方式,目前该软件只支持GET、POST这两种方式;
* 4.url : 用于保存访问文件信息,有些地方叫uri;
* 5.path : 用于保存文件路径;
* 6.i, j : 处理数据时的下标;
* 7.st : 在判断文件类型、是否存在的时候用到;
* 8.cgi : 是否调用cgi程序的标志.
*/
char buf[];
int numchars;
char method[];
char url[];
char path[];
size_t i, j;
struct stat st;
int cgi = ; /* becomes true if server decides this is a CGI
* program */
char *query_string = NULL; /**
* 判断程序是否是GET、POST请求两种的其中一种,如果不是则报错.
*/
numchars = get_line(client, buf, sizeof(buf));
i = ; j = ;
while (!ISspace(buf[j]) && (i < sizeof(method) - ))
{
method[i] = buf[j];
i++; j++;
}
method[i] = '\0'; if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
unimplemented(client);
return;
} /**
* 该程序把POST请求定义为cgi请求.
*/
if (strcasecmp(method, "POST") == )
cgi = ; /**
* 获取当前url,这里的url不过括网址,而是除去网址之后的东西,
* 如浏览器中输入:http://127.0.0.1:8080/example/index.html
* 得到的url:/example/index.html
* 在有些地方不称这个为url,称之为uri
*/
i = ;
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
while (!ISspace(buf[j]) && (i < sizeof(url) - ) && (j < sizeof(buf)))
{
url[i] = buf[j];
i++; j++;
}
url[i] = '\0'; /**
* 每次运行的时候都会出现2次这个,目前还不知道是什么原因导致的原因,
* 这是本人在源代码的基础上添加的调试输出.
* url: /favicon.ico
* url: /favicon.ico
*/
printf("url: %s\n", url); /**
* 如果是GET请求,如果带了请求参数,那么也是cgi请求,并且从url中分离出请求参数
*/
if (strcasecmp(method, "GET") == )
{
query_string = url;
while ((*query_string != '?') && (*query_string != '\0'))
query_string++;
if (*query_string == '?')
{
cgi = ;
*query_string = '\0';
query_string++;
}
} /**
* 所有的需要的html文件、cgi程序都在htdocs文件夹中,
* 如果没有指定html文件,或者cgi程序,那么使用默认的index.html文件
* 作为目标输出文件.
*/
sprintf(path, "htdocs%s", url);
if (path[strlen(path) - ] == '/')
strcat(path, "index.html"); /**
* 检查要访问的文件的状态,如:
* 1.是否存在;
* 2.是否是一个文件夹;
* 3.如果是cgi程序,是否用于对应的权限.
* 当然如果执行stat时就出错了,那么,直接将socket中的数据读完,
* 然后返回没有找到相关内容的信息提示.
*/
if (stat(path, &st) == -) {
while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
not_found(client);
}
else
{
if ((st.st_mode & S_IFMT) == S_IFDIR) /* 如果是一个文件夹 */
strcat(path, "/index.html");
if ((st.st_mode & S_IXUSR) ||
(st.st_mode & S_IXGRP) ||
(st.st_mode & S_IXOTH) ) /* 权限问题 */
cgi = ; /**
* 通过cgi变量来判断是执行cgi程序,还是仅仅是返回一个html页面.
*/
if (!cgi)
serve_file(client, path); /* 向客户端返回一个html文件 */
else
execute_cgi(client, path, method, query_string); /* 执行一个cgi程序 */
} close(client);
} void bad_request(int client)
{
char buf[]; sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
send(client, buf, sizeof(buf), );
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, sizeof(buf), );
sprintf(buf, "\r\n");
send(client, buf, sizeof(buf), );
sprintf(buf, "<P>Your browser sent a bad request, ");
send(client, buf, sizeof(buf), );
sprintf(buf, "such as a POST without a Content-Length.\r\n");
send(client, buf, sizeof(buf), );
} /**
* 主要完成将resource指向的文件内容拷贝输出到客户端浏览器中
*/
void cat(int client, FILE *resource)
{
char buf[]; fgets(buf, sizeof(buf), resource);
while (!feof(resource))
{
send(client, buf, strlen(buf), );
fgets(buf, sizeof(buf), resource);
}
} void cannot_execute(int client)
{
char buf[]; sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "Content-type: text/html\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
send(client, buf, strlen(buf), );
} void error_die(const char *sc)
{
perror(sc);
exit();
} void execute_cgi(int client, const char *path,
const char *method, const char *query_string)
{
/**
* 局部变量说明:
* 1.buf : buffer缩写;
* 2.cgi_output : 用于保存输出管道的文件描述符;
* 3.cgi_input : 用于保存输入管道的文件描述符;
* 4.pid : 进程pid,最后父进程退出之前,等待子进程先退出,
* 并回收相关的资源,这部分工作主要由waitpid()来完成;
* 5.status : 在waitpid()中用于保存子进程的退出状态,本程序没有具体使用;
* 6.i : 计数器;
* 7.c : POST读取请求参数时,读取到的字符保存在这里;
* 8.numchars : 读取的字符个数;
* 9.conten_length : 内容实体的字符数;
*/
char buf[];
int cgi_output[];
int cgi_input[];
pid_t pid;
int status;
int i;
char c;
int numchars = ;
int content_length = -; /**
* 在本程序中,GET请求的消息头没有任何用处,直接处理掉就行了,
* 而如果是POST请求,需要的消息头中的获取实体的大小,也就是Content-Length:后面跟的数字
*/
buf[] = 'A'; buf[] = '\0';
if (strcasecmp(method, "GET") == )
while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf));
else /* POST */
{
numchars = get_line(client, buf, sizeof(buf));
while ((numchars > ) && strcmp("\n", buf))
{
buf[] = '\0';
if (strcasecmp(buf, "Content-Length:") == )
content_length = atoi(&(buf[]));
numchars = get_line(client, buf, sizeof(buf));
}
if (content_length == -) {
bad_request(client);
return;
}
} /**
* 返回返回行信息.
*/
sprintf(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), ); /**
* 父子进程通过管道通信.
*/
if (pipe(cgi_output) < ) {
cannot_execute(client);
return;
}
if (pipe(cgi_input) < ) {
cannot_execute(client);
return;
} /**
* 创建子进程,用于执行cgi程序,父进程接受子进程的结果,并返回给浏览器
*/
if ( (pid = fork()) < ) {
cannot_execute(client);
return;
}
if (pid == ) /* child: CGI script */
{
char meth_env[]; //cgi 请求方式环境变量
char query_env[]; //cgi GET请求参数环境变量
char length_env[]; //cgi POST请求参数内容大小环境变量 /**
* 重定向标准输入输出,并设置好对应的环境变量.
*/
dup2(cgi_output[], );
dup2(cgi_input[], );
close(cgi_output[]);
close(cgi_input[]);
sprintf(meth_env, "REQUEST_METHOD=%s", method);
putenv(meth_env);
if (strcasecmp(method, "GET") == ) {
sprintf(query_env, "QUERY_STRING=%s", query_string);
putenv(query_env);
}
else { /* POST */
sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
putenv(length_env);
}
/* 执行对应的程序 */
execl(path, path, NULL);
exit();
} else { /* parent */
close(cgi_output[]);
close(cgi_input[]);
/**
* 对于POST请求,将实体中的请求参数通过管道传送到cgi程序中
*/
if (strcasecmp(method, "POST") == )
for (i = ; i < content_length; i++) {
recv(client, &c, , );
write(cgi_input[], &c, );
}
/**
* 读取cgi程序的执行结果,返回给浏览器
*/
while (read(cgi_output[], &c, ) > )
send(client, &c, , ); close(cgi_output[]);
close(cgi_input[]);
/**
* 等待子进程运行结束,并回收子进程的资源,
* 防止出现孤儿进程
*/
waitpid(pid, &status, );
}
} int get_line(int sock, char *buf, int size)
{
/**
* 局部变量说明:
* 1.i : 数组下标计数,不能大于size;
* 2.c : 每次读到的字符保存在这里面;
* 3.n : 每次读到的字符个数.
*/
int i = ;
char c = '\0';
int n; /**
* 一直读到buf满了,或者遇到了'\n'为止.
*/
while ((i < size - ) && (c != '\n'))
{
n = recv(sock, &c, , );
/* DEBUG printf("%02X\n", c); */
if (n > )
{
/**
* 读到'\r'也算是结束,通过判断后面有没有跟'\n'来判断是否要将下
* 一个字符取出来,并且无论'\r'后面跟不跟'\n',都将'\r'换成'\n'.
*/
if (c == '\r')
{
n = recv(sock, &c, , MSG_PEEK);
/* DEBUG printf("%02X\n", c); */
if ((n > ) && (c == '\n'))
recv(sock, &c, , );
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0'; /* 字符串结尾 */ return(i);
} void headers(int client, const char *filename)
{
char buf[];
(void)filename; /* could use filename to determine file type */ strcpy(buf, "HTTP/1.0 200 OK\r\n");
send(client, buf, strlen(buf), );
strcpy(buf, SERVER_STRING);
send(client, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), );
strcpy(buf, "\r\n");
send(client, buf, strlen(buf), );
} void not_found(int client)
{
char buf[]; sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "your request because the resource specified\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "is unavailable or nonexistent.\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), );
} void serve_file(int client, const char *filename)
{
/**
* 局部变量说明:
* 1.resource : 打开的文件的文件指针;
* 2.numchars : 每次读到的字符个数;
* 3.buf : buffer的缩写.
*/
FILE *resource = NULL;
int numchars = ;
char buf[]; /**
* 在本程序中消息头对于纯GET请求没有什么用,直接读取丢掉.
*/
buf[] = 'A'; buf[] = '\0';
while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof(buf)); resource = fopen(filename, "r");
if (resource == NULL)
not_found(client);
else
{
/* 发送消息头 */
headers(client, filename);
/* 发送内容实体 */
cat(client, resource);
}
fclose(resource);
} /**
* startup 函数完成内容:
* 1.获取一个作为服务器的socket;
* 2.绑定服务器端的socket;
* 3.通过判断参数port的值,确定是否需要动态分配端口号;
* 4.服务器开启监听;
* 5.返回服务器段的socket文件描述符.
*/
int startup(u_short *port)
{
/**
* 局部变量说明:
* 1.httpd : 保存服务器socket描述符,并作为返回值返回;
* 2.name : 用于保存服务器本身的socket信息,创建服务器.
*/
int httpd = ;
struct sockaddr_in name; httpd = socket(PF_INET, SOCK_STREAM, );
if (httpd == -)
error_die("socket"); memset(&name, , sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(*port);
name.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < )
error_die("bind"); if (*port == ) /* if dynamically allocating a port */
{
int namelen = sizeof(name);
if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -)
error_die("getsockname");
*port = ntohs(name.sin_port);
} if (listen(httpd, ) < )
error_die("listen");
return(httpd);
} void unimplemented(int client)
{
char buf[]; sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), );
sprintf(buf, "Content-Type: text/html\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "</TITLE></HEAD>\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
send(client, buf, strlen(buf), );
sprintf(buf, "</BODY></HTML>\r\n");
send(client, buf, strlen(buf), );
} /**********************************************************************/ int main(void)
{
/**
* 局部变量说明:
* 1.server_sock : 服务器端的socket描述符;
* 2.port : 服务器端的socket端口号,如果是0的,startup()将会采用
* 自动生成的方式生成新的端口号供使用;
* 3.client_sock : 客户端连接进来产生的客户端socket描述符;
* 4.client_name : 用于保存客户端连接进来的socket信息;
* 5.client_name_len : struct sockaddr_in结构体的大小,在accpet的时候
* 需要用到,这个参数必须传,否则会出错;
* 6.newthread : 用于保存新创建的线程的ID.
*/
int server_sock = -;
u_short port = ;
int client_sock = -;
struct sockaddr_in client_name;
int client_name_len = sizeof(client_name);
pthread_t newthread; /**
* startup 函数完成内容:
* 1.获取一个作为服务器的socket;
* 2.帮定服务器断的sockt;
* 3.通过判断参数port的值,确定是否需要动态分配端口号;
* 4.服务器开启监听.
*/
server_sock = startup(&port);
printf("httpd running on port %d\n", port); while ()
{
/**
* 等待客户端的连接,使用client_name保存客户端socket信息,
* client_name_len是client_name对应结构体的长度.
*/
client_sock = accept(server_sock,
(struct sockaddr *)&client_name,
&client_name_len);
if (client_sock == -)
error_die("accept");
/**
* 创建一个新的线程来处理任务,并把客户端的socket描述符作为参数传给accept_request,
* accept_request 函数说明:
* 1.获取请求方式,目前只支持GET、POST请求;
* 2.在本程序中所有的POST请求、带参数的GET请求都都被定义为访问cgi程序;
* 3.从带参数的GET请求中分离出请求参数;
* 4.如果没有指定需要访问的文件,使用index.html文件作为默认访问文件;
* 5.检查需要访问的文件是否存在,以及其是否具有对应的权限;
* 6.根据是否是cgi程序访问,来执行对应的任务.
*/
if (pthread_create(&newthread , NULL, accept_request, client_sock) != )
perror("pthread_create");
} /**
* 不知道为什么,这条语句在while外边,竟然会影响到程序的关闭 :(
* 这行代码注释掉才能连续访问,不注释,只能访问一次,所以直接注释了
* 反正程序停止都使用ctrl+c,不影响程序的运行.
*/
//close(server_sock); return();
}
tinyhttpd-0.1.0_hacking的更多相关文章
- ZAM 3D 制作简单的3D字幕 流程(二)
原地址:http://www.cnblogs.com/yk250/p/5663907.html 文中表述仅为本人理解,若有偏差和错误请指正! 接着 ZAM 3D 制作简单的3D字幕 流程(一) .本篇 ...
- ZAM 3D 制作3D动画字幕 用于Xaml导出
原地址-> http://www.cnblogs.com/yk250/p/5662788.html 介绍:对经常使用Blend做动画的人来说,ZAM 3D 也很好上手,专业制作3D素材的XAML ...
- 微信小程序省市区选择器对接数据库
前言,小程序本身是带有地区选着器的(网站:https://mp.weixin.qq.com/debug/wxadoc/dev/component/picker.html),由于自己开发的程序的数据是很 ...
- osg编译日志
1>------ 已启动全部重新生成: 项目: ZERO_CHECK, 配置: Debug x64 ------1> Checking Build System1> CMake do ...
- tinyhttpd源码分析
我们经常使用网页,作为开发人员我们也部署过httpd服务器,比如开源的apache,也开发过httpd后台服务,比如fastcgi程序,不过对于httpd服务器内部的运行机制,却不是非常了解,前几天看 ...
- tinyhttpd服务器源码学习
下载地址:http://sourceforge.net/projects/tinyhttpd/ /* J. David's webserver */ /* This is a simple webse ...
- HTTP服务器的本质:tinyhttpd源码分析及拓展
已经有一个月没有更新博客了,一方面是因为平时太忙了,另一方面是想积攒一些干货进行分享.最近主要是做了一些开源项目的源码分析工作,有c项目也有python项目,想提升一下内功,今天分享一下tinyhtt ...
- Tinyhttpd 代码学习
前阵子,参加了实习生面试,被面试官各种虐,问我说有没有读过一些开源的代码.对于只会用框架的我来说真的是硬伤啊,在知乎大神的推荐下在EZLippi-浮生志找了一些源代码来阅读,于是从小型入手,找了Tin ...
- Tinyhttpd阅读笔记
1.简介 tinyhttpd是一个开源的超轻量型Http Server,阅读其源码,可以对http协议,微型服务器有进一步的了解. 源码链接: 参考博客:tinyhttpd源码分析 2.笔记 ---- ...
随机推荐
- WPF的Binding功能解析
1,Data Binding在WPF中的地位 程序的本质是数据+算法.数据会在存储.逻辑和界面三层之间流通,所以站在数据的角度上来看,这三层都很重要.但算法在3层中的分布是不均匀的,对于一个3层结构的 ...
- jquery $post $get $
Jquery在异步提交方面封装的很好,直接用AJAX非常麻烦,Jquery大大简化了我们的操作,不用考虑浏览器的诧异了. 推荐一篇不错的jQuery Ajax 实例文章,忘记了可以去看看,地址为:ht ...
- db2权限控制(转)
转自:http://gocom.primeton.com/blog16274_23254.htm db2权限控制 1. DB2 权限控制数据库安全性计划的以下几方面: 授予用户的权限级别 允许用户运行 ...
- S1 :数组迭代方法
ECMAScript 5 还新增了两个归并数组的方法:reduce()和reduceRight().这两个方法都会迭代数组的所有项,然后构建一个最终返回的值.其中,reduce()方法从数组的第一项开 ...
- SQL Server 自定义字符串分割函数
一.按指定符号分割字符串,返回分割后的元素个数,方法很简单,就是看字符串中存在多少个分隔符号,然后再加一,就是要求的结果(标量值函数) create function Func_StrArrayL ...
- [转]jQuery Pagination Ajax分页插件中文详解
在做项目时需要用到在前端页面中需要实现分页显示的功能,类似于博客园下面的分页导航.从网上找了几个,觉得下面这个使用起来非常简单,也很方便.特在这里记录一下. 以下为文章原文. 中文项目地址:http: ...
- Delphi的TListView控件拖放选定行操作
http://www.tansoo.cn/?p=401 Delphi的TListView控件拖放选定行操作的例子,效果图如下:TListView控件拖动选定行到指定位置 具体实现步骤: 一.新建一个D ...
- WP8 学习 在APP.XAML中加入Resources
<Application.Resources> <local:LocalizedStrings xmlns:local="clr-namespace:test1" ...
- Android中findViewById()获取EditText 空指针问题
因为EditText editText = (EditText)layout.findViewById(R.id.input_content);是从Dialog对话框布局layout中寻找ID为inp ...
- 今年plan,做好四件事情
写代码, 写博客, 学英语, 锻炼身体.