我的WEB SERVER 1.0

来源:互联网 发布:超星电子图书数据库 编辑:程序博客网 时间:2024/06/05 19:44

一. 引子

出于业务相关,需要熟悉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];//methodchar ver[16];//versionchar uri[MAX_CHAR_URI];//request-uri};

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


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

struct RequestHeader{char host[64];//Hostchar ifunmodsin[MAX_CHAR_TIME];//If-Unmodified-Sincechar ifmodsin[MAX_CHAR_TIME];//If-Modified-Sincechar uagnt[MAX_CHAR_UAGNT];//user-agentchar referer[MAX_CHAR_URI];//referer};


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

struct EntityHeader{char lstmod[MAX_CHAR_TIME];//Last-Modifiedchar expires[MAX_CHAR_TIME];//expiresint  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-5XXchar 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];//methodchar ver[16];//versionchar uri[MAX_CHAR_URI];//request-uri};struct GeneralHeader{char cachctl[16];//Cache-Controlchar conn[16];//Connectionchar date[MAX_CHAR_TIME];//Date HTTP1.1 14.18;须尽量提供消息产生的时间};struct RequestHeader{char host[64];//Hostchar ifunmodsin[MAX_CHAR_TIME];//If-Unmodified-Sincechar ifmodsin[MAX_CHAR_TIME];//If-Modified-Sincechar uagnt[MAX_CHAR_UAGNT];//user-agentchar referer[MAX_CHAR_URI];//referer};struct EntityHeader{char lstmod[MAX_CHAR_TIME];//Last-Modifiedchar expires[MAX_CHAR_TIME];//expiresint  conlen;//content-length 即使请求头存在这个域也不必解析char contyp[MAX_CHAR_CONTPE];//content-type};struct StatusLine{char ver[16];int status;//1XX-5XXchar 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");}elseif(*cur == ' '){printf(" (空格)");}else{printf("%c",*cur);}cur++;}}

Makefile

CC = gccLCFLAGS = -O2 -I./LDFLAGS = -lpthreadOBJECTS = format.o debug.o httpd.ohttpd:$(OBJECTS)$(CC) $(LCFLAGS) $(LDFLAGS) $(OBJECTS) -o httpd$(OBJECTS):httpd.hclean:rm *.o httpd

httpd.c

#include "httpd.h"//全局变量int servSock = -1;//服务器socket idint 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.haddr.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);}elseif(len > 12 && len < sizeof(gh->conn)+12 &&strncmp(s,"Connection: ",12) == 0){strncpy(gh->conn,s+12,len-12);}elseif(len > 6 && len < sizeof(gh->date)+6 &&strncmp(s,"Date: ",6) == 0){strncpy(gh->date,s+6,len-6);}}//从一串字符里提取有效的request-headervoid 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);}elseif(len > 19 && len < sizeof(rh->ifmodsin)+19 &&strncmp(s,"If-Modified-Since: ",19) == 0){strncpy(rh->ifmodsin,s+19,len-19);}elseif(len > 21 && len < sizeof(rh->ifunmodsin)+21 &&strncmp(s,"If-Unmodified-Since: ",21) == 0){strncpy(rh->ifunmodsin,s+21,len-21);}elseif(len > 12 && len < sizeof(rh->uagnt)+12 &&strncmp(s,"User-Agent: ",12) == 0){strncpy(rh->uagnt,s+12,len-12);}elseif(len > 9 && len < sizeof(rh->referer)+9 &&strncmp(s,"Referer: ",9) == 0){strncpy(rh->referer,s+9,len-9);}}//从一串字符里提取有效的entity-headervoid 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);}elseif(len > 9 && len < sizeof(eh->expires)+9 &&strncmp(s,"Expires: ",9) == 0){strncpy(eh->expires,s+9,len-9);}elseif(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;//统计连续出现的\nint cnt = 0;//每次读一字节,如果连接还没断开并且没数据可读,则阻塞//为了避免大量\r的情况下while循环无法退出;需要cnt来计数//此外,这里还需要一定的计时机制,避免当无输入时,进程永久阻塞在recvwhile((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-linedrawReqLine(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-headerdrawReqHeader(start,len,&(rh->rh));//抽取request-headerdrawEntHeader(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 linesprintf(buf,"%s %d %s\r\n",rep->sl.ver,rep->sl.status,rep->sl.reason);//general headerif(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 headersprintf(buf+strlen(buf),"Server: %s\r\n",rep->rh.server);//entity headerif(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;}elseif(isReadForbidden(path)){//403 forbiddenrep->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 bodyrep->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-Modifiedchar 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 modifiedreturn;}getFileLen(path,rep->eh.conlen);//Content-Length;sendHeader(clntSock,rep_h);sendBody(clntSock,rep_h); *///释放内存free(path);}void initResponseHeaders(struct ResponseHeaders * rhs){//status-linestrcpy(rhs->sl.ver , "HTTP/1.1");//general-headertime_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-headerstrcpy(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;}
















原创粉丝点击