2024/02/18

GNU Libmicrohttpd

GNU Libmicrohttpd 是支援 HTTP 1.1 的函式庫, 可以內嵌一個 http server 到應用程式。當有一些簡單而且臨時的服務需求而不想要安裝 Http server 的時候, 就可以考慮使用 Libmicrohttpd。 Libmicrohttpd 有二種授權,當 disable HTTPS/TLS support 時可以選擇使用 eCos, 否則應該使用 GNU LGPL v2.1 (or at your option any later version)。

Libmicrohttpd 使用 callback 模式,使用 MHD_start_daemon 建立 daemon, 使用 MHD_stop_daemon 停止 daemon;而當有 request 進來的時候就呼叫 callback function 處理。

下面是 Hello 版本的程式:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <microhttpd.h>

#define PAGE                                                                   \
    "<html><head><title>libmicrohttpd demo</title>"                            \
    "</head><body>libmicrohttpd demo</body></html>"

static int ahc_echo(void *cls, struct MHD_Connection *connection,
                                const char *url, const char *method,
                                const char *version, const char *upload_data,
                                size_t *upload_data_size, void **ptr) {
    static int dummy;
    const char *page = cls;
    struct MHD_Response *response;
    int ret;

    if (0 != strcmp(method, "GET"))
        return MHD_NO; /* unexpected method */

    if (&dummy != *ptr) {
        /* The first time only the headers are valid,
           do not respond in the first round... */
        *ptr = &dummy;
        return MHD_YES;
    }

    if (0 != *upload_data_size)
        return MHD_NO; /* upload data in a GET!? */

    *ptr = NULL;       /* clear context pointer */
    response = MHD_create_response_from_buffer(strlen(page), (void *)page,
                                               MHD_RESPMEM_PERSISTENT);
    ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
    MHD_destroy_response(response);
    return ret;
}

int main(int argc, char **argv) {
    struct MHD_Daemon *d;
    if (argc != 2) {
        printf("%s PORT\n", argv[0]);
        return 1;
    }
    d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, atoi(argv[1]), NULL,
                         NULL, &ahc_echo, PAGE, MHD_OPTION_END);
    if (d == NULL)
        return 1;
    (void)getc(stdin);
    MHD_stop_daemon(d);
    return 0;
}

下面修改自官網的範例, 是使用 https(Libmicrohttpd 使用 GnuTLS)的例子, 是一個簡單的靜態檔案伺服器:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <microhttpd.h>

#define BUF_SIZE 1024
#define MAX_URL_LEN 255

#define EMPTY_PAGE                                                             \
    "<html><head><title>File not found</title></head><body>File not "          \
    "found</body></html>"

/* test server key */
const char key_pem[] = "-----BEGIN PRIVATE KEY-----\n\
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCff7amw9zNSE+h\n\
rOMhBrzbbsJluUP3gmd8nOKY5MUimoPkxmAXfp2L0il+MPZT/ZEmo11q0k6J2jfG\n\
UBQ+oZW9ahNZ9gCDjbYlBblo/mqTai+LdeLO3qk53d0zrZKXvCO6sA3uKpG2WR+g\n\
+sNKxfYpIHCpanqBU6O+degIV/+WKy3nQ2Fwp7K5HUNj1u0pg0QQ18yf68LTnKFU\n\
HFjZmmaaopWki5wKSBieHivzQy6w+04HSTogHHRK/y/UcoJNSG7xnHmoPPo1vLT8\n\
CMRIYnSSgU3wJ43XBJ80WxrC2dcoZjV2XZz+XdQwCD4ZrC1ihykcAmiQA+sauNm7\n\
dztOMkGzAgMBAAECggEAIbKDzlvXDG/YkxnJqrKXt+yAmak4mNQuNP+YSCEdHSBz\n\
+SOILa6MbnvqVETX5grOXdFp7SWdfjZiTj2g6VKOJkSA7iKxHRoVf2DkOTB3J8np\n\
XZd8YaRdMGKVV1O2guQ20Dxd1RGdU18k9YfFNsj4Jtw5sTFTzHr1P0n9ybV9xCXp\n\
znSxVfRg8U6TcMHoRDJR9EMKQMO4W3OQEmreEPoGt2/+kMuiHjclxLtbwDxKXTLP\n\
pD0gdg3ibvlufk/ccKl/yAglDmd0dfW22oS7NgvRKUve7tzDxY1Q6O5v8BCnLFSW\n\
D+z4hS1PzooYRXRkM0xYudvPkryPyu+1kEpw3fNsoQKBgQDRfXJo82XQvlX8WPdZ\n\
Ts3PfBKKMVu3Wf8J3SYpuvYT816qR3ot6e4Ivv5ZCQkdDwzzBKe2jAv6JddMJIhx\n\
pkGHc0KKOodd9HoBewOd8Td++hapJAGaGblhL5beIidLKjXDjLqtgoHRGlv5Cojo\n\
zHa7Viel1eOPPcBumhp83oJ+mQKBgQDC6PmdETZdrW3QPm7ZXxRzF1vvpC55wmPg\n\
pRfTRM059jzRzAk0QiBgVp3yk2a6Ob3mB2MLfQVDgzGf37h2oO07s5nspSFZTFnM\n\
KgSjFy0xVOAVDLe+0VpbmLp1YUTYvdCNowaoTE7++5rpePUDu3BjAifx07/yaSB+\n\
W+YPOfOuKwKBgQCGK6g5G5qcJSuBIaHZ6yTZvIdLRu2M8vDral5k3793a6m3uWvB\n\
OFAh/eF9ONJDcD5E7zhTLEMHhXDs7YEN+QODMwjs6yuDu27gv97DK5j1lEsrLUpx\n\
XgRjAE3KG2m7NF+WzO1K74khWZaKXHrvTvTEaxudlO3X8h7rN3u7ee9uEQKBgQC2\n\
wI1zeTUZhsiFTlTPWfgppchdHPs6zUqq0wFQ5Zzr8Pa72+zxY+NJkU2NqinTCNsG\n\
ePykQ/gQgk2gUrt595AYv2De40IuoYk9BlTMuql0LNniwsbykwd/BOgnsSlFdEy8\n\
0RQn70zOhgmNSg2qDzDklJvxghLi7zE5aV9//V1/ewKBgFRHHZN1a8q/v8AAOeoB\n\
ROuXfgDDpxNNUKbzLL5MO5odgZGi61PBZlxffrSOqyZoJkzawXycNtoBP47tcVzT\n\
QPq5ZOB3kjHTcN7dRLmPWjji9h4O3eHCX67XaPVMSWiMuNtOZIg2an06+jxGFhLE\n\
qdJNJ1DkyUc9dN2cliX4R+rG\n\
-----END PRIVATE KEY-----";

/* test server CA signed certificates */
const char cert_pem[] = "-----BEGIN CERTIFICATE-----\n\
MIIFSzCCAzOgAwIBAgIBBDANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMCUlUx\n\
DzANBgNVBAgMBk1vc2NvdzEPMA0GA1UEBwwGTW9zY293MRswGQYDVQQKDBJ0ZXN0\n\
LWxpYm1pY3JvaHR0cGQxITAfBgkqhkiG9w0BCQEWEm5vYm9keUBleGFtcGxlLm9y\n\
ZzEQMA4GA1UEAwwHdGVzdC1DQTAgFw0yMjA0MjAxODQzMDJaGA8yMTIyMDMyNjE4\n\
NDMwMlowZTELMAkGA1UEBhMCUlUxDzANBgNVBAgMBk1vc2NvdzEPMA0GA1UEBwwG\n\
TW9zY293MRswGQYDVQQKDBJ0ZXN0LWxpYm1pY3JvaHR0cGQxFzAVBgNVBAMMDnRl\n\
c3QtbWhkc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn3+2\n\
psPczUhPoazjIQa8227CZblD94JnfJzimOTFIpqD5MZgF36di9IpfjD2U/2RJqNd\n\
atJOido3xlAUPqGVvWoTWfYAg422JQW5aP5qk2ovi3Xizt6pOd3dM62Sl7wjurAN\n\
7iqRtlkfoPrDSsX2KSBwqWp6gVOjvnXoCFf/list50NhcKeyuR1DY9btKYNEENfM\n\
n+vC05yhVBxY2ZpmmqKVpIucCkgYnh4r80MusPtOB0k6IBx0Sv8v1HKCTUhu8Zx5\n\
qDz6Nby0/AjESGJ0koFN8CeN1wSfNFsawtnXKGY1dl2c/l3UMAg+GawtYocpHAJo\n\
kAPrGrjZu3c7TjJBswIDAQABo4HmMIHjMAsGA1UdDwQEAwIFoDAMBgNVHRMBAf8E\n\
AjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMBMDEGA1UdEQQqMCiCDnRlc3QtbWhk\n\
c2VydmVyhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQWBBQ57Z06WJae\n\
8fJIHId4QGx/HsRgDDAoBglghkgBhvhCAQ0EGxYZVGVzdCBsaWJtaWNyb2h0dHBk\n\
IHNlcnZlcjARBglghkgBhvhCAQEEBAMCBkAwHwYDVR0jBBgwFoAUWHVDwKVqMcOF\n\
Nd0arI3/QB3W6SwwDQYJKoZIhvcNAQELBQADggIBAI7Lggm/XzpugV93H5+KV48x\n\
X+Ct8unNmPCSzCaI5hAHGeBBJpvD0KME5oiJ5p2wfCtK5Dt9zzf0S0xYdRKqU8+N\n\
aKIvPoU1hFixXLwTte1qOp6TviGvA9Xn2Fc4n36dLt6e9aiqDnqPbJgBwcVO82ll\n\
HJxVr3WbrAcQTB3irFUMqgAke/Cva9Bw79VZgX4ghb5EnejDzuyup4pHGzV10Myv\n\
hdg+VWZbAxpCe0S4eKmstZC7mWsFCLeoRTf/9Pk1kQ6+azbTuV/9QOBNfFi8QNyb\n\
18jUjmm8sc2HKo8miCGqb2sFqaGD918hfkWmR+fFkzQ3DZQrT+eYbKq2un3k0pMy\n\
UySy8SRn1eadfab+GwBVb68I9TrPRMrJsIzysNXMX4iKYl2fFE/RSNnaHtPw0C8y\n\
B7memyxPRl+H2xg6UjpoKYh3+8e44/XKm0rNIzXjrwA8f8gnw2TbqmMDkj1YqGnC\n\
SCj5A27zUzaf2pT/YsnQXIWOJjVvbEI+YKj34wKWyTrXA093y8YI8T3mal7Kr9YM\n\
WiIyPts0/aVeziM0Gunglz+8Rj1VesL52FTurobqusPgM/AME82+qb/qnxuPaCKj\n\
OT1qAbIblaRuWqCsid8BzP7ZQiAnAWgMRSUg1gzDwSwRhrYQRRWAyn/Qipzec+27\n\
/w0gW9EVWzFhsFeGEssi\n\
-----END CERTIFICATE-----";

static char *basedir = NULL;

static ssize_t file_reader(void *cls, uint64_t pos, char *buf, size_t max) {
    FILE *file = cls;

    (void)fseek(file, pos, SEEK_SET);
    return fread(buf, 1, max, file);
}

static void file_free_callback(void *cls) {
    FILE *file = cls;
    fclose(file);
}

/* HTTP access handler call back */
static int http_ahc(void *cls, struct MHD_Connection *connection,
                    const char *url, const char *method, const char *version,
                    const char *upload_data, size_t *upload_data_size,
                    void **ptr) {
    static int aptr;
    struct MHD_Response *response;
    int ret;
    FILE *file;
    int fd;
    struct stat buf;
    char *realpath = NULL;

    if (0 != strcmp(method, MHD_HTTP_METHOD_GET))
        return MHD_NO;
    if (&aptr != *ptr) {
        /* do never respond on first call */
        *ptr = &aptr;
        return MHD_YES;
    }
    *ptr = NULL; /* reset when done */

    if (strcmp(url, "/") != 0) {
        asprintf(&realpath, "%s%s", basedir, url);
    } else {
        asprintf(&realpath, "%s/index.html", basedir);
    }

    file = fopen(realpath, "rb");
    if (NULL != file) {
        fd = fileno(file);
        if (-1 == fd) {
            (void)fclose(file);
            return MHD_NO; /* internal error */
        }

        if ((0 != fstat(fd, &buf)) || (!S_ISREG(buf.st_mode))) {
            /* not a regular file, refuse to serve */
            fclose(file);
            file = NULL;
        }
    }

    if (NULL == file) {
        response = MHD_create_response_from_buffer(
            strlen(EMPTY_PAGE), (void *)EMPTY_PAGE, MHD_RESPMEM_PERSISTENT);
        ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
        MHD_destroy_response(response);
    } else {
        response = MHD_create_response_from_callback(
            buf.st_size, 32 * 1024, /* 32k PAGE_NOT_FOUND size */
            &file_reader, file, &file_free_callback);
        if (NULL == response) {
            fclose(file);
            if (realpath)
                free(realpath);
            return MHD_NO;
        }
        ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
        MHD_destroy_response(response);
    }

    if (realpath)
        free(realpath);
    return ret;
}

int main(int argc, char *const *argv) {
    int index = 1;
    unsigned short port = 8888;
    struct MHD_Daemon *TLS_daemon;

    for (index = 1; index < argc; index++) {
        if (strcmp(argv[index], "-dir") == 0) {
            if (argv[index + 1] != NULL && strlen(argv[index + 1]) > 0) {
                asprintf(&basedir, "%s", argv[index + 1]);
            } else {
                printf("dir parameter is invalid.\n");
                return 1;
            }

            index++;
        } else if (strcmp(argv[index], "-port") == 0) {
            if (argv[index + 1] != NULL && strlen(argv[index + 1]) > 0) {
                port = atoi(argv[index + 1]);
                if ((1 > port) || (port > UINT16_MAX)) {
                    fprintf(stderr,
                            "Port must be a number between 1 and 65535\n");
                    return 1;
                }

                index++;
            } else {
                printf("port parameter is invalid.\n");
                return 1;
            }
        }
    }

    if (basedir == NULL) {
        asprintf(&basedir, ".");
    }

    TLS_daemon = MHD_start_daemon(
        MHD_USE_THREAD_PER_CONNECTION | MHD_USE_INTERNAL_POLLING_THREAD |
            MHD_USE_ERROR_LOG | MHD_USE_TLS,
        (uint16_t)port, NULL, NULL, &http_ahc, NULL,
        MHD_OPTION_CONNECTION_TIMEOUT, 256, MHD_OPTION_HTTPS_MEM_KEY, key_pem,
        MHD_OPTION_HTTPS_MEM_CERT, cert_pem, MHD_OPTION_END);
    if (NULL == TLS_daemon) {
        fprintf(stderr, "Error: failed to start TLS_daemon.\n");
        return 1;
    }
    printf("MHD daemon listening on port %u\n", (unsigned int)port);

    (void)getc(stdin);

    MHD_stop_daemon(TLS_daemon);
    if (basedir)
        free(basedir);
    return 0;
}

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。