源码分析之tinyhttpd-0.1

1. 简介:

tinyhttpd是使用c语言开发的超轻量级http服务器,通过代码流程可以了解http服务器的基本处理流程,

并且涉及了网络套接字,线程,父子进程,管道等等知识点;

 

项目地址:http://sourceforge.net/projects/tinyhttpd/

 

2. 流程介绍:

(1) 服务器启动,等待客户端请求到来;

(2) 客户端请求到来,创建新线程处理该请求;

(3) 读取httpHeader中的method,截取url,其中GET方法需要记录url问号之后的参数串;

(4) 根据url构造完整路径,如果是/结尾,则指定为该目录下的index.html;

(5) 获取文件信息,如果找不到文件,返回404,找到文件则判断文件权限;

(6) 如果是GET请求并且没有参数,或者文件不可执行,则直接将文件内容构造http信息返回给客户端;

(7) 如果是GET带参数,POST,文件可执行,则执行CGI;

(8) GET请求略过httpHeader,POST方法需要记录httpHeader中的Content-Length:xx;

(9) 创建管道用于父子进程通信,fork产生子进程;

(10) 子进程设置环境变量,将标准输入和输出与管道相连,并且通过exec执行CGI;

(11) 如果是POST,父进程将读到post内容发送给子进程,并且接收子进程的输出,输出给客户端;

 

3. 管道说明:

 

4. 代码注释:

  1 /* J. David's webserver */
  2 /* This is a simple webserver.
  3  * Created November 1999 by J. David Blackstone.
  4  * CSE 4344 (Network concepts), Prof. Zeigler
  5  * University of Texas at Arlington
  6  */
  7 /* This program compiles for Sparc Solaris 2.6.
  8  * To compile for Linux:
  9  *  1) Comment out the #include <pthread.h> line.
 10  *  2) Comment out the line that defines the variable newthread.
 11  *  3) Comment out the two lines that run pthread_create().
 12  *  4) Uncomment the line that runs accept_request().
 13  *  5) Remove -lsocket from the Makefile.
 14  */
 15 #include <stdio.h>
 16 #include <sys/socket.h>
 17 #include <sys/types.h>
 18 #include <netinet/in.h>
 19 #include <arpa/inet.h>
 20 #include <unistd.h>
 21 #include <ctype.h>
 22 #include <strings.h>
 23 #include <string.h>
 24 #include <sys/stat.h>
 25 #include <pthread.h>
 26 #include <sys/wait.h>
 27 #include <stdlib.h>
 28 
 29 #define ISspace(x) isspace((int)(x))
 30 
 31 #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
 32 
 33 void accept_request(int);
 34 void bad_request(int);
 35 void cat(int, FILE *);
 36 void cannot_execute(int);
 37 void error_die(const char *);
 38 void execute_cgi(int, const char *, const char *, const char *);
 39 int get_line(int, char *, int);
 40 void headers(int, const char *);
 41 void not_found(int);
 42 void serve_file(int, const char *);
 43 int startup(u_short *);
 44 void unimplemented(int);
 45 
 46 /**********************************************************************/
 47 /* A request has caused a call to accept() on the server port to
 48  * return.  Process the request appropriately.
 49  * Parameters: the socket connected to the client */
 50 /**********************************************************************/
 51 void accept_request(int client)
 52 {
 53     char buf[1024];
 54     int numchars;
 55     char method[255];
 56     char url[255];
 57     char path[512];
 58     size_t i, j;
 59     struct stat st;
 60     int cgi = 0;      /* becomes true if server decides this is a CGI
 61                * program */
 62     char *query_string = NULL;
 63 
 64     //读取第一行数据
 65     numchars = get_line(client, buf, sizeof(buf));
 66     i = 0; j = 0;
 67     //读取http的头部method字段,读到空白为止
 68     while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
 69     {
 70         method[i] = buf[j];
 71         i++; j++;
 72     }
 73     method[i] = '\0';
 74 
 75     //只支持GET和POST请求,其他请求方式返回未实现
 76     if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
 77     {
 78         unimplemented(client);
 79         return;
 80     }
 81     //如果是POST请求,设置cgi标志为1
 82     if (strcasecmp(method, "POST") == 0)
 83         cgi = 1;
 84 
 85     i = 0;
 86     //跳过空白字符
 87     while (ISspace(buf[j]) && (j < sizeof(buf)))
 88         j++;
 89     //读取url字串
 90     while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
 91     {
 92         url[i] = buf[j];
 93         i++; j++;
 94     }
 95     url[i] = '\0';
 96     //如果是GET请求,需要从url中解析参数
 97     if (strcasecmp(method, "GET") == 0)
 98     {
 99         query_string = url;
100         //找到?位置
101         while ((*query_string != '?') && (*query_string != '\0'))
102             query_string++;
103         //当前字符为?字符
104         if (*query_string == '?')
105         {
106             cgi = 1; //标记cgi字段
107             *query_string = '\0'; //将?替换成\0
108             query_string++; //query_string指向get参数
109         }
110     }
111     //连接url资源路径
112     sprintf(path, "htdocs%s", url);
113     //如果访问的是/结尾的目录,那么指定为目录下的index.html
114     if (path[strlen(path) - 1] == '/')
115         strcat(path, "index.html");
116     //获取文件信息失败
117     if (stat(path, &st) == -1) {
118         //将header中的信息都丢弃
119         while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
120             numchars = get_line(client, buf, sizeof(buf));
121         //返回404
122         not_found(client);
123     }
124     else
125     {
126         //如果访问的是目录,那么指定为目录下的index.html
127         if ((st.st_mode & S_IFMT) == S_IFDIR)
128             strcat(path, "/index.html");
129         //如果具有可执行权限,标记cgi
130         if ((st.st_mode & S_IXUSR) ||
131                 (st.st_mode & S_IXGRP) ||
132                 (st.st_mode & S_IXOTH)    )
133             cgi = 1;
134         //不需要cgi参与的文件直接进行服务
135         if (!cgi)
136             serve_file(client, path);
137         //否则执行cgi
138         else
139             execute_cgi(client, path, method, query_string);
140     }
141 
142     close(client);
143 }
144 
145 /**********************************************************************/
146 /* Inform the client that a request it has made has a problem.
147  * Parameters: client socket */
148 /**********************************************************************/
149 void bad_request(int client)
150 {
151     char buf[1024];
152     //发送BAD REQUEST提示信息到客户端
153     sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
154     send(client, buf, sizeof(buf), 0);
155     sprintf(buf, "Content-type: text/html\r\n");
156     send(client, buf, sizeof(buf), 0);
157     sprintf(buf, "\r\n");
158     send(client, buf, sizeof(buf), 0);
159     sprintf(buf, "<P>Your browser sent a bad request, ");
160     send(client, buf, sizeof(buf), 0);
161     sprintf(buf, "such as a POST without a Content-Length.\r\n");
162     send(client, buf, sizeof(buf), 0);
163 }
164 
165 /**********************************************************************/
166 /* Put the entire contents of a file out on a socket.  This function
167  * is named after the UNIX "cat" command, because it might have been
168  * easier just to do something like pipe, fork, and exec("cat").
169  * Parameters: the client socket descriptor
170  *             FILE pointer for the file to cat */
171 /**********************************************************************/
172 void cat(int client, FILE *resource)
173 {
174     char buf[1024];
175     //循环读取并发送文件内容
176     fgets(buf, sizeof(buf), resource);
177     while (!feof(resource))
178     {
179         send(client, buf, strlen(buf), 0);
180         fgets(buf, sizeof(buf), resource);
181     }
182 }
183 
184 /**********************************************************************/
185 /* Inform the client that a CGI script could not be executed.
186  * Parameter: the client socket descriptor. */
187 /**********************************************************************/
188 void cannot_execute(int client)
189 {
190     char buf[1024];
191     //发送500服务器内部错误到客户端
192     sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
193     send(client, buf, strlen(buf), 0);
194     sprintf(buf, "Content-type: text/html\r\n");
195     send(client, buf, strlen(buf), 0);
196     sprintf(buf, "\r\n");
197     send(client, buf, strlen(buf), 0);
198     sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
199     send(client, buf, strlen(buf), 0);
200 }
201 
202 /**********************************************************************/
203 /* Print out an error message with perror() (for system errors; based
204  * on value of errno, which indicates system call errors) and exit the
205  * program indicating an error. */
206 /**********************************************************************/
207 void error_die(const char *sc)
208 {
209     //打印错误信息并退出
210     perror(sc);
211     exit(1);
212 }
213 
214 /**********************************************************************/
215 /* Execute a CGI script.  Will need to set environment variables as
216  * appropriate.
217  * Parameters: client socket descriptor
218  *             path to the CGI script */
219 /**********************************************************************/
220 void execute_cgi(int client, const char *path,
221         const char *method, const char *query_string)
222 {
223     char buf[1024];
224     int cgi_output[2];
225     int cgi_input[2];
226     pid_t pid;
227     int status;
228     int i;
229     char c;
230     int numchars = 1;
231     int content_length = -1;
232 
233     buf[0] = 'A'; buf[1] = '\0';
234     //如果是GET请求则读取并丢掉头部信息
235     if (strcasecmp(method, "GET") == 0)
236         while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
237             numchars = get_line(client, buf, sizeof(buf));
238     //如果是POST请求
239     else    /* POST */
240     {
241         //读取一行数据
242         numchars = get_line(client, buf, sizeof(buf));
243         while ((numchars > 0) && strcmp("\n", buf))
244         {
245             //截取Content-Length:字段
246             buf[15] = '\0';
247             //如果找到该字段,将该字段后面的字串转成整数长度
248             if (strcasecmp(buf, "Content-Length:") == 0)
249                 content_length = atoi(&(buf[16]));
250             //读取头部内容
251             numchars = get_line(client, buf, sizeof(buf));
252         }
253         //没有找到Content-Length,发送bad request
254         if (content_length == -1) {
255             bad_request(client);
256             return;
257         }
258     }
259     //发送http200头
260     sprintf(buf, "HTTP/1.0 200 OK\r\n");
261     send(client, buf, strlen(buf), 0);
262     //创建输出管道,构造父子进程通信
263     if (pipe(cgi_output) < 0) {
264         cannot_execute(client);
265         return;
266     }
267     //创建输入管道,构造父子进程通信
268     if (pipe(cgi_input) < 0) {
269         cannot_execute(client);
270         return;
271     }
272     //创建子进程
273     if ( (pid = fork()) < 0 ) {
274         cannot_execute(client);
275         return;
276     }
277     //子进程执行CGI脚本
278     if (pid == 0)  /* child: CGI script */
279     {
280         char meth_env[255];
281         char query_env[255];
282         char length_env[255];
283 
284         //子进程的标准输入输出与管道对接
285         dup2(cgi_output[1], 1); //将标准输出重定向到cgi输出管道的写端
286         dup2(cgi_input[0], 0); //将标准输入重定向到cgi输入管道的读端
287 
288         close(cgi_output[0]); //关闭cgi输出管道的读端
289         close(cgi_input[1]); //关闭cgi输入管道的写端
290         //设置method环境变量
291         sprintf(meth_env, "REQUEST_METHOD=%s", method);
292         putenv(meth_env);
293         //如果是GET方式设置请求参数环境变量
294         if (strcasecmp(method, "GET") == 0) {
295             sprintf(query_env, "QUERY_STRING=%s", query_string);
296             putenv(query_env);
297         }
298         //如果是POST方式设置内容长度环境变量
299         else {   /* POST */
300             sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
301             putenv(length_env);
302         }
303         //执行CGI
304         execl(path, path, NULL);
305         exit(0);
306     } else {    /* parent */
307         close(cgi_output[1]); //关闭cgi输出管道的写端
308         close(cgi_input[0]); //关闭cgi输入管道的读端
309         //如果是POST请求,循环读取post内容,并且输入到cgi子进程
310         if (strcasecmp(method, "POST") == 0)
311             for (i = 0; i < content_length; i++) {
312                 recv(client, &c, 1, 0);
313                 write(cgi_input[1], &c, 1);
314             }
315         //从cgi中循环读取输出内容,发送到客户端
316         while (read(cgi_output[0], &c, 1) > 0)
317             send(client, &c, 1, 0);
318 
319         //关闭管道
320         close(cgi_output[0]);
321         close(cgi_input[1]);
322         //等待子进程结束
323         waitpid(pid, &status, 0);
324     }
325 }
326 
327 /**********************************************************************/
328 /* Get a line from a socket, whether the line ends in a newline,
329  * carriage return, or a CRLF combination.  Terminates the string read
330  * with a null character.  If no newline indicator is found before the
331  * end of the buffer, the string is terminated with a null.  If any of
332  * the above three line terminators is read, the last character of the
333  * string will be a linefeed and the string will be terminated with a
334  * null character.
335  * Parameters: the socket descriptor
336  *             the buffer to save the data in
337  *             the size of the buffer
338  * Returns: the number of bytes stored (excluding null) */
339 /**********************************************************************/
340 int get_line(int sock, char *buf, int size)
341 {
342     int i = 0;
343     char c = '\0';
344     int n;
345     //接收\n结束的一行数据或接收满缓冲区
346     while ((i < size - 1) && (c != '\n'))
347     {
348         //接收一个字节
349         n = recv(sock, &c, 1, 0);
350         /* DEBUG printf("%02X\n", c); */
351         if (n > 0)
352         {
353             //如果接收到了\r符号
354             if (c == '\r')
355             {
356                 //将下一个字字符预取出来,注意MSG_PEEK本地接收窗口不滑动,下次读取仍然可以读取到该字符
357                 n = recv(sock, &c, 1, MSG_PEEK);
358                 /* DEBUG printf("%02X\n", c); */
359                 //如果下一个字符是\n的话,那么接收这个字符
360                 if ((n > 0) && (c == '\n'))
361                     recv(sock, &c, 1, 0);
362                 //不是\n的话,那么将\r替换成\n
363                 else
364                     c = '\n';
365             }
366             //字符存入buf,继续读取
367             buf[i] = c;
368             i++;
369         }
370         else
371             c = '\n';
372     }
373     //设置buf字符串结束符
374     buf[i] = '\0';
375 
376     return(i);
377 }
378 
379 /**********************************************************************/
380 /* Return the informational HTTP headers about a file. */
381 /* Parameters: the socket to print the headers on
382  *             the name of the file */
383 /**********************************************************************/
384 void headers(int client, const char *filename)
385 {
386     char buf[1024];
387     (void)filename;  /* could use filename to determine file type */
388     //发送http头 http码 服务器信息 内容类型等头部信息
389     strcpy(buf, "HTTP/1.0 200 OK\r\n");
390     send(client, buf, strlen(buf), 0);
391     strcpy(buf, SERVER_STRING);
392     send(client, buf, strlen(buf), 0);
393     sprintf(buf, "Content-Type: text/html\r\n");
394     send(client, buf, strlen(buf), 0);
395     strcpy(buf, "\r\n");
396     send(client, buf, strlen(buf), 0);
397 }
398 
399 /**********************************************************************/
400 /* Give a client a 404 not found status message. */
401 /**********************************************************************/
402 void not_found(int client)
403 {
404     char buf[1024];
405     //发送http头 http码 服务器信息 内容类型等头部信息 html提示信息
406     sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
407     send(client, buf, strlen(buf), 0);
408     sprintf(buf, SERVER_STRING);
409     send(client, buf, strlen(buf), 0);
410     sprintf(buf, "Content-Type: text/html\r\n");
411     send(client, buf, strlen(buf), 0);
412     sprintf(buf, "\r\n");
413     send(client, buf, strlen(buf), 0);
414     sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
415     send(client, buf, strlen(buf), 0);
416     sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
417     send(client, buf, strlen(buf), 0);
418     sprintf(buf, "your request because the resource specified\r\n");
419     send(client, buf, strlen(buf), 0);
420     sprintf(buf, "is unavailable or nonexistent.\r\n");
421     send(client, buf, strlen(buf), 0);
422     sprintf(buf, "</BODY></HTML>\r\n");
423     send(client, buf, strlen(buf), 0);
424 }
425 
426 /**********************************************************************/
427 /* Send a regular file to the client.  Use headers, and report
428  * errors to client if they occur.
429  * Parameters: a pointer to a file structure produced from the socket
430  *              file descriptor
431  *             the name of the file to serve */
432 /**********************************************************************/
433 void serve_file(int client, const char *filename)
434 {
435     FILE *resource = NULL;
436     int numchars = 1;
437     char buf[1024];
438 
439     //读取丢弃所有头部信息
440     buf[0] = 'A'; buf[1] = '\0';
441     while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
442         numchars = get_line(client, buf, sizeof(buf));
443 
444     //打开资源文件
445     resource = fopen(filename, "r");
446     //打开失败,发送404
447     if (resource == NULL)
448         not_found(client);
449     else
450     {
451         headers(client, filename); //发送http头
452         cat(client, resource); //发送资源文件内容
453     }
454     //关闭资源文件
455     fclose(resource);
456 }
457 
458 /**********************************************************************/
459 /* This function starts the process of listening for web connections
460  * on a specified port.  If the port is 0, then dynamically allocate a
461  * port and modify the original port variable to reflect the actual
462  * port.
463  * Parameters: pointer to variable containing the port to connect on
464  * Returns: the socket */
465 /**********************************************************************/
466 int startup(u_short *port)
467 {
468     int httpd = 0;
469     struct sockaddr_in name;
470     //创建tcp socket
471     httpd = socket(PF_INET, SOCK_STREAM, 0);
472     if (httpd == -1)
473         error_die("socket");
474     memset(&name, 0, sizeof(name));
475     //设置sockaddr地址结构
476     name.sin_family = AF_INET;
477     name.sin_port = htons(*port);
478     name.sin_addr.s_addr = htonl(INADDR_ANY);
479     //绑定到本地地址port指定端口
480     if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
481         error_die("bind");
482     //如果没有指定端口,则由系统指定,此处或得到系统指定的端口
483     if (*port == 0)  /* if dynamically allocating a port */
484     {
485         int namelen = sizeof(name);
486         if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
487             error_die("getsockname");
488         *port = ntohs(name.sin_port);
489     }
490     //服务器开始监听
491     if (listen(httpd, 5) < 0)
492         error_die("listen");
493     return(httpd);
494 }
495 
496 /**********************************************************************/
497 /* Inform the client that the requested web method has not been
498  * implemented.
499  * Parameter: the client socket */
500 /**********************************************************************/
501 void unimplemented(int client)
502 {
503     char buf[1024];
504     //发送未实现的请求方法和提示消息给客户端
505     sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
506     send(client, buf, strlen(buf), 0);
507     sprintf(buf, SERVER_STRING);
508     send(client, buf, strlen(buf), 0);
509     sprintf(buf, "Content-Type: text/html\r\n");
510     send(client, buf, strlen(buf), 0);
511     sprintf(buf, "\r\n");
512     send(client, buf, strlen(buf), 0);
513     sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
514     send(client, buf, strlen(buf), 0);
515     sprintf(buf, "</TITLE></HEAD>\r\n");
516     send(client, buf, strlen(buf), 0);
517     sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
518     send(client, buf, strlen(buf), 0);
519     sprintf(buf, "</BODY></HTML>\r\n");
520     send(client, buf, strlen(buf), 0);
521 }
522 
523 /**********************************************************************/
524 
525 int main(void)
526 {
527     int server_sock = -1;
528     u_short port = 0;
529     int client_sock = -1;
530     struct sockaddr_in client_name;
531     int client_name_len = sizeof(client_name);
532     pthread_t newthread;
533 
534     server_sock = startup(&port);
535     printf("httpd running on port %d\n", port);
536 
537     while (1)
538     {
539         //等待客户端连接到来
540         client_sock = accept(server_sock,
541                 (struct sockaddr *)&client_name,
542                 &client_name_len);
543         if (client_sock == -1)
544             error_die("accept");
545         /* accept_request(client_sock); */
546         //开启一个新线程处理客户端请求
547         if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
548             perror("pthread_create");
549     }
550     //关闭服务器
551     close(server_sock);
552 
553     return(0);
554 }

 

你可能感兴趣的:(源码分析之tinyhttpd-0.1)