tinyhttpd ------ C 語言實現最簡單的 HTTP 服務器


工作流程:

1>服務器啟動,在指定端口或隨機選取端口綁定httpd服務。

2>收到一個http請求時(其實就是listen端口accept的時候),派生一個線程運行accept_request函數。

3>取出http請求中method(getpost)url,對於get方法,如果有攜帶參數,則query_string指針指向url?后面的get參數。

4>格式化urlpath數組,表示瀏覽器請求的文件路徑,在tinyhttpd中服務器文件是在htdocs文件夾下。當url/結尾,或者url是個目錄,則默認在path中加上index.thml,表示訪問主頁。

5>如果文件路徑合法,對於無參數的get請求,直接輸出服務器文件到瀏覽器,即用http格式寫到套接字上,跳到(10)。其他情況(帶參數getpost方法,url為科執行文件),則調用execute_cgi函數執行cgi腳本。

6>讀取整個http請求並丟棄,如果是post則找出content-length,把http狀態碼200寫到套接字里面。

7>建立兩個管道,cgi_inputcgi_output,fork一個子進程。

8>在子進程中,把stdout重定向到cgi_output的寫入端,把stdin重定向到cgi_input的讀取端,關閉cgi_input的寫入端和cgi_output的讀取端,是指request_method的環境變量,get的話設置query_string的環境變量,post的話設置content-length的環境變量,這些環境變量都是為了給cgi腳本調用,接着用execl運行cgi程序。

9>在父進程中,關閉cgi_input的讀取端和cgi_output的寫入端,如果post的話,把post數據寫入到cgo_input,已被重定向到stdin讀取cgi_output的管道輸出到客戶端,等待子進程結束。

10>關閉與瀏覽器的鏈接,完成一次http請求與回應,因為http是無連接的。

  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 //處理從套接字上監聽到的一個HTTP請求,在這里可以很大一部分的體現服務器處理請求的流程
 35 void bad_request(int);
 36 //返回給客戶端這是個錯誤請求,HTTP狀態碼是400 BAD REQUEST
 37 void cat(int, FILE *);
 38 //讀取服務器上某個文件寫到socket套接字
 39 void cannot_execute(int);
 40 //主要執行在處理cgi程序的處理,也是個主要函數
 41 void error_die(const char *);
 42 //把錯誤信息寫到perror並退出
 43 void execute_cgi(int, const char *, const char *, const char *);
 44 //運行cgi程序的處理,也是哥主函數
 45 int get_line(int, char *, int);
 46 //讀取套接字的一行,把回車換行等情況都統一為換行符結束
 47 void headers(int, const char *);
 48 //把HTTP相應頭寫到套接字
 49 void not_found(int);
 50 //主要處理找不到請求的文件時的情況
 51 void serve_file(int, const char *);
 52 //調用cat把服務器文件返回給瀏覽器
 53 int startup(u_short *);
 54 //初始化httpd服務,包括建立套接字,綁定端口,進行監聽等
 55 void unimplemented(int);
 56 //返回給瀏覽器表示接收到的http請求所用的method不被支持
 57 
 58 /**********************************************************************/
 59 /* A request has caused a call to accept() on the server port to
 60  * return.  Process the request appropriately.
 61  * Parameters: the socket connected to the client */
 62 /**********************************************************************/
 63 void accept_request(int client)
 64 {
 65     
 66     char buf[1024];
 67     int numchars;
 68     char method[255];
 69     char url[255];
 70     char path[512];
 71     size_t i, j;
 72     struct stat st;
 73     int cgi = 0;      /* becomes true if server decides this is a CGI
 74                     * program */
 75     char *query_string = NULL;
 76 
 77     numchars = get_line(client, buf, sizeof(buf));
 78     //讀取 client端發送的數據並且 返回的參數是 numchars
 79     i = 0;
 80     j = 0;
 81     while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
 82     {
 83         method[i] = buf[j];
 84         i++;
 85         j++;
 86     }
 87     //得到傳遞的參數是post 還是get方法 還是其他
 88     method[i] = '\0';
 89 
 90     if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
 91     {
 92     //回給瀏覽器表明收到的 HTTP 請求所用的 method 不被支持
 93         unimplemented(client);
 94         return;
 95     }
 96 
 97     if (strcasecmp(method, "POST") == 0)
 98         cgi = 1;
 99 
100     i = 0;
101     //http請求行格式是 method urI http-version
102     while (ISspace(buf[j]) && (j < sizeof(buf)))
103         j++;
104     while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
105     {
106         url[i] = buf[j]; //包含請求行中的URI
107         i++;
108         j++;
109     }
110     url[i] = '\0';
111     ///獲取發送數據中的URI
112     if (strcasecmp(method, "GET") == 0)
113     {
114     //get方法 是將參數傳遞在URI中的?后面如果元素多用&進行鏈接
115         query_string = url;
116         while ((*query_string != '?') && (*query_string != '\0'))
117             query_string++;
118          // get 請求? 后面為參數
119      if (*query_string == '?')
120         {
121             cgi = 1;
122             *query_string = '\0';
123             query_string++;
124         }
125     }
126     //格式url存儲在path數組 並且html存儲在 htdocs文件中
127     sprintf(path, "htdocs%s", url);
128     if (path[strlen(path) - 1] == '/')
129         strcat(path, "index.html");  //字符串進行銜接 + index.html
130    //stat函數 根據path路徑 獲取文件內容 存儲在 st 結構體中  成功返回0 錯誤返回-1
131     if (stat(path, &st) == -1)
132     {
133         while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
134             numchars = get_line(client, buf, sizeof(buf));
135         not_found(client);
136     }
137     else
138     {
139         if ((st.st_mode & S_IFMT) == S_IFDIR)
140             strcat(path, "/index.html");
141         //文件的權限 屬主 屬組 其它 三種任一個擁有執行權
142     if ((st.st_mode & S_IXUSR) ||
143                 (st.st_mode & S_IXGRP) ||
144                 (st.st_mode & S_IXOTH)    )
145             cgi = 1;
146     //調用cat 把服務器文件返回給瀏覽器  post方法或者擁有執行權限
147         if (!cgi)
148             serve_file(client, path);
149         else
150             execute_cgi(client, path, method, query_string)
151     //運行cgi程序的處理,也是個主要函數
152     }
153 
154     close(client);
155 }
156 
157 /**********************************************************************/
158 /* Inform the client that a request it has made has a problem.
159  * Parameters: client socket */
160 /**********************************************************************/
161 void bad_request(int client)
162 {
163     char buf[1024];
164 
165     sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
166     send(client, buf, sizeof(buf), 0);
167     sprintf(buf, "Content-type: text/html\r\n");
168     send(client, buf, sizeof(buf), 0);
169     sprintf(buf, "\r\n");
170     send(client, buf, sizeof(buf), 0);
171     sprintf(buf, "<P>Your browser sent a bad request, ");
172     send(client, buf, sizeof(buf), 0);
173     sprintf(buf, "such as a POST without a Content-Length.\r\n");
174     send(client, buf, sizeof(buf), 0);
175 }
176 
177 /**********************************************************************/
178 /* Put the entire contents of a file out on a socket.  This function
179  * is named after the UNIX "cat" command, because it might have been
180  * easier just to do something like pipe, fork, and exec("cat").
181  * Parameters: the client socket descriptor
182  *             FILE pointer for the file to cat */
183 /**********************************************************************/
184 //讀取服務器上的某個文件 寫到socket套接字上
185 void cat(int client, FILE *resource)
186 {
187     char buf[1024];
188 
189     fgets(buf, sizeof(buf), resource);
190     //檢測流上的文件結束符  如果文件結束,則返回非0值,否則返回0,文件結束符只能被clearerr()清除。
191     while (!feof(resource))
192     {
193         send(client, buf, strlen(buf), 0);
194         fgets(buf, sizeof(buf), resource);
195     }
196 }
197 
198 /**********************************************************************/
199 /* Inform the client that a CGI script could not be executed.
200  * Parameter: the client socket descriptor. */
201 /**********************************************************************/
202 void cannot_execute(int client)
203 {
204     char buf[1024];
205 
206     sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
207     send(client, buf, strlen(buf), 0);
208     sprintf(buf, "Content-type: text/html\r\n");
209     send(client, buf, strlen(buf), 0);
210     sprintf(buf, "\r\n");
211     send(client, buf, strlen(buf), 0);
212     sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
213     send(client, buf, strlen(buf), 0);
214 }
215 
216 /**********************************************************************/
217 /* Print out an error message with perror() (for system errors; based
218  * on value of errno, which indicates system call errors) and exit the
219  * program indicating an error. */
220 /**********************************************************************/
221 void error_die(const char *sc)
222 {
223     perror(sc);
224     exit(1);
225 }
226 
227 /**********************************************************************/
228 /* Execute a CGI script.  Will need to set environment variables as
229  * appropriate.
230  * Parameters: client socket descriptor
231  *             path to the CGI script */
232 /**********************************************************************/
233 
234 //運行cgi程序  也是個主函數
235 void execute_cgi(int client, const char *path,
236                  const char *method, const char *query_string)
237 {
238     //在父進程中,關閉cgi_input的讀入端和cgi_output的寫入端,如果post的話
239     //把post數據寫入到cgi_input,已被重定向到stdin,讀取cgi_output的管道
240     //輸出到客戶端,該管道輸入是stdout,接着關閉所有管道,等待子進程結束。
241     
242     char buf[1024];
243     int cgi_output[2];
244     int cgi_input[2];
245     //cgi_output[1] cgi_input[1] 為輸入端
246     //cgi_input[0] cgi_output[0] 為輸出端
247     pid_t pid;
248     int status;
249     int i;
250     char c;
251     int numchars = 1;
252     int content_length = -1;
253 
254     buf[0] = 'A';
255     buf[1] = '\0';
256     //讀入請求頭
257     if (strcasecmp(method, "GET") == 0)
258         while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
259             numchars = get_line(client, buf, sizeof(buf));
260     else    /* POST */
261     {
262         numchars = get_line(client, buf, sizeof(buf));
263         while ((numchars > 0) && strcmp("\n", buf))
264         {
265             buf[15] = '\0';
266             if (strcasecmp(buf, "Content-Length:") == 0)
267                 content_length = atoi(&(buf[16]));
268             numchars = get_line(client, buf, sizeof(buf));
269         }
270         if (content_length == -1)
271         {
272             bad_request(client);
273             return;
274         }
275     }
276 
277     sprintf(buf, "HTTP/1.0 200 OK\r\n");
278     send(client, buf, strlen(buf), 0);
279 
280     if (pipe(cgi_output) < 0)
281     {
282         cannot_execute(client);
283         return;
284     }
285     if (pipe(cgi_input) < 0)
286     {
287         cannot_execute(client);
288         return;
289     }
290 
291     if ( (pid = fork()) < 0 )
292     {
293         cannot_execute(client);
294         return;
295     }
296     if (pid == 0)  /* child: CGI script */
297     {
298         char meth_env[255];
299         char query_env[255];
300         char length_env[255];
301     //把stdout重定向到cgi_output的寫入端
302         dup2(cgi_output[1], 1);
303     //把stdin重定向到cgi_input的讀入端
304         dup2(cgi_input[0], 0);
305     //關閉cgi_output的讀入端 和 cgi_input的 寫入端
306         close(cgi_output[0]);
307         close(cgi_input[1]);
308     //設置request_method的環境變量
309         sprintf(meth_env, "REQUEST_METHOD=%s", method);
310         putenv(meth_env);
311         if (strcasecmp(method, "GET") == 0)
312         {
313         //設置query_string的環境變量
314             sprintf(query_env, "QUERY_STRING=%s", query_string);
315             putenv(query_env);
316         }
317         else     /* POST */
318         {
319         //設置content_length的環境變量
320             sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
321             putenv(length_env);
322         }
323         //用execl運行cgi程序
324     execl(path, path, NULL);
325         exit(0);
326     }
327     else        /* parent */
328     {
329         close(cgi_output[1]);
330         close(cgi_input[0]);
331         //關閉cgi_output的 寫入端 和 cgi_input的讀取端
332     if (strcasecmp(method, "POST") == 0)
333        //接受post的數據
334             for (i = 0; i < content_length; i++)
335             {
336                 recv(client, &c, 1, 0);
337                 write(cgi_input[1], &c, 1);
338                 //講post數據寫入cgi_input,並且重定向到stdin中
339              }
340     //讀取cgi_output的管道輸出到客戶端,該管道輸入時stdout
341         while (read(cgi_output[0], &c, 1) > 0)
342             send(client, &c, 1, 0);
343     //關閉管道
344         close(cgi_output[0]);
345         close(cgi_input[1]);
346         //等待子進程
347     waitpid(pid, &status, 0);
348     }
349 }
350 
351 /**********************************************************************/
352 /* Get a line from a socket, whether the line ends in a newline,
353  * carriage return, or a CRLF combination.  Terminates the string read
354  * with a null character.  If no newline indicator is found before the
355  * end of the buffer, the string is terminated with a null.  If any of
356  * the above three line terminators is read, the last character of the
357  * string will be a linefeed and the string will be terminated with a
358  * null character.
359  * Parameters: the socket descriptor
360  *             the buffer to save the data in
361  *             the size of the buffer
362  * Returns: the number of bytes stored (excluding null) */
363 /**********************************************************************/
364 int get_line(int sock, char *buf, int size)
365 {
366     int i = 0;
367     char c = '\0';
368     int n;
369 
370     while ((i < size - 1) && (c != '\n'))
371     {
372         //每次接受一個字符
373     n = recv(sock, &c, 1, 0);
374         /* DEBUG printf("%02X\n", c); */
375         if (n > 0)
376         {
377             if (c == '\r')
378             {
379                 n = recv(sock, &c, 1, MSG_PEEK);
380                 /* DEBUG printf("%02X\n", c); */
381                 if ((n > 0) && (c == '\n'))
382                     recv(sock, &c, 1, 0);
383                 else
384                     c = '\n';
385             }
386             buf[i] = c;
387             i++;
388         }
389         else
390             c = '\n';
391     }
392     buf[i] = '\0';
393 
394     return(i);
395 }
396 
397 /**********************************************************************/
398 /* Return the informational HTTP headers about a file. */
399 /* Parameters: the socket to print the headers on
400  *             the name of the file */
401 /**********************************************************************/
402 //http 響應體
403 void headers(int client, const char *filename)
404 {
405     char buf[1024];
406     (void)filename;  /* could use filename to determine file type */
407 
408     strcpy(buf, "HTTP/1.0 200 OK\r\n");//響應的狀態行
409     send(client, buf, strlen(buf), 0);
410     strcpy(buf, SERVER_STRING);
411     send(client, buf, strlen(buf), 0);
412     sprintf(buf, "Content-Type: text/html\r\n"); //響應頭
413     send(client, buf, strlen(buf), 0);
414     strcpy(buf, "\r\n");        //響應正文段
415     send(client, buf, strlen(buf), 0);
416 }
417 
418 /**********************************************************************/
419 /* Give a client a 404 not found status message. */
420 /**********************************************************************/
421 void not_found(int client)
422 {
423     //客戶端發送的請求無法實現
424     char buf[1024];
425    //響應行
426     sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
427     send(client, buf, strlen(buf), 0);
428     sprintf(buf, SERVER_STRING);
429     send(client, buf, strlen(buf), 0);
430     //響應頭
431     sprintf(buf, "Content-Type: text/html\r\n");
432     send(client, buf, strlen(buf), 0);
433     sprintf(buf, "\r\n");
434     //響應頭和響應正文段之間有一個空行 表示響應行結束
435     send(client, buf, strlen(buf), 0);
436     //響應正文段 text/html
437     sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
438     send(client, buf, strlen(buf), 0);
439     sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
440     send(client, buf, strlen(buf), 0);
441     sprintf(buf, "your request because the resource specified\r\n");
442     send(client, buf, strlen(buf), 0);
443     sprintf(buf, "is unavailable or nonexistent.\r\n");
444     send(client, buf, strlen(buf), 0);
445     sprintf(buf, "</BODY></HTML>\r\n");
446     send(client, buf, strlen(buf), 0);
447 }
448 
449 /**********************************************************************/
450 /* Send a regular file to the client.  Use headers, and report
451  * errors to client if they occur.
452  * Parameters: a pointer to a file structure produced from the socket
453  *              file descriptor
454  *             the name of the file to serve */
455 /**********************************************************************/
456 void serve_file(int client, const char *filename)
457 {
458     FILE *resource = NULL;
459     int numchars = 1;
460     char buf[1024];
461 
462     buf[0] = 'A';
463     buf[1] = '\0';
464     while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
465         numchars = get_line(client, buf, sizeof(buf));
466 
467     resource = fopen(filename, "r");
468     if (resource == NULL)
469         not_found(client);
470     else
471     {
472         headers(client, filename);
473         cat(client, resource);
474     }
475     fclose(resource);
476 }
477 
478 /**********************************************************************/
479 /* This function starts the process of listening for web connections
480  * on a specified port.  If the port is 0, then dynamically allo        //響應正文段cate a
481  * port and modify the original port variable to reflect the actual
482  * port.
483  * Parameters: pointer to variable containing the port to connect on
484  * Returns: the socket */
485 /**********************************************************************/
486 int startup(u_short *port)
487 {
488     int httpd = 0;
489     struct sockaddr_in name;
490     //建立套接字
491     httpd = socket(PF_INET, SOCK_STREAM, 0);
492     if (httpd == -1)
493         error_die("socket");
494     memset(&name, 0, sizeof(name));
495     name.sin_family = AF_INET;
496     name.sin_port = htons(*port);
497     name.sin_addr.s_addr = htonl(INADDR_ANY);
498     //綁定端口和ip
499     if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
500         error_die("bind");
501     if (*port == 0)  /* if dynamically allocating a port */
502     {
503         int namelen = sizeof(name);
504         if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
505             error_die("getsockname");
506         *port = ntohs(name.sin_port);
507     }
508     //監聽
509     if (listen(httpd, 5) < 0)
510         error_die("listen");
511     return(httpd);
512 }
513 
514 /**********************************************************************/
515 /* Inform the client that the requested web method has not been
516  * implemented.
517  * Parameter: the client socket */
518 /**********************************************************************/
519 void unimplemented(int client)
520 {
521     //發回響應信息  http的請求方法不被接受
522     char buf[1024];
523     //返回501 錯誤 未實現 (Not implemented)是指Web 服務器不理解或不支持發送給它的 HTTP 數據流中找到的 HTTP 方法
524     sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
525     send(client, buf, strlen(buf), 0);
526     sprintf(buf, SERVER_STRING);
527     send(client, buf, strlen(buf), 0);
528     sprintf(buf, "Content-Type: text/html\r\n");//響應頭
529     send(client, buf, strlen(buf), 0);
530     sprintf(buf, "\r\n");
531     send(client, buf, strlen(buf), 0); //響應正文段
532     sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
533     send(client, buf, strlen(buf), 0);
534     sprintf(buf, "</TITLE></HEAD>\r\n");
535     send(client, buf, strlen(buf), 0);
536     sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
537     send(client, buf, strlen(buf), 0);
538     sprintf(buf, "</BODY></HTML>\r\n");
539     send(client, buf, strlen(buf), 0);
540 }
541 
542 /**********************************************************************/
543 
544 int main(void)
545 {
546     int server_sock = -1;
547     u_short port = 0;
548     int client_sock = -1;
549     struct sockaddr_in client_name;
550     int client_name_len = sizeof(client_name);
551     pthread_t newthread;
552 
553     server_sock = startup(&port);
554     printf("httpd running on port %d\n", port);
555 
556     while (1)
557     {
558         client_sock = accept(server_sock,
559                              (struct sockaddr *)&client_name,
560                              &client_name_len);
561         //建立鏈接
562     if (client_sock == -1)
563             error_die("accept");
564         /* accept_request(client_sock); */
565         //多線程進行控制
566     if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
567             perror("pthread_create");
568     }
569 
570     close(server_sock);
571 
572     return(0);
573 }

 


免責聲明!

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



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