GNU Libmicrohttpd 是一個函式庫,授權為 GNU LGPL v2.1 (使用者也可以選擇 LGPL v2.1 之後的版本), 最主要的功能是內嵌 HTTP server 到應用程式內,有多種 threading mode 可以選擇, 支援 HTTP/1.1 與 IPv6,並且透過 GnuTLS 支援 SSL/TLS 協定。 因此,當應用程式需要內嵌 web server 作為簡單的 web 介面時,可以考慮使用 GNU Libmicrohttpd。
在 openSUSE Tumbleweed 安裝相關的開發檔案:
sudo zypper in libmicrohttpd-devel
下面是首頁上的範例:
#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define PAGE \
"<html><head><title>libmicrohttpd demo</title>" \
"</head><body>libmicrohttpd demo</body></html>"
static enum MHD_Result 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;
}
下面是加入 SSL/TLS 以及靜態檔案的支援:
#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.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 */
static 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 */
static 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 ssize_t file_reader(void *cls, uint64_t pos, char *buf, size_t max) {
FILE *file = (FILE *)cls;
size_t bytes_read;
/* 'fseek' may not support files larger 2GiB, depending on platform.
* For production code, make sure that 'pos' has valid values, supported by
* 'fseek', or use 'fseeko' or similar function. */
if (0 != fseek(file, (long)pos, SEEK_SET))
return MHD_CONTENT_READER_END_WITH_ERROR;
bytes_read = fread(buf, 1, max, file);
if (0 == bytes_read)
return (0 != ferror(file)) ? MHD_CONTENT_READER_END_WITH_ERROR
: MHD_CONTENT_READER_END_OF_STREAM;
return (ssize_t)bytes_read;
}
static void file_free_callback(void *cls) {
FILE *file = cls;
fclose(file);
}
/* HTTP access handler call back */
static enum MHD_Result 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 **req_cls) {
static int aptr;
struct MHD_Response *response;
enum MHD_Result ret;
FILE *file;
int fd;
struct stat buf;
(void)cls; /* Unused. Silent compiler warning. */
(void)version; /* Unused. Silent compiler warning. */
(void)upload_data; /* Unused. Silent compiler warning. */
(void)upload_data_size; /* Unused. Silent compiler warning. */
if (0 != strcmp(method, MHD_HTTP_METHOD_GET))
return MHD_NO; /* unexpected method */
if (&aptr != *req_cls) {
/* do never respond on first call */
*req_cls = &aptr;
return MHD_YES;
}
*req_cls = NULL; /* reset when done */
file = fopen(&url[1], "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_static(
strlen(EMPTY_PAGE), (const void *)EMPTY_PAGE);
ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
MHD_destroy_response(response);
} else {
response = MHD_create_response_from_callback(
(size_t)buf.st_size, 32 * 1024, /* 32k page size */
&file_reader, file, &file_free_callback);
if (NULL == response) {
fclose(file);
return MHD_NO;
}
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
}
return ret;
}
int main(int argc, char *const *argv) {
struct MHD_Daemon *TLS_daemon;
int port;
if (argc != 2) {
printf("%s PORT\n", argv[0]);
return 1;
}
port = atoi(argv[1]);
if ((1 > port) || (port > 65535)) {
fprintf(stderr, "Port must be a number between 1 and 65535.\n");
return 1;
}
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);
return 0;
}
接下來使用自簽憑證設定 HTTPS 並且採用 GnuTLS 載入檔案的方式。
首先建立 ssl.conf 設定檔:
[req]
prompt = no
default_md = sha256
default_bits = 2048
distinguished_name = dn
x509_extensions = v3_req
[dn]
C = TW
ST = Taiwan
L = Taipei
O = Orange Inc.
OU = IT Department
emailAddress = admin@example.com
CN = localhost
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = *.localhost
DNS.2 = localhost
IP.1 = 127.0.0.1
透過 OpenSSL 指令建立開發測試用途的自簽憑證:
openssl req -x509 -new -nodes -sha256 -utf8 -days 3650 \
-newkey rsa:2048 -keyout httpd.key -out httpd.crt -config ssl.conf
下面是一個使用的例子:
#include <microhttpd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.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>"
#include <gnutls/gnutls.h>
#include <gnutls/abstract.h>
/**
* A hostname, server key and certificate.
*/
struct Hosts {
struct Hosts *next;
const char *hostname;
gnutls_pcert_st pcrt;
gnutls_privkey_t key;
};
#define PAGE \
"<html><head><title>libmicrohttpd https demo</title>" \
"</head><body>libmicrohttpd https demo</body></html>"
static struct Hosts *hosts;
/* Load the certificate and the private key.
* (This code is largely taken from GnuTLS).
*/
static void load_keys(const char *hostname, const char *CERT_FILE,
const char *KEY_FILE) {
int ret;
gnutls_datum_t data;
struct Hosts *host;
host = malloc(sizeof(struct Hosts));
if (NULL == host)
abort();
host->hostname = hostname;
host->next = hosts;
hosts = host;
ret = gnutls_load_file(CERT_FILE, &data);
if (ret < 0) {
fprintf(stderr, "*** Error loading certificate file %s.\n", CERT_FILE);
exit(1);
}
ret = gnutls_pcert_import_x509_raw(&host->pcrt, &data, GNUTLS_X509_FMT_PEM,
0);
if (ret < 0) {
fprintf(stderr, "*** Error loading certificate file: %s\n",
gnutls_strerror(ret));
exit(1);
}
gnutls_free(data.data);
ret = gnutls_load_file(KEY_FILE, &data);
if (ret < 0) {
fprintf(stderr, "*** Error loading key file %s.\n", KEY_FILE);
exit(1);
}
gnutls_privkey_init(&host->key);
ret = gnutls_privkey_import_x509_raw(host->key, &data, GNUTLS_X509_FMT_PEM,
NULL, 0);
if (ret < 0) {
fprintf(stderr, "*** Error loading key file: %s\n",
gnutls_strerror(ret));
exit(1);
}
gnutls_free(data.data);
}
static int cert_callback(gnutls_session_t session,
const gnutls_datum_t *req_ca_dn, int nreqs,
const gnutls_pk_algorithm_t *pk_algos,
int pk_algos_length, gnutls_pcert_st **pcert,
unsigned int *pcert_length, gnutls_privkey_t *pkey) {
char name[256];
size_t name_len;
struct Hosts *host;
unsigned int type;
(void)req_ca_dn;
(void)nreqs;
(void)pk_algos;
(void)pk_algos_length; /* Unused. Silent compiler warning. */
name_len = sizeof(name);
if (GNUTLS_E_SUCCESS !=
gnutls_server_name_get(session, name, &name_len, &type, 0 /* index */))
return -1;
for (host = hosts; NULL != host; host = host->next)
if (0 == strncmp(name, host->hostname, name_len))
break;
if (NULL == host) {
fprintf(stderr, "Need certificate for %.*s\n", (int)name_len, name);
return -1;
}
#if 1
fprintf(stderr, "Returning certificate for %.*s\n", (int)name_len, name);
#endif
*pkey = host->key;
*pcert_length = 1;
*pcert = &host->pcrt;
return 0;
}
static ssize_t file_reader(void *cls, uint64_t pos, char *buf, size_t max) {
FILE *file = (FILE *)cls;
size_t bytes_read;
/* 'fseek' may not support files larger 2GiB, depending on platform.
* For production code, make sure that 'pos' has valid values, supported by
* 'fseek', or use 'fseeko' or similar function. */
if (0 != fseek(file, (long)pos, SEEK_SET))
return MHD_CONTENT_READER_END_WITH_ERROR;
bytes_read = fread(buf, 1, max, file);
if (0 == bytes_read)
return (0 != ferror(file)) ? MHD_CONTENT_READER_END_WITH_ERROR
: MHD_CONTENT_READER_END_OF_STREAM;
return (ssize_t)bytes_read;
}
static void file_free_callback(void *cls) {
FILE *file = cls;
fclose(file);
}
/* HTTP access handler call back */
static enum MHD_Result 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 **req_cls) {
static int aptr;
struct MHD_Response *response;
enum MHD_Result ret;
FILE *file;
int fd;
struct stat buf;
(void)cls; /* Unused. Silent compiler warning. */
(void)version; /* Unused. Silent compiler warning. */
(void)upload_data; /* Unused. Silent compiler warning. */
(void)upload_data_size; /* Unused. Silent compiler warning. */
if (0 != strcmp(method, MHD_HTTP_METHOD_GET))
return MHD_NO; /* unexpected method */
if (&aptr != *req_cls) {
/* do never respond on first call */
*req_cls = &aptr;
return MHD_YES;
}
*req_cls = NULL; /* reset when done */
file = fopen(&url[1], "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_static(
strlen(EMPTY_PAGE), (const void *)EMPTY_PAGE);
ret = MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
MHD_destroy_response(response);
} else {
response = MHD_create_response_from_callback(
(size_t)buf.st_size, 32 * 1024, /* 32k page size */
&file_reader, file, &file_free_callback);
if (NULL == response) {
fclose(file);
return MHD_NO;
}
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
}
return ret;
}
int main(int argc, char *const *argv) {
struct MHD_Daemon *TLS_daemon;
int port;
if (argc != 2) {
printf("%s PORT\n", argv[0]);
return 1;
}
port = atoi(argv[1]);
if ((1 > port) || (port > 65535)) {
fprintf(stderr, "Port must be a number between 1 and 65535.\n");
return 1;
}
load_keys("localhost", "./httpd.crt", "./httpd.key");
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, (unsigned int)120,
MHD_OPTION_HTTPS_CERT_CALLBACK, &cert_callback,
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);
return 0;
}
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。