先了解一下HTTP响应行的组成。HTTP响应和HTTP请求是相似的。一个HTTP响应的组成有:一个响应行,后面跟随零个或者更多的响应报头,再跟随一个终止报头的空行,再跟随响应主体。响应行的格式是:
<version><status code><status message>
版本字段描述了响应所遵循的HTTP版本。状态码是一个三位的正整数,指明对请求的处理。状态消息给出与错误代码等价的英文描述。
clienterror()发送一个HTTP响应报文个给客户端,在响应行中包含状态码和状态消息,响应主体包含一个HTML文件,来向用户解释错误。
void clienterror(
int fd, char
*cause, char
*errnum,
char
*shortmsg, char
*longmsg)
{
char buf[MAXLINE], body[MAXBUF];
/*Build the HTTP response body*/
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);
/*Print the HTTP response*/
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));
}
4.read_requesthdrs()来跳过请求报头的信息,直到遇见表示报头结束的空文本行。
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;
}
5.parse_uri()解析URI参数
parse_uri()将URI解析为一个文件名和一个可选的CGI参数字符串。如果是静态内容,我们将清除CGI参数串(即将cgiargs置空),然后将URI转换为一个相对的UNIX路径名,例如./home.html。如果URI是以/结尾的则要在后面添加默认文件名home.html。如果是动态内容,URI中的文件名和CGI参数是以?分割,以此可以抽出CGI参数和文件名。
int parse_uri(
char *uri,
char *filename,
char *cgiargs)
{
char *ptr;
if(!
strstr(uri,
"cgi-bin"))
//static content
{
strcpy(cgiargs,
"");
strcpy(filename,
".");
strcat(filename, uri);
if(uri[
strlen(uri)-
1] ==
'/')
strcat(uri,
"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;
}
}
6.serve_static()处理静态内容
Tiny提供四种不同的静态内容:HTML文件、无格式文本文件、编码为GIF和JPEG格式的图片。
serve_static()函数发送一个HTTP响应,响应的主体包括一个本地文件的内容。首先,我们检查文件名的后缀来判断文件类型,并在响应报头的Content-type字段中显示出来。随后我们发送响应行和响应报头给客户端,用一个空行来终止报头。
然后我们以只读的方式打开所请求的文件获得文件句柄srcfd,并用mmap函数将文件映射到虚拟存储器空间,代码中是将srcfd指向的文件的前filesize个字节映射到一个地址从srcp开始的只读私有虚拟存储器区域。一旦映射成功我们就可以通过srcp来操作文件不再需要文件句柄了,所以我们关闭srcfd。然后我们使用Rio_writen()将文件传送到客户端。这时静态内容的处理操作已经完成了,我们释放srcp的虚拟存储器映射。
void serve_static(
int fd,
char *filename,
int filesize)
{
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXBUF];
/*Send response headers to client*/
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-lenght: %d\r\n", buf, filesize);
sprintf(buf,
"%sContent-type: %s\r\n\r\n", buf, filetype);
Rio_writen(fd, buf,
strlen(buf));
/*Send response body to client*/
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 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");
}
7.serve_dynamic()处理动态内容