Mongoose源码剖析:核心处理模块

来源:互联网 发布:巫师三优化 编辑:程序博客网 时间:2024/06/05 18:51
前面我们介绍了Mongoose所有的几个主要的数据结构mg_context、mg_connection、mg_request_info,还有Mongoose的生命主线。有了这些基础就可以来看看Mongoose的核心处理工作是怎样的。

本文从下面几个方面去介绍Mongoose的核心处理模块,连接建立之后的:

  • 请求解析
  • 请求验证
  • 请求满足

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.         conn.birth_time = time(NULL);
  3.         conn.ctx = ctx;

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

  15.         close_connection(&conn);
  16.     }
其中以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.     request_len = 0;
  13.     while (*nread < bufsiz && request_len == 0) {
  14.         n = pull(fp, sock, ssl, buf + *nread, bufsiz - *nread);
  15.         if (<= 0) {
  16.             break;
  17.         } else {
  18.             *nread += n;
  19.             request_len = get_request_len(buf, (size_t) *nread);
  20.         }
  21.     }

  22.     return (request_len);
  23. }
其中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.     if (ssl != NULL) {
  10.         nread = SSL_read(ssl, buf, len);
  11.     } else if (fp != NULL) {
  12.         nread = fread(buf, 1, (size_t) len, fp);
  13.         if (ferror(fp))
  14.             nread = -1;
  15.     } else {
  16.         nread = recv(sock, buf, (size_t) len, 0);
  17.     }

  18.     return (nread);
  19. }
这样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.     ri->request_method = skip(&buf, " ");
  10.     ri->uri = skip(&buf, " ");
  11.     http_version = skip(&buf, "\r\n");

  12.     if (is_known_http_method(ri->request_method) &&
  13.      ri->uri[0] == '/' &&
  14.      sscanf(http_version, "HTTP/%d.%d%n",
  15.      &ri->http_version_major, &ri->http_version_minor, &n) == 2 &&
  16.      http_version[n] == '\0') {
  17.         parse_http_headers(&buf, ri);
  18.         ri->remote_port = ntohs(usa->u.sin.sin_port);
  19.         (void) memcpy(&ri->remote_ip, &usa->u.sin.sin_addr.s_addr, 4);
  20.         ri->remote_ip = ntohl(ri->remote_ip);
  21.         success_code = TRUE;
  22.     }

  23.     return (success_code);
  24. }
它的主要工作就是从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.     begin_word = *buf;
  11.     end_word = begin_word + strcspn(begin_word, delimiters);
  12.     end_delimiters = end_word + strspn(end_word, delimiters);

  13.     for (= end_word; p < end_delimiters; p++)
  14.         *= '\0';

  15.     *buf = end_delimiters;

  16.     return (begin_word);
  17. }
我们来分析一下skip的作用及实现。如要从buf中解析出client请求的methods是哪个(PUT、GET、POST等等)?只需要这样做就可以了: 
ri->request_method = skip(&buf, " "); 
为了分析,到底是如何实现这个的,我在porcess_new_connection()中加入下面一行输出buf信息的代码:
  1. /在process_new_connection()中
  2. /* 0-terminate the request: parse_request uses sscanf */
  3.  buf[request_len - 1] = '\0';
  4. //******************************************************************
  5. //打印buf内容
  6. printf("\n******process_new_connection()******\n");
  7. printf("add by fanguotao,just for debug!! receive request_info is \n%s",buf);
  8. //******************************************************************
看当我们想mongoose发送的请求信息,这时我们在浏览其中输入http://ip:8080,终端会输出buf的信息,如下:
      
看到第一行就是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);
下面解释它们的作用:
  1. DESCRIPTION 
  2.        The strspn() function calculates the length of the initial segment of s 
  3. which consists entirely of characters in accept.

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

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

  7.        The strcspn() function returns the number of characters in the initial segment of s which are not in the string reject.
现在已经万事俱备了,skip(&buf, " ")的执行情况如下:
                 image

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.     if ((conn->request_info.query_string = strchr(uri, '?')) != NULL)
  15.         * conn->request_info.query_string++ = '\0';

  16.     (void) url_decode(uri, (int) strlen(uri), uri, strlen(uri) + 1, FALSE);
  17.     remove_double_dots_and_double_slashes(uri);
  18.     convert_uri_to_file_name(conn, uri, path, sizeof(path));

  19.     if (!check_authorization(conn, path)) {
  20.         send_authorization_request(conn);
  21.     } else if (check_embedded_authorization(conn) == FALSE) {
  22.         /*
  23.          * Embedded code failed authorization. Do nothing here, since
  24.          * an embedded code must handle this itself by either
  25.          * showing proper error message, or redirecting to some
  26.          * sort of login page, or something else.
  27.          */
  28.     } else if ((cb = find_callback(conn->ctx, FALSE, uri, -1)) != NULL) {
  29.         if ((strcmp(ri->request_method, "POST") != 0 &&
  30.          strcmp(ri->request_method, "PUT") != 0) ||
  31.          handle_request_body(conn, NULL))
  32.             cb->func(conn, &conn->request_info, cb->user_data);
  33.     } else if (strstr(path, PASSWORDS_FILE_NAME)) {
  34.         /* Do not allow to view passwords files */
  35.         send_error(conn, 403, "Forbidden", "Access Forbidden");
  36.     } else if ((!strcmp(ri->request_method, "PUT") ||
  37.      !strcmp(ri->request_method, "DELETE")) &&
  38.      (conn->ctx->options[OPT_AUTH_PUT] == NULL ||
  39.      !is_authorized_for_put(conn))) {
  40.         send_authorization_request(conn);
  41.     } else if (!strcmp(ri->request_method, "PUT")) {
  42.         put_file(conn, path);
  43.     } else if (!strcmp(ri->request_method, "DELETE")) {
  44.         if (mg_remove(path) == 0)
  45.             send_error(conn, 200, "OK", "");
  46.         else
  47.             send_error(conn, 500, http_500_error,
  48.              "remove(%s): %s", path, strerror(ERRNO));
  49.     } else if (mg_stat(path, &st) != 0) {
  50.         send_error(conn, 404, "Not Found", "%s", "File not found");
  51.     } else if (st.is_directory && uri[strlen(uri) - 1] != '/') {
  52.         (void) mg_printf(conn,
  53.          "HTTP/1.1 301 Moved Permanently\r\n"
  54.          "Location: %s/\r\n\r\n", uri);
  55.     } else if (st.is_directory &&
  56.      substitute_index_file(conn, path, sizeof(path), &st) == FALSE) {
  57.         if (is_true(conn->ctx->options[OPT_DIR_LIST])) {
  58.             send_directory(conn, path);
  59.         } else {
  60.             send_error(conn, 403, "Directory Listing Denied",
  61.              "Directory listing denied");
  62.         }
  63. #if !defined(NO_CGI)
  64.     } else if (match_extension(path,
  65.      conn->ctx->options[OPT_CGI_EXTENSIONS])) {
  66.         if (strcmp(ri->request_method, "POST") &&
  67.          strcmp(ri->request_method, "GET")) {
  68.             send_error(conn, 501, "Not Implemented",
  69.              "Method %s is not implemented", ri->request_method);
  70.         } else {
  71.             send_cgi(conn, path);
  72.         }
  73. #endif /* NO_CGI */
  74. #if !defined(NO_SSI)
  75.     } else if (match_extension(path,
  76.      conn->ctx->options[OPT_SSI_EXTENSIONS])) {
  77.         send_ssi(conn, path);
  78. #endif /* NO_SSI */
  79.     } else if (is_not_modified(conn, &st)) {
  80.         send_error(conn, 304, "Not Modified", "");
  81.     } else {
  82.         send_file(conn, path, &st);
  83.     }
  84. }

  《通过反复的看,对mongoose—web服务器的工作原理有了较深的了解,其工作模型以及实现方法方面,非常值得学习继续努力哈,梦醒潇湘love》
0 0