我的WEB SERVER 1.0

一. 引子

出于业务相关,需要熟悉HTTP协议的考虑,决定自己写个WEB SERVER。打肿脸看英文版RFC2616,囫囵吞枣。万事开头难,首先是看协议,把握请求与应答之间的逻辑关系;其次是必须在时间、能力、野心三者之间来回周旋,确定一个目标。边看便动手,折腾了两三天,才决定实现一个最简单的服务器,提供GET功能就好了(其实还应该顺便实现HEAD的)。

前几天刚刚用了下线程的条件变量,目前遗忘差不多了。知识就是得大量应用才能记得住啊。写这文章是把自己做过的事情总结总结,免得遗忘太快。

二. 进程/线程

在决定使用进程、线程还是进程+线程来实现服务器的时候,想到了APACHE的WORDER模式和PREFORK模式。其中,WORKER模式就是多进程+多线程模式;PREFORK模式是多进程模式。

多线程相比于多进程的优点是:

1.如果有多处理器则能提高效率(超线程技术:多线程的硬件化)

2.线程间共享内存比进程间方便(线程共享进程的内存,不需要像多进程那样,需要通过共享内存)

3. 创建线程消耗少(每个进程创建的时候会把父进程的栈拷贝一遍;而线程不拷贝,只是共享)

4. 兄弟线程切换比兄弟进程切换要快(兄弟线程共享进程的内存,内存切换少;兄弟进程切换内存切换消耗大)

缺点则是:

1. 一个线程崩溃可能导致其它兄弟线程和父进程崩溃,因为共享。所以,貌似WORKER模式给每个线程指定了能够处理的消息的上限,超过上限则线程退出,以期避免内存问题累积导致不稳定。

2. 而进程能够把问题控制在自身范围内,因而稳定。从这个角度讲,WORKER模式结合了多进程的稳定和多线程的低消耗的好处。

哈哈,最开始的时候,我连该服务器大致该怎么写都不知道,最后一咬牙,算了,就用多进程,并且也不学APACHE那样控制进程数目、读配置文件啥的了。先实现最简单的,后续有空再搞别的。


三. 流程、逻辑

1. 总体流程

/*start 套接字*/
while(1)
{
	侦听请求,创建子进程处理请求,向子进程传递套接字的句柄
}
/*end 套接字*/

/*start 进程*/
1. 从套接字读取并解析HTTP请求头
2. 生成响应头
3. 发送响应头(message header)
4. 发送响应体(message body),如果有
5. 关闭套接字
/*end 进程*/

2. 返回码的生成逻辑

1)304  not modified

if(请求头的Cache-Control域不是no-cache或者no-store)
{
	if(请求头设置了 If-Modified-Since)
	{
		if(文件在 If-Modified-Since 指出的时间之后确实修改过)
		{
			//返回200;相应消息体中包含该文件
		}
		else
		{
			//返回304;无响应消息体
		}
	}
}

2)400 bad request

if(URI 中包含"..")//见 RFC2616 15.2
{
	//返回400;构造相应的 message-body 返回给客户端
}
if(URI 指向不存在的文件)
{
	//返回404;构造相应的 message-body 返回给客户端
}

不过我把这两个情况合并了,都返回404码,这是没有依据的吼吼

3)200 OK

这是默认的状态码


3. 关于相应消息头

1)为了缓存

如果客户端没有指出自己不愿接受缓存,我的代码默认是开启缓存的。

if(请求头的Cache-Control域不包含no-cache或者no-store)
{
	设置响应头的 Cache-Control 为 public
	设置 Last-Modified 为文件的最后修改时间
}

2)不支持长连接

因为太水了,所以不支持长连接。我在每个相应头里加了Connection: close (见RFC2616 14.10 讲到为什么要这么做),这样客户端才能知道我要释放连接,因此它也关闭socket。否则客户端会在长时间等待之后才关闭。

3)Content-Length

不指出 Content-Length , 客户端无从知道响应消息的主体部分的长度。

四. 模型

1. 请求

RFC2616 指定了HTTP请求头的格式如下:

Request = Request-Line
		*((general-header
		| request-header
		| entity-header)CRLF
		)
		CRLF
		[message-body]


其中的 *(xxxxx)表示xxxxx可以出现0或多次。据此可以看出,遇到两对连续的CRLF,则标志着请求头部结束。剩下的,如果有,就是message-body了。

根据这个BNF范式以及其它的范式,我创建了以下结构体以描述头部。

struct RequestHeaders
{
	struct RequestLine rl;
	struct GeneralHeader gh;
	struct RequestHeader rh;
	struct EntityHeader eh;
};

其中,RequestLine 包含请求的方法,协议版本号,请求的URI:
struct RequestLine
{
	char mthd[16];//method
	char ver[16];//version
	char uri[MAX_CHAR_URI];//request-uri
};

General-Header 中包含的域是请求和响应中共有的用于描述消息的域;
struct GeneralHeader
{
	char cachctl[16];//Cache-Control
	char conn[16];//Connection
	char date[MAX_CHAR_TIME];//Date HTTP1.1 14.18;须尽量提供消息产生的时间
};


RequestHeader 是请求独有的用于描述请求的域;

struct RequestHeader
{
	char host[64];//Host
	char ifunmodsin[MAX_CHAR_TIME];//If-Unmodified-Since
	char ifmodsin[MAX_CHAR_TIME];//If-Modified-Since
	char uagnt[MAX_CHAR_UAGNT];//user-agent
	char referer[MAX_CHAR_URI];//referer
};


EntityHeader 封装了用于描述Entity的域。Entity就是消息体中包含的所有内容。这里我只把我的实现用到的域放入相应的结构体中,刚开始想把RFC2616上出现的所有域都放进来,发现太费事了,且很没意义。

struct EntityHeader
{
	char lstmod[MAX_CHAR_TIME];//Last-Modified
	char expires[MAX_CHAR_TIME];//expires
	int  conlen;//content-length 即使请求头存在这个域也不必解析
	char contyp[MAX_CHAR_CONTPE];//content-type
};

2. 响应

同样,根据文档,我创建的模型:

struct ResponseHeaders
{
	struct StatusLine sl;
	struct GeneralHeader gh;
	struct ResponseHeader rh;
	struct EntityHeader eh;
};

其中状态行包括协议版本;响应状态码;状态码附加说明三部分:
struct StatusLine
{
	char ver[16];
	int status;//1XX-5XX
	char reason[128];//对status做出解释
};
至于 ResponseHeader 我只支持了 server域,用于描述我的服务器版本号:

struct ResponseHeader
{
    char server[MAX_CHAR_SERVER];
};

我没有对消息体进行封装,因为大小不固定,且没有固定格式。


五. 代码特点

1. 在解析请求头和解析URI的过程中,良好地处理了内存溢出风险

2. 动态内存分配也仔细回收

3. 功能过分简单;代码组织、功能划分还不够漂亮;改进空间大。


六. 不行,哥困了

代码如下:

httpd.h

#include <malloc.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h> //strncmp strncpy strcasecmp
#include <stdlib.h>
#include <time.h> //strftime
#include "stdio.h"
#include <unistd.h> //access 不过发现 R_OK undeclared;于是改用 stat
#include <sys/stat.h> //stat()
#include <sys/time.h>//gettimeofday()
#include <fcntl.h> //open()

#define PORT 1500
#define MAX_CLIENTS 200 //可以并发处理的连接请求
#define MAX_CHAR_TIME 64 
#define MAX_CHAR_UAGNT 128 //Request-Header User-Agent
#define MAX_CHAR_URI 256
#define MAX_CHAR_SERVER 128
#define MAX_CHAR_CONTPE 32
#define MAX_CHAR_REQHEAD 4096 //读取请求头部的过程中,如果直到4096个字符还没读完头部,则默认失败
#define MAX_CHAR_REPHEAD 4096 //应答头的最大长度

#define bool int
#define true 1
#define false 0

#define RFC1123DATEFMT "%a, %d %b %Y %H:%M:%S GMT"

struct RequestLine
{
	char mthd[16];//method
	char ver[16];//version
	char uri[MAX_CHAR_URI];//request-uri
};
struct GeneralHeader
{
	char cachctl[16];//Cache-Control
	char conn[16];//Connection
	char date[MAX_CHAR_TIME];//Date HTTP1.1 14.18;须尽量提供消息产生的时间
};
struct RequestHeader
{
	char host[64];//Host
	char ifunmodsin[MAX_CHAR_TIME];//If-Unmodified-Since
	char ifmodsin[MAX_CHAR_TIME];//If-Modified-Since
	char uagnt[MAX_CHAR_UAGNT];//user-agent
	char referer[MAX_CHAR_URI];//referer
};
struct EntityHeader
{
	char lstmod[MAX_CHAR_TIME];//Last-Modified
	char expires[MAX_CHAR_TIME];//expires
	int  conlen;//content-length 即使请求头存在这个域也不必解析
	char contyp[MAX_CHAR_CONTPE];//content-type
};

struct StatusLine
{
	char ver[16];
	int status;//1XX-5XX
	char reason[128];//对status做出解释
};
struct ResponseHeader
{
	char server[MAX_CHAR_SERVER];
};
struct RequestHeaders
{
	struct RequestLine rl;
	struct GeneralHeader gh;
	struct RequestHeader rh;
	struct EntityHeader eh;
};

struct ResponseHeaders
{
	struct StatusLine sl;
	struct GeneralHeader gh;
	struct ResponseHeader rh;
	struct EntityHeader eh;
};


format.c

#include "httpd.h"

//使用宏注意安全;给x加上括号是好习惯
//#define ishex(x) (((x)>='0'&&(x)<='9')||((x)>='A'&&(x)<='F')||((x)>='a'&&(x)<='f'))

bool ishex(char c)
{
	if(c >= '0' && c <= '9'){
		return true;
	}
	if(c >= 'A' && c <= 'F'){
		return true;
	}
	if(c >= 'a' && c <= 'f'){
		return true;
	}
	return false;
}

int hex2int(char c)
{
	if(c >= '0' && c <= '9')
	{
		return (int)(c - '0');
	}
	if(c >= 'a' && c <= 'f')
	{
		return 10 + (int)(c - 'a');
	}
	if(c >= 'A' && c <= 'F')
	{
		return 10 + (int)(c - 'A');
	}
	return 0;
}
void decodeUri(char * s)
{
	char * pEnc = s, * pDec = s;
	while(*pEnc != '\0')//或者直接 while(*pEnc)
	{
		//由于历史原因,空格并非用%20编码;而是+
		if(*pEnc == '+'){
			*pDec++ = ' ';
			pEnc++;
			continue;
		}
		//注意别内存越界
		if(*pEnc == '%' && pEnc[1] != '\0' && pEnc[2] != '\0' && 
			ishex(pEnc[1]) && ishex(pEnc[2])
		)
		{
			*pDec++ = (char) (hex2int(pEnc[1]) * 16 + hex2int(pEnc[2]));
			pEnc += 3;
			continue;
		}
		*pDec++ = *pEnc++;
	}
	*pDec = '\0';
}

void uniqSlashes(char * s)
{
	printf("before transform:%s\n",s);
	char * bfr = s, * aft = s;//bfr 遍历转换之前的字符串; aft 指向转换之后的字符串
	int seqslash = 0;//当前连续slash的数量
	while(*bfr != '\0')
	{
		if(*bfr == '/')
		{
			seqslash++;
			bfr++;
		}
		else
		{
			if(seqslash > 0)
			{
				*aft++ = '/';
				seqslash = 0;
			}
			*aft++ = *bfr++;
		}
	}
	*aft = '\0';
	printf("after transform:%s\n",s);
}

debug.c //用来调试的一些函数

#include "httpd.h"
void debugPrintReqLine(struct RequestLine * rl)
{
	printf("METHOD:%s\n",rl->mthd);
	printf("REQUEST-URI:%s\n",rl->uri);
	printf("HTTP-VERSION:%s\n",rl->ver);
}
void debugPrintGenHeader(struct GeneralHeader * gh)
{
	printf("CACHE-CONTROL:%s\n",gh->cachctl);
	printf("CONNECTION:%s\n",gh->conn);
	printf("DATE:%s\n",gh->date);
}
void debugPrintReqHeader(struct RequestHeader * rh)
{
	printf("HOST:%s\n",rh->host);
	printf("IF-MODIFIED-SINCE:%s\n",rh->ifunmodsin);
	printf("USER-AGENT:%s\n",rh->uagnt);
	printf("REFERER:%s\n",rh->referer);
}
void debugPrintEntHeader(struct EntityHeader * eh)
{
	printf("LAST-MODIFIED:%s\n",eh->lstmod);
	printf("EXPIRES:%s\n",eh->expires);
	printf("CONTENT-LENGTH:%d\n",eh->conlen);
	printf("CONTENT-TYPE:%s\n",eh->contyp);
}
void debugPrintReqHeaders(struct RequestHeaders * rh)
{
	printf("---------------pid=%d;debug start---------------\n",getpid());
	debugPrintReqLine(&(rh->rl));
	debugPrintGenHeader(&(rh->gh));
	debugPrintReqHeader(&(rh->rh));
	debugPrintEntHeader(&(rh->eh));
	printf("---------------pid=%d;debug end  ---------------\n",getpid());
}
void debugPrintStatusLine(const struct StatusLine * sl)
{
	printf("VERSION:%s\n",sl->ver);
	printf("STATUS:%d\n",sl->status);
	printf("REASON:%s\n",sl->reason);
}
void debugPrintRepHeader(const struct ResponseHeader * rh)
{
	printf("SERVER:%s\n",rh->server);
}
void debugPrintResponseHeaders(struct ResponseHeaders * rh)
{
	printf("---------------pid=%d;debug responseheaders start---------------\n",getpid());
	debugPrintStatusLine(&(rh->sl));
	debugPrintGenHeader(&(rh->gh));
	debugPrintRepHeader(&(rh->rh));
	debugPrintEntHeader(&(rh->eh));
	printf("---------------pid=%d;debug responseheaders end  ---------------\n",getpid());
}
void debugVisualizeStr(char * s)
{
	char * cur = s;
	while(*cur != '\0')
	{
		if(*cur == '\n'){
			printf("(回车)\n");
		}
		else
		if(*cur == ' '){
			printf(" (空格)");
		}else
		{
			printf("%c",*cur);
		}
		
		cur++;
	}
}

Makefile

CC = gcc
LCFLAGS = -O2 -I./
LDFLAGS = -lpthread
OBJECTS = format.o debug.o httpd.o

httpd:$(OBJECTS)
	$(CC) $(LCFLAGS) $(LDFLAGS) $(OBJECTS) -o httpd

$(OBJECTS):httpd.h

clean:
	rm *.o httpd

httpd.c

#include "httpd.h"
//全局变量
int servSock = -1;//服务器socket id
int reuseAddr = 1;
void build_socket()
{
	struct sockaddr_in addr;
	int len = sizeof(struct sockaddr_in);
	addr.sin_family = htons(AF_INET);
	addr.sin_port = htons(PORT);//httpd.h
	addr.sin_addr.s_addr = INADDR_ANY;
	
	if((servSock = socket(AF_INET,SOCK_STREAM,0)) == -1){
		perror("when call socket");
		exit(1);
	}
	if(setsockopt(servSock,SOL_SOCKET,SO_REUSEADDR,(void*)&reuseAddr,sizeof(int)) == -1){
		perror("when call setsockopt");
		exit(1);
	}
	if(bind(servSock,(struct sockaddr *)&addr,len)){
		perror("when call bind");
		exit(1);
	}
	if(listen(servSock,MAX_CLIENTS) == -1){
		perror("when call listen");
		exit(1);
	}
}
//从一串字符里提取有效的request-line 域
void drawReqLine(const char * const s, const int len,struct RequestLine * rl)
{
	//为了避免内存溢出,务必做好边界检查
	if(len >= sizeof(struct RequestLine))
	{
		return;
	}
	//%[^ \n] 表示读取每个参数的过程中;读到空格或者回车就停止
	int argcnt = -1;
	argcnt = sscanf(s,"%[^ \n] %[^ \n] %[^ \n]\n",rl->mthd,rl->uri,rl->ver);
	//sscanf 还是可能有越界问题,这样就能确保安全了
	rl->mthd[sizeof(rl->mthd)-1] = '\0';
	rl->ver[sizeof(rl->ver)-1] = '\0';
	rl->uri[sizeof(rl->uri)-1] = '\0';

}
//从一串字符里提取有效的general-header 域
void drawGenHeader(const char * const s, const int len,struct GeneralHeader * gh)
{
	if(len > 15 && len < sizeof(gh->cachctl)+15 &&
		strncmp(s,"Cache-Control: ",15) == 0)
	{
		strncpy(gh->cachctl,s+15,len-15);
	}else
	if(len > 12 && len < sizeof(gh->conn)+12 &&
		strncmp(s,"Connection: ",12) == 0)
	{
		strncpy(gh->conn,s+12,len-12);
	}else
	if(len > 6 && len < sizeof(gh->date)+6 &&
		strncmp(s,"Date: ",6) == 0)
	{
		strncpy(gh->date,s+6,len-6);
	}

}
//从一串字符里提取有效的request-header
void drawReqHeader(const char * const s, const int len,struct RequestHeader * rh)
{
	if(len > 6 && len < sizeof(rh->host)+6 &&
		strncmp(s,"Host: ",6) == 0)
	{
		strncpy(rh->host,s+6,len-6);
	}else
	if(len > 19 && len < sizeof(rh->ifmodsin)+19 &&
		strncmp(s,"If-Modified-Since: ",19) == 0)
	{
		strncpy(rh->ifmodsin,s+19,len-19);
	}else
	if(len > 21 && len < sizeof(rh->ifunmodsin)+21 &&
		strncmp(s,"If-Unmodified-Since: ",21) == 0)
	{
		strncpy(rh->ifunmodsin,s+21,len-21);
	}else
	if(len > 12 && len < sizeof(rh->uagnt)+12 &&
		strncmp(s,"User-Agent: ",12) == 0)
	{
		strncpy(rh->uagnt,s+12,len-12);
	}else
	if(len > 9 && len < sizeof(rh->referer)+9 &&
		strncmp(s,"Referer: ",9) == 0)
	{
		strncpy(rh->referer,s+9,len-9);
	}
}
//从一串字符里提取有效的entity-header
void drawEntHeader(const char * const s, const int len,struct EntityHeader * eh)
{
	if(len > 15 && len < sizeof(eh->lstmod)+15 &&
		strncmp(s,"Last-Modified: ",15) == 0)
	{
		strncpy(eh->lstmod,s+15,len-15);
	}else
	if(len > 9 && len < sizeof(eh->expires)+9 &&
		strncmp(s,"Expires: ",9) == 0)
	{
		strncpy(eh->expires,s+9,len-9);
	}else
	if(len > 14 && len < sizeof(eh->contyp)+14 &&
		strncmp(s,"Content-Type: ",14) == 0)
	{
		strncpy(eh->contyp,s+14,len-14);
	}
}
//从socket重读取请求头,请求头以\r\n\r\n为结束标志
//发现\r\n\r\n之前,如果缓冲区已不够用,则读取失败,返回 false
//此外,\r\n 都被转化成 \n 存入缓冲区
bool readHeadersFromSock(int clntSock,char * const buf)
{
	char * cur = buf;
	int lfcnt = 0;//统计连续出现的\n
	int cnt = 0;
	//每次读一字节,如果连接还没断开并且没数据可读,则阻塞
	//为了避免大量\r的情况下while循环无法退出;需要cnt来计数
	//此外,这里还需要一定的计时机制,避免当无输入时,进程永久阻塞在recv
	while((cnt++ < MAX_CHAR_REQHEAD) && (recv(clntSock,cur,(size_t)1,0) > 0))
	{
		if(*cur == '\r')
		{
			continue;
		}
		if(*cur != '\n')
		{
			lfcnt = 0;//重新开始计数连续的回车 (\n)
		}
		if(*cur == '\n')
		{
			lfcnt++;
		}
		cur++;
		if((cur-buf >= MAX_CHAR_REQHEAD) || lfcnt == 2)
		{
			break;
		}
	}
	*(cur+1) = '\0';//务必收尾
	return (lfcnt==2)?true:false;
}
void buildLFIdx(const char * const buf,int * const lfidx, int * lftotal)
{
	char * cur = (char *)buf;
	*lftotal = 0;
	while(*cur != '\0')
	{
		if(*cur == '\n')
		{
			lfidx[*lftotal] = cur - buf;
			*lftotal = *lftotal + 1;
		}
		cur++;
	}
}
void getReqHeaders(int clntSock,struct RequestHeaders * rh)
{
	char * buf = NULL, * cur = NULL, * start = NULL, * end = NULL;
	int * lfidx = NULL;
	int lftotal = 0;
	int i = -1,len = -1;//临时下标
	buf = (char *)calloc(MAX_CHAR_REQHEAD,sizeof(char));//请求的头部最多不超过 MAX_CHAR_REQHEAD 个字符
	lfidx = (int *)calloc(MAX_CHAR_REQHEAD/2,sizeof(int));
	if(readHeadersFromSock(clntSock,buf))//成功地读取请求头到缓冲区中
	{
		debugVisualizeStr(buf);//debug
		//创建所有回车符(\n)的索引,存放在数组lfidx中,lftotal记录回车符总数
		buildLFIdx(buf,lfidx,&lftotal);
		//抽取 request-line
		drawReqLine(buf, lfidx[0],&(rh->rl));
		//遍历所有的 filed:value\n 串,提取受支持的filed值
		for(i = 1; i <= lftotal-1;i++)
		{
			start = buf + lfidx[i-1] + 1;//start 指向一个换行符(\n)后面的字符
			end = buf + lfidx[i];//end 指向一个换行符(\n)
			len = end - start;
			drawGenHeader(start,len,&(rh->gh));//抽取general-header
			drawReqHeader(start,len,&(rh->rh));//抽取request-header
			drawEntHeader(start,len,&(rh->eh));//抽取entity-header
		}
	}

	free(buf);//无论是否正常返回,务必回收内存
	free(lfidx);
	return;
}
void getFilePath(const struct RequestHeaders * req, char * path)
{
	strcpy(path,"/home/yaozhiyi/dove/htdocs");//htdocs路径
	char * ed = path + strlen(path);
	char * ask = NULL;//?的位置
	int askpos = 0;
	if((ask = strstr(req->rl.uri,"?")) == NULL)
	{
		strcat(path,req->rl.uri);
	}
	else
	{
		askpos = ask - req->rl.uri;
		strncpy(ed,req->rl.uri,askpos);
		ed[askpos] =  '\0';//收尾
	}
}
bool validFilePath(char * path)
{
	struct stat st;
	//检查是否含 ..
	if(strstr(path,"..") != NULL)
	{
		return false;
	}
	if(stat(path,&st) == -1)//如果找不到文件
	{
		return false;
	}
	if(! S_ISREG(st.st_mode))//如果不是普通文件
	{
		return false;
	}

	//判断是否可读
	
	return true;
	
}
bool isReadForbidden()
{
	return false;
}
void closeSocket(int clntSock)
{
}
void sendResponseHeaders(int clntSock, const struct ResponseHeaders * rep)
{
	char * buf = (char *) calloc(sizeof(char),MAX_CHAR_REPHEAD);
	//status line
	sprintf(buf,"%s %d %s\r\n",rep->sl.ver,rep->sl.status,rep->sl.reason);
	//general header
	if(strlen(rep->gh.cachctl) != 0)
	{
		sprintf(buf+strlen(buf),"Cache-Control: %s\r\n",rep->gh.cachctl);
	}
	if(strlen(rep->gh.conn) != 0)
	{
		sprintf(buf+strlen(buf),"Connection: %s\r\n",rep->gh.conn);
	}
	if(strlen(rep->gh.date) != 0)
	{
		sprintf(buf+strlen(buf),"Date: %s\r\n",rep->gh.date);
	}
	//response header
	sprintf(buf+strlen(buf),"Server: %s\r\n",rep->rh.server);
	//entity header
	if(strlen(rep->eh.lstmod) > 0)
	{
		sprintf(buf+strlen(buf),"Last-Modified: %s\r\n" , rep->eh.lstmod);
	}
	if(strlen(rep->eh.expires) > 0)
	{
		sprintf(buf+strlen(buf),"Expires: %s\r\n" , rep->eh.expires);
	}
	if(strlen(rep->eh.contyp) > 0)
	{
		sprintf(buf+strlen(buf),"Content-Type: %s\r\n" , rep->eh.contyp);
	}
	if(rep->eh.conlen > 0)
	{
		sprintf(buf+strlen(buf),"Content-Length: %d\r\n" , rep->eh.conlen);
	}
	write(clntSock,buf,strlen(buf));
	write(clntSock,"\r\n",2);
	free(buf);
}
void getMimeType(const char * file, char * mm)
{
	char * mimap[20][2] = {
		{".html","text/html"},
		{".htm","text/html"},
		{".xhtml","text/html"},
		{".js","application/x-javascript"},
		{".css","text/css"},
		{".jpg","iamge/jpg"},
		{".gif","image/gif"},
		{".jpeg","image/jpeg"},
		{".png","image/png"},
		{".mp3","video/mpeg"},
		{".php","application/octet-stream"},
		{".doc","application/msword"},
		{"",""}
	};
	
	
	char * pslash = NULL,*ppoint = NULL;
	int i = 0;
	strcpy(mm,"text/plain");//默认
	if((ppoint = strrchr(file,'.')) == NULL)
	{
		strcpy(mm,"txt/plain");
		return;
	}
	while(strcmp(mimap[i][0],"") != 0)//可以删掉!=0效果一样;
	{
		if(strcmp(mimap[i][0],ppoint) == 0)
		{
			strcpy(mm,mimap[i][1]);
			break;
		}
		i++;
	}
	
	
}
bool fileUnmodifiedSince(const char * file , const char * tgttime)
{
	struct stat st;
	char mtime[MAX_CHAR_TIME];
	if(stat(file,&st) < 0)
	{
		return false;
	}
	strftime(mtime,MAX_CHAR_TIME,RFC1123DATEFMT,gmtime(&(st.st_mtime)));
	if(strcmp(mtime,tgttime) != 0)
	{
		return false;
	}
	return true;
}
int getFileLen(const char * file)
{
	struct stat st;
	int len = 0;
	if(stat(file,&st) == 0){
		len = (int)(st.st_size);
	}
	return len;
}
void sendFile(int clntSock,struct ResponseHeaders * rep,const char * file)
{
	char * buf = (char *) calloc(4096,1);
	int i = 0,fd = -1,conlen = rep->eh.conlen,cnt = 0;
	if((fd=open(file,O_RDONLY)) == -1)
	{
		//打开文件失败
		//由于请求头已经发出去并且指出了长度,那就干脆输出对应数量的空格
		for(i = 0; i < conlen; i++)
		{
			write(clntSock," ",1);
		}
	}
	else
	{
		//打开文件成功
		//失败返回-1;EOF返回0;否则返回读取的字节数
		while((cnt=read(fd,buf,4096)) > 0)
		{
			write(clntSock,buf,cnt);
		}
	
	}
	
	//释放内存
	free(buf);
	
}
void solveGet(int clntSock, const struct RequestHeaders * req,struct ResponseHeaders * rep)
{
	//因为还需要给URI增加HTDOCS根目录才能定位文件,多加64字节
	char * path = (char *) calloc(MAX_CHAR_URI+64,1);
	char * errstr = NULL;
	getFilePath(req,path);
	uniqSlashes(path);
	if(! validFilePath(path))
	{
		//400 bad request; 或者用 404 不需提供具体原因;
		rep->sl.status = 400;
		strcpy(rep->sl.reason,"INVALID PATH");
		errstr = "<html><head><title>400</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body>服务器: YZY-WS-1.0 错误:400 bad request</body></html>";
		rep->eh.conlen = strlen(errstr);
		sendResponseHeaders(clntSock,rep);
		write(clntSock,errstr,strlen(errstr));
		return;
	}else
	if(isReadForbidden(path))
	{
		//403 forbidden
		rep->sl.status = 403;
		strcpy(rep->sl.reason,"ACCESS FORBIDDEN");
		
		errstr = errstr = "<html><head><title>403</title><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head><body>服务器: YZY-WS-1.0 错误:403 forbidden</body></html>";
		rep->eh.conlen = strlen(errstr);
		sendResponseHeaders(clntSock,rep);
		write(clntSock,errstr,strlen(errstr));
		return;
	}
	
	getMimeType(path,rep->eh.contyp);
	//如果客户端不反对缓存
	if(strstr(req->gh.cachctl,"no-cache") == NULL && 
		strstr(req->gh.cachctl,"no-store") == NULL
	)
	{
		strcpy(rep->gh.cachctl,"public");
		if(strlen(req->rh.ifmodsin) > 0 &&
			fileUnmodifiedSince(path,req->rh.ifmodsin)
		)
		{
			//304不能有message body
			rep->sl.status = 304;
			strcpy(rep->sl.reason,"Not Modified");
			sendResponseHeaders(clntSock,rep);
			return;
		}
	}
	//如果客户端不反对缓存
	if(strstr(req->gh.cachctl,"no-cache") == NULL && 
		strstr(req->gh.cachctl,"no-store") == NULL
	)
	{
		//设置 Last-Modified
		char mtime[MAX_CHAR_TIME];
		struct stat st;
		stat(path,&st);
		strftime(mtime,MAX_CHAR_TIME,RFC1123DATEFMT,gmtime(&(st.st_mtime)));
		strcpy(rep->eh.lstmod,mtime);
	}
	rep->sl.status = 200;
	strcpy(rep->sl.reason , "OK");
	rep->eh.conlen = getFileLen(path);
	sendResponseHeaders(clntSock,rep);
	sendFile(clntSock,rep,path); 
	
/*else 
	if(! mimeSopported(path))
	{
		//415 
		return;
	}
	getFileModTime(path,stime);
	if(strcmp(req->rh.ifunmodsin,stime) == 0)
	{
		//304 not modified
		return;
	}
	getFileLen(path,rep->eh.conlen);//Content-Length;
	sendHeader(clntSock,rep_h);
	sendBody(clntSock,rep_h); */
	//释放内存
	free(path);
	
}
void initResponseHeaders(struct ResponseHeaders * rhs)
{
	//status-line
	strcpy(rhs->sl.ver , "HTTP/1.1");
	//general-header
	time_t now = time((time_t *)0);
	strftime(rhs->gh.date,sizeof(rhs->gh.date),RFC1123DATEFMT,gmtime(&now));
	strcpy(rhs->gh.cachctl,"no-cache");
	strcpy(rhs->gh.conn,"close");
	//response-header
	strcpy(rhs->rh.server,"YZY-WS-1.0");//服务器名(web-server 1.0)猥琐1.0
	//entity-header
}
//被子进程调用,用于处理请求
void dorequest(int clntSock)
{
	struct RequestHeaders * req_h = (struct RequestHeaders *)calloc(sizeof(struct RequestHeaders),1);
	struct ResponseHeaders * rep_h = (struct ResponseHeaders *)calloc(sizeof(struct ResponseHeaders),1);
	getReqHeaders(clntSock,req_h);//从socket中抽取请求头并存入req_h指向的结构体对象中
	//debugPrintReqHeaders(req_h);
	initResponseHeaders(rep_h);
	//debugPrintResponseHeaders(rep_h);
	if(strcasecmp(req_h->rl.mthd,"GET") == 0)//request-line method 为 get
	{
		//printf("method:get\n");
		solveGet(clntSock,req_h,rep_h);
	}else 
	if(strcasecmp(req_h->rl.mthd,"POST") == 0)
	{
		//printf("method:post\n");
	}
	else
	{
	}
	//关闭套接字
	close(clntSock);
	//所有任务执行完,务必释放内存
	free(req_h);
	free(rep_h);
}
void loop_accept()
{
	struct sockaddr_in addr;
	socklen_t len = sizeof(struct sockaddr_in);
	int child = -1;
	while(1)
	{
		int clntSock = accept(servSock,(struct sockaddr*)&addr,&len);//阻塞直到客户端发来请求
		if((child=fork()) == 0)//子进程
		{
			dorequest(clntSock);
			break;
		}
	}
}
int main()
{
	build_socket();
	loop_accept();
	return 0;
}
















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值