//通过libevent编写的web服务器
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "pub.h"
#include <event.h>
#include <event2/listener.h>
#include <dirent.h>
#define _WORK_DIR_ "%s/webpath"
#define _DIR_PREFIX_FILE_ "html/dir_header.html"
#define _DIR_TAIL_FILE_ "html/dir_tail.html"
//copy_header(bev,200,"OK",get_mime_type("ww.html"),sb.st_size);
int copy_header(struct bufferevent *bev,int op,char *msg,char *filetype,long filesize)
{
char buf[4096]={0};
//http应答
sprintf(buf,"HTTP/1.1 %d %s\r\n",op,msg);
sprintf(buf,"%sContent-Type: %s\r\n",buf,filetype);
if(filesize >= 0){
sprintf(buf,"%sContent-Length:%ld\r\n",buf,filesize);
}
strcat(buf,"\r\n");
bufferevent_write(bev,buf,strlen(buf));
return 0;
}
//copy_file(bev, strPath);
int copy_file(struct bufferevent *bev,const char *strFile)
{
int fd = open(strFile,O_RDONLY);
char buf[1024]={0};
int ret;
while( (ret = read(fd,buf,sizeof(buf))) > 0 ){
bufferevent_write(bev,buf,ret);
}
close(fd);
return 0;
}
//发送目录,实际上组织一个html页面发给客户端,目录的内容作为列表显示
//send_dir(bev,strPath);
int send_dir(struct bufferevent *bev,const char *strPath)
{
//需要拼出来一个html页面发送给客户端
copy_file(bev,_DIR_PREFIX_FILE_);
//send dir info
DIR *dir = opendir(strPath);
if(dir == NULL){
perror("opendir err");
return -1;
}
char bufline[1024]={0};
struct dirent *dent = NULL;
while(dent= readdir(dir)){
struct stat sb;
//将状态信息存储到sb stat结构体中
stat(dent->d_name,&sb);
if(dent->d_type == DT_DIR){
//目录文件 特殊处理
//格式 <a href="dirname/">dirname</a><p>size</p><p>time</p></br>
memset(bufline,0x00,sizeof(bufline));
sprintf(bufline,"<li><a href='%s/'>%32s</a>%8ld</li>",dent->d_name,dent->d_name,sb.st_size);
bufferevent_write(bev,bufline,strlen(bufline));
}
else if(dent->d_type == DT_REG){
//普通文件 直接显示列表即可
memset(bufline,0x00,sizeof(bufline));
sprintf(bufline,"<li><a href='%s'>%32s</a>%8ld</li>",dent->d_name,dent->d_name,sb.st_size);
bufferevent_write(bev,bufline,strlen(bufline));
}
}
closedir(dir);
copy_file(bev,_DIR_TAIL_FILE_);
//bufferevent_free(bev);
return 0;
}
//http_request(bev, path);//处理请求
int http_request(struct bufferevent *bev,char *path)
{
//调用void strdecode(char *to, char *from)
//将中文问题转码成utf-8格式的字符串
//[/%E8%8B%A6%E7%93%9C.txt]
printf("path转码前%s\n", path);
strdecode(path, path);
printf("path转码后%s\n", path);
char *strPath = path;
//判断是否是目录[GET] [/pic/aa/bb/] [HTTP/1.1]
if(strcmp(strPath,"/") == 0 || strcmp(strPath,"/.") == 0){
strPath = "./";
}
else{
strPath = path+1;
}
struct stat sb;
if(stat(strPath,&sb) < 0){
//不存在 ,给404页面
copy_header(bev,404,"NOT FOUND",get_mime_type("error.html"),-1);
copy_file(bev,"error.html");
return -1;
}
if(S_ISDIR(sb.st_mode)){
//处理目录
copy_header(bev,200,"OK",get_mime_type("ww.html"),sb.st_size);
send_dir(bev,strPath);
}
if(S_ISREG(sb.st_mode)){
//处理文件
//写头
copy_header(bev,200,"OK",get_mime_type(strPath),sb.st_size);
//写文件内容
copy_file(bev,strPath);
}
return 0;
}
void read_cb(struct bufferevent *bev, void *ctx)
{
char buf[256]={0};
char method[10],path[256],protocol[10];
//ret为bufferevent_read读取的字节数
int ret = bufferevent_read(bev, buf, sizeof(buf));
if(ret > 0){
// GET /demo.html HTTP/1.1
sscanf(buf,"%[^ ] %[^ ] %[^ \r\n]",method,path,protocol);
if(strcasecmp(method,"get") == 0){
//处理客户端的请求
char bufline[256];
write(STDOUT_FILENO,buf,ret);
//确保数据读完
while( (ret = bufferevent_read(bev, bufline, sizeof(bufline)) ) > 0){
write(STDOUT_FILENO,bufline,ret);
}
http_request(bev,path);//处理请求
}
}
}
void bevent_cb(struct bufferevent *bev, short what, void *ctx)
{
if(what & BEV_EVENT_EOF){//客户端关闭
printf("client closed\n");
bufferevent_free(bev);
}
else if(what & BEV_EVENT_ERROR){
printf("err to client closed\n");
bufferevent_free(bev);
}
else if(what & BEV_EVENT_CONNECTED){//连接成功
printf("client connect ok\n");
}
}
void listen_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{
printf("new\n");
//定义与客户端通信的bufferevent
struct event_base *base = (struct event_base *)arg;
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev,read_cb,NULL,bevent_cb,base);//设置回调
bufferevent_enable(bev,EV_READ|EV_WRITE);//启用读和写
}
int main(int argc,char *argv[])
{
//切换目录
char workdir[256] = {0};
strcpy(workdir,getenv("PWD"));
printf("%s\n",workdir);
chdir(workdir);
//创建根节点
struct event_base *base = event_base_new();
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(9999);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
//连接监听器,LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE关闭时自动释放,端口复用
struct evconnlistener * listener =evconnlistener_new_bind(base,
listen_cb, base, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,(struct sockaddr *)&serv, sizeof(serv));//连接监听器
event_base_dispatch(base);//循环
event_base_free(base); //释放根节点
evconnlistener_free(listener);//释放链接监听器
return 0;
}
pub.c
#include "pub.h"
//通过文件名字获得文件类型
char *get_mime_type(char *name)
{
char* dot;
dot = strrchr(name, '.'); //自右向左查找‘.’字符;如不存在返回NULL
if (dot == (char*)0)
return "text/plain; charset=utf-8";
if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
return "text/html; charset=utf-8";
if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
return "image/jpeg";
if (strcmp(dot, ".gif") == 0)
return "image/gif";
if (strcmp(dot, ".png") == 0)
return "image/png";
if (strcmp(dot, ".css") == 0)
return "text/css";
if (strcmp(dot, ".au") == 0)
return "audio/basic";
if (strcmp( dot, ".wav") == 0)
return "audio/wav";
if (strcmp(dot, ".avi") == 0)
return "video/x-msvideo";
if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
return "video/quicktime";
if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
return "video/mpeg";
if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
return "model/vrml";
if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
return "audio/midi";
if (strcmp(dot, ".mp3") == 0)
return "audio/mpeg";
if (strcmp(dot, ".ogg") == 0)
return "application/ogg";
if (strcmp(dot, ".pac") == 0)
return "application/x-ns-proxy-autoconfig";
return "text/plain; charset=utf-8";
}
//获得一行数据,每行以\r\n作为结束标记
int get_line(int sock, char *buf, int size)
{
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && (c != '\n'))
{
n = recv(sock, &c, 1, 0);
/* DEBUG printf("%02X\n", c); */
if (n > 0)
{
if (c == '\r')
{
n = recv(sock, &c, 1, MSG_PEEK);//MSG_PEEK 从缓冲区读数据,但是数据不从缓冲区清除
/* DEBUG printf("%02X\n", c); */
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0';
return(i);
}
void strdecode(char *to, char *from)
{
for ( ; *from != '\0'; ++to, ++from) {
//依次判断from中 %20 三个字符
//[/%E8%8B%A6%E7%93%9C]转化为 0xe8,0x8b,0xa6,0xe7,0x93,0x9c
if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) {
*to = hexit(from[1])*16 + hexit(from[2]);
//移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
from += 2;
} else
*to = *from;
}
*to = '\0';
}
//16进制数转化为10进制, return 0不会出现
int hexit(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return 0;
}
//"编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
//strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{
int tolen;
for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {
*to = *from;
++to;
++tolen;
} else {
sprintf(to, "%%%02x", (int) *from & 0xff);
to += 3;
tolen += 3;
}
}
*to = '\0';
}