一. 引子
出于业务相关,需要熟悉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;
}