Mongoose源碼剖析:核心處理模塊

引言

前面我們介紹了Mongoose所有的幾個主要的數據結構mg_context、mg_connection、mg_request_info,還有Mongoose的生命主線。有了這些基礎就可以來看看Mongoose的核心處理工作是怎樣的。如果你還沒有閱讀前面的文章,你可以通過下面的隧道直通:

  1. Mongoose源碼剖析:外篇之web服務器
  2. Mongoose源碼剖析:Introduction and Installation
  3. Mongoose源碼剖析:數據結構篇
  4. Mongoose源碼剖析:mongoose的工作模型

本文從下面幾個方面去介紹Mongoose的核心處理模塊,連接建立之後的:

  1. 請求解析
  2. 請求驗證
  3. 請求滿足

1、連接的建立

Mongoose的主線程master_thread在接受一個新的client連接請求時,會將client的socket地址放入一個queue(調用put_socket()方法);而當worker_thread線程處理client的請求時,是通過get_socket()方法從queue取出client的socket地址,然後與它建立連接。

建立連接就用到了數據結構mg_connection,該結構保存了client的連接信息。該結構體中有兩個非常重要的成員:mg_request_info用於保存client的請求信息、mg_context用於保存該client請求的mongoose上下文。建立連接的代碼片段如下:

  1. while (get_socket(ctx, &conn.client) == TRUE) 
  2. {  
  3.         conn.birth_time = time(NULL);  
  4.         conn.ctx = ctx;  
  5.  
  6.         if (conn.client.is_ssl &&  
  7.             (conn.ssl = SSL_new(conn.ctx->ssl_ctx)) == NULL) {  
  8.             cry(&conn, "%s: SSL_new: %d", __func__, ERRNO);  
  9.         } else if (conn.client.is_ssl &&  
  10.             SSL_set_fd(conn.ssl, conn.client.sock) != 1) {  
  11.             cry(&conn, "%s: SSL_set_fd: %d", __func__, ERRNO);  
  12.         } else if (conn.client.is_ssl && SSL_accept(conn.ssl) != 1) {  
  13.             cry(&conn, "%s: SSL handshake error", __func__);  
  14.         } else {  
  15.             process_new_connection(&conn);  
  16.         }  
  17.  
  18.         close_connection(&conn);  

其中以SSL_開頭的函數都是加載自SSL的庫,加載庫調用瞭如下接口:static bool_t set_ssl_option(struct mg_context *ctx, const char *pem),有興趣的話你可以追蹤下去。

2、請求信息獲取

建立連接之後,在process_new_connection中會去讀取client的請求信息,然後纔去解析請求。讀取client端的請求的信息用到了下面的方法:

  1. /*  
  2.  * Keep reading the input (either opened file descriptor fd, or socket sock,  
  3.  * or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the  
  4.  * buffer (which marks the end of HTTP request). Buffer buf may already  
  5.  * have some data. The length of the data is stored in nread.  
  6.  * Upon every read operation, increase nread by the number of bytes read.  
  7. */ 
  8. static int 
  9. read_request(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz, int *nread)  
  10. {  
  11.     int n, request_len;  
  12.  
  13.     request_len = 0;  
  14.     while (*nread < bufsiz && request_len == 0) {  
  15.         n = pull(fp, sock, ssl, buf + *nread, bufsiz - *nread);  
  16.         if (n <= 0) {  
  17.             break;  
  18.         } else {  
  19.             *nread += n;  
  20.             request_len = get_request_len(buf, (size_t) *nread);  
  21.         }  
  22.     }  
  23.  
  24.     return (request_len);  

其中pull()方法的代碼如下:

  1. /*  
  2.  * Read from IO channel - opened file descriptor, socket, or SSL descriptor.  
  3.  * Return number of bytes read.  
  4. */ 
  5. static int 
  6. pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len)  
  7. {  
  8.     int nread;  
  9.  
  10.     if (ssl != NULL) {  
  11.         nread = SSL_read(ssl, buf, len);  
  12.     } else if (fp != NULL) {  
  13.         nread = fread(buf, 1, (size_t) len, fp);  
  14.         if (ferror(fp))  
  15.             nread = -1;  
  16.     } else {  
  17.         nread = recv(sock, buf, (size_t) len, 0);  
  18.     }  
  19.  
  20.     return (nread);  

這樣client發送的HTTP請求消息就被worker_thread讀取到了,並存儲在buf中, 接下來的工作就是解析讀取到的請求信息,明白client到底想幹嘛,說白了就從buf中提取信息並存儲到結構體mg_request_info中去。

3、請求解析

請求解析的工作都封裝在parse_http_request()函數匯中,它的代碼如下:

  1. /*  
  2.  * Parse HTTP request, fill in mg_request_info structure.  
  3. */ 
  4. static bool_t  
  5. parse_http_request(char *buf, struct mg_request_info *ri, const struct usa *usa)  
  6. {  
  7.     char    *http_version;  
  8.     int n, success_code = FALSE;  
  9.  
  10.     ri->request_method = skip(&buf, " ");  
  11.     ri->uri = skip(&buf, " ");  
  12.     http_version = skip(&buf, "\r\n");  
  13.  
  14.     if (is_known_http_method(ri->request_method) &&  
  15.         ri->uri[0] == '/' &&  
  16.         sscanf(http_version, "HTTP/%d.%d%n",  
  17.         &ri->http_version_major, &ri->http_version_minor, &n) == 2 &&  
  18.         http_version[n] == '\0') {  
  19.         parse_http_headers(&buf, ri);  
  20.         ri->remote_port = ntohs(usa->u.sin.sin_port);  
  21.         (void) memcpy(&ri->remote_ip, &usa->u.sin.sin_addr.s_addr, 4);  
  22.         ri->remote_ip = ntohl(ri->remote_ip);  
  23.         success_code = TRUE;  
  24.     }  
  25.  
  26.     return (success_code);  

它的主要工作就是從buf中提取出信息放到ri(一個mg_request_info結構)中去,因爲buf是一個無結構的字符串數組。要將它存儲到ri中去,需要找到對應的子串。

這裏主要用到了skip()、parse_http_headers()方法,其中skip()很關鍵,代碼如下:

  1. /*  
  2.  * Skip the characters until one of the delimiters characters found.  
  3.  * 0-terminate resulting word. Skip the rest of the delimiters if any.  
  4.  * Advance pointer to buffer to the next word. Return found 0-terminated word.  
  5. */ 
  6. static char *  
  7. skip(char **buf, const char *delimiters)  
  8. {  
  9.     char    *p, *begin_word, *end_word, *end_delimiters;  
  10.  
  11.     begin_word = *buf;  
  12.     end_word = begin_word + strcspn(begin_word, delimiters);  
  13.     end_delimiters = end_word + strspn(end_word, delimiters);  
  14.  
  15.     for (p = end_word; p < end_delimiters; p++)  
  16.         *p = '\0';  
  17.  
  18.     *buf = end_delimiters;  
  19.  
  20.     return (begin_word);  

我們來分析一下skip的作用及實現。如要從buf中解析出client請求的methods是哪個(PUT、GET、POST等等)?只需要這樣做就可以了:

  1. ri->request_method = skip(&buf, " ");  

爲了分析,到底是如何實現這個的,我在porcess_new_connection()中加入下面一行輸出buf信息的代碼:

Mongoose源碼剖析:核心處理模塊

看當我們想mongoose發送的請求信息,這時我們在瀏覽其中輸入http://ip:8080,終端會輸出buf的信息,如下:

Mongoose源碼剖析:核心處理模塊

看到第一行就是GET /favicon.ico HTTP/1.1。知道了buf中的字符信息,但在我們分析skip(&buf, " ")是如何提取出GET的之前,還要知道strcspn、strspn的作用,下面是它們的原型:

  1. #include <string.h>  
  2. size_t strspn(const char *s, const char *accept);  
  3. size_t strcspn(const char *s, const char *reject); 

下面解釋它們的作用:

DESCRIPTION
       The strspn() function calculates the length of the initial segment of s
which consists entirely of characters in accept.

       The strcspn() function calculates the length of the initial segment  of s which consists entirely of characters not in reject.

RETURN VALUE
       The  strspn()  function returns the number of characters in the initial segment of s which consist only of characters from accept.

       The strcspn() function returns the number of characters in the  initial segment of s which are not in the string reject.

現在已經萬事俱備了,skip(&buf, " ")的執行情況如下:

Mongoose源碼剖析:核心處理模塊

其餘的解析工作也是類似地進行的,我就不一一闡述了。

4、請求驗證

請求驗證分佈在從連接請求開始到請求得到迴應的整個過程中。在請求解析之前,比如驗證socket的合法性等。在請求解析之後,從buf中解析出HTTP請求消息的各個字段之後,就做一些簡單的驗證工作,比如說HTTP版本的驗證。如果在解析buf時出錯,說明請求的格式不對。

而且在滿足client請求的時候也要進行一些驗證,諸如是否有瀏覽目錄的權限、請求的文件是否存在等等,我就不在詳述了。

5、請求滿足

在parse_http_request()之後,調用analyze_request()去滿足client的請求。這是Mongoose的核心內容,也是不同web服務器軟件相區別的地方。analyze_request()封裝了一些操作,即調用了一些接口去滿足client的請求,代碼如下:

  1. /*  
  2.  * This is the heart of the Mongoose's logic.  
  3.  * This function is called when the request is read, parsed and validated,  
  4.  * and Mongoose must decide what action to take: serve a file, or  
  5.  * a directory, or call embedded function, etcetera.  
  6. */ 
  7. static void 
  8. analyze_request(struct mg_connection *conn)  
  9. {  
  10.     struct mg_request_info *ri = &conn->request_info;  
  11.     char            path[FILENAME_MAX], *uri = ri->uri;  
  12.     struct mgstat       st;  
  13.     const struct callback   *cb;  
  14.  
  15.     if ((conn->request_info.query_string = strchr(uri, '?')) != NULL)  
  16.         * conn->request_info.query_string++ = '\0';  
  17.  
  18.     (void) url_decode(uri, (int) strlen(uri), uri, strlen(uri) + 1, FALSE);  
  19.     remove_double_dots_and_double_slashes(uri);  
  20.     convert_uri_to_file_name(conn, uri, path, sizeof(path));  
  21.  
  22.     if (!check_authorization(conn, path)) {  
  23.         send_authorization_request(conn);  
  24.     } else if (check_embedded_authorization(conn) == FALSE) {  
  25.         /*  
  26.          * Embedded code failed authorization. Do nothing here, since  
  27.          * an embedded code must handle this itself by either  
  28.          * showing proper error message, or redirecting to some  
  29.          * sort of login page, or something else.  
  30.          */ 
  31.     } else if ((cb = find_callback(conn->ctx, FALSE, uri, -1)) != NULL) {  
  32.         if ((strcmp(ri->request_method, "POST") != 0 &&  
  33.             strcmp(ri->request_method, "PUT") != 0) ||  
  34.             handle_request_body(conn, NULL))  
  35.             cb->func(conn, &conn->request_info, cb->user_data);  
  36.     } else if (strstr(path, PASSWORDS_FILE_NAME)) {  
  37.         /* Do not allow to view passwords files */ 
  38.         send_error(conn, 403, "Forbidden""Access Forbidden");  
  39.     } else if ((!strcmp(ri->request_method, "PUT") ||  
  40.         !strcmp(ri->request_method, "DELETE")) &&  
  41.         (conn->ctx->options[OPT_AUTH_PUT] == NULL ||  
  42.          !is_authorized_for_put(conn))) {  
  43.         send_authorization_request(conn);  
  44.     } else if (!strcmp(ri->request_method, "PUT")) {  
  45.         put_file(conn, path);  
  46.     } else if (!strcmp(ri->request_method, "DELETE")) {  
  47.         if (mg_remove(path) == 0)  
  48.             send_error(conn, 200, "OK""");  
  49.         else 
  50.             send_error(conn, 500, http_500_error,  
  51.                 "remove(%s): %s", path, strerror(ERRNO));  
  52.     } else if (mg_stat(path, &st) != 0) {  
  53.         send_error(conn, 404, "Not Found""%s""File not found");  
  54.     } else if (st.is_directory && uri[strlen(uri) - 1] != '/') {  
  55.         (void) mg_printf(conn,  
  56.             "HTTP/1.1 301 Moved Permanently\r\n" 
  57.             "Location: %s/\r\n\r\n", uri);  
  58.     } else if (st.is_directory &&  
  59.         substitute_index_file(conn, path, sizeof(path), &st) == FALSE) {  
  60.         if (is_true(conn->ctx->options[OPT_DIR_LIST])) {  
  61.             send_directory(conn, path);  
  62.         } else {  
  63.             send_error(conn, 403, "Directory Listing Denied",  
  64.                 "Directory listing denied");  
  65.         }  
  66. #if !defined(NO_CGI)  
  67.     } else if (match_extension(path,  
  68.         conn->ctx->options[OPT_CGI_EXTENSIONS])) {  
  69.         if (strcmp(ri->request_method, "POST") &&  
  70.             strcmp(ri->request_method, "GET")) {  
  71.             send_error(conn, 501, "Not Implemented",  
  72.                 "Method %s is not implemented", ri->request_method);  
  73.         } else {  
  74.             send_cgi(conn, path);  
  75.         }  
  76. #endif /* NO_CGI */  
  77. #if !defined(NO_SSI)  
  78.     } else if (match_extension(path,  
  79.         conn->ctx->options[OPT_SSI_EXTENSIONS])) {  
  80.         send_ssi(conn, path);  
  81. #endif /* NO_SSI */  
  82.     } else if (is_not_modified(conn, &st)) {  
  83.         send_error(conn, 304, "Not Modified""");  
  84.     } else {  
  85.         send_file(conn, path, &st);  
  86.     }  

上面的代碼比較好理解我就不把它嚼爛之後,再展現給你!那樣也沒有意思~\(≧▽≦)/~啦啦啦。感興趣的自己去逐一分析。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章