⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 mini_httpd.c

📁 red had Linux上的迷你http服务器
💻 C
📖 第 1 页 / 共 2 页
字号:
/*
 * 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 + -