简介

Tinyhttp是一个轻量型Http Server,使用C语言开发,全部代码只500多行,还包括一个简单Client。

Tinyhttp程序的逻辑为:一个无线循环,一个请求,创建一个线程,之后线程函数处理每个请求,然后解析HTTP请求,做一些判断,之后判断文件是否可执行,不可执行,打开文件,输出给客户端(浏览器),可执行就创建管道,父子进程进行通信。父子进程通信,用到了dup2和execl函数。

模型图

源码剖析

 #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(void *);
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); /**********************************************************************/
/*功能:处理请求
*参数:连接到客户端的套接字*/
/**********************************************************************/
void *accept_request(void *arg)
{
int client = *(int *)arg; //接收客户端的套接字
char buf[];
int numchars;
char method[];
char url[];
char path[];
size_t i, j;
struct stat st;
int cgi = ; char *query_string = NULL;
// "GET /index.html HTTP/1.1\n",'\000' <repeats 319 times>...
numchars = get_line(client, buf, sizeof(buf)); //读取一行存放buf中
i = ;
j = ;
//判断buf中第一个空格前面的字符串的请求方式
while (!ISspace(buf[j]) && (i < sizeof(method) - ))
{
method[i] = buf[j]; //解析出请求的方法放在method中
i++;
j++;
}
method[i] = '\0';
//如果是其他的请求方式,除了GET和POST外,如:HEAD、DELETE等回复未实现方法
if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) //strcasecmp用忽略大小写比较字符串
{
unimplemented(client); //回复请求的方法未实现
return ;
} //如果是POST方式请求,表示执行cgi
if (strcasecmp(method, "POST") == )
cgi = ; i = ;
//从上面的第一个空格后继续开始
while (ISspace(buf[j]) && (j < sizeof(buf)))
j++;
//POST或者GET空格后面的内容 如:"GET /index.html HTTP/1.1\n"
while (!ISspace(buf[j]) && (i < sizeof(url) - ) && (j < sizeof(buf)))
{
url[i] = buf[j]; //解析出url要请求的地址,如:"/index.html"
i++;
j++;
}
url[i] = '\0'; //如果是GET方式请求,如:/login.cgi?user=123&password=456
if (strcasecmp(method, "GET") == )
{
query_string = url;
while ((*query_string != '?') && (*query_string != '\0'))
query_string++;
if (*query_string == '?') //如果遇到了?表示执行cgi
{
cgi = ;
*query_string = '\0';
query_string++; //?后面的内容是发送的信息(如用户名和密码信息)
}
}
//拼接url地址路径
sprintf(path, "htdocs%s", url); //文件的路径放在path中 if (path[strlen(path) - ] == '/') //判断是否是根目录
strcat(path, "index.html"); // "htdocs/index.html" if (stat(path, &st) == -)
{ //获取文件信息到st结构体失败
while ((numchars > ) && strcmp("\n", buf)) //读并丢弃标题(浏览器的信息),如果不读,浏览器发送来的数据,会使服务器处于阻塞状态
numchars = get_line(client, buf, sizeof(buf));
not_found(client); //给客户端一条404未找到的状态消息
}
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程序 if (!cgi) //根据cgi的值,为0执行文件
serve_file(client, path);
else //cgi为1,执行cgi
execute_cgi(client, path, method, query_string);
}
close(client);
return ;
} /**********************************************************************/
/* 通知客户它提出的请求有问题
* 参数: 接收客户端套接字描述符 */
/**********************************************************************/
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);
}
} /**********************************************************************/
/* 通知客户端无法执行CGI脚本。
* 参数:客户端套接字描述符。*/
/**********************************************************************/
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), );
} /**********************************************************************/
/* 打印带有perror()的错误消息并退出指示错误的程序。 */
/**********************************************************************/
void error_die(const char *sc)
{
perror(sc);
exit();
} /**********************************************************************/
/* 执行一个cgi程序,需要环境的支持
* 参数: 接受客户端套接字描述符client,拼接后的地址路径path,请求的方法method,接受的url?后面的内容*/
/**********************************************************************/
void execute_cgi(int client, const char *path, const char *method, const char *query_string)
{
char buf[];
int cgi_output[];
int cgi_input[];
pid_t pid;
int status;
int i;
char c;
int numchars = ;
int content_length = -; buf[] = 'A';
buf[] = '\0';
if (strcasecmp(method, "GET") == )
while ((numchars > ) && strcmp("\n", buf)) //读并丢弃标题(浏览器的信息),如果不读,浏览器发送来的数据,会使服务器处于阻塞状态
numchars = get_line(client, buf, sizeof(buf)); else //POST
{
numchars = get_line(client, buf, sizeof(buf)); //读取一行到buf中
while ((numchars > ) && strcmp("\n", buf))
{
buf[] = '\0';
if (strcasecmp(buf, "Content-Length:") == ) //拷贝Content-Length:到字符串中
content_length = atoi(&(buf[])); //获得content_length内容长度
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;
}
//创建一个进程
if ((pid = fork()) < )
{
cannot_execute(client);
return;
}
if (pid == ) //子进程
{
char meth_env[];
char query_env[];
char length_env[]; dup2(cgi_output[], ); //dup2做了重定向,Linux中0是标准输入(键盘),1是标准输出(屏幕)
dup2(cgi_input[], ); //将cgi_output[1]描述符拷贝到标准输出,原来输出到屏幕的,现在写到cgi_output[1管道中]
//cgi_output[1]用来写
//cgi_input[0]用来读
close(cgi_output[]);
close(cgi_input[]); sprintf(meth_env, "REQUEST_METHOD=%s", method); //将请求的方法加到环境变量
putenv(meth_env); //putenv增加环境变量的内容
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); //execl执行给出的path路径下的path程序(cgi程序),l表示以list方式传参,经过重定向后,path路径中的程序执行后,读数据从cgi_intput[0],中读取,写数据到cgi_output[1]中欧个
exit();
}
else
{ //父进程
//cgi_output[0]管道用来读
//cgi_input[1]管道用来写
close(cgi_output[]);
close(cgi_input[]);
if (strcasecmp(method, "POST") == ) //如果是POST,循环每次一个字符,写入管道
for (i = ; i < content_length; i++)
{
recv(client, &c, , );
write(cgi_input[], &c, );
}
while (read(cgi_output[], &c, ) > ) //循环从管道中读出数据发送给客户端
send(client, &c, , ); close(cgi_output[]);
close(cgi_input[]);
waitpid(pid, &status, );
}
} /**********************************************************************/
/*返回从接受客户端套接字中读取一行的字节数
*参数:客户端套接字sock,缓冲区的指针buf,缓冲区大小size*/
/**********************************************************************/
int get_line(int sock, char *buf, int size)
{
int i = ;
char c = '\0';
int n; while ((i < size - ) && (c != '\n'))
{
n = recv(sock, &c, , ); if (n > )
{
if (c == '\r')
{
n = recv(sock, &c, , MSG_PEEK); if ((n > ) && (c == '\n'))
recv(sock, &c, , );
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0'; return (i);
} /**********************************************************************/
/*发送有关HTTP头文件的信息
*参数:客户端接收套接字,文件的名称*/
/**********************************************************************/
void headers(int client, const char *filename)
{
char buf[];
(void)filename; /*可以使用文件名来确定文件类型*/ 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), );
} /**********************************************************************/
/* 给客户端一条404未找到的状态消息。*/
/**********************************************************************/
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)
{
FILE *resource = NULL;
int numchars = ;
char buf[]; buf[] = 'A';
buf[] = '\0';
while ((numchars > ) && strcmp("\n", buf)) //读并丢弃标题(浏览器的信息),如果不读,浏览器发送来的数据,会使服务器处于阻塞状态
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);
} /**********************************************************************/
/*返回创建指定的端口的监听套接字
*参数:指定的端口port*/
/**********************************************************************/
int startup(u_short *port)
{
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 = inet_addr("192.168.137.114"); int on = ;
setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < )
error_die("bind");
if (*port == ) /* if dynamically allocating a port */
{
socklen_t 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);
} /**********************************************************************/
/*通知客户端,所请求的web方法尚未实现
*参数:接受客户端套接字 */
/**********************************************************************/
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)
{
int server_sock = -;
u_short port = ;
int client_sock = -;
struct sockaddr_in client_name;
socklen_t client_name_len = sizeof(client_name);
pthread_t newthread; server_sock = startup(&port);
printf("httpd running on port %d\n", port); while ()
{
//父进程每接收一个客户端创建一个线程
client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len);
if (client_sock == -)
error_die("accept");
//线程标识符、线程属性、线程运行函数、运行函数参数(接收套接字)
if (pthread_create(&newthread, NULL, accept_request, &client_sock) != )
perror("pthread_create");
} close(server_sock); return ();
}

Tinyhttp源码分析的更多相关文章

  1. HTTP服务器的本质:tinyhttpd源码分析及拓展

    已经有一个月没有更新博客了,一方面是因为平时太忙了,另一方面是想积攒一些干货进行分享.最近主要是做了一些开源项目的源码分析工作,有c项目也有python项目,想提升一下内功,今天分享一下tinyhtt ...

  2. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  3. HashMap与TreeMap源码分析

    1. 引言     在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...

  4. nginx源码分析之网络初始化

    nginx作为一个高性能的HTTP服务器,网络的处理是其核心,了解网络的初始化有助于加深对nginx网络处理的了解,本文主要通过nginx的源代码来分析其网络初始化. 从配置文件中读取初始化信息 与网 ...

  5. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

  6. zookeeper源码分析之四服务端(单机)处理请求流程

    上文: zookeeper源码分析之一服务端启动过程 中,我们介绍了zookeeper服务器的启动过程,其中单机是ZookeeperServer启动,集群使用QuorumPeer启动,那么这次我们分析 ...

  7. zookeeper源码分析之三客户端发送请求流程

    znode 可以被监控,包括这个目录节点中存储的数据的修改,子节点目录的变化等,一旦变化可以通知设置监控的客户端,这个功能是zookeeper对于应用最重要的特性,通过这个特性可以实现的功能包括配置的 ...

  8. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  9. ABP源码分析二:ABP中配置的注册和初始化

    一般来说,ASP.NET Web应用程序的第一个执行的方法是Global.asax下定义的Start方法.执行这个方法前HttpApplication 实例必须存在,也就是说其构造函数的执行必然是完成 ...

随机推荐

  1. 51 nod 1212 无向图最小生成树

    http://www.51nod.com/Challenge/Problem.html#problemId=1212 代码 #include<bits/stdc++.h> using na ...

  2. 栈和队列----将单链表的每K个节点之间逆序

    将单链表的每K个节点之间逆序 给定一个单链表的头节点head,实现一个调整链表的函数,使得每K 个节点之间逆序,如果最后剩下不够K 个节点,则不调整最后几个. 例如: 链表:1—>2—>3 ...

  3. 常用sql 2018.08.31

    concat()函数 concat(str1, str2,...)功能:将多个字符串连接成一个字符串 返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null. 如:CONCA ...

  4. 分类问题(三)混淆矩阵,Precision与Recall

    混淆矩阵 衡量一个分类器性能的更好的办法是混淆矩阵.它基于的思想是:计算类别A被分类为类别B的次数.例如在查看分类器将图片5分类成图片3时,我们会看混淆矩阵的第5行以及第3列. 为了计算一个混淆矩阵, ...

  5. 微信小程序:如何判断数组中的条数?

    可以<view wx:if="{{list.length == 0}}"> </view> 可以在 {{}} 内进行简单的运算,包括三元运算符.逻辑判断.算 ...

  6. redis五大数据类型以及常用操作命令

    Redis的五大数据类型 String(字符串) string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value.string类型是二进制安全的.意 ...

  7. java 学习(day2) 时钟类

    看过python的面向对象,所以入手java总的来说还是比较快的. 看视频学习之后写了一个简单的时钟 一个时钟有两部分组成小时和分钟.再大一些还有月,日. 分析一下就是,上述这些属性到达一个值之后,就 ...

  8. 还不错的Table样式和form表单样式

    作为一个后台开发人员而言,拥有一套属于自己的前台样式是比较重要的,这里分享一下自己感觉还不错的样式,以后遇到好的,还会陆续添加 上图: 带鼠标滑动效果的table样式看起来比较清爽 样式 <he ...

  9. 意外发现--http-server使用

    http-server 在很多情况下,需要在本地开启http服务器来测试.所以就需要一个简单的省事好用的http服务器.以前的时候,都是使用php的本地环境,但是,自从学了nodejs,发现了http ...

  10. OpenCV-Mat结构详解

    前面博客中Mat函数谈到一些理解,但是理解的比较浅显,下面谈谈通道,行列等意义: Mat的常见属性 opencv中type类型· CV_<bit_depth>(S|U|F)C<num ...