web服务器之mongoose:核心处理模块

2023-05-16

引言

 

前面我们介绍了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.  
  5.         if (conn.client.is_ssl &&
  6.          (conn.ssl = SSL_new(conn.ctx->ssl_ctx)) == NULL) {
  7.             cry(&conn, "%s: SSL_new: %d", __func__, ERRNO);
  8.         } else if (conn.client.is_ssl &&
  9.          SSL_set_fd(conn.ssl, conn.client.sock) != 1) {
  10.             cry(&conn, "%s: SSL_set_fd: %d", __func__, ERRNO);
  11.         } else if (conn.client.is_ssl && SSL_accept(conn.ssl) != 1) {
  12.             cry(&conn, "%s: SSL handshake error", __func__);
  13.         } else {
  14.             process_new_connection(&conn);
  15.         }
  16.  
  17.         close_connection(&conn);
  18.     }

其中以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 (<= 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);
  25. }

其中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);
  21. }

这样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);
  27. }

它的主要工作就是从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 (= end_word; p < end_delimiters; p++)
  16.         *= '\0';
  17.  
  18.     *buf = end_delimiters;
  19.  
  20.     return (begin_word);
  21. }

我们来分析一下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.  
  5.        The strcspn() function calculates the length of the initial segment of s which consists entirely of characters not in reject.
  6.  
  7. RETURN VALUE 
  8.        The strspn() function returns the number of characters in the initial segment of s which consist only of characters from accept.
  9.  
  10.        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.  
  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.     }
  87. }
  88.  
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

web服务器之mongoose:核心处理模块 的相关文章

  • System.Web.HttpException 无法加载类型“[命名空间].???”

    这开始于无法加载类型 全局 错误 在我尝试了一些方法后 没有找到删除 Global asax 文件的位置 现在错误是无法加载类型 namespace 在哪里 是我尝试加载的每个页面的类名 该网站 在 VS2008 本地开发计算机中执行时 工
  • 为什么要使用除 div 以外的任何东西? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 使用“邮递员”chrome 应用程序的肥皂请求正文

    假日网络服务 的肥皂请求正文会是什么样子 http www holidaywebservice com HolidayService v2 HolidayService2 asmx wsdl http www holidaywebservi
  • MongoDB $orderby 和 Sort 之间的区别

    我想获取最新的文档 这显然是一个文档 因此findOne应该可以正常工作 但findOne这里返回插入的第一个文档 所以我现在有两个选择要么使用 orderBy with findOne or use sort 功能与 limit in f
  • Mongoose 对 Promise 拒绝处理

    我有点与 NodeJS 中的 Promise 模式作斗争 我正在数据库中查找用户 然后使用用户引用保存新实体 但是当用户不在数据库中时 我应该返回拒绝 但我不确定如何正确执行 有没有办法做得更好 顺便说一句 对不起 咖啡脚本 User fi
  • MongoDB 和 Mongoose 访问一个数据库,同时针对另一个数据库进行身份验证(NodeJS、Mongoose)

    我有几个数据库 不想为每个数据库创建单独的用户帐户 MongoDB 支持使用另一个数据库中定义的帐户来验证对数据库的访问的概念 但语法示例很难获得 当我终于弄清楚时 我正准备提出一个问题 如果它对其他人有帮助 就放在这里 这是 mongod
  • isModified 并预保存 mongoose...Nodejs

    您好 我只想在密码更改时使用散列密码保存 因此我在预保存中使用了 isModified 函数 但即使我更改了密码 它也总是返回 false 我尝试这样做的原因是因为我不想在更改其他属性时更改并保存我的密码 router post chang
  • Mongoose 查找 array.length 大于 0 的所有文档并对数据进行排序

    我正在使用 mongoose 对 MongoDB 执行 CRUD 操作 这就是我的架构的样子 var EmployeeSchema new Schema name String description type String default
  • keystonejs 模型中的动态类型选择

    我想在 adminUI 中使用一个组合框 其中包含来自 Web 服务的字段 我正在考虑使用预 查找 挂钩获取数据 然后覆盖模式中 受众 属性的选项属性 Schema Compliance add title type Types Text
  • IIS 8 HTTPS/需要 SSL 导致超时错误

    尝试通过 IIS 8 通过 SSL 发布网站 但出现超时错误 任何帮助表示赞赏 采取的步骤 已验证该网站可以通过 HTTP 访问 http xxx xxx xxx xxx有效 此时使用 IP 地址 如果重要的话 IIS gt 服务器证书 g
  • 最小验证在 Mongoose 中不起作用

    我有一个架构 其中余额字段的声明如下所示 balance type Number min 0 default 30 我将 0 设置为最小值 这样余额就不会为负值 但是当我通过更新查询减少余额值时 余额结果是负值 我的更新查询 User up
  • 重新加载更新的 javascript> 代码而不完全重新加载 html 页面

    我正在开发一个单页 Web 应用程序 它具有许多不同的功能和形式 当开发一个深度 我的意思是主页上没有的 功能时 我会经历这个循环 开发代码 编辑类和函数 刷新整个页面 一路点击 直到到达我需要测试的部分 有时加起来大约一分钟 测试新代码
  • Mongoose 和 Promise:如何获取查询结果数组?

    使用猫鼬从数据库和 Q 中查询结果以获取承诺 但发现很难只获取可用用户列表 目前我有一些这样的东西 var checkForPerson function person people mongoose model Person Person
  • 同源政策目的可疑

    正如我所读到的 同源策略是防止源自 邪恶 域 A 的脚本向 良好 域 B 发出请求 换句话说 跨站点请求伪造 玩了一下我了解到的Access Control Allow Origin标头和CORS据我了解 它允许从好域 B 指定服务器 域
  • 在 MongoDB 中删除时自动删除引用对象

    假设我有一个这样的架构 var Person new Schema name String var Assignment new Schema name String person ObjectID 如果我删除一个人 仍然可能会留下引用不存
  • 使用 Mongoose 将数组(“标签”)保存到 MongoDB

    我正在玩 Mongoose 但在保存到数组时遇到问题 例如 我在页面上有一个以逗号分隔的输入字段tags 我从 req body tags 中获取这些内容 删除空格 然后用逗号分隔它们以获得标签数组 现在 如何将该数组保存回我的数据库 我猜
  • 如何在猫鼬中使用聚合

    如何在 mongoose 中定义以下 MongoDB 聚合查询 db contacts aggregate group id code Code name Name 查询的目的是获取不同代码和名称的列表 我当前的模型代码是 use stri
  • 为什么我在 Nodejs 中收到“在将标头发送到客户端后无法设置标头”错误?

    我正进入 状态 Cannot set headers after they are sent to the clientNodejs 中出现错误 我无法弄清楚原因 代码如下 我正在使用 mongoose 将数据保存在 mongodb 中 我
  • Mongoose:转换为 ObjectId 失败

    我正在尝试在 MongoDB 中创建一个类别层次结构 以便通过 Mongoose 与 Node js 一起使用 我正在使用祖先数组方法 http docs mongodb org manual tutorial model tree str
  • 如何用 C 语言通过 HTTP 协议发送图像?

    我是一名正在做网络服务器练习的学生 我需要一些帮助 我的网络服务器在文本页面上运行良好 但是每当浏览器发送一个 GET img jpg HTTP 1 1请求 我不知道如何处理 我听说 HTTP 协议是基于文本的 那么如何在 HTTP 响应中

随机推荐

  • 在ROS环境下用yolov3-tiny训练自己的数据集

    由于整个过程新建 修改 设置的文件和参数较多 所以我先放张整个流程分析图在这 xff0c 方便大家结合下列步骤去更好理解 0 提前准备 xff1a 下载darknet xff08 yolov3 xff09 并编译 span class to
  • ROS下使用usb_cam驱动读取摄像头数据

    因为darknet ros会直接订阅指定的图像话题名 xff0c 然后对图像进行检测 xff0c 绘制检测框 xff0c 并发布相应的检测话题 xff0c 因此首先需要找一个能够发布图像话题的ROS包 xff0c 这里经推荐使用ROS官方提
  • python 各种加密

    背景 加密学习 对称加密 对称密钥加密 xff0c 又叫私钥加密 即信息发送的方和接受方用一个密钥去加密和揭秘数据 最大的优势是 加解密速度快 xff0c 适合对大量数据进行加密 xff0c 对称加密的缺点是密钥的管理和分配 xff0c 换
  • 参加中国移动开发者大会有感

    作为我校CSDN俱乐部的主席 xff0c 我很荣幸受到CSDN的邀请参加中国移动开发者大会 xff0c 下面写点自己的感悟 xff1a 移动互联网应用大时代已经到来 xff0c 所有的IT应用都将移动化 xff0c 所有的信息服务都将移动化
  • IntelD435摄像头将深度图与摄像头进行像素对齐(并抽帧保存)

    coding 61 utf 8 import pyrealsense2 as rs import numpy as np import cv2 import os 创建一个管道 pipeline 61 rs pipeline Create
  • python3 可视化COCO格式json文件目标检测矩形框

    import os import json import argparse from PIL import Image ImageDraw ImageFont FONT SIZE 61 13 2 IMAGE FONT 61 ImageFon
  • 对比图像清晰度(模糊度)之灰度方差算法 python代码实现

    灰度方差算法 图像最清晰 xff0c 图像中的高频分量也最多 xff0c 该算法以图像所有像素的灰度平均值为参考 xff0c 对每个像素点的灰度值求差后求平方和 xff0c 然后用像素总数标准化 xff0c 它表征了图像灰度变化的平均程度
  • pytorch使用 ROIalign 代码实例

    batch size 61 x shape 0 all roi align feats 61 for i in range batch size hmap1 s 61 out hmap 1 i hmap1 s 61 hmap1 s unsq
  • python resize dicom(dcm)代码

    from glob import glob import os import pydicom import numpy as np import cv2 dcm list 61 sorted os listdir 39 xxxx 39 fo
  • 基于opencv和ffmpeg的图片转换成视频的两种方法实现,可生成H264格式

    opencv转换图片为视频 xff08 生成视频格式为MJPG xff09 encoding UTF 8 import glob as gb import cv2 图片文件夹地址 img path 61 gb glob 34 G temp
  • VGG16提取图片特征

    import os import numpy as np import torch import torch nn import torchvision models as models from torch autograd import
  • 防火墙之数据包过滤iptables

    内容简介 防火墙的概述 1 iptables简介 2 iptables基础 3 iptables语法 4 iptables实例 案例详解 xff08 一 xff09 防火墙的简介 防火墙是指设置在不同网络或网络安全域之间的一系列部件的组合
  • Ubuntu apt-get 和 pip 更换源

    Ubuntu系统自带的源都是国外的网址 xff0c 国内用户在使用的时候网速比较慢 一个软件的下载是十分痛苦的 xff0c 这里讲解一下如何将国外源更换为国内源 xff0c 让你的网速Biu Biu Biu 更换apt get数据源 1 备
  • Linux如何解决动态库的版本控制

    xff08 换句话说 xff0c soname不是真实存在的文件 xff0c 只是在此库中和将来调用此库的文件中保存的一个名字 xff0c 在加载时去找这个名字 xff0c 使用时创建一个软连接来指向真实文件 xff0c 这样真实文件的版本
  • C语言中可变参数函数实现原理

    C函数调用的栈结构 可变参数函数的实现与函数调用的栈结构密切相关 xff0c 正常情况下C的函数参数入栈规则为 stdcall 它是从右到左的 xff0c 即函数中的最右边的参数最先入栈 例如 xff0c 对于函数 xff1a void f
  • Linux串口测试应用程序

    1 Linux终端 串口 210开发板有4个串口 2440开发板有3个串口 在2440开发板中三个串口设备对应如下 串口名字 主设备号 次设备号 s3c2410 serial0 204 64 s3c2410 serial1 204 65 s
  • 开发板上syslog使用方法小结

    首先我们需要先确认我们的开发板上是否安装了syslog服务 xff0c 在开发板的文件系统下输入syslogd help命令查看如果不支持就是busybox的版本太低了 当确定安装了后我们根据help显示的默认配置文件来查看该配置文件是否存
  • Perl中文件读取操作

    一 打开 关闭文件 语法为open filevar filename xff0c 其中filevar为文件句柄 xff0c 或者说是程序中用来代表某文件的代号 xff0c filename为文件名 xff0c 其路径可为相对路径 xff0c
  • web服务器之mongoose:安装

    引言 Mongoose开源项目的主页是http code google com p mongoose Mongoose是一个web服务器 xff0c 通过提供一个web接口给它 xff0c 它可以嵌入到现有的应用程序中去 Mongoose
  • web服务器之mongoose:核心处理模块

    引言 前面我们介绍了Mongoose所有的几个主要的数据结构mg context mg connection mg request info xff0c 还有Mongoose的生命主线 有了这些基础就可以来看看Mongoose的核心处理工作