本章编写的web服务器本质上只是一个集成了cat和ls的unix shell。
学习要点:
(1)HTTP协议
浏览器与web服务器之间的交互主要包含客户的请求和服务器的应答。请求和应答的格式在超文本传输协议(HTTP)中有定义。
1.HTTP请求:GET
GET /index.html HTTP/1.0
因此我们需要知道如何识别命令和根据参数进行处理。
process_rq(char *rq,int fd)
{
char cmd[BUFSIZ],arg[BUFSIZ];
if(fork() != 0) //建立新进程
return;
strcpy(arg,"./");
if(sscanf(rq,"%s%s",cmd,arg+2) != 2)//分解命令和参数
return;
if(strcmp(cmd,"GET") != 0)//根据参数不同,返回不同内容
cannot_do(fd);
else if(not_exist(arg))
do_404(arg,fd);
else if(isadir(arg))
do_ls(arg,fd);
else if(ends_in_cgi(arg))
do_exec(arg,fd);
else
do_cat(arg,fd);
}
2.HTTP应答:OK
服务器读取请求,检查请求,然后返回一个请求,应答分为头部和内容。
头部
header(FILE *fp,char * content_type)
{
fprintf(fp,"HTTP/1.0 200 OK\r\n");
if(content_type)
fprintf(fp,"Content-type:%s\r\n",content_type);
}
(2)设计web服务器
1.建立服务器
使用socklib.c中的make_server_socket
2.接收请求
使用accept来得到指向客户端的文件描述符。可以使用fdopen使得该文件描述符转换为缓冲流
3.读取请求
上面已经说明
4.处理请求
如何列出目录信息,cat文件以及运行程序
5.发送应答
已说明
代码实现:
编译命令
gcc socklib.c webserv.c -o webserv
socklib.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <time.h>
#include <string.h>
#define HOSTLEN 256
#define BACKLOG 1
int make_server_socket_q(int,int);
int make_server_socket(int portnum)
{
return make_server_socket_q(portnum,BACKLOG);
}
int make_server_socket_q(int portnum,int backlog)
{
struct sockaddr_in saddr;
struct hostent *hp;
char hostname[HOSTLEN];
int sock_id;
//向内核申请一个socket
sock_id = socket(PF_INET,SOCK_STREAM,0);
if(sock_id == -1)
return -1;
//绑定地址到socket,地址包括主机名和端口
bzero((void *)&saddr,sizeof(saddr));
gethostname(hostname,HOSTLEN);
hp = gethostbyname(hostname);
bcopy((void *)hp->h_addr,(void *)&saddr.sin_addr,hp->h_length);
saddr.sin_port = htons(portnum);
saddr.sin_family = AF_INET;
if(bind(sock_id,(struct sockaddr *)&saddr,sizeof(saddr)) != 0)
return -1;
//监听socket上的连接
if(listen(sock_id,backlog) != 0)
return -1;
return sock_id;
}
//未用到
int connect_to_server(char *host,int portnum)
{
int sock;
struct sockaddr_in servadd;
struct hostent *hp;
sock = socket(AF_INET,SOCK_STREAM,0);
if(sock == -1)
return -1;
bzero(&servadd,sizeof(servadd));
hp = gethostbyname(host);
if(hp == NULL)
return -1;
bcopy(hp->h_addr,(struct sockaddr *)&servadd.sin_addr,hp->h_length);
servadd.sin_port = htons(portnum);
servadd.sin_family = AF_INET;
if(connect(sock,(struct sockaddr *)&servadd,sizeof(servadd))!=0)
return -1;
return sock;
}
webserv.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
int main(int ac,char *av[])
{
int sock,fd;
FILE *fpin;
char request[BUFSIZ];
if(ac == 1){
fprintf(stderr,"usage:ws portnum\n");
exit(1);
}
sock = make_server_socket(atoi(av[1]));
if(sock == -1)
exit(2);
while(1){
fd = accept(sock,NULL,NULL);//接收socket连接
fpin = fdopen(fd,"r");//使用fdopen使得该文件描述符转换为缓冲流
fgets(request,BUFSIZ,fpin);//获取请求字符串
printf("got a call:request = %s",request);
read_til_crnl(fpin);
process_rq(request,fd);//处理请求
fclose(fpin);
}
}
read_til_crnl(FILE *fp)
{
char buf[BUFSIZ];
while(fgets(buf,BUFSIZ,fp) != NULL&& strcmp(buf,"\r\n") != 0);
}
process_rq(char *rq,int fd)
{
char cmd[BUFSIZ],arg[BUFSIZ];
if(fork() != 0)
return;
strcpy(arg,"./");
if(sscanf(rq,"%s%s",cmd,arg+2) != 2)//分解请求和参数
return;
if(strcmp(cmd,"GET") != 0) //非get类型不处理
cannot_do(fd);
else if(not_exist(arg)) //不存在打印404
do_404(arg,fd);
else if(isadir(arg)) //目录执行ls -l
do_ls(arg,fd);
else if(ends_in_cgi(arg)) //cgi执行对应的命令
do_exec(arg,fd);
else
do_cat(arg,fd); //显示html文件等
}
header(FILE *fp,char * content_type) //响应头
{
fprintf(fp,"HTTP/1.0 200 OK\r\n");
if(content_type)
fprintf(fp,"Content-type:%s\r\n",content_type);
}
cannot_do(int fd)
{
FILE *fp = fdopen(fd,"w");
fprintf(fp,"HTTP/1.0 501 Not Implemented\r\n");
fprintf(fp,"Content-type:text/palain\r\n");
fprintf(fp,"\r\n");
fprintf(fp,"That command is not yet implemented\r\n");
fclose(fp);
}
do_404(char *item,int fd)
{
FILE *fp = fdopen(fd,"w");
fprintf(fp,"HTTP/1.0 404 Not Found\r\n");
fprintf(fp,"Content-type:text/plain\r\n");
fprintf(fp,"\r\n");
fprintf(fp,"The item you requested:%s\r\n is not found\r\n",item);
fclose(fp);
}
isadir(char *f)
{
struct stat info;
return (stat(f,&info) != -1 && S_ISDIR(info.st_mode));
}
not_exist(char *f)
{
struct stat info;
return (stat(f,&info) == -1);
}
do_ls(char *dir,int fd)
{
FILE *fp;
fp = fdopen(fd,"w");
header(fp,"text/plain");
fprintf(fp,"\r\n");
fflush(fp);
dup2(fd,1);
dup2(fd,2);
close(fd);
execlp("ls","ls","-l",dir,NULL);//执行ls -l
perror(dir);
exit(1);
}
char *file_type(char *f)
{
char *cp;
if((cp = strrchr(f,'.')) != NULL)
return cp +1;
return "";
}
ends_in_cgi(char *f)
{
return (strcmp(file_type(f),"cgi") == 0);
}
do_exec(char *prog ,int fd)
{
FILE *fp;
fp = fdopen(fd,"w");
header(fp,NULL);
fflush(fp);
dup2(fd,1);
dup2(fd,2);
close(fd);
execl(prog,prog,NULL);
perror(prog);
}
do_cat(char *f,int fd)
{
char *extension = file_type(f);//判断文件类型
char *content = "text/plain";
FILE *fpsock,*fpfile;
int c;
if(strcmp(extension,"html") == 0)
content = "text/html";
else if(strcmp(extension,"gif") == 0)
content = "image/gif";
else if(strcmp(extension,"jpg") == 0)
content = "image/jpeg";
else if(strcmp(extension,"jpeg") == 0)
content = "image/jpeg";
fpsock = fdopen(fd,"w");
fpfile = fopen(f,"r");
if(fpsock != NULL&&fpfile != NULL)
{
header(fpsock,content);
fprintf(fpsock,"\r\n");
while((c = getc(fpfile)) != EOF)//响应内容
putc(c,fpsock);
fclose(fpfile);
fclose(fpsock);
}
exit(0);
}
功能验证
(1)运行服务器
./webserv 端口号
(2)在浏览器地址栏输入
主机名:端口号/文件路径
(3)查看目录