A. 因为 read_requesthdrs 中已经打印出了请求报头,所以只要打印请求行即可。在doit函数中第一个sscanf语句之后添加下面的语句即可: printf ( "%s %s %s\n" , method, uri, version);
B. 用火狐浏览器输出结果:
另外,如果要存成文件的话,可能需要另存为?
C. A的结果可以表明,浏览器 使用HTTP/1.1
D. 请求行和报头如下:
GET /clockwise.gif HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:20.0) Gecko/20100101 Firefox/20.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
User-Agent: 系统以及浏览器情况
Accept:可以接受的媒体;
Accept-Encoding:可以接受的编码方案;
Accept-Language:能够接受的语言;
其实上述内容是百度得到的
在get_filetype函数里面添加:
else if (strstr (filename, ".mpg" ) || strstr (filename, ".mp4" )) strcpy (filetype, "video/mpg" );
并没有想到很好的方法。
因为结束子进程之前,我们不可以关闭已连接描述符。这就导致我们还是得让serve_dynamic或者是doit函数或者main函数中要等待子进程结束。迭代服务器的设计让这个问题变得比较无聊。
在main函数之前加入代码:
int chdEnded ;
#include void child_signal (int sig) { pid_t pid;
while ((pid = waitpid (-1 , NULL, WNOHANG)) > 0 ) ;
chdEnded = 1; }
在main函数中添加语句 signal(SIGCHILD, child_handle);
每次accept之前,让chdEnded = 0;
并且在doit()中的serve_dynamic之后添加:
while(! chdEnded ) pause();//or do sth
删掉serve_dynamic里的wait(NULL);
serve_static中的存储器映射语句改为:
srcfd = open (filename, O_RDONLY, 0 ); srcp = (char *)malloc (sizeof (char )*filesize);rio_readn (srcfd, srcp, filesize);close (srcfd);rio_writen (fd, srcp, filesize);free (srcp);
HTML文件:
method="get">
Num1:
Num2:
因为提交的表单里面有参数名字(num1=x&num2=y),所以要 修改相应的adder.c:
int parseNum (char *s) { int i = strlen (s) - 1 ; while (i>0 && s[i-1 ]>='0' &&s[i-1 ]<='9' ) i--; return atoi (s+i); }int main (void ) { char *buf, *p; char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE]; int n1=0 , n2=0 ; /* Extract the two arguments */ if ((buf = getenv ("QUERY_STRING" )) != NULL) { p = strchr (buf, '&' ); *p = 0 ; strcpy (arg1, buf); strcpy (arg2, p+1 ); n1 = parseNum (arg1); n2 = parseNum (arg2); } /* Make the response body */ sprintf (content, "Welcome to add.com: " ); sprintf (content, "%sTHE Internet addition portal.\r\n"
, content); sprintf (content, "%sThe answer is: %d + %d = %d\r\n"
, content, n1, n2, n1 + n2); sprintf (content, "%sThanks for visiting!\r\n" , content); /* Generate the HTTP response */ printf ("Content-length: %d\r\n" , (int )strlen (content)); printf ("Content-type: text/html\r\n\r\n" ); printf ("%s" , content); fflush (stdout); exit (0 ); }
更好的做法是在本页显示,目前暂时不会。
想到的办法就是定义一个变量rmtd,表示请求的方法。
在client_error,serve_static和serve_dynamic中添加一个参数mtd(改的地方也比较多),表示方法。如果mtd为HEAD,就只打印头部。
结果如下:
bnuzhanyu@ubuntu:~/CSAPP11/cgi-bin$ telnet localhost 12345
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD / HTTP/1.1
HTTP/1.0 200 OK
Server: Tiny Web Server
Content-length: 2722
Content-type: text/html
Connection closed by foreign host.
bnuzhanyu@ubuntu:~/CSAPP11/cgi-bin$ telnet localhost 12345
Trying ::1...
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HEAD /clockwise.gif HTTP/1.1
HTTP/1.0 200 OK
Server: Tiny Web Server
Content-length: 126150
Content-type: image/gif
Connection closed by foreign host.
还是使用rmtd表示方法。
这里需要改的还有读取报头。
用POST方法,表单的参数是在报头之后。
报头中有一项是Content-Length,读取出来, 在报头读完之后,接着读取Content-Length个字节(注意最后添0),这些字节就是form用post方法提交的数据。
主要修改的就是doit方法和read_request方法。
下面的程序只能针对参数为文本的情况,且参数总长度最大不超过MAXLINE。
#define M_GET 0 #define M_POST 1 #define M_HEAD 2 #define M_NONE (-1)
void doit (int fd) { int is_static; int rmtd = 0 ; struct stat sbuf; char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE]; char filename[MAXLINE], cgiargs[MAXLINE]; rio_t rio; /*for post*/ int contentLen; char post_content[MAXLINE]; /* Read request line and headers */ rio_readinitb (&rio, fd); rio_readlineb (&rio, buf, MAXLINE); sscanf (buf, "%s %s %s" , method, uri, version); printf ("%s %s %s\n" , method, uri, version); if (strcmp (method, "GET" ) == 0 ) rmtd = M_GET; else if (strcmp (method, "POST" ) == 0 ) rmtd = M_POST; else if (strcmp (method, "HEAD" ) == 0 ) rmtd = M_HEAD; else rmtd = M_NONE; if (rmtd == M_NONE) { clienterror (fd, method, "501" , "Not Implemented" , "Tiny does not implement this method" , rmtd ); return ; } contentLen = read_requesthdrs (&rio, post_content, rmtd); /* 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" , rmtd ); 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" , rmtd ); return ; } serve_static (fd, filename, sbuf.st_size, rmtd ); } 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" , rmtd ); return ; } if (rmtd == M_POST) strcpy (cgiargs, post_content); serve_dynamic (fd, filename, cgiargs, rmtd ); } }
int read_requesthdrs (rio_t *rp, char * content, int rmtd) { char buf[MAXLINE]; int contentLength = 0 ; char *begin; rio_readlineb (rp, buf, MAXLINE); while (strcmp (buf, "\r\n" )) { rio_readlineb (rp, buf, MAXLINE); printf ("%s" , buf); if (rmtd == M_POST && strstr (buf, "Content-Length: ")==buf) contentLength = atoi (buf+strlen ("Content-Length: ")); } if (rmtd == M_POST){ contentLength = rio_readnb (rp, content, contentLength); content[contentLength] = 0; printf ("POST_CONTENT: %s\n", content); } return contentLength; }
strstr那句也可以用strncmp代替(效率应该高一些)。
表单改成Post之后,运行效果:
为了测试EPIPE错误,我在read_requesthdrs里面添加了sleep(5)。
于是,在浏览器里请求之后,立即断开。进程 出现错误:
GET /add.html HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:20.0) Gecko/20100101 Firefox/20.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Segmentation fault
bnuzhanyu@ubuntu:~/CSAPP11$
为了解决这个问题,我用了setjmp和longjmp。
当进程捕捉到SIGPIPE时,进入一个信号处理函数:
jmp_buf buf;void epipe_signal (int sig) { longjmp (buf, 1 ); }
而在main函数中,doit部分需要这样改:
rc = setjmp (buf);if (rc == 0 ) { doit (connfd); close (connfd); }
这样做虽然能解决这个问题,然而,如果是在子进程中longjmp(也就是adder里sleep(5)时,浏览器关闭了),那么会不会有问题呢?
为了测试,我又在adder中加入了sleep(5)。
并且setjmp后面的语句改为:
if (rc == 0 ) { printf ("rc=%d pid=%d\n" , rc, getpid ()); doit (connfd); close (connfd); }else printf ("rc=%d pid=%d\n" , rc, getpid ());
结果是,当我在adder的sleep期间关闭浏览器,没有输出rc的消息。
后来想起来,输出已经被重定向了,而且是子进程和父进程的输出都被重定向了(没懂为什么都重定向了)。 即,当浏览器再次请求时,rc=0也不会输出。
后来在setjmp后添加了dup2(2,1),即将标准输出重定向到标准错误,就可以看见输出了。
奇怪的是,pid都是main的pid。这是不是说明,在子进程中调用longjmp仍然可以回到父进程。那么子进程去哪儿了呢?这个问题还是需要研究一下。
今天测试了一下,子进程的longjmp还是在子进程中。
所以,现在不知道之前那个程序为什么一直输出main的pid,这太奇怪了。
这也就证明我上面的写法是不正确的。
但是,想一想,觉得如果是在子进程中遇到关闭描述符的问题,那么可以直接退出这个进程啊,因为没必要再让这个进程存活下去了。所以,只要简单地判断当前进程是不是子进程,如果是,则exit,如果不是,则longjmp。
先用一个变量mainpid在主函数开始时赋值为getpid();
if(getpid() == mainpid) longjmp(buf,1); else exit(0);