最近工作中需要客户端和服务器使用https协议进行通信,我负责客户端程序的编写,想到以前在深入理解操作系统中看过web服务器的实现,代码整理如:
#include"apue.h"
extern char **environ;
void clienterror(int fd,char *cause,char *errnum,char *shortmsg,char *longmsg)
{
char buf[MAXLINE],body[MAXLINE];
sprintf(body,"<html><title>Tiny Error</title>");
sprintf(body,"%s<body bgcolor=""ffffff"">\r\n",body);
sprintf(body,"%s%s:%s\r\n",body,errnum,shortmsg);
sprintf(body,"%s<p>%s:%s\r\n",body,longmsg,cause);
sprintf(body,"%s<hr><em>The tiny Web server</em?\r\n",body);
sprintf(buf,"HTTP/1.0 %s %s\r\n",errnum,shortmsg);
rio_writen(fd,buf,strlen(buf));
sprintf(buf,"Content-type:text/html\r\n");
rio_writen(fd,buf,strlen(buf));
sprintf(buf,"Content-length:%d\r\n\r\n",(int)strlen(body));
rio_writen(fd,buf,strlen(buf));
rio_writen(fd,body,strlen(body));
}
void read_requesthdrs(rio_t *rp)
{
char buf[MAXLINE];
rio_readlineb(rp,buf,MAXLINE);
while(strcmp(buf,"\r\n"))
{
rio_readlineb(rp,buf,MAXLINE);
printf("%s",buf);
}
return;
}
int parse_uri(char *uri,char *filename,char *cgiargs)
{
char *ptr;
printf("parse_uri(uri):%s\n",uri);
if(!strstr(uri,"cgi-bin"))
{
strcpy(cgiargs,"");
strcpy(filename,".");
strcat(filename,uri);
if(uri[strlen(uri)-1]=='/')
strcat(filename,"home.html");
return 1;
}
else
{
ptr=index(uri,'?');
if(ptr)
{
strcpy(cgiargs,ptr+1);
*ptr='\0';
}
else
strcpy(cgiargs,"");
strcpy(filename,".");
strcat(filename,uri);
return 0;
}
}
void get_filetype(char *filename,char *filetype)
{
if(strstr(filename,".html"))
strcpy(filetype,"text/html");
else if(strstr(filename,".gif"))
strcpy(filetype,"image/gif");
else if(strstr(filename,".jpg"))
strcpy(filetype,"image/jpeg");
else
strcpy(filetype,"text/plain");
}
void serve_dynamic(int fd,char *filename,char *caiargs)
{
char buf[MAXLINE],*emptylist[]={NULL};
sprintf(buf,"HTTP/1.0 200 OK\r\n");
rio_writen(fd,buf,strlen(buf));
sprintf(buf,"Server:Tiny Web Server\r\n");
rio_writen(fd,buf,strlen(buf));
if(fork()==0)
{
setenv("QUERY_STRING",caiargs,1);
dup2(fd,STDOUT_FILENO);
execve(filename,emptylist,environ);
}
wait(NULL);
}
void serve_static(int fd,char *filename,int filesize)
{
int srcfd;
char *srcp,filetype[MAXLINE],buf[MAXLINE];
get_filetype(filename,filetype);
sprintf(buf,"HTTP/1.0 200 OK\r\n");
sprintf(buf,"%sServer:Tiny Web Server\r\n",buf);
sprintf(buf,"%sContent-length:%d\r\n",buf,filesize);
sprintf(buf,"%sContent-type:%s\r\n\r\n",buf,filetype);
rio_writen(fd,buf,strlen(buf));
srcfd=open(filename,O_RDONLY,0);
srcp=mmap(0,filesize,PROT_READ,MAP_PRIVATE,srcfd,0);
close(srcfd);
rio_writen(fd,srcp,filesize);
munmap(srcp,filesize);
}
void doit(int fd)
{
int is_static;
struct stat sbuf;
char buf[MAXLINE],method[MAXLINE],uri[MAXLINE],version[MAXLINE];
char filename[MAXLINE],cgiargs[MAXLINE];
rio_t rio;
rio_readinitb(&rio,fd);
rio_readlineb(&rio,buf,MAXLINE);
sscanf(buf,"%s %s %s",method,uri,version);
if(strcasecmp(method,"GET"))
{
clienterror(fd,method,"501","Not implemented","Tiny does not implemented this method");
return;
}
read_requesthdrs(&rio);
is_static=parse_uri(uri,filename,cgiargs);
if(stat(filename,&sbuf)<0)
{
clienterror(fd,filename,"404","Not found","Tiny couldn't find this file");
return;
}
if(is_static)
{
if(!(S_ISREG(sbuf.st_mode))||!(S_IRUSR&sbuf.st_mode))
{
clienterror(fd,filename,"403","Forbidden","Tiny could't read the file");
return;
}
serve_static(fd,filename,sbuf.st_size);
}
else
{
if(!(S_ISREG(sbuf.st_mode))|!(S_IXUSR&sbuf.st_mode))
{
clienterror(fd,filename,"403","Forbidden","Tiny could't run the CGI program");
return;
}
serve_dynamic(fd,filename,cgiargs);
}
}
int main(int argc,char **argv)
{
int listenfd,connfd,port,clientlen;
struct sockaddr_in clientaddr;
if(argc!=2)
{
fprintf(stderr,"usage:%s <port> \n",argv[0]);
exit(0);
}
port=atoi(argv[1]);
listenfd=open_listenfd(port);
while(1)
{
clientlen=sizeof(clientaddr);
connfd=accept(listenfd,(SA*)&clientaddr,&clientlen);
doit(connfd);
close(connfd);
}
}
我们可以直接使用浏览器测试上面的程序,比如当前服务器程序的目录下面有一个index.html,只要我们在浏览器中输入:
localhost:<port>/index.html就可以请求到index.html
服务器端收到的浏览器请求行如下: