上一小节已经实现了浏览器发送请求,然后服务器给出应答信息,然后浏览器显示出服务器发送过来的网页。一切看起来都是那么的美好。这一小节就准备实现可以根据地址栏url的不同来返回指定的网页。目前还不考虑带参数的问题。
stat函数
#include <sys/stat.h>
int stat(const char *restrict pathname,struct stat * restrict buf);
int fstat(int filedes,struct stat * buf);
int lstat(const char *restrict pathname,struct stat * restrict buf);
给出pathname,stat函数就返回与此命名文件有关的信息结构。fstat函数获取已在描述符filedes上打开文件的有关信息。lstat函数类似与stat,但是命名的文件不是个符号链接。
实现指定url访问指定目录的web服务器
int WebServer::ServerRequest(int cli_fd)
{
char buf[];
int size=;
int i,j;
char method[];//用于保存请求方式
char url[];
char path[];
struct stat st;
int cgi;
memset(buf,,sizeof(buf));
cgi=;
//获取第一行请求信息 一般格式为: GET / HTTP/1.1
// POST / HTTP/1.1
size=get_line(cli_fd,buf,sizeof(buf));
cout<<"\t\t"<<buf<<endl;
i=,j=;
//截取第一个单词
while(!isspace(buf[j]) && (i<sizeof(method)-))
{
method[i]=buf[j];
i++;j++;
}
method[i]='\0';
//取第一个与第二个单词之间的空格
while(isspace(buf[j]) && (j<sizeof(buf)))
j++;
//截取第二个单词
i=;
while(!isspace(buf[j]) && (i<sizeof(url)-) && (j<sizeof(buf)))
{
url[i]=buf[j];
i++;j++;
}
url[i]='\0'; if(strcasecmp(method,"GET") && strcasecmp(method,"POST"))
{
Page_501(cli_fd);
return -;
} if(strcasecmp(method,"GET")==)
{
cout<<"此次请求的方式是GET方法"<<endl;
}
else if(strcasecmp(method,"POST")==)
{
cout<<"此次请求的方式是POST方法"<<endl;
}
cout<<"此次请求的地址为:"<<url<<endl; sprintf(path,"www%s",url);//这个是web服务器的主目录,这个以后可以处理成读取配置文件,这里就先写固定的www目录
if(path[strlen(path)-]=='/')
strcat(path,"index.html");//同上 //根据文件名,获取该文件的文件信息。如果为-1,表示获取该文件失败
if(stat(path,&st)==-)
{
while((size>) && strcmp("\n",buf))//去除掉多余的请求头信息
size=get_line(cli_fd,buf,sizeof(buf));
Page_404(cli_fd);
}
else
{
if((st.st_mode & S_IFMT)== S_IFDIR)//判断url地址,如果是个目录,那么就访问该目录的index.html
{
strcat(path,"/index.html");
}
if((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH))//判断该url地址所对应的文件是否是可执行,并且是否有权限
{
cgi=;//是一个cgi程序
}
if(cgi==)//如果cgi为0,那么就表示该url所对应的文件不是cgi程序,而是一个简单的静态页面
{
ServerCatHttpPage(cli_fd,path);
}
} if(fork()==)
{
//处理阶段
//execl("/bin/ls","ls","/home/myuser/",NULL);
//Page_200(cli_fd);
}
close(cli_fd);
return ;
}
返回页的代码
int WebServer::ServerCatHttpPage(int cli_fd,char *path)
{
FILE * resource=NULL;
int size=;
char buf[];
buf[]=;buf[]=;
while((size>) && strcmp("\n",buf))//去除掉多余的请求头信息
size=get_line(cli_fd,buf,sizeof(buf)); resource=fopen(path,"r");//根据GET后面的文件吗,将文件打开
if(resource==NULL)//打开文件失败
{
Page_404(cli_fd);
}
else
{
char type[]="text/html";
char * p =type;
Page_Headers(cli_fd,p);
Page_Cat(cli_fd,resource);
}
fclose(resource);
return ;
}
int WebServer::Page_Headers(int cli_fd,char * type)
{
char buf[];
strcpy(buf,"HTTP/1.1 200 OK\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "Server:wunaozai.cnblogs.com\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "Content-Type: %s\r\n",type);
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(cli_fd, buf, strlen(buf), );
return ;
}
int WebServer::Page_Cat(int cli_fd,FILE * fp)
{
char buf[]; fgets(buf,sizeof(buf),fp);
while(!feof(fp))
{
send(cli_fd,buf,strlen(buf),);
fgets(buf,sizeof(buf),fp);
}
return ;
}
代码写好了,我在当前目录下创建一个www的目录在里面有个index.html和text.html的页面。然后我们通过浏览器进行返回。得到的结果如下:
可以看出都显示了指定的网页信息,而最后一个是404页面,可是为什么会有乱码呢,应该是在应答信息哪里没有指点编码格式。所以我们在Page_404这个函数里的Content-Type这一行进行如下修改
sprintf(buf, "Content-Type: text/html;charset=utf-8\r\n");
当然还可以在html网页上进行指定。
本小结篇幅比较少,接下来就实现传输一个ico图标吧。我们都知道一个html网页是通过一个url进行查找文件然后以http协议发送个浏览器。但是我们服务器怎么发送css或js或图片给浏览器呢?怎么知道那些是要的那些是不要的。一看是还以为很难,上网查了一下,原来很简单的。浏览器接收到根据url发送过来的html文件,然后浏览器会分析这个html文件中代码的图片文件,css文件等,然后在跟服务器建立一个http请求,请求一个新的文件。在发送的过程中,不是直接发送图片过去的,而是先编织成HTTP的格式发送给浏览器,其中还要指定这个图片的格式,大概就是这样了。说起来比较抽象,我用wireshark抓一个包看看。
可以看出这个应答信息的格式跟以前讲的是一样的。也是一个应答头,然后在应答头里有个Content-Length属性,里面包含接下来要接收的文件大小。
一开始使用下面代码进行文件的读取
int WebServer::Page_Cat(int cli_fd,FILE * fp)
{
char buf[]; fgets(buf,sizeof(buf),fp);
while(!feof(fp))
{
send(cli_fd,buf,strlen(buf),);
fgets(buf,sizeof(buf),fp);
}
return ;
}
然后在浏览器进行访问,然后就是一直访问不到图片资源,一直弄到凌晨几点。今天,想了个办法,对于图片一个字节一个字节的打印出来,弄了好久才知道,原来是因为图片资源里面有ascii码为0的字符,所以导致在发送的时候使用strlen时发送数据会不完整。哎......这个以后要注意啊。所以我准备使用fgetc来获取数据,要注意fgetc的返回值是int型,用char会出错,应该没有人跟我一样不小心吧。
int WebServer::Page_Headers(int cli_fd,char * type,int filesize)
{
char buf[];
strcpy(buf,"HTTP/1.1 200 OK\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "Server:wunaozai.cnblogs.com\r\n");
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "Content-Type: %s\r\n",type);
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "Content-Length: %d\r\n",filesize);
send(cli_fd, buf, strlen(buf), );
sprintf(buf, "\r\n");
send(cli_fd, buf, strlen(buf), );
return ;
}
int WebServer::Page_Cat(int cli_fd,FILE * fp)
{
int c; while((c=fgetc(fp))!=EOF)
{
send(cli_fd,&c,,);
}
return ;
}
ServerCatHttpPage函数的代码如下
int WebServer::ServerCatHttpPage(int cli_fd,char *path,int filesize)
{
FILE * resource=NULL;
int size=;
char buf[];
char type[];
char * p =type;
buf[]=;buf[]=;
while((size>) && strcmp("\n",buf))//去除掉多余的请求头信息
size=get_line(cli_fd,buf,sizeof(buf)); //判断文件类型
int len=strlen(path);
cout<<path<<":"<<filesize<<endl;
if(path[len-]=='h'&&path[len-]=='t'&&path[len-]=='m'&&path[len-]=='l')
{
strcpy(type,"text/html");
}
else if(path[len-]=='.'&&path[len-]=='i'&&path[len-]=='c'&&path[len-]=='o')
{
strcpy(type,"image/x-icon");
}
else if(path[len-]=='.'&&path[len-]=='j'&&path[len-]=='p'&&path[len-]=='g')
{
strcpy(type,"image/jpeg");
}
else
{
strcpy(type,"text/html");
}
cout<<"请求资源的类型:"<<type<<endl; resource=fopen(path,"r");//根据GET后面的文件吗,将文件打开
if(resource==NULL)//打开文件失败
{
Page_404(cli_fd);
}
else
{
Page_Headers(cli_fd,p,filesize);
Page_Cat(cli_fd,resource);
}
fclose(resource);
return ;
}
好了,感觉还不错的样子。
参考资料: http://bbs.csdn.net/topics/100130327