📄 mini_httpd.c
字号:
/*
* Software name: mini_httpd
* Version: 1.0
* Description: simple and portable HTTP server
* Features: web page, cgi
* Not implemented: If-Modified-Since, SSL, Basic Auth, Cookies support
* Author: zhukenan <zhekenan@gmail.com>
*/
#define VERSION "1.0" /* 版本号 */
#define BUFSIZE 16*1024 /* 缓存大小 */
#define MAXHEADER 64 /* 允许的最大请求头数目 */
#define MAXARG 64 /* 允许的最大参数数目 */
#define NUM_HANDLER_THREADS 3 /* 处理请求的线程数 */
#include <stdio.h>
#include <sys/socket.h>
#include <pthread.h>
#include <string.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
/*************************************************数据结构********************************************************/
/*
* 连接结构,存储提出请求的client端的socket、
* 请求字符串及其长度、client端的地址及其长度
* 和指向链表中下一个结构的指针
*/
struct connect
{
int fdclient; /* client端的socket文件描述符 */
char buffer[BUFSIZE]; /* 请求字符串 */
int buflen; /* 请求字符串长度 */
struct sockaddr_in clientAddr; /* client端的地址 */
int addrLen; /* client端的地址长度 */
struct connect *next; /* 指向链表中下一个结构的指针 */
};
/*
* 向量结构,用来通过指针和长度表示一个字符串
*/
struct vec
{
int len;
const void *ptr;
};
/*
* 名值对结构,用来表示HTTP请求头和cgi请求参数
*/
struct nv
{
struct vec name;
struct vec value;
};
/*
* 请求结构,存储与请求相关的信息
*/
struct request
{
int reqlen; /* 请求行长度 */
int totlen; /* 请求行+请求头长度 */
struct vec method; /* 请求采用的方法 */
struct vec url; /* 请求的url,包括uri和参数 */
struct vec proto; /* 请求的协议 */
struct vec uri; /* 请求的uri */
struct vec argstr; /* 请求的url中‘?’后面的字符串 */
struct nv arg[MAXARG]; /* cgi请求的参数数组 */
int numOfArg; /* cgi请求的参数个数 */
struct nv header[MAXHEADER]; /* 请求头数组 */
int numOfHeader; /* 请求头个数 */
/* 根据请求头进行设置,用来进行授权检测扩展或设置环境变量 */
struct vec auth; /* Auth: */
struct vec cookie; /* Cookie: */
struct vec clength; /* Content-Length: */
struct vec ctype; /* Content-Type: */
struct vec location; /* Location: */
struct vec status; /* Status: */
time_t ims; /* If-Modified-Since: */
};
/*
* HTTP请求头回调函数
*/
typedef void (*hcb_t)(const struct nv *header, struct request *reqp);
/*************************************************全局变量********************************************************/
int sockPort = 80; /* 服务器端口号 */
const char *docroot = "."; /* 访问根目录,初时化为当前工作目录 */
int num_of_connect = 0; /* 连接数 */
struct connect *chead; /* 连接链表的首 */
struct connect *ctail; /* 连接链表的尾 */
pthread_mutex_t connect_mutex; /* 连接链表的互斥量 */
pthread_mutex_t condition_mutex; /* 条件变量的互斥量 */
pthread_cond_t got_request = PTHREAD_COND_INITIALIZER; /* 程序的全局条件变量 */
int readPortOfPipeIsClosed = 0;
char *path_php = "/root/bin/php/bin/php";
/*************************************************方法函数********************************************************/
/*
* 在分析http请求时(函数parseRequest),调用的回调函数,对每
* 个请求头调用该回调函数,设置request的相应的成员变量
*/
void
cbheader(const struct nv *header, struct request *reqp)
{
struct vec auth = {13, "Authorization"},
cookie = {6, "Cookie"},
cl = {14, "Content-Length"},
loc = {8, "Location"},
status = {6, "Status"},
ims = {18, "If-Modified-Since:"},
ct = {12, "Content-Type"};
if (vcasecmp(&header->name, &auth))
reqp->auth = header->value;
else if (vcasecmp(&header->name, &cookie))
reqp->cookie = header->value;
else if (vcasecmp(&header->name, &cl))
reqp->clength = header->value;
else if (vcasecmp(&header->name, &ct))
reqp->ctype = header->value;
else if (vcasecmp(&header->name, &loc))
reqp->location = header->value;
else if (vcasecmp(&header->name, &status))
reqp->status = header->value;
/*else if (vcasecmp(&header->name, &ims))
reqp->ims = datetosec(&header->value);*/
}
/*
* 根据存储着数字信息的vec返回一个int型数据
*/
int
vtoint(const struct vec *v)
{
int i, n;
for (i = n = 0; i < v->len; i++)
{
n *= 10;
n += ((char *) v->ptr)[i] - '0';
}
return (n);
}
/*
* 显示出错信息
*/
void
showError(char *message)
{
printf("%s\n", message);
fflush(stdout);
}
/*
* 比较两个vec是否相等
*/
int
vcasecmp(struct vec *v1, const struct vec *v2)
{
char *s1;
char *s2;
char *end;
int equal = 0;
if (v1->len == v2->len)
{
for (s1 = (char*)v1->ptr, s2 = (char *)v2->ptr, end = s1 + v1->len; s1 < end; s1++, s2++)
{
if (tolower(*s1) != tolower(*s2))
{
break;
}
}
if (s1 == end)
{
equal++;
}
}
return (equal);
}
/*
* 发送响应头,包括要给出mime类型
*/
int
sendheaders(struct request *reqp, int fdclient)
{
int i;
int n;
char headers[2048];
char *mime = "text/plain";
struct vec tmpv;
struct
{
struct vec v;
char *mime;
} tab[] = {
{{5, ".html"}, "text/html"},
{{4, ".htm"}, "text/html"},
{{4, ".css"}, "text/css"},
{{4, ".gif"}, "image/gif"},
{{4, ".jpg"}, "image/jpeg"},
{{5, ".jpeg"}, "image/jpeg"},
{{4, ".png"}, "image/png"},
{{4, ".mpg"}, "video/mpeg"},
{{4, ".asf"}, "video/x-ms-asf"},
{{4, ".avi"}, "video/x-msvideo"},
{{4, ".bin"}, "application/octet-stream"},
{{4, ".bmp"}, "image/bmp"},
{{4, ".doc"}, "application/msword"},
{{4, ".exe"}, "application/octet-stream"},
{{4, ".zip"}, "application/zip"},
};
/* 判断mime类型 */
for (i = 0; i < (int)(sizeof(tab)/sizeof(tab[0])); i++)
{
if (reqp->uri.len <= tab[i].v.len)
{
continue;
}
tmpv.ptr = (char *)reqp->uri.ptr + reqp->uri.len-tab[i].v.len;
tmpv.len = tab[i].v.len;
if(vcasecmp(&tmpv, &tab[i].v) == 1)
{
mime = tab[i].mime;
break;
}
}
n = snprintf(headers, sizeof(headers),
"HTTP/1.0 200 OK\r\n"
"Content-type: %s\r\n"
"\r\n", mime);
/* 发送响应头 */
if(send(fdclient, headers, n, 0) == -1)
{
showError("sendheaders : send");
return -1;
}
return 0;
}
/*
* 发送错误信息
*/
int
sendError(int fdclient, int status, const char *descr, const char *headers, const char *fmt, ...)
{
char msg[512];
va_list ap;
int n;
n = snprintf(msg, sizeof(msg), "HTTP/1.0 %d %s\r\n%s\r\n\r\n", status, descr, headers);
/* 可变参数的用法 */
va_start(ap, fmt);
n += vsnprintf(msg + n, sizeof(msg) - n, fmt, ap);
va_end(ap);
if(send(fdclient, msg, n, 0) == -1)
{
showError("sendError : send");
return -1;
}
return 0;
}
/*
* 处理页面请求
*/
int
dealPage(struct request *reqp, int fdclient)
{
char file[BUFSIZE];
FILE *fpfile;
FILE *fpsock;
int ch;
struct stat filestate;
strcpy(file, docroot);
strncat(file, reqp->uri.ptr, reqp->uri.len);
/* 文件不存在 */
if (stat(file, &filestate) != 0)
{
if(sendError(fdclient, 404, "Not Found", "", "Not Found") == -1)
{
showError("dealPage : sendError");
return -1;
}
}
/* 禁止访问文件夹 */
else if (filestate.st_mode & S_IFDIR)
{
if(sendError(fdclient, 403, "Forbidden", "", "Directory listing denied") == -1)
{
showError("dealPage : sendError");
return -1;
}
}
/* 正常访问文件 */
else if((fpfile = fopen(file, "r")) != NULL)
{
if((fpsock = fdopen(fdclient, "w")) != NULL)
{
if(sendheaders(reqp, fdclient) == -1)
{
showError("dealPage : sendheaders");
return -1;
}
while( (ch = getc(fpfile) ) != EOF )
{
putc(ch, fpsock);
}
fclose(fpfile);
fclose(fpsock);
}
else
{
showError("fdopen error");
return -1;
}
}
else
{
if(sendError(fdclient, 404, "Not Found", "", "File Not Found") == -1)
{
showError("dealPage : sendError");
return -1;
}
}
return 0;
}
/*
* parseRequest():分析发送来的请求字符串,产生相应的request结构,
* 在处理完所有请求头后如果有实体体(即方法为POST),
* 则将实体体的内容拷贝到request结构的argstr中.
*/
struct request *
parseRequest(char *buffer, int buflen, hcb_t cb)
{
int i;
char *p;
enum { WAIT, CONT, HDR, HDRVAL } state;
struct vec h;
struct vec v;
struct vec vec[3] = {{0,0}, {0,0}, {0,0}};
struct nv *hdr;
struct request *reqptr;
char postmd[4];
struct vec tmpv;
strcpy(postmd, "post");
tmpv.ptr = postmd;
tmpv.len = 4;
/* 为request结构申请内存空间 */
reqptr = (struct request *)calloc(1,sizeof(struct request));
if(reqptr == NULL)
{
showError("calloc error");
return NULL;
}
/* 对请求数据串进行解析,得到method、url、protocol和请求头 */
for(p = buffer, state = WAIT, i = 0; p < buffer+buflen; p++)
{
switch (state)
{
/* 前两个case是处理请求行的,得到method、url、proto存入hti结构中 */
case WAIT:
/* 过滤空格 */
if (*p != ' ')
{
state = CONT;
vec[i].ptr = p;
vec[i].len = 0;
}
break;
case CONT:
vec[i].len++;
if (*p == ' ' && i < 2)
{
state = WAIT;
i++;
}
else if (*p == '\n')
{
/* 遇到回车换行,表示请求行已被处理结束 */
reqptr->reqlen = p - buffer + 1;
if (vec[i].len > 0 && p[-1] == '\r')
{
vec[i].len--;
}
/* 进入到下一行,继续分析消息头,h表示头名字,v表示头的值 */
h.ptr = p + 1;
h.len = v.len = 0;
v.ptr = NULL;
/* 将得到的method、url、proto存入hti结构中 */
reqptr->method = vec[0];
reqptr->url = vec[1];
reqptr->proto = vec[2];
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -