源碼分析之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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM