Mongoose 笔记系列用于记录学习 Mongoose 的一些内容。
Mongoose 是一个 C/C++ 的网络库。它为 TCP、UDP、HTTP、WebSocket、MQTT 实现了事件驱动的、非阻塞的 API。
项目地址:
https://github.com/cesanta/mongoose
下面通过学习 Mongoose 项目代码中的 http-reverse-proxy 示例程序 ,来学习如何使用 Mongoose 实现一个简单的 HTTP 反向代理。使用树莓派平台进行开发验证。
http-reverse-proxy 的示例程序不长,代码如下:
// Copyright (c) 2020 Cesanta Software Limited
// All rights reserved
//
// Example HTTP reverse proxy
// 1. Run `make`. This builds and starts a proxy on port 8000
// 2. Start your browser, go to https://localhost:8000
//
// To enable SSL/TLS, add SSL=OPENSSL or SSL=MBEDTLS
static const char *s_backend_url =
#if defined(MG_ENABLE_MBEDTLS) || defined(MG_ENABLE_OPENSSL)
"https://cesanta.com";
#else
"http://info.cern.ch";
#endif
static const char *s_listen_url = "http://localhost:8000";
#include "mongoose.h"
// Forward client request to the backend connection, rewriting the Host header
static void forward_request(struct mg_http_message *hm,
struct mg_connection *c) {
size_t i, max = sizeof(hm->headers) / sizeof(hm->headers[0]);
struct mg_str host = mg_url_host(s_backend_url);
mg_printf(c, "%.*s\r\n",
(int) (hm->proto.ptr + hm->proto.len - hm->message.ptr),
hm->message.ptr);
for (i = 0; i < max && hm->headers[i].name.len > 0; i++) {
struct mg_str *k = &hm->headers[i].name, *v = &hm->headers[i].value;
if (mg_strcmp(*k, mg_str("Host")) == 0) v = &host;
mg_printf(c, "%.*s: %.*s\r\n", (int) k->len, k->ptr, (int) v->len, v->ptr);
}
mg_send(c, "\r\n", 2);
mg_send(c, hm->body.ptr, hm->body.len);
MG_DEBUG(("FORWARDING: %.*s %.*s", (int) hm->method.len, hm->method.ptr,
(int) hm->uri.len, hm->uri.ptr));
}
static void fn2(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
struct mg_connection *c2 = fn_data;
if (ev == MG_EV_READ) {
// All incoming data from the backend, forward to the client
if (c2 != NULL) mg_send(c2, c->recv.buf, c->recv.len);
mg_iobuf_del(&c->recv, 0, c->recv.len);
} else if (ev == MG_EV_CLOSE) {
if (c2 != NULL) c2->fn_data = NULL;
}
(void) ev_data;
}
static void fn(struct mg_connection *c, int ev, void *ev_data, void *fn_data) {
struct mg_connection *c2 = fn_data;
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
// Client request, create backend connection Note that we're passing
// client connection `c` as fn_data for the created backend connection.
c2 = mg_connect(c->mgr, s_backend_url, fn2, c);
if (c2 == NULL) {
mg_error(c, "Cannot create backend connection");
} else {
if (mg_url_is_ssl(s_backend_url)) {
struct mg_tls_opts opts = {.ca = "ca.pem"};
mg_tls_init(c2, &opts);
}
c->fn_data = c2;
forward_request(hm, c2);
c2->is_hexdumping = 1;
}
} else if (ev == MG_EV_CLOSE) {
if (c2 != NULL) c2->is_closing = 1;
if (c2 != NULL) c2->fn_data = NULL;
}
}
int main(void) {
struct mg_mgr mgr;
mg_log_set(MG_LL_DEBUG); // Set log level
mg_mgr_init(&mgr); // Initialise event manager
mg_http_listen(&mgr, s_listen_url, fn, NULL); // Start proxy
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
mg_mgr_free(&mgr);
return 0;
}
下面从main函数开始分析代码。
定义变量,struct mg_mgr是用于保存所有活动连接的事件管理器。
struct mg_mgr mgr;
设置 Mongoose 日志记录级别,设置等级为 MG_LL_DEBUG。
mg_log_set(MG_LL_DEBUG); // Set log level
初始化一个事件管理器,也就是将上面定义的struct mg_mgr变量 mgr 中的数据进行初始化。
mg_mgr_init(&mgr); // Initialise event manager
启动代理,通过 mg_http_listen 创建一个 HTTP 监听连接,监听地址s_listen_url。其中fn是事件处理函数。
mg_http_listen(&mgr, s_listen_url, fn, NULL); // Start proxy
参数s_listen_url是一个全局变量,默认值为http://localhost:8000。
static const char *s_listen_url = "http://localhost:8000";
接下来是事件循环,mg_mgr_poll 遍历所有连接,接受新连接,发送和接收数据,关闭连接,并为各个事件调用事件处理函数。
for (;;) mg_mgr_poll(&mgr, 1000); // Event loop
调用 mg_mgr_free 关闭所有连接,释放所有资源。
mg_mgr_free(&mgr);
分析完main函数后,我们看下事件处理函数fn的代码。
首先判断是否接收到的事件是 HTTP 请求事件MG_EV_HTTP_MSG,如果是则表示收到客户端的请求,开始 HTTP 请求的处理。
struct mg_connection *c2 = fn_data;
if (ev == MG_EV_HTTP_MSG) {
将函数参数ev_data转换为 struct mg_http_message,其中包含已解析的 HTTP 请求。
struct mg_http_message *hm = (struct mg_http_message *) ev_data;
接着通过mg_connect创建一个新的连接,用于与后端服务器连接,fn2是事件处理函数,并将c传递进去。
c2 = mg_connect(c->mgr, s_backend_url, fn2, c);
如果c2为NULL,则连接失败,不为NULL则使用mg_url_is_ssl函数检查给定的 URL 是否使用加密方案,如果s_backend_url是https://,则连接使用 TLS,调用mg_tls_init函数初始化 TLS。将c2的值赋给c->fn_data,接着调用forward_request函数转发客户端请求,然后c2->is_hexdumping置1,开启Hexdump in/out traffic功能。
if (c2 == NULL) {
mg_error(c, "Cannot create backend connection");
} else {
if (mg_url_is_ssl(s_backend_url)) {
struct mg_tls_opts opts = {.ca = "ca.pem"};
mg_tls_init(c2, &opts);
}
c->fn_data = c2;
forward_request(hm, c2);
c2->is_hexdumping = 1;
}
然后是判断是否接收到MG_EV_CLOSE事件,表示连接关闭。如果是则将c2->is_closing置 1,关闭与后端服务器连接,并将c2->fn_data置为NULL,因为与客户端的连接关闭了,所以之前给c2->fn_data赋的值c也就无效了。
} else if (ev == MG_EV_CLOSE) {
if (c2 != NULL) c2->is_closing = 1;
if (c2 != NULL) c2->fn_data = NULL;
}
接下来看下forward_request函数是如何转发客户端请求的。
定义变量,max的值为最大 HTTP 消息头(Message Headers)的最大数量。
size_t i, max = sizeof(hm->headers) / sizeof(hm->headers[0]);
使用mg_url_host函数从 s_backend_url 中提取主机名,用于修改 Host 字段的值。
struct mg_str host = mg_url_host(s_backend_url);
将请求行的数据通过mg_printf放入输出缓冲区。其中hm->message.ptr是包含请求行(Request-Line),消息头(Message Headers),消息体(Message Body)的内容,hm->proto.ptr则是只包含请求行中协议版本(HTTP-Version)的内容,通过hm->proto.ptr + hm->proto.len得到协议版本内容的结尾位置,再减去hm->message.ptr的地址得到请求行的数据长度。
mg_printf(c, "%.*s\r\n",
(int) (hm->proto.ptr + hm->proto.len - hm->message.ptr),
hm->message.ptr);
接下来要重写 Host 字段的值,逐个匹配字符串是否等于Host,如果等于Host,则将对应的值改成从 s_backend_url 中提取的主机名。这个程序会将该字段改为Host: info.cern.ch。然后将这些消息头信息通过mg_printf放入输出缓冲区。
for (i = 0; i < max && hm->headers[i].name.len > 0; i++) {
struct mg_str *k = &hm->headers[i].name, *v = &hm->headers[i].value;
if (mg_strcmp(*k, mg_str("Host")) == 0) v = &host;
mg_printf(c, "%.*s: %.*s\r\n", (int) k->len, k->ptr, (int) v->len, v->ptr);
}
mg_send(c, "\r\n", 2);
发送消息体(Message Body)数据。
mg_send(c, hm->body.ptr, hm->body.len);
打印日志,将方法和 URI 打印出来。
MG_DEBUG(("FORWARDING: %.*s %.*s", (int) hm->method.len, hm->method.ptr,
(int) hm->uri.len, hm->uri.ptr));
forward_request函数就结束了。
接下来看下后端的事件处理函数fn2。
判断是否收到MG_EV_READ事件,当有从套接字socket接收到数据时,就会发送MG_EV_READ事件。
然后使用mg_send函数将所有来自后端服务器的数据,转发到客户端。并调用mg_iobuf_del函数将接收缓冲区的数据清空。
struct mg_connection *c2 = fn_data;
if (ev == MG_EV_READ) {
// All incoming data from the backend, forward to the client
if (c2 != NULL) mg_send(c2, c->recv.buf, c->recv.len);
mg_iobuf_del(&c->recv, 0, c->recv.len);
}
最后判断是否收到MG_EV_CLOSE事件,当客户端与代理的连接关闭后,会发送MG_EV_CLOSE事件通知关闭与后端服务器的连接。关闭时清空c2->fn_data。
} else if (ev == MG_EV_CLOSE) {
if (c2 != NULL) c2->fn_data = NULL;
}
http-reverse-proxy 的示例程序代码就都解析完了,下面实际运行一下 http-reverse-proxy 程序。
打开示例程序,编译并运行:
pi@raspberrypi:~ $ cd Desktop/study/mongoose/examples/http-reverse-proxy/
pi@raspberrypi:~/Desktop/study/mongoose/examples/http-reverse-proxy $ make clean all
rm -rf example *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb
cc ../../mongoose.c -I../.. -W -Wall -DMG_IO_SIZE=128 -o example main.c
./example
6c5a6 3 mongoose.c:3496:mg_listen 1 0x4 http://localhost:8000
代理已经启动了,接下来我们使用curl作为客户端来访问这个代理:
pi@raspberrypi:~ $ curl http://localhost:8000
<html><head></head><body><header>
<title>http://info.cern.ch</title>
</header>
<h1>http://info.cern.ch - home of the first website</h1>
<p>From here you can:</p>
<ul>
<li><a href="http://info.cern.ch/hypertext/WWW/TheProject.html">Browse the first website</a></li>
<li><a href="http://line-mode.cern.ch/www/hypertext/WWW/TheProject.html">Browse the first website using the line-mode browser simulator</a></li>
<li><a href="http://home.web.cern.ch/topics/birth-web">Learn about the birth of the web</a></li>
<li><a href="http://home.web.cern.ch/about">Learn about CERN, the physics laboratory where the web was born</a></li>
</ul>
</body></html>
可以看到访问成功,获取到了网页原码。
再回头看 http-reverse-proxy 程序的日志:
pi@raspberrypi:~/Desktop/study/mongoose/examples/http-reverse-proxy $ make clean all
rm -rf example *.o *.dSYM *.gcov *.gcno *.gcda *.obj *.exe *.ilk *.pdb
cc ../../mongoose.c -I../.. -W -Wall -DMG_IO_SIZE=128 -o example main.c
./example
6c5a6 3 mongoose.c:3496:mg_listen 1 0x4 http://localhost:8000
81cc5 3 mongoose.c:4388:accept_conn 2 0x5 accepted 7f000001.39110 -> 7f000001.8000
81cc5 3 mongoose.c:4244:read_conn 2 0x5 snd 0/0 rcv 0/128 n=78 err=0
81cc6 3 mongoose.c:3473:mg_connect 3 0xffffffff http://info.cern.ch
81cc6 3 mongoose.c:3473:mg_connect 4 0xffffffff udp://8.8.8.8:53
81cc6 3 mongoose.c:4116:mg_send 4 0x6 0:0 30 err 0
81cc6 3 main.c:36:forward_request FORWARDING: GET /
81ce8 3 mongoose.c:4244:read_conn 4 0x6 snd 0/0 rcv 0/128 n=70 err=9
81ce8 3 mongoose.c:278:dns_cb 3 webafs706.cern.ch is 188.184.21.108
81ce8 3 mongoose.c:4330:mg_connect_reso 3 0x7 -> bcb8156c:80 pend
81dcf 3 mongoose.c:4255:write_conn 3 0x7 snd 76/128 rcv 0/0 n=76 err=115
81dcf 2 mongoose.c:4075:iolog
-- 3 192.168.1.40:42096 -> 188.184.21.108:80 76
0000 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a GET / HTTP/1.1..
0010 48 6f 73 74 3a 20 69 6e 66 6f 2e 63 65 72 6e 2e Host: info.cern.
0020 63 68 0d 0a 55 73 65 72 2d 41 67 65 6e 74 3a 20 ch..User-Agent:
0030 63 75 72 6c 2f 37 2e 36 34 2e 30 0d 0a 41 63 63 curl/7.64.0..Acc
0040 65 70 74 3a 20 2a 2f 2a 0d 0a 0d 0a ept: */*....
81eac 3 mongoose.c:4244:read_conn 3 0x7 snd 0/128 rcv 0/128 n=128 err=115
81eac 2 mongoose.c:4075:iolog
-- 3 192.168.1.40:42096 <- 188.184.21.108:80 128
0000 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d HTTP/1.1 200 OK.
0010 0a 44 61 74 65 3a 20 53 61 74 2c 20 31 39 20 4e .Date: Sat, 19 N
0020 6f 76 20 32 30 32 32 20 31 30 3a 32 37 3a 34 34 ov 2022 10:27:44
0030 20 47 4d 54 0d 0a 53 65 72 76 65 72 3a 20 41 70 GMT..Server: Ap
0040 61 63 68 65 0d 0a 4c 61 73 74 2d 4d 6f 64 69 66 ache..Last-Modif
0050 69 65 64 3a 20 57 65 64 2c 20 30 35 20 46 65 62 ied: Wed, 05 Feb
0060 20 32 30 31 34 20 31 36 3a 30 30 3a 33 31 20 47 2014 16:00:31 G
0070 4d 54 0d 0a 45 54 61 67 3a 20 22 32 38 36 2d 34 MT..ETag: "286-4
81ead 3 mongoose.c:4244:read_conn 3 0x7 snd 0/128 rcv 0/128 n=128 err=115
81ead 2 mongoose.c:4075:iolog
-- 3 192.168.1.40:42096 <- 188.184.21.108:80 128
0000 66 31 61 61 64 62 33 31 30 35 63 30 22 0d 0a 41 f1aadb3105c0"..A
0010 63 63 65 70 74 2d 52 61 6e 67 65 73 3a 20 62 79 ccept-Ranges: by
0020 74 65 73 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65 6e tes..Content-Len
0030 67 74 68 3a 20 36 34 36 0d 0a 43 6f 6e 6e 65 63 gth: 646..Connec
0040 74 69 6f 6e 3a 20 63 6c 6f 73 65 0d 0a 43 6f 6e tion: close..Con
0050 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 78 74 2f tent-Type: text/
0060 68 74 6d 6c 0d 0a 0d 0a 3c 68 74 6d 6c 3e 3c 68 html....<html><h
0070 65 61 64 3e 3c 2f 68 65 61 64 3e 3c 62 6f 64 79 ead></head><body
81ead 3 mongoose.c:4255:write_conn 2 0x5 snd 256/256 rcv 0/128 n=256 err=115
81ead 3 mongoose.c:4244:read_conn 3 0x7 snd 0/128 rcv 0/128 n=128 err=115
81ead 2 mongoose.c:4075:iolog
-- 3 192.168.1.40:42096 <- 188.184.21.108:80 128
0000 3e 3c 68 65 61 64 65 72 3e 0a 3c 74 69 74 6c 65 ><header>.<title
0010 3e 68 74 74 70 3a 2f 2f 69 6e 66 6f 2e 63 65 72 >http://info.cer
0020 6e 2e 63 68 3c 2f 74 69 74 6c 65 3e 0a 3c 2f 68 n.ch</title>.</h
0030 65 61 64 65 72 3e 0a 0a 3c 68 31 3e 68 74 74 70 eader>..<h1>http
0040 3a 2f 2f 69 6e 66 6f 2e 63 65 72 6e 2e 63 68 20 ://info.cern.ch
0050 2d 20 68 6f 6d 65 20 6f 66 20 74 68 65 20 66 69 - home of the fi
0060 72 73 74 20 77 65 62 73 69 74 65 3c 2f 68 31 3e rst website</h1>
0070 0a 3c 70 3e 46 72 6f 6d 20 68 65 72 65 20 79 6f .<p>From here yo
81ead 3 mongoose.c:4244:read_conn 3 0x7 snd 0/128 rcv 0/128 n=128 err=115
81ead 2 mongoose.c:4075:iolog
-- 3 192.168.1.40:42096 <- 188.184.21.108:80 128
0000 75 20 63 61 6e 3a 3c 2f 70 3e 0a 3c 75 6c 3e 0a u can:</p>.<ul>.
0010 3c 6c 69 3e 3c 61 20 68 72 65 66 3d 22 68 74 74 <li><a href="htt
0020 70 3a 2f 2f 69 6e 66 6f 2e 63 65 72 6e 2e 63 68 p://info.cern.ch
0030 2f 68 79 70 65 72 74 65 78 74 2f 57 57 57 2f 54 /hypertext/WWW/T
0040 68 65 50 72 6f 6a 65 63 74 2e 68 74 6d 6c 22 3e heProject.html">
0050 42 72 6f 77 73 65 20 74 68 65 20 66 69 72 73 74 Browse the first
0060 20 77 65 62 73 69 74 65 3c 2f 61 3e 3c 2f 6c 69 website</a></li
0070 3e 0a 3c 6c 69 3e 3c 61 20 68 72 65 66 3d 22 68 >.<li><a href="h
81ead 3 mongoose.c:4255:write_conn 2 0x5 snd 256/256 rcv 0/128 n=256 err=115
81ead 3 mongoose.c:4244:read_conn 3 0x7 snd 0/128 rcv 0/128 n=128 err=115
81ead 2 mongoose.c:4075:iolog
-- 3 192.168.1.40:42096 <- 188.184.21.108:80 128
0000 74 74 70 3a 2f 2f 6c 69 6e 65 2d 6d 6f 64 65 2e ttp://line-mode.
0010 63 65 72 6e 2e 63 68 2f 77 77 77 2f 68 79 70 65 cern.ch/www/hype
0020 72 74 65 78 74 2f 57 57 57 2f 54 68 65 50 72 6f rtext/WWW/ThePro
0030 6a 65 63 74 2e 68 74 6d 6c 22 3e 42 72 6f 77 73 ject.html">Brows
0040 65 20 74 68 65 20 66 69 72 73 74 20 77 65 62 73 e the first webs
0050 69 74 65 20 75 73 69 6e 67 20 74 68 65 20 6c 69 ite using the li
0060 6e 65 2d 6d 6f 64 65 20 62 72 6f 77 73 65 72 20 ne-mode browser
0070 73 69 6d 75 6c 61 74 6f 72 3c 2f 61 3e 3c 2f 6c simulator</a></l
81ead 3 mongoose.c:4244:read_conn 3 0x7 snd 0/128 rcv 0/128 n=128 err=115
81ead 2 mongoose.c:4075:iolog
-- 3 192.168.1.40:42096 <- 188.184.21.108:80 128
0000 69 3e 0a 3c 6c 69 3e 3c 61 20 68 72 65 66 3d 22 i>.<li><a href="
0010 68 74 74 70 3a 2f 2f 68 6f 6d 65 2e 77 65 62 2e http://home.web.
0020 63 65 72 6e 2e 63 68 2f 74 6f 70 69 63 73 2f 62 cern.ch/topics/b
0030 69 72 74 68 2d 77 65 62 22 3e 4c 65 61 72 6e 20 irth-web">Learn
0040 61 62 6f 75 74 20 74 68 65 20 62 69 72 74 68 20 about the birth
0050 6f 66 20 74 68 65 20 77 65 62 3c 2f 61 3e 3c 2f of the web</a></
0060 6c 69 3e 0a 3c 6c 69 3e 3c 61 20 68 72 65 66 3d li>.<li><a href=
0070 22 68 74 74 70 3a 2f 2f 68 6f 6d 65 2e 77 65 62 "http://home.web
81eae 3 mongoose.c:4255:write_conn 2 0x5 snd 256/256 rcv 0/128 n=256 err=115
81eae 3 mongoose.c:4244:read_conn 3 0x7 snd 0/128 rcv 0/128 n=110 err=115
81eae 2 mongoose.c:4075:iolog
-- 3 192.168.1.40:42096 <- 188.184.21.108:80 110
0000 2e 63 65 72 6e 2e 63 68 2f 61 62 6f 75 74 22 3e .cern.ch/about">
0010 4c 65 61 72 6e 20 61 62 6f 75 74 20 43 45 52 4e Learn about CERN
0020 2c 20 74 68 65 20 70 68 79 73 69 63 73 20 6c 61 , the physics la
0030 62 6f 72 61 74 6f 72 79 20 77 68 65 72 65 20 74 boratory where t
0040 68 65 20 77 65 62 20 77 61 73 20 62 6f 72 6e 3c he web was born<
0050 2f 61 3e 3c 2f 6c 69 3e 0a 3c 2f 75 6c 3e 0a 3c /a></li>.</ul>.<
0060 2f 62 6f 64 79 3e 3c 2f 68 74 6d 6c 3e 0a /body></html>.
81eae 3 mongoose.c:4244:read_conn 3 0x7 snd 0/128 rcv 0/128 n=-1 err=115
81eae 3 mongoose.c:3450:mg_close_conn 3 0x7 closed
81eae 3 mongoose.c:4255:write_conn 2 0x5 snd 110/128 rcv 0/128 n=110 err=115
81eae 3 mongoose.c:4244:read_conn 2 0x5 snd 0/128 rcv 0/128 n=-1 err=115
81eae 3 mongoose.c:3450:mg_close_conn 2 0x5 closed
可以看到许多交互过程的信息都被打印了出来。
本文链接:https://blog.csdn.net/u012028275/article/details/128160095