【原始碼剖析】tinyhttpd —— C 語言實現最簡單的 HTTP 伺服器
tinyhttpd 是一個不到 500 行的超輕量型 Http Server,用來學習非常不錯,可以幫助我們真正理解伺服器程式的本質。
看完所有原始碼,真的感覺有很大收穫,無論是 unix 的程式設計,還是 GET/POST 的 Web 處理流程,都清晰了不少。廢話不說,開始我們的 Server 探索之旅。
(水平有限,如有錯誤之處,歡迎指正)
專案主頁
主要函式
這是所有函式的宣告:
- void accept_request(int);
-
void bad_request(int
- void cat(int, FILE *);
- void cannot_execute(int);
- void error_die(constchar *);
- void execute_cgi(int, constchar *, constchar *, constchar *);
- int get_line(int, char *, int);
- void headers(int, constchar *);
- void not_found(int);
- void serve_file(int, constchar *);
-
int startup(u_short *);
- 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 是無連線的。
註釋版原始碼
懶得跳轉的同學看下面....
- /* J. David's webserver */
- /* This is a simple webserver.
- * Created November 1999 by J. David Blackstone.
- * CSE 4344 (Network concepts), Prof. Zeigler
- * University of Texas at Arlington
- */
- /* This program compiles for Sparc Solaris 2.6.
- * To compile for Linux:
- * 1) Comment out the #include <pthread.h> line.
- * 2) Comment out the line that defines the variable newthread.
- * 3) Comment out the two lines that run pthread_create().
- * 4) Uncomment the line that runs accept_request().
- * 5) Remove -lsocket from the Makefile.
- */
- #include <stdio.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <unistd.h>
- #include <ctype.h>
- #include <strings.h>
- #include <string.h>
- #include <sys/stat.h>
- #include <pthread.h>
- #include <sys/wait.h>
- #include <stdlib.h>
- #define ISspace(x) isspace((int)(x))
- #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
- void accept_request(int);
- void bad_request(int);
- void cat(int, FILE *);
- void cannot_execute(int);
- void error_die(constchar *);
- void execute_cgi(int, constchar *, constchar *, constchar *);
- int get_line(int, char *, int);
- void headers(int, constchar *);
- void not_found(int);
- void serve_file(int, constchar *);
- int startup(u_short *);
- void unimplemented(int);
- /**********************************************************************/
- /* A request has caused a call to accept() on the server port to
- * return. Process the request appropriately.
- * Parameters: the socket connected to the client */
- /**********************************************************************/
- void accept_request(int client)
- {
- char buf[1024];
- int numchars;
- char method[255];
- char url[255];
- char path[512];
- size_t i, j;
- struct stat st;
- int cgi = 0; /* becomes true if server decides this is a CGI program */
- char *query_string = NULL;
- /*得到請求的第一行*/
- numchars = get_line(client, buf, sizeof(buf));
- i = 0; j = 0;
- /*把客戶端的請求方法存到 method 陣列*/
- while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
- {
- method[i] = buf[j];
- i++; j++;
- }
-
相關推薦
no