TINY Web Server源码阅读

TINY Web Server源码阅读

最近利用闲暇时间在阅读《HTTP权威指南》,阅读到第5章提到了最小的Perl Web Server,就想到了如何实现一个简单的web server,谷歌后发现方式很多,比如nc -kl , python SimpleHttpServer, Jetty等,但是感觉最适合的还是CSAPP中11章涉及的tiny web server,之前阅读CSAPP到这里的时候只是粗略的理解,但是在深入理解HTTP结构后,画面就更清晰了。

之所以要学习这个tiny web server, CSAPP已经告诫我们了。

Tiny is an interesting program. It combines many of the ideas that we have learned about, such as process control, Unix I/O, the sockets interface, and HTTP, in only 250 lines of code. While it lacks the functionality, robustness, and security of a real server, it is powerful enough to serve both static and dynamic content to real Web browsers. We encourage you to study it and implement it yourself. It is quite exciting (even for the authors!) to point a real browser at your own server and watch it display a complicated Web page with text and graphics.

TINY的主程序main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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(1);
}
port = atoi(argv[1]);
//打开监听套接字
listenfd = Open_listenfd(port);
while (1) {
clientlen = sizeof(clientaddr);
// 连接请求
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
// 具体处理请求
doit(connfd);
Close(connfd);
}
}

Open_clientfd 就是打开一个TCP套接字,设置SO_REUSEADDR选项;服务器采用循环处理模式。

处理HTTP请求doit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/*
* doit - handle one HTTP request/response transaction
*/

void doit(int fd)
{

int is_static;
struct stat sbuf;
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
// Robust IO 看CSAPP第10章
rio_t rio;

/* Read request line and headers */
Rio_readinitb(&rio, fd);
// 读取一行,在这里是request line
Rio_readlineb(&rio, buf, MAXLINE);
// 解析出请求行中的HTTP 方法,uri,版本信息
sscanf(buf, "%s %s %s", method, uri, version);
if (strcasecmp(method, "GET")) {
// 为了简单现在只是支持GET方法,非GET方法,就告知客户端我们无能为力
clienterror(fd, method, "501", "Not Implemented",
"Tiny does not implement this method");
return;
}
// 消耗请求头部
read_requesthdrs(&rio);

/* Parse URI from GET request */
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) { /* Serve static content */
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size);
}
else { /* Serve dynamic content */
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn't run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs); }
}

错误处理 clienterror

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* clienterror - returns an error message to the client
*/

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);

/* 构建响应行,响应头部 */
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));//空行结束response headers
Rio_writen(fd, buf, strlen(buf));
Rio_writen(fd, body, strlen(body));
}

处理请求头部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* read_requesthdrs - read and parse HTTP request headers
*/

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;
}

这里仅仅是打印出来。

解析URI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
* parse_uri - parse URI into filename and CGI args
* return 0 if dynamic content, 1 if static
*/

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(filename, "home.html");
return 1;
}
else { /* Dynamic content */
ptr = index(uri, '?');
if (ptr) {
strcpy(cgiargs, ptr+1);
*ptr = '\0';
}
else
strcpy(cgiargs, "");

strcpy(filename, ".");
strcat(filename, uri);
return 0;
}
}

解析uri是处理资源映射的问题,这里直接看uri中是否包含字符串cgi-bin。

处理静态请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
* serve_static - copy a file back to the client
*/

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-length: %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);
}

/*
* get_filetype - derive file type from file name
*/

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");
}

先返回了响应头,然后采用内存映射读取文件,发送给client。

处理动态请求cgi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* serve_dynamic - run a CGI program on behalf of the client
*/

void serve_dynamic(int fd, char *filename, char *cgiargs)
{

char buf[MAXLINE], *emptylist[] = { NULL };

/* Return first part of HTTP response */
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) { /* child */
/* Real server would set all CGI vars here */
setenv("QUERY_STRING", cgiargs, 1);
Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */
Execve(filename, emptylist, environ); /* Run CGI program */
}
Wait(NULL); /* Parent waits for and reaps child */
}

在子进程中执行对应的cgi程序,参数的传递是通过环境变量,而且子进程继承了父进程描述符,所以可以操作连接套接字。

小插曲