2024/11/25

魔獸爭霸III

《魔獸爭霸III:混亂之治》(Warcraft III: Reign of Chaos)是一款即時戰略遊戲,屬於暴雪娛樂出品的《魔獸爭霸》系列第三代作品, 於 2002 年發行。2003 年 5 月暴雪公司又發行了《魔獸爭霸III:寒冰霸權》(Warcraft III: The Frozen Throne),它是本作的資料片。 在 2020 年發行的《魔獸爭霸III:淬鍊重生》(Warcraft III: Reforged)為 2002 年的《魔獸爭霸III》之重製版, 包括《混亂之治》與《寒冰霸權》的全部劇情。

玩家可以選擇在《魔獸爭霸III》中操控四個種族,分別是 Human,Orc, Night Elf 與 Undead。 Naga 在資料片製作時曾經設想作為第五個可操控的種族,但在遊戲測試時刪去。而官方解釋是:加上 Naga 之後很難保證遊戲的平衡性, 因為 Naga 的部隊可直接跨過水域。而今它則出現在戰役模式和中立生物當中。

遊戲也加入了一種可透過獲取經驗值升級並增強能力的戰鬥單位,是為英雄。其可分為本族英雄和中立英雄。 每當己方殺滅一個敵方單位或中立生物時,英雄便可獲取相當的經驗值而累積一定程度時其等級就會上升。等級每當上升時, 英雄便可通過添加技能點學習或加強一項技能,不同英雄可學習之技能亦有所異。除此以外,英雄們亦可以攜帶於商店購得或戰勝中立生物後獲取之物品, 而此等物品則可提高英雄或整體的作戰能力。遊戲過程中,一方最多可以有三個英雄出現,但需要將自己的主城升級,可將己方建築物解鎖進而可建築出來。 而倘若有英雄於戰鬥中陣亡,則可在祭壇中加以復活重生回到戰場上。

如上所述,中立生物可視為遊戲中創新之處。即便多人遊戲中,亦存有完全由電腦控制的中立生物,以守衛地圖中關鍵的位置及中立的建築物。 它也成為選手間爭奪的資源,擊殺中立生物後便可獲得相當的經驗值、金錢及物品。使這個類似於角色扮演遊戲的玩家更具「攻擊性」而不會過於防衛, 從而提高遊戲的娛樂性及可觀性。

而另一影響遊戲策略的操施就是維護費徵取,此操施源於一種概念——擁有越多戰鬥單位就須要付出越多的資源去滿足部隊的需要, 如士兵需要補給、武器需要打造、盔甲需要鍛煉等。該遊戲則以上稅實行此概念。當玩家部隊的數目到達一定程度時, 就須於每採集十塊黃金上繳其中三塊。當部隊數目抵達更高程度時,每十塊就須上繳六塊。

在發展的科技樹上可以分為三級(以 Orc 為例,就是 Great Hall 升到 Stronghold 升到 Fortress)。 《魔獸爭霸III》的資源為 GoldLumber,需要透過工人採集。 Human 透過 Peasant, Orc 透過 Peon, Night Elf 透過 Wisp, Undead 則較為特別,Acolyte 採集 Gold 而 Ghoul 採集 Lumber。 也因為如此,通常 Human 第一個建築是生產英雄的 Arcane Sanctum, Orc 第一個建築是 Altar of Storms, Night Elf 第一個建築是 Altar of Elders, 而 Undead 通常第一個建築是生產 Ghoul 的 Crypt,再來才是英雄的 Altar of Darkness。

Human 特別的地方在於可以多個工人一起加速建造建築,同時工人可以化身為民兵進行防守; Orc 則是工人可以躲在 Orc Burrow 中以及可以研發 Spiked Barricades 加強防守; Night Elf 則是有些建築是可以移動的,並且有 Moon Well 可以回復生命與魔力: 而 Undead 的建築則是除了 Haunted Goldmine 與 Necropolis,需要建立在腐土的上面, 以及 Acolyte 在建造建築時不用等建築完成就可以繼續做下一件事。

《魔獸爭霸III:寒冰霸權》各種族都增加了商店這個建築,提供了各個時期可能需要的物品。 Human 為 Arcane Vault,Orc 為 Voodoo Lounge, Night Elf 為 Ancient of Wonders, Undead 為 Tomb of Relics。

下面是《魔獸爭霸III》的英雄列表。

  • Human Alliance
    • Paladin
    • Archmage
    • Mountain King
    • Blood Mage
  • Orcish Horde
    • Blademaster
    • Far Seer
    • Tauren Chieftain
    • Shadow Hunter
  • Undead Scourge
    • Death Knight
    • Lich
    • Dread Lord
    • Crypt Lord
  • Night Elf Sentinels
    • Keeper of the Grove
    • Priestess of the Moon
    • Demon Hunter
    • Warden
  • Neutral
    • Naga Sea Witch
    • Dark Ranger
    • Pandaren Brewmaster
    • Beastmaster
    • Pit Lord
    • Tinker
    • Firelord
    • Alchemist

《魔獸爭霸III:混亂之治》與《魔獸爭霸III:寒冰霸權》自 1.21b 開始提供官方免光碟, 而 1.27b 則是遊戲最後一版不用線上更新的版本。

《魔獸爭霸III》在 1.27b 之後架構改變,不再提供線下更新的下載。同時也帶來了不少遊戲中科技樹的變化, 舉例來說,在 1.30.0 版 Orc 方面最大的變化是將 Spirit Walker 的生產改到 Tauren Totem, 也因此 Tauren Totem 的科技樹改變,改為第二級就可以建造。1.31.1 則是《魔獸爭霸III:寒冰霸權》的最後一版更新版, 之後的版本為《魔獸爭霸III:淬鍊重生》的更新。

另外,如果使用 Wine 玩《魔獸爭霸III》,1.27b 版的開頭動畫以及一些遊戲中的動畫是無法正確播放的, 而且找不到方式修正。還有就是如果使用 Wine,可以嘗試使用 OpenGL 模式 (加上 -opengl)。

2024/11/22

PugiXML

PugiXML 是一個 C++ XML parser 函式庫,支援 DOM-like interface 與 XPATH 1.0 標準。 PugiXML 在是否容易使用、執行速度以及支援功能中取得良好的平衡,其中一個特點就是容易與其它程式整合, 將 pugixml.cpp, pugixml.hpp 與 pugiconfig.hpp 複製到原始碼目錄下就可以開始使用了。

下面是 tree.xml

<?xml version="1.0"?>
<mesh name="mesh_root">
    <!-- here is a mesh node -->
    some text
    <![CDATA[someothertext]]>
    some more text
    <node attr1="value1" attr2="value2" />
    <node attr1="value2">
        <innernode/>
    </node>
</mesh>
<?include somedata?>

下面是載入 XML 檔案的程式:

#include "pugixml.hpp"
#include <iostream>

int main() {
    pugi::xml_document doc;

    pugi::xml_parse_result result = doc.load_file("tree.xml");

    std::cout << "Load result: " << result.description()
              << ", mesh name: " << doc.child("mesh").attribute("name").value()
              << std::endl;
}

下面的程式使用 libcurl 自網站下載 ATOM XML 的資料, 下載以後使用 PugiXML 分析並且將 title 與 link 的資料儲存為 html 格式。

#include "pugixml.hpp"
#include <cstdio>
#include <cstdlib>
#include <curl/curl.h>
#include <fstream>
#include <iostream>

int get_rss(const char *url, const char *outfile) {
    FILE *feedfile = fopen(outfile, "w");

    if (!feedfile)
        return -1;

    CURL *curl = curl_easy_init();
    if (!curl)
        return -1;

    curl_easy_setopt(curl, CURLOPT_URL, url);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, feedfile);

    CURLcode res = curl_easy_perform(curl);
    if (res)
        return -1;
    curl_easy_cleanup(curl);
    fclose(feedfile);
    return 0;
}

int main(int argc, char *argv[]) {
    char *url = NULL;
    char *filename = NULL;
    char *outfile = NULL;
    if (argc == 4) {
        url = argv[1];
        filename = argv[2];
        outfile = argv[3];
    } else {
        printf("Not valid arguments.\n");
    }

    get_rss(url, filename);

    pugi::xml_document doc;
    pugi::xml_parse_result result = doc.load_file(filename);

    pugi::xpath_node_set title = doc.select_nodes("/feed/entry/title");
    pugi::xpath_node_set link =
        doc.select_nodes("/feed/entry/link[@rel='alternate']");

    std::ofstream ofile(outfile);

    pugi::xpath_node_set::const_iterator it1 = title.begin();
    pugi::xpath_node_set::const_iterator it2 = link.begin();
    while (it1 != title.end() && it2 != link.end()) {
        pugi::xpath_node node1 = *it1;
        pugi::xpath_node node2 = *it2;
        ofile << "<a href=\"" << node2.node().attribute("href").value() << "\">"
              << node1.node().text().get() << "</a><br>" << std::endl;

        it1++;
        it2++;
    }
    ofile.close();
}

參考連結

2024/11/21

GNU Libmicrohttpd

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;
}

參考連結