Mongoose Web Server 是一款易于使用的Web服务器,它可以嵌入到其它应用程序中,为其提供Web接口。

主要特写:

  • 跨平台,支持Windows、OS X 和 Linux。
  • 支持 CGI、SSL、SSI、Digest (MD5) 认证,WebSocket 和 WebDAV。
  • 支持断点续传和 URL 重写。
  • 基于 IP 的 ACL,支持 Windows 服务,支持 GETPOSTHEADPUTDELETE 方法。
  • Excluding files from serving by URI pattern。
  • 下载速度限制,基于客户端子网和 URI 模式。
  • 体积小,可执行文件只有 40k 左右。
  • 可嵌入式,提供简单的 API (mongoose.h),只需一个源码文件 mongoose.c
  • 嵌入式实例:hello.c , post.cupload.cwebsocket.c
  • 提供 Python 和 C# 版本。
  • 采用 GPLv2 授权协议(商业不友好的)和商业授权。

Mongoose

在实际应用中归纳了Mongoose的几个特点:

  • 只需在源代码中包含mongoose.hmongoose.c文件即可,没有其他项目依赖,使用起来很简单。
  • 支持多线程(one thread per connection),所以并发很高,并且需要控制很多线程,例如控制内存的使用,这点不是很好。
  • 性能很好。
  • API使用起来很简单,但是需要你自己拼接HTTP响应头(HTTP headers),所以需要了解HTTP协议。
  • 支持解析multipart/form-dataapplication/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.hcivetweb.chandle_form.inlmd5.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的示例中查看。

标签: http, Mongoose, CivetWeb, web server

添加新评论