使用Mongoose开发Web Server
Mongoose Web Server 是一款易于使用的Web服务器,它可以嵌入到其它应用程序中,为其提供Web接口。
主要特写:
- 跨平台,支持Windows、OS X 和 Linux。
- 支持 CGI、SSL、SSI、Digest (MD5) 认证,WebSocket 和 WebDAV。
- 支持断点续传和 URL 重写。
- 基于 IP 的 ACL,支持 Windows 服务,支持
GET
、POST
、HEAD
、PUT
、DELETE
方法。 - Excluding files from serving by URI pattern。
- 下载速度限制,基于客户端子网和 URI 模式。
- 体积小,可执行文件只有 40k 左右。
- 可嵌入式,提供简单的 API (mongoose.h),只需一个源码文件 mongoose.c 。
- 嵌入式实例:hello.c , post.c ,upload.c ,websocket.c 。
- 提供 Python 和 C# 版本。
- 采用 GPLv2 授权协议(商业不友好的)和商业授权。
Mongoose
在实际应用中归纳了Mongoose
的几个特点:
- 只需在源代码中包含
mongoose.h
和mongoose.c
文件即可,没有其他项目依赖,使用起来很简单。 - 支持多线程(one thread per connection),所以并发很高,并且需要控制很多线程,例如控制内存的使用,这点不是很好。
- 性能很好。
- API使用起来很简单,但是需要你自己拼接HTTP响应头(HTTP headers),所以需要了解HTTP协议。
- 支持解析
multipart/form-data
、application/x-www-form-urlencoded
。
下面是一个使用Mongoose
的例子:
#include "mongoose.h"
#include <string>
using namespace std;
static const char* s_http_port = "8080";
static const char* s_ssl_cert = "ca/server.pem";
static const char* s_ssl_key = "ca/server.key";
static struct mg_serve_http_opts s_http_server_opts;
// caller mast to free
char* utf8_to_ansi(const char* sz_utf8)
{
// convert multibyte UTF-8 to wide string UTF-16
int wlength = MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)sz_utf8, -1, NULL, 0);
if (wlength > 0)
{
wchar_t* wstr = new wchar_t[wlength];
MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)sz_utf8, -1, wstr, wlength);
// convert it to ANSI
int length = WideCharToMultiByte(CP_ACP, NULL, wstr, wlength, NULL, 0, NULL, NULL);
if (length > 0)
{
char* ansi = new char[length];
WideCharToMultiByte(CP_ACP, NULL, wstr, wlength, ansi, length, NULL, NULL);
delete[] wstr;
return ansi;
}
delete[] wstr;
}
return nullptr;
}
static void ev_handler(struct mg_connection *nc, int ev, void *p)
{
if (ev == MG_EV_HTTP_REQUEST)
{
struct http_message* hm = (struct http_message*)p;
// send 404 not found
//mg_send_head(nc, 404, strlen("not found"), nullptr);
//mg_send(nc, "not found", strlen("not found"));
mg_serve_http(nc, hm, s_http_server_opts);
return;
}
}
static void test_cb(struct mg_connection *nc, int ev, void *p)
{
if (ev == MG_EV_HTTP_REQUEST)
{
char b[100];
char c[100];
struct http_message* hm = (struct http_message*)p;
// get query string
int x1 = mg_url_decode(hm->query_string.p, hm->query_string.len, c, 100, 1);
int x = mg_get_http_var(&hm->query_string, "a", b, 100);
x = mg_url_decode(b, x, c, 100, 1);
std::string d;
char* xxx = utf8_to_ansi(b);
// get header variables
struct mg_str *ha = mg_get_http_header(hm, "a");
// get form variables(x-www-form-urlencoded)
x = mg_get_http_var(&hm->body, "a", b, 100);
// get form variables(form-data)
{
char var_name[100], file_name[100];
const char *chunk;
size_t chunk_len, n1, n2;
n1 = n2 = 0;
while ((n2 = mg_parse_multipart(hm->body.p + n1,
hm->body.len - n1,
var_name, sizeof(var_name),
file_name, sizeof(file_name),
&chunk, &chunk_len)) > 0) {
printf("var: %s, file_name: %s, size: %d, chunk: [%.*s]\n",
var_name, file_name, (int)chunk_len,
(int)chunk_len, chunk);
n1 += n2;
}
}
//Sleep(10000
mg_send_head(nc, 200, 0, nullptr);
/* Close connection for non-keep-alive requests */
mg_str* hdr;
if (mg_vcmp(&hm->proto, "HTTP/1.1") != 0 ||
((hdr = mg_get_http_header(hm, "Connection")) != NULL &&
mg_vcmp(hdr, "keep-alive") != 0)) {
nc->flags |= MG_F_SEND_AND_CLOSE;
}
return;
}
}
int main()
{
struct mg_mgr mgr;
struct mg_connection* nc;
struct mg_bind_opts bind_opts;
const char* err;
mg_mgr_init(&mgr, nullptr);
memset(&bind_opts, 0, sizeof(bind_opts));
bind_opts.ssl_cert = s_ssl_cert;
bind_opts.ssl_key = s_ssl_key;
bind_opts.error_string = &err;
printf("Starting HTTP server on port %s \n", s_http_port);
nc = mg_bind_opt(&mgr, s_http_port, ev_handler, bind_opts);
if (nc == nullptr)
{
printf("Failed to create listener: %s \n", err);
goto __exit;
}
mg_register_http_endpoint(nc, "/test", test_cb);
// Set up HTTP server parameters
mg_set_protocol_http_websocket(nc);
mg_enable_multithreading(nc);
s_http_server_opts.document_root = "."; // server current directory
s_http_server_opts.enable_directory_listing = "yes"; // server current directory
for (;;)
{
mg_mgr_poll(&mgr, 1000);
}
__exit:
mg_mgr_free(&mgr);
return 0;
}
CivetWeb
如果你很介意Mongoose
的授权协议,那么CivetWeb
是一个很好的选择。CivetWeb
通过精心挑选的功能列表保持功能和简单性之间的平衡:
- 自由的、商业友好的、宽松的,MIT 许可
- Free from copy-left licenses, like GPL, because you should innovate without restrictions.
- Forked from Mongoose in 2013, before it changed the licence from MIT to commercial + GPL. A lot of enchancements have been added since that time, see RELEASE_NOTES.md.
- Works on Windows, Mac, Linux, UNIX, iPhone, Android, Buildroot, and many other platforms.
- Scripting and database support (Lua scipts, Lua Server Pages, CGI + SQLite database, Server side javascript). This provides a ready to go, powerful web development platform in a one single-click executable with no dependencies.
- Support for CGI, HTTPS (SSL/TLS), SSI, HTTP digest (MD5) authorization, Websocket, WEbDAV.
- Optional support for authentication using client side X.509 certificates.
- Resumed download, URL rewrite, file blacklist, IP-based ACL, Windows service.
- Download speed limit based on client subnet or URI pattern.
- Simple and clean embedding API.
- The source is in single file to make things easy.
- Embedding examples included.
- HTTP client capable of sending arbitrary HTTP/HTTPS requests.
- Websocket client functionality available (WS/WSS).
CivetWeb
虽然支持解析 multipart/form-data
但是需要使用回调函数的形式,使用起来很麻烦。值的一提的是,CivetWeb
支持限制工作线程数,详细信息请查看docs/UserManual.md
文件。
CivetWeb
使用起来也很简单,只需在源代码中包含civetweb.h
、civetweb.c
、handle_form.inl
和md5.inl
文件即可。
CivetWeb
的用法和Mongoose
基本相同,下面是一个使用CivetWeb
的例子:
#include "civetweb.h"
#include <Windows.h>
int field_found(const char *key,
const char *filename,
char *path,
size_t pathlen,
void *user_data)
{
return FORM_FIELD_STORAGE_GET;
}
int field_get(const char *key,
const char *value,
size_t valuelen,
void *user_data)
{
return 0;
}
int field_store(const char *path, long long file_size, void *user_data)
{
return 0;
}
int test_cb(struct mg_connection *conn, void *cbdata)
{
const struct mg_request_info *request_info = mg_get_request_info(conn);
mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n");
mg_printf(conn, "<html><body>");
mg_printf(conn, "<h2>This is the 'Test' handler!!!</h2>\n<p>");
mg_printf(conn, "you ip is: %s<br>", request_info->remote_addr);
mg_printf(conn, "request_method: %s<br>", request_info->request_method);
mg_printf(conn, "uri: %s<br>", request_info->local_uri);
mg_printf(conn, "query_string: %s<br>", request_info->query_string);
mg_printf(conn, "</p></body></html>\n");
// get query param
char param[100];
mg_get_var(request_info->query_string, strlen(request_info->query_string), "b",
param, 100);
if (mg_strcasecmp(request_info->request_method, "POST") == 0)
{
const char* content_type = mg_get_header(conn, "Content-type");
if (content_type != nullptr && mg_strcasecmp(content_type, "application/x-www-form-urlencoded") == 0)
{
// 使用下面两种方法都可以解析
if (false)
{
char* buf = new char[request_info->content_length+1];
int x = mg_read(conn, buf, request_info->content_length);
mg_get_var(buf, strlen(buf), "b", param, 100);
delete buf;
}
else
{
struct mg_form_data_handler fdh = { field_found, field_get, field_store, 0 };
fdh.user_data = (void*)conn;
int ret = mg_handle_form_request(conn, &fdh);
}
}
else if (content_type != nullptr && mg_strncasecmp(content_type, "multipart/form-data;", strlen("multipart/form-data;")) == 0)
{
struct mg_form_data_handler fdh = { field_found, field_get, field_store, 0};
fdh.user_data = (void*)conn;
int ret = mg_handle_form_request(conn, &fdh);
int xx = 0;
}
}
return 1;
}
int main()
{
const char *options[] = {
"document_root", ".",
"listening_ports", "8080",
"error_log_file", "error.log",
"access_log_file", "access.log",
NULL
};
struct mg_context *ctx = mg_start(NULL, NULL, options);
if (ctx == nullptr)
{
return 1;
}
mg_set_request_handler(ctx, "/test", test_cb, 0);
while (true)
{
Sleep(1000);
}
mg_stop(ctx);
return 0;
}
更多用法可以在CivetWeb
的示例中查看。