简介

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 ();
}

最新文章

  1. [Bootstrap-插件使用]Jcrop+fileinput组合实现头像上传功能
  2. KVC &amp; KVO
  3. nginx相关的一些记录
  4. mysql 乱码问题(程序界面显示正常,mysql command line显示乱码)
  5. Android入门(五)UI-单位与尺寸、ListView
  6. asp.net—缓存
  7. vim YouCompleteMe
  8. javascript栈的建立样码
  9. ArcGIS Engine Style文件操作
  10. 数据结构(树状数组):HEOI2012 采花
  11. libxml两种换行方法
  12. Java进阶 创建和销毁对象
  13. js相关小实例——div实现下拉菜单
  14. flex弹性布局学习
  15. java整数溢出问题及提升为long型
  16. Centos7安装Tomcat8
  17. Vasya and a Tree CodeForces - 1076E(线段树+dfs)
  18. sublime 成对括号高亮显示设置
  19. Ubuntu下搭建tftp服务器最简单方法
  20. mvn archetype:generate 创建Maven项目

热门文章

  1. linux中Jenkins启动/重启/停止命令
  2. K3/Cloud 执行计划任务错误排查
  3. TD - SimpleTextarea
  4. 在myEclipse中根据图表自动生成Hibernate文件
  5. 4817 [Sdoi2017]树点涂色
  6. JS高级---案例:验证用户输入的是不是中文名字
  7. 腾讯云COS对象存储
  8. 笔记本u盘插上不显示
  9. AcWing 892. 台阶-Nim游戏
  10. 洛谷P1147 连续自然数和