mongoose Web服务器
概述:
简介:
Mongoose是c语言写成的网络库。它为TCP、UDP、HTTP、WebSocket、CoAP、MQTT实现了事件驱动型的非阻塞api。其具有以下特性:
跨平台:可在linux/unix macos QNX eCos Windows Android Iphone FreeRtos上运行
原生支持PicoTCP的嵌入式tcp/ip协议栈,支持LWIP嵌入式tcp/ip协议栈
单线程,异步,非阻塞核心与简单的基于事件的API
可配置为:纯TCP,纯UDP SSL/TLS但双向的客户端和服务器;HTTP,WebSocket,MQTT,CoAP,DNS的客户端和服务器,同时可作为异步dns解析器
在运行和占用很小的内存,源代码符合ISO C 和ISO c++
同时仅仅复制mongoose.c mongoose.h到你的工程即可完成整合
1.Mongoose设计思路:
拥有3个基本类型的数据结构
struct mg_mgr;///事件管理器,保存所有的活动链接
struct mg_connection;///描述一个链接
struct mbuf;///接收和发送的数据
一个链接可以是listening(监听),outbound(出站)或者inbound(入站)。outbound(出站)链接可通过调用mg_connect()产生。listening(监听)链接可通过调用mg_bind()产生。(入站)inbound链接是由listening(监听)链接所收到的链接。每个链接都使用struct mg_connection进行描述,此结构中有,socket,事件处理函数,发送/接收缓冲区,以及其他标志。
使用mongoose的应用程序应该遵循事件驱动的标准模式:
struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL);////创建并初始化事件管理器
struct mg_connection *c = mg_bind(&mgr, "80", ev_handler_function);
mg_set_protocol_http_websocket(c);///创建链接,上面2行代码是服务器应用程序创建的监听链接
for (;;) {
mg_mgr_poll(&mgr, 1000);
}///通过调用mg_mgr_pool()创建事件循环。
mg_mgr_poll()遍历所有的套接字,接受新链接,发送,接收数据,关闭链接。并为各自的事件调用事件处理函数。有关完整实例,可参考TCP echo server的使用实例。
2.内存缓冲区:
每个链接都有接收和发送的数据缓冲区:struct mg_connection::recv_mbuf,struct mg_connection::send_mbuf.当接收到数据的时候Mongoose将数据追加到recv_mbuf,并且触发MG_EV_RECV事件。用户可调用mg_send(),mg_printf()这些输出函数将数据追加到send_mbuf中并发送回去。当mongoose成功地向socket写入数据的时候,mongoose会将此数据从send_mbuf中丢弃,并发送MG_EV_SEND事件。当链接关闭的时候,发送MG_EV_CLOSE事件。
3.事件处理函数:
每个链接都有其与之相关的事件处理函数。此事件处理函数由用户自己实现。事件处理函数是mongoose程序的关键,因为其定义了应用程序的具体行为。事件处理函数如下:
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
switch (ev) {
/* Event handler code that defines behavior of the connection */
...
}
}
////////////////////////////////////解读////////////////////////////////////////////
struct mg_connection *nc;///连接所接收到的事件
int ev;///事件编号,可在mongoose.h中找到。当(入站)链接收到数据的时候,ev=MG_EV_RECV
void *ev_data;///此指针指向事件特定数据,对于不同事件有不同含义。每个事件都描述了ev_data的具体含义。特定协议的事件ev_data通常指向包含协议特定信息的结构。
/*
MG_EV_RECV事件:ev_data是一个int *,保存从链接中接收,并保存到接收IO缓冲区中字节数
struct mg_connection 有应用程序特定数据占位符void *user_data。
mongoose不适用指针,事件处理函数user_data这个指针,事件处理函数可在此存储任何信息。
*/
4.事件:
mongoose接收链接,读写数据并为每个链接调用指定的事件处理函数。典型的事件序列:
出站链接:MG_EV_CONNECT->(MG_EV_RECV,MG_EV_SEND,MG_EV_POLL...)->MG_EV_CLOSE
入站链接:MG_EV_ACCEPT->(MG_EV_RECV,MG_EV_SEND,MG_EV_POLL...)->MG_EV_CLOSE
下面是mongoose触发的核心事件列表:(除了核心事件外,每个协议都会触发协议的特定事件)
MG_EV_ACCEPT:监听链接接收到一个新的链接,void *ev_data 是远程链接的union socket_addres
MG_EV_ACCEPT:监听链接接收到一个新的链接,void *ev_data 是远程链接的union socket_addres
MG_EV_CONNECT:mg_connect()创建一个出站链接(无论mg_connect()调用是否成功),void *ev_data 是 int *success.success=0,链接建立成功,否则链接建立失败,并包含一个错误码。可查看mg_connect_opt()查看代码实例。
MG_EV_RECV:接收到新数据,并将数据追加到recv_mbuf.void * ev_data是 int *num_received_bytes.通常情况下事件处理程序应该在nc->recv_mbuf()检查接受到的数据,通过调用mbuf_remove()丢弃处理后的数据,必要时设置链接标志nc->flags(查看struct mg_connection).使用mg_send()向远程链接点发送数据。
mongoose使用realloc()来扩展接收缓冲区,从接收缓冲区的开头丢弃处理过的数据是用户的责任。注意上面的mbuf_remove().
MG_EV_SEND:mongoos向远程连接点写入数据,并将mg_connectiong::send_mbuf中数据丢弃。void *ev_data是int *num_sent_bytes即发送的字节数。mongoose输出函数仅仅是将数据追加到sned_mbuf,不做任何socket写操作。实际的IO写操作是由mg_mgr_pool()完成,MG_EV_SEND仅仅是一个关于IO操作已经完成的通知。
MG_EV_POLL:将每次调用mg_mgr_poll()发送到每个链接。此事件可做任何事情。例如检测某个超时链接是否关闭,或者发送心跳。
MG_EV_TIMER: 向某个调用mg_set_timer()的链接发送
5.链接flags:
每个链接都有flasg位域。有些flags是由mongoose设置的,例如如果用户使用udp://1.2.3.4:5678创建一个出站的UDP链接。mongoose会为此链接设置MG_F_UDP标记。其他标志只能由用户事件处理程序设置,告诉mongoose做何种操作。下面是由事件处理程序设置的链接flags列表:
MG_F_FINISHED_SENDING_DATA:告诉mongoose所有数据已经追加到send_mbuf,只要mongoose将数据写入socket,此链接就会关闭。
MG_F_BUFFER_BUT_DONT_SEND:告诉mongoose追加数据到send_mbuf,但数据要马上发送,因为此数据稍后会被修改。然后通过清除MG_F_BUFFER_BUT_DONT_SEND标志将数据发送出去。
MG_F_CLOSE_IMMEDIATELY:告诉mongoose立即关闭链接,通常在产生错误后发送此事件。
MG_F_CLOSE_IMMEDIATELY:告诉mongoose立即关闭链接,通常在产生错误后发送此事件。
MG_USER_1,MG_USER_2,MG_USER_3,MG_USER_4:开发者可用它来存储特定应用程序的状态
下面是由mongoose设置的flags:
MG_F_SSL_HANDSHAKE_DONE:仅使用ssl,在ssl握手完成时设置
MG_F_CONNECTING:在mg_connect()调用后链接处于链接状态但未完成链接时设置。
MG_F_LISTENING:设置所有监听链接
MG_F_LISTENING:设置所有监听链接
MG_F_UDP:链接是udp时设置。
MG_F_WEBSOCKET:链接是websocket时设置。
MG_F_WEBSOCKET_NO_DEFRAG:如果用户想关闭websocket的自动帧碎片整理功能,则由用户设置此标记。
MG_F_WEBSOCKET_NO_DEFRAG:如果用户想关闭websocket的自动帧碎片整理功能,则由用户设置此标记。
6.编译选项:
mongoose源代在一个.c文件中,此文件包含所有受支持协议(模块)的功能。可以在编译时设置预处理器编制来禁用模块,以减少可执行文件的大小。一些预处理器标志可以调整mongoose的内部参数。在编译期间可使用-D<PREPROCESSOR_FLAG>设置编译器选项。例如要禁用MQTT和CoAP,编译应用程序my_app.c在linux下用:
启用标志 | |
Flag | 介绍 |
MG_ENABLE_SSL | 启用SSL/TLS支持(OpenSSL API) |
MG_ENABLE_IPV6 | 启用IPV6支持 |
MG_ENABLE_MQTT | 启用MQTT客户端(默认情况下设置为0表示禁用) |
MG_ENABLE_MQTT_BROKER | 启用MQTT broker |
MG_ENABLE_DNS_SERVER | 启用DNS服务器 |
MG_ENABLE_COAP | 启用CoAP协议 |
MG_ENABLE_HTTP | 启用HTTP协议支持(默认情况下,设置0表示禁用) |
MG_ENABLE_HTTP_CGI | 启用CGI |
MG_ENABLE_HTTP_SSI | 启用Server Side |
MG_ENABLE_HTTP_SSI_EXEC | 启用SSI exec操作 |
MG_ENABLE_HTTP_WEBDAV | 启用HTTP的WebDAV扩展 |
MG_ENABLE_HTTP_WEBSOCKET | 启用HTTP的WebSocket扩展,默认情况下 设置0禁止 |
MG_ENABLE_BROADCAST | 启用mg_broadcast()API |
MG_ENABLE_GETADDRINFO | 启用在mg_resolve2()中的getaddrinfo() |
MG_ENABLE_THREADS | 启用在mg_start_thread()API |
禁用标志 | |
MG_DISABLE_HTTP_DIGEST_AUTH | 禁用HTTP摘要(MD5)授权支持 |
CS_DISABLE_SHA1 | 禁用WebSocket中的SHA1支持 |
CS_DISABLE_MD5 | 禁用HTTP鉴权中的MD5支持 |
MG_DISABLE_HTTP_KEEP_ALIVE | 用于嵌入式系统节省资源 |
平台相关选项(mongoose会尽可能的检测目标平台,但是在某些情况下,必须明确声明目标平台的部分特性) | |
MG_CC3200 | 为TICC3200板子启用工作区 |
MG_ESP8266 | 为ESP8266板子添加RTOS_SDK来指定RTOS_SDK风格 |
可调整 | |
MG_MALLOC,MG_CALLOC,MG_REALLOC,MG_FREE | 允许使用开发者自定义的内存分配处理函数 -DMG_MALLCO=my_malloc |
MG_USE_READ_WRITE | 设置后,将recv的调用替换为read,send。从而允许向事件管理器添加任何类型的文件描述符(文件,串行设备) |
MG_SSL_CRYPTO_MODERN,MG_SSL_CRYPTO_OLD | 选择modern或者old密码替换默认的Intermediate密码。在下面网址查看各种密码形式的详细信息 https://wiki.mozilla.org/Security/Server_Side_TLS#Recommended_configurations |
MG_USER_FILE_FUNCTIONS | 允许开发者通过定义自己的mg_stat,mg_fopen,mg_open,mg_fread,mg_fwrite去自定义文件操作函数 |
实例:TCP echo server
复制mongoose.c mongoose.h到你的工程;在my_app.c中使用mongoose的API写代码。编译代码
$$cc my_app.c mongoose.c
/********************my_app.c*******************/
#include "mongoose.h" // Include Mongoose API definitions
// Define an event handler function
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct mbuf *io = &nc->recv_mbuf;
switch (ev) {
case MG_EV_RECV:
// This event handler implements simple TCP echo server
mg_send(nc, io->buf, io->len); // Echo received data back
mbuf_remove(io, io->len); // Discard data from recv buffer
break;
default:
break;
}
}
int main(void) {
struct mg_mgr mgr;
mg_mgr_init(&mgr, NULL); // Initialize event manager object
// Note that many connections can be added to a single event manager
// Connections can be created at any point, e.g. in event handler function
mg_bind(&mgr, "1234", ev_handler); // Create listening connection and add it to the event manager
for (;;) { // Start infinite event loop
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
HTTP实例:
实例一:HTTP server
1,使用mg_bind()或者mg_bind_opt()创建立监听链接
2.调用mg_set_protocol_http_websocket()去监听所有链接。此函数附加了一个内置的http事件处理事件,用于解析并触发特定的HTTP事件。例如当HTTP请求被完全缓冲时,内置的HTPP处理程序将解析请求并使用MG_EV_HTTP_REQUEST事件调用用户定义的事件处理函数,并将HTTP请求解析为事件数据。
3.创建事件处理函数。事件处理程序接收所有的事件(低级别TCP事件(MG_EV_RECV)和高级别HTTP事件(MG_EV_HTTP_REQUEST))。通常事件处理函数应该只处理高级别事件MG_EV_HTTP_REQUEST
下面是HTTP server的实例代码,其中省略了错误检查
// Copyright (c) 2015 Cesanta Software Limited
// All rights reserved
#include "mongoose.h"
static const char *s_http_port = "8000";
static struct mg_serve_http_opts s_http_server_opts;
static void ev_handler(struct mg_connection *nc, int ev, void *p) {
if (ev == MG_EV_HTTP_REQUEST) {
mg_serve_http(nc, (struct http_message *) p, s_http_server_opts);
}
}
int main(void) {
struct mg_mgr mgr;
struct mg_connection *nc;
mg_mgr_init(&mgr, NULL);
printf("Starting web server on port %s\n", s_http_port);
nc = mg_bind(&mgr, s_http_port, ev_handler);
if (nc == NULL) {
printf("Failed to create listener\n");
return 1;
}
// Set up HTTP server parameters
mg_set_protocol_http_websocket(nc);
s_http_server_opts.document_root = "."; // Serve current directory
s_http_server_opts.enable_directory_listing = "yes";
for (;;) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
实例二:HTTP Client
1:使用mg_connect_http()创建出站链接
2:创建事件处理函数用于处理MG_EV_HTTP_REPLY事件
/*
* Copyright (c) 2014 Cesanta Software Limited
* All rights reserved
*
* This program fetches HTTP URLs.
*/
#include "mongoose.h"
static int s_exit_flag = 0;
static int s_show_headers = 0;
static const char *s_show_headers_opt = "--show-headers";
static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
struct http_message *hm = (struct http_message *) ev_data;
switch (ev) {
case MG_EV_CONNECT:
if (*(int *) ev_data != 0) {
fprintf(stderr, "connect() failed: %s\n", strerror(*(int *) ev_data));
s_exit_flag = 1;
}
break;
case MG_EV_HTTP_REPLY:
nc->flags |= MG_F_CLOSE_IMMEDIATELY;
if (s_show_headers) {
fwrite(hm->message.p, 1, hm->message.len, stdout);
} else {
fwrite(hm->body.p, 1, hm->body.len, stdout);
}
putchar('\n');
s_exit_flag = 1;
break;
case MG_EV_CLOSE:
if (s_exit_flag == 0) {
printf("Server closed connection\n");
s_exit_flag = 1;
}
break;
default:
break;
}
}
int main(int argc, char *argv[]) {
struct mg_mgr mgr;
int i;
mg_mgr_init(&mgr, NULL);
/* Process command line arguments */
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], s_show_headers_opt) == 0) {
s_show_headers = 1;
} else if (strcmp(argv[i], "--hexdump") == 0 && i + 1 < argc) {
mgr.hexdump_file = argv[++i];
} else {
break;
}
}
if (i + 1 != argc) {
fprintf(stderr, "Usage: %s [%s] [--hexdump <file>] <URL>\n", argv[0],
s_show_headers_opt);
exit(EXIT_FAILURE);
}
mg_connect_http(&mgr, ev_handler, argv[i], NULL, NULL);
while (s_exit_flag == 0) {
mg_mgr_poll(&mgr, 1000);
}
mg_mgr_free(&mgr);
return 0;
}
HTTP事件:
正如在概述中所讨论的,mg_set_protocol_http_websocket()函数解析传入的数据,将其视为HTTP或WebSocket,并触发高级HTTP或WebSocket事件。下面是一个特定于HTTP的事件列表:
MG_EV_HTTP_REQUEST:收到一个http请求,解析后的请求整理为struct http_message结构,并通过void * ev_data指针传递。
MG_EV_HTTP_REPLY:收到一个http回应,解析后的回应整理为struct http_message结构,并通过void * ev_data指针传递。
MG_EV_HTTP_MULTIPART_REQUEST:收到一个分多个包传递的http请求,在http的body开始解析前发送此事件。在此事件后用户会收到一些列的MG_HTTP_PART_BEGIN/DATA/END请求。这也是最后一次访问标头和http中其他请求字段。
MG_EV_HTTP_MULTIPART_REQUEST:收到一个分多个包传递的http请求,在http的body开始解析前发送此事件。在此事件后用户会收到一些列的MG_HTTP_PART_BEGIN/DATA/END请求。这也是最后一次访问标头和http中其他请求字段。
MG_EV_HTTP_CHUNK:收到一个http编码块,解析后的http应答整理为struct http_message结构,并通过void * ev_data指针传递。http_message::body将包含重新组装的不完整body块。http_message::body伴随着每个新块的到达而增长,此过程可能消耗大量内存。事件处理函数可在块到来的时候处理body,并通过设置在mg_connection::flags设置MG_F_DELETE_CHUNK标记来告知mongoose去删除已经解析处理过的body块。(如果事件处理函数没有告知mongoose去删除已经处理过的body)当接收到最后一个0数据块的时候,mongoose发送MG_F_HTTP_REPLY事件并带有重组的完整的body.
MG_F_HTTP_PART_BEGIN:开始接收 分包传递的消息的一部分消息,并在mg_http_multipart中国传递额外参数。
MG_F_HTTP_PART_DATA:开始接收 分包传递的消息的新数据部分没有额外的表头可用只有数据和数据大小
MG_F_HTTP_PART_END:收到最后一个边界,类似可能用来找到包的结尾。需要注意的是mongoose在编译的时候应该设置MG_ENABLE_HTTP_MULTIPART去使能多部分事件。
Serving files:
mg_serve_http()函数使得从文件系统提供文件变得时分简单。一般来此函数是http server实现的用于提供cgi,ssl等静态文件的。它的行为合并到了struct mg_serve_http_opts结构的选项列表中.mg_serve_http()功能的完整列表可参考struct mg_serve_http_opts.
为了创建一个服务于当前目录中的静态文件的web服务器。按如下方式实现事件处理器函数:
static void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_REQUEST) {
struct mg_serve_http_opts opts;
memset(&opts, 0, sizeof(opts); // Reset all options to defaults
opts.document_root = "."; // Serve files from the current directory
mg_serve_http(c, (struct http_message *) ev_data, s_http_server_opts);
}
}
有时候不需要完整的静态web服务器。例如在RESTful的服务器上工作,如果某些端点必须返回静态文件的内容,可使用简单的mg_http_serve_file()函数
static void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
switch (ev) {
case MG_EV_HTTP_REQUEST: {
struct http_message *hm = (struct http_message *) ev_data;
mg_http_serve_file(c, hm, "file.txt",
mg_mk_str("text/plain"), mg_mk_str(""));
break;
}
...
}
}
CGI:
cgi是一种简单的动态内容生成机制。为了去使用cgi,调用mg_serve_http()函数并对cgi文件使用.cgi扩展名即可。更准确地说,匹配struct mg_serve_opts中cgi_file_pattern模式的文件都视为cgi,如果cgi_file_pattern是NULL则**.cgi$或者**.php$都可使用。
如果mongoose将一个文件看作是cgi文件,它会去执行此文件,并其输出发送回client.因此cgi文件必须是可执行的。如果同时使用PHP和Perl CGIs,那么在各自cgi脚本的第一行必须是#!/path/to/php-cgi.exe和#!/path/to/perl.exe.可以为所有的cgi脚本硬编码到cgi解释器的路径而不考虑shebang line。为此在mg_serve_http_opts中设置cgi_interpreter.注意php脚本必须使用php-cgi.exe作为cgi解析器而不是php.exe
opts.cgi_interpreter = "C:\\ruby\\ruby.exe";
在cgi处理程序中,我们没有显式地使用系统调用waitpid()来获取僵尸进程。相反我们将SIGCHLD处理程序设置为SIG_IGN.其导致僵尸进程会自动获取。入股所有的SIGCHLD被忽略,并不是所有的操作系统都会获取僵尸进程。
SSI:
文件上传:
为了实现文件上传,使用如下html
<form method="POST" action="/upload" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
上传文件会通过POST请求将文件发送到/upload目录。HTTP的body将会包含文件内容的多部分编码缓冲区。为保存文件使用如下:
struct mg_str cb(struct mg_connection *c, struct mg_str file_name) {
// Return the same filename. Do not actually do this except in test!
// fname is user-controlled and needs to be sanitized.
return file_name;
}
void ev_handler(struct mg_connection *c, int ev, void *ev_data) {
switch (ev) {
...
case MG_EV_HTTP_PART_BEGIN:
case MG_EV_HTTP_PART_DATA:
case MG_EV_HTTP_PART_END:
mg_file_upload_handler(c, ev, ev_data, cb);
break;
}
}
使能SSL(HTTPS):
在http服务器端使能ssl需要按照如下步骤:
1.获取ssl证书和私钥文件
2.声明struct mg_bind_opts,初始化化ssl_cert和ssl_key
3.使用mg_bind_opt()去创建监听socket
实例:(更多介绍:查看examples中的https server 实例)
int main(void) {
struct mg_mgr mgr;
struct mg_connection *c;
struct mg_bind_opts bind_opts;
mg_mgr_init(&mgr, NULL);
memset(&bind_opts, 0, sizeof(bind_opts));
bind_opts.ssl_cert = "server.pem";
bind_opts.ssl_key = "key.pem";
// Use bind_opts to specify SSL certificate & key file
c = mg_bind_opt(&mgr, "443", ev_handler, bind_opts);
mg_set_protocol_http_websocket(c);
...
}
Digest身份认证
mongoose有一个内置的digest(md5)认证支持。为了启用digest认证,需要再保护目录中创建一个.htpasswd文件。此文件应该采用apache的htdigest使用程序的格式。
你可以使用Apache的htdigest实例,或者mongoose在 https://www.cesanta.com/binary.htm预构建一个二进制并添加新用户到这个文件。
mongoose -A /path/to/.htdigest mydomain.com joe joes_password
通用API参考:
struct http_message {
struct mg_str message; /* Whole message: request line + headers + body */
struct mg_str body; /* Message body. 0-length for requests with no body */
/* HTTP Request line (or HTTP response line) */
struct mg_str method; /* "GET" */
struct mg_str uri; /* "/my_file.html" */
struct mg_str proto; /* "HTTP/1.1" -- for both request and response */
/* For responses, code and response status message are set */
int resp_code;
struct mg_str resp_status_msg;
/*
* Query-string part of the URI. For example, for HTTP request
* GET /foo/bar?param1=val1¶m2=val2
* | uri | query_string |
*
* Note that question mark character doesn't belong neither to the uri,
* nor to the query_string
*/
struct mg_str query_string;
/* Headers */
struct mg_str header_names[MG_MAX_HTTP_HEADERS];
struct mg_str header_values[MG_MAX_HTTP_HEADERS];
};///HTTP消息
struct websocket_message {
unsigned char *data;
size_t size;
unsigned char flags;
};///websocket消息
struct mg_http_multipart_part {
const char *file_name;
const char *var_name;
struct mg_str data;
int status; /* <0 on error */
void *user_data;
};///HTTP分包部分
struct mg_ssi_call_ctx {
struct http_message *req; /* The request being processed. */
struct mg_str file; /* Filesystem path of the file being processed. */
struct mg_str arg; /* The argument passed to the tag: <!-- call arg -->. */
};///SSI调用上下文
void mg_set_protocol_http_websocket(struct mg_connection *nc);
void mg_send_websocket_handshake(struct mg_connection *nc, const char *uri,
const char *extra_headers);
void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path,
const char *host, const char *protocol,
const char *extra_headers);
void mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,
const char *host, const char *protocol,
const char *extra_headers, const char *user,
const char *pass);
void mg_send_websocket_handshake3v(struct mg_connection *nc,
const struct mg_str path,
const struct mg_str host,
const struct mg_str protocol,
const struct mg_str extra_headers,
const struct mg_str user,
const struct mg_str pass);
struct mg_connection *mg_connect_ws(struct mg_mgr *mgr,
MG_CB(mg_event_handler_t event_handler,
void *user_data),
const char *url, const char *protocol,
const char *extra_headers);
struct mg_connection *mg_connect_ws_opt(
struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
struct mg_connect_opts opts, const char *url, const char *protocol,
const char *extra_headers);
void mg_send_websocket_frame(struct mg_connection *nc, int op_and_flags,
const void *data, size_t data_len);
void mg_send_websocket_framev(struct mg_connection *nc, int op_and_flags,
const struct mg_str *strings, int num_strings);
void mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags,
const char *fmt, ...);
int mg_url_decode(const char *src, int src_len, char *dst, int dst_len,
int is_form_url_encoded);
extern void mg_hash_md5_v(size_t num_msgs, const uint8_t *msgs[],
const size_t *msg_lens, uint8_t *digest);
extern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],
const size_t *msg_lens, uint8_t *digest);
http server端API参考:
int mg_parse_http(const char *s, int n, struct http_message *hm, int is_req);
/**解析http消息,如果is_req=1 此消息是http请求,is_req=0 此消息是http回应
*返回解析的字节数 如果http消息不完整则返回0 如果解析出错,则返回负数*/
struct mg_str *mg_get_http_header(struct http_message *hm, const char *name);
/**搜索并返回解析后的http消息hm中的表头名称,如果没有找到标头,则返回NULL
*实例:struct mg_str *host_hdr = mg_get_http_header(hm, "Host");*/
int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,
size_t buf_size);
/**解析http头hdr,查找变量var_name并将其值存储到buf中,如果没有找到变量,返回0否则返回非0。此函数用于解析cookies验证头等。如果成功则返回变量值的长度,如果buf缓冲区不够大,或者没有找到此变量则返回0
char user[20];
struct mg_str *hdr = mg_get_http_header(hm, "Authorization");
mg_http_parse_header(hdr, "username", user, sizeof(user));
*/
int mg_get_http_basic_auth(struct http_message *hm, char *user, size_t user_len,
char *pass, size_t pass_len);
/*获取并解析授权,如果没有找到授权头或者mg_parse_http_basic_auth解析结果头失败*/
int mg_parse_http_basic_auth(struct mg_str *hdr, char *user, size_t user_len,
char *pass, size_t pass_len);
/*解析授权,如授权类型不是Basic或者出现其他错误(如用于base64编码的用户密码不对)基本头返回-1*/
size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
size_t var_name_len, char *file_name,
size_t file_name_len, const char **chunk,
size_t *chunk_len);
/*解析包含多部分表单数据库的缓冲区buf,buf_len,将块名存储在var_name,var_name_len缓冲区中。如果块是上传文件,那么file_name,file_name_len将会被一个上传的文件名填充。chunk,chunk_len指向块数据。返回要跳到下一个块的字节数,如果没有更多块则返回0*/
int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,
size_t dst_len);
/*获取Http表单变量。从buf获取变量名,到指定dst指定长度dst_len的缓冲区。目的地址总是0终止,返回获取到的变量的长度,如果没有找到变量,则返回0.buf必须是有效的url编码缓冲区。如果dst长度太小或者发生错误返回负数*/
//此结构定义了mg_serve_http()的工作方式,最佳是设置需要的,其余为NULL
struct mg_serve_http_opts {
const char *document_root;///web根目录路径
const char *index_files;///索引文件列表,默认是""
/*
per_directory_auth_file =NULL表示禁用身份验证
per_directory_auth_file =".htpasswd" 要使用身份认证来保护目录,然后在任何目录创建使用 digest认证的.htpasswd文件。使用mongoose web服务器二进制文件或 者Apache的htpasswd实例创建/操作密码文件
确保auth_domain是一个有效的域
*/
const char *per_directory_auth_file;
const char *auth_domain;//授权域。即web服务器的域名
/*
global_auth_file = NULL 禁用身份认证
通常只保护document_root根目录选定的目录。如果对web服务器的所有访问都必须经过身份验证, 不管URI是什么,将此选项设置为密码文件的路径。此文件的格式与.htpasswd的格式一样,并把此 文件放在document_root根目录之外,以防他人获取到此文件
*/
const char *global_auth_file;
const char *enable_directory_listing;//设置为"no"禁用目录列表,默认启用
const char *ssi_pattern;///ssi匹配模式 源码有详细介绍
const char *ip_acl;///ip_acl=NULL表示所有IP都可链接
#if MG_ENABLE_HTTP_URL_REWRITES
const char *url_rewrites;/////源码有详细介绍
#endif
const char *dav_document_root;///DAV的根目录,dav_document_root=NULL 则DAV请求失败
const char *dav_auth_file;//DAV密码文件,dav_auth_file=NULL 则DAV请求失败 dav_auth_file="-" 禁用DAV鉴权
const char *hidden_file_pattern;///文件隐藏的Glob模式
const char *cgi_file_pattern;///cgi_file_pattern != NULL 则使能cgi,即此目录下满足 **.cgi$|**.php$"都视为cgi文件
const char *cgi_interpreter;///如果不是NULL,请忽略CGI脚本hashbang并使用这个解释器
const char *custom_mime_types;
/*Comma-separated list of Content-Type overrides for path suffixes,".txt=text/plain; charset=utf-8,.c=text/plain"e.g.*/
const char *extra_headers;///添加到每个http相应的额外的header,要启用CORS,请将此设置为"Access-Control-Allow-Origin: *"。
}
void mg_serve_http(struct mg_connection *nc, struct http_message *hm,
struct mg_serve_http_opts opts);
/*根据opts的内容,提供特定的http请求*/
void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm,
const char *path, const struct mg_str mime_type,
const struct mg_str extra_headers);
/*提供具有给定MIME类型和可选extra_headers头的特定文件*/
typedef struct mg_str (*mg_fu_fname_fn)(struct mg_connection *nc,
struct mg_str fname);///mg_file_upload_handler()的回调函数原型
void mg_file_upload_handler(struct mg_connection *nc, int ev, void *ev_data,
mg_fu_fname_fn local_name_fn
MG_UD_ARG(void *user_data));
/*文件上传处理程序。这个处理程序可以用最少的代码实现文件上传.此程序处理MG_EV_HTTPPART事件并保存文件数据到本地。local_name_fn将以客户端提供的名字调用并以期望的本地文件名称打开。如果返回NULL将终止文件上传(客户端得到403),如果返回不是NULL,返回的字符串必须是堆分配的,且调用方需要释放这些字符串。异常情况:返回完全相同的字符串,
*/
void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
MG_CB(mg_event_handler_t handler,
void *user_data));
/*为nc注册回调函数,如果注册了回调函数,其会被调用欧冠,而不是调用mg_bind()中提供的回调函数*/
struct mg_http_endpoint_opts {
void *user_data;
/*授权域 (realm) */
const char *auth_domain;
const char *auth_file;
};
void mg_register_http_endpoint_opt(struct mg_connection *nc,
const char *uri_path,
mg_event_handler_t handler,
struct mg_http_endpoint_opts opts);
int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,
FILE *fp);
/*根据打开的密码文件验证HTTP请求。如果经过身份验证,返回1,否则返回0*/
int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
struct mg_str username, struct mg_str cnonce,
struct mg_str response, struct mg_str qop,
struct mg_str nc, struct mg_str nonce,
struct mg_str auth_domain, FILE *fp);
/*对打开的密码文件验证给定的响应参数。如果经过身份验证,返回1,否则返回0。
由 mg_http_check_digest_auth().调用
*/
void mg_send_http_chunk(struct mg_connection *nc, const char *buf, size_t len);
/*
使用组块HTTP编码向客户端发送大小为len的缓冲区buf.这个函数首先将 发送缓冲区大小(16进制)+换行符+缓冲区+换行符发出。例如,mg_send_http_chunk(nc,“foo”,3)将把 "3\r\nfoo\r\n"字符串追加到nc->send_mbuf输出IO缓冲区
HTTP头“传输编码:块化”应该在使用此函数之前发送
不要忘记在响应结束时发送一个空块,告诉客户端所有内容都已发送
*/
void mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...);
/*发送一个printf格式的HTTP块。功能类似于mg_send_http_chunk()。*/
void mg_send_response_line(struct mg_connection *nc, int status_code,
const char *extra_headers);
/*发送响应状态行。如果extra_headers不是NULL,那么extra_headers也会在响应行之后发送。extra_headers不能以新行结束*/
void mg_http_send_error(struct mg_connection *nc, int code, const char *reason);
/*发送一个错误的回应。如果原因为空,消息将从错误代码中推断出来(如果支持的话)*/
void mg_http_send_redirect(struct mg_connection *nc, int status_code,
const struct mg_str location,
const struct mg_str extra_headers);
/*发送一个重定向响应。status_code应该是301或302,位置指向新位置。如果extra_headers不是空的,那么extra_headers也会在响应行之后发送。extra_headers不能以新行结束。*/
void mg_send_head(struct mg_connection *n, int status_code,
int64_t content_length, const char *extra_headers);
/*发送响应行和标题。这个函数用status_code发送响应行,并自动发送一个标题:"Content-Length"或 "Transfer-Encoding"。如果content_length为负,则发送“Transfer-Encoding: chunked”报头,否则发送“Content-Length”报头。如果转换编码被分割,那么消息体必须使用mg_send_http_chunk()或mg_printf_http_chunk()函数发送。否则,必须使用mg_send()或mg_printf()。额外的标题可以通过extra_headers设置。注意,extra_headers不能被一个新的行终止*/
void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...);
/*发送一个打印格式的HTTP块,转义HTML标记*/
void mg_http_reverse_proxy(struct mg_connection *nc,
const struct http_message *hm, struct mg_str mount,
struct mg_str upstream);
/*将给定的请求代理给给定的上游http服务器.挂载中的路径前缀将被剥离到上游服务器请求的路径,例如如果挂载为/api,上游为 http://localhost:8001/foo 那么向/api传入的请求将传入到 http://localhost:8001/foo/bar*/
client 端API参考:
struct mg_connection *mg_connect_http(
struct mg_mgr *mgr,
MG_CB(mg_event_handler_t event_handler, void *user_data), const char *url,
const char *extra_headers, const char *post_data);
/*创建出站HTTP连接的助手函数
url是要获取的url。它必须被正确地url编码,例如没有空格,等等.默认情况下,mg_connect_http()发送连接和主机头
extra_headers是一个额外的HTTP头:"User-Agent: my-app\r\n"
如果post_data为空,则创建一个GET请求。否则,将使用指定的POST数据创建POST请求。
注意,如果要提交的数据是表单提交,那么应该相应地设置Content-Type报头(参见下面的示例)。
nc1 = mg_connect_http(mgr, ev_handler_1, "http://www.google.com", NULL,
NULL);
nc2 = mg_connect_http(mgr, ev_handler_1, "https://github.com", NULL, NULL);
nc3 = mg_connect_http(
mgr, ev_handler_1, "my_server:8000/form_submit/",
"Content-Type: application/x-www-form-urlencoded\r\n",
"var_1=value_1&var_2=value_2");
*/
struct mg_connection *mg_connect_http_opt(
struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
struct mg_connect_opts opts, const char *url, const char *extra_headers,
const char *post_data);
/*创建出站HTTP连接的助手函数.与mg_connect_http基本相同,但允许提供额外的参数(例如SSL参数)*/
int mg_http_create_digest_auth_header(char *buf, size_t buf_len,
const char *method, const char *uri,
const char *auth_domain, const char *user,
const char *passwd);
/*为客户机请求创建摘要身份验证头*/