1. 程式人生 > >【原始碼剖析】tinyhttpd —— C 語言實現最簡單的 HTTP 伺服器

【原始碼剖析】tinyhttpd —— C 語言實現最簡單的 HTTP 伺服器

    tinyhttpd 是一個不到 500 行的超輕量型 Http Server,用來學習非常不錯,可以幫助我們真正理解伺服器程式的本質。

    看完所有原始碼,真的感覺有很大收穫,無論是 unix 的程式設計,還是 GET/POST 的 Web 處理流程,都清晰了不少。廢話不說,開始我們的 Server 探索之旅。

    (水平有限,如有錯誤之處,歡迎指正)

     專案主頁

     主要函式

     這是所有函式的宣告:

  1. void accept_request(int);  
  2. void bad_request(int
    );  
  3. void cat(intFILE *);  
  4. void cannot_execute(int);  
  5. void error_die(constchar *);  
  6. void execute_cgi(intconstchar *, constchar *, constchar *);  
  7. int get_line(intchar *, int);  
  8. void headers(intconstchar *);  
  9. void not_found(int);  
  10. void serve_file(intconstchar *);  
  11. int startup(u_short *);  
  12. void unimplemented(int);  

     先簡單地解釋每個函式的作用:

     accept_request:  處理從套接字上監聽到的一個 HTTP 請求,在這裡可以很大一部分地體現伺服器處理請求流程。

     bad_request: 返回給客戶端這是個錯誤請求,HTTP 狀態嗎 400 BAD REQUEST.

     cat: 讀取伺服器上某個檔案寫到 socket 套接字。

     cannot_execute: 主要處理髮生在執行 cgi 程式時出現的錯誤。

     error_die: 把錯誤資訊寫到 perror 並退出。

     execute_cgi: 執行 cgi 程式的處理,也是個主要函式。

     get_line: 讀取套接字的一行,把回車換行等情況都統一為換行符結束。

     headers: 把 HTTP 響應的頭部寫到套接字。

     not_found: 主要處理找不到請求的檔案時的情況。

     sever_file: 呼叫 cat 把伺服器檔案返回給瀏覽器。

     startup: 初始化 httpd 服務,包括建立套接字,繫結埠,進行監聽等。

     unimplemented: 返回給瀏覽器表明收到的 HTTP 請求所用的 method 不被支援。

     建議原始碼閱讀順序: main -> startup -> accept_request -> execute_cgi, 通曉主要工作流程後再仔細把每個函式的原始碼看一看。

     工作流程

     (1) 伺服器啟動,在指定埠或隨機選取埠繫結 httpd 服務。

     (2)收到一個 HTTP 請求時(其實就是 listen 的埠 accpet 的時候),派生一個執行緒執行 accept_request 函式。

     (3)取出 HTTP 請求中的 method (GET 或 POST) 和 url,。對於 GET 方法,如果有攜帶引數,則 query_string 指標指向 url 中 ? 後面的 GET 引數。

     (4) 格式化 url 到 path 陣列,表示瀏覽器請求的伺服器檔案路徑,在 tinyhttpd 中伺服器檔案是在 htdocs 資料夾下。當 url 以 / 結尾,或 url 是個目錄,則預設在 path 中加上 index.html,表示訪問主頁。

     (5)如果檔案路徑合法,對於無引數的 GET 請求,直接輸出伺服器檔案到瀏覽器,即用 HTTP 格式寫到套接字上,跳到(10)。其他情況(帶引數 GET,POST 方式,url 為可執行檔案),則呼叫 excute_cgi 函式執行 cgi 指令碼。

    (6)讀取整個 HTTP 請求並丟棄,如果是 POST 則找出 Content-Length. 把 HTTP 200  狀態碼寫到套接字。

    (7) 建立兩個管道,cgi_input 和 cgi_output, 並 fork 一個程序。

    (8) 在子程序中,把 STDOUT 重定向到 cgi_outputt 的寫入端,把 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 資料寫入 cgi_input,已被重定向到 STDIN,讀取 cgi_output 的管道輸出到客戶端,該管道輸入是 STDOUT。接著關閉所有管道,等待子程序結束。這一部分比較亂,見下圖說明:

                                                     

                                                                                                         圖 1    管道初始狀態

                                                                                            

                                                                                                        圖 2  管道最終狀態 

    (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. #define ISspace(x) isspace((int)(x))
  29. #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
  30. void accept_request(int);  
  31. void bad_request(int);  
  32. void cat(intFILE *);  
  33. void cannot_execute(int);  
  34. void error_die(constchar *);  
  35. void execute_cgi(intconstchar *, constchar *, constchar *);  
  36. int get_line(intchar *, int);  
  37. void headers(intconstchar *);  
  38. void not_found(int);  
  39. void serve_file(intconstchar *);  
  40. int startup(u_short *);  
  41. void unimplemented(int);  
  42. /**********************************************************************/
  43. /* A request has caused a call to accept() on the server port to 
  44.  * return.  Process the request appropriately. 
  45.  * Parameters: the socket connected to the client */
  46. /**********************************************************************/
  47. void accept_request(int client)  
  48. {  
  49.     char buf[1024];  
  50.     int numchars;  
  51.     char method[255];  
  52.     char url[255];  
  53.     char path[512];  
  54.     size_t i, j;  
  55.     struct stat st;  
  56.     int cgi = 0;      /* becomes true if server decides this is a CGI program */
  57.     char *query_string = NULL;  
  58.     /*得到請求的第一行*/
  59.     numchars = get_line(client, buf, sizeof(buf));  
  60.     i = 0; j = 0;  
  61.     /*把客戶端的請求方法存到 method 陣列*/
  62.     while (!ISspace(buf[j]) && (i < sizeof(method) - 1))  
  63.     {  
  64.         method[i] = buf[j];  
  65.         i++; j++;  
  66.     }  
  67. 相關推薦

    no