[Webserver]httplib webserver+virtualhost(Linux)

๐Ÿ‘‰๐Ÿป ๊ฐ€์ƒํ˜ธ์ŠคํŠธ / VirtualHost

๐Ÿ‘‰๐Ÿป ๊ฐ€์ƒํ˜ธ์ŠคํŠธ๋Š” ํ•˜๋‚˜์˜ ๋ฌผ๋ฆฌ์ ์ธ ์„œ๋ฒ„์— ์—ฌ๋Ÿฌ๊ฐœ์˜ ์›น์‚ฌ์ดํŠธ๋ฅผ ์šด์˜ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.
A virtual host refers to running multiple websites on a single physical server.

๐Ÿ‘‰๐Ÿป ๋ณดํ†ต www.domain.com www2.domain.com๋˜๋Š” www.secdomain.org๋“ฑ์œผ๋กœ ๋„๋ฉ”์ธ์ด๋‚˜ ํ˜ธ์ŠคํŠธ ์ด๋ฆ„์œผ๋กœ ๊ตฌ๋ถ„ํ•ฉ๋‹ˆ๋‹ค.
They are usually distinguished by domain or host names, such as www.domain.com, www2.domain.com, or www.secdomain.org.

๐Ÿ‘‰๐Ÿป ๊ธฐ๋ณธ์ ์œผ๋กœ dns์„œ๋ฒ„์— ํ˜ธ์ŠคํŠธ,๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ๋ ˆ์ฝ”๋“œ ์„ค์ •์ด ๋˜์–ด ์žˆ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.
Basically, records for the host and domain must be configured on the DNS server.

๐Ÿ‘‰๐Ÿป ๋ฐฉํ™”๋ฒฝ ์„ค์ • ๋ถ€๋ถ„ ์„ค๋ช…์€ ์ด์ „ ๊ฒŒ์‹œ๋ฌผ์„ ์ฐธ์กฐํ•˜์„ธ์š”
Please refer to the previous post for the explanation of the firewall settings.

๐Ÿ‘‰๐Ÿป httplib.h ๋‹ค์šด๋กœ๋“œ / download

โœ”๏ธ httplib.h๋ฒ„์ „์ด 0.42.0์œผ๋กœ ์—…๋ฐ์ดํŠธ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
httplib.h version has been updated to 0.42.0.

curl -L https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h -o httplib.h

๐Ÿ‘‰๐Ÿป https์„ค์ • / https settings

โœ”๏ธ https์ธ์ฆ์„œ ์žฌ๋ฐœ๊ธ‰ / HTTPS certificate reissuance

— ๊ธฐ์กด์— host1.freelifemakers.org์˜ ์ธ์ฆ์„œ๋ฅผ ๋ฐ›์•˜์–ด๋„ silverhand.freelifemakers.org์— ๋Œ€ํ•œ ์ธ์ฆ์„œ๊ฐ€ ์—†๋‹ค๋ฉด ์žฌ๋ฐœ๊ธ‰ ๋ฐ›์•„์•ผํ•ฉ๋‹ˆ๋‹ค.
Even if you have previously obtained a certificate for host1.freelifemakers.org, if you do not have a certificate for silverhand.freelifemakers.org, you must obtain a new one.

— ํ•˜๋‚˜์˜ ์ธ์ฆ์„œ์— ๋„๋งค์ธ์„ ๋” ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
You can add more domains to a single certificate.

— ์ธ์ฆ์„œ ๊ฒฝ๋กœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. / The certificate path is as follows.
/etc/letsencrypt/live/host1.domain.org/fullchain.pem

~/http_lib_server_virtual$ sudo certbot certonly --standalone \
  -d host1.domain.org \
  -d silverhand.domain.org
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
You have an existing certificate that contains a portion of the domains you
requested (ref: /etc/letsencrypt/renewal/host1.domain.org.conf)

It contains these names: host1.domain.org

You requested these names for the new certificate:
host1.domain.org, silverhand.domain.org.

Do you want to expand and replace this existing certificate with the new
certificate?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(E)xpand/(C)ancel: e
Renewing an existing certificate for host1.domain.org and silverhand.domain.org

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/host1.domain.org/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/host1.domain.org/privkey.pem
This certificate expires on 2028-10-10.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

— ์ธ์ฆ์„œ๋ฅผ ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ์— ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.
Copy the certificate to the current directory.

$ sudo cp /etc/letsencrypt/live/host1.domain.org/fullchain.pem ./
$ sudo cp /etc/letsencrypt/live/host1.domain.org/privkey.pem ./
# ๋‚ด ๊ณ„์ •์œผ๋กœ ์†Œ์œ ๊ถŒ ๋ณ€๊ฒฝ / Change ownership to my account
$ sudo chown username:username *.pem  

๐Ÿ‘‰๐ŸปHttps ์„œ๋ฒ„(๋ฆฌ๋ˆ…์Šค์—์„œ ํ…Œ์ŠคํŠธ) / HTTPS server (tested on Linux)

โœ”๏ธ ๊ฐ€์ƒํ˜ธ์ŠคํŠธ๋ณ„ ๋‹ค๋ฅธ ๋ฉ”์„ธ์ง€ ๋ณด์—ฌ์ฃผ๊ธฐ
Show different messages per virtual host

โœ”๏ธ server.cpp ์ฝ”๋“œ/code

#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "httplib.h"
#include <iostream>
#include <unordered_map>

int main() {
    // https setting
    httplib::SSLServer svr("./fullchain.pem", "./privkey.pem");

    if (!svr.is_valid()) {
        std::cerr << "SSL server setup failed\n";
        return 1;
    }

    // ๊ฐ€์ƒํ˜ธ์ŠคํŠธ ๋ผ์šฐํŒ… / Virtual Host Routing
    svr.set_pre_routing_handler([&](const auto& req, auto& res) {

        std::string host = req.get_header_value("Host");  // Host ๊ธฐ์ค€
        if (auto pos = host.find(':'); pos != std::string::npos) {
            host = host.substr(0, pos);
        }

        if (host == "host1.domain.org") {
            res.set_content("Hello VirtuslHost ๐Ÿ˜‰", "text/plain; charset=utf-8"");
            return httplib::Server::HandlerResponse::Handled;
        }
        if (host == "silverhand.domain.org") {
            res.set_content("Hello Silverhand ๐Ÿ‘‹", "text/plain; charset=utf-8"");
            return httplib::Server::HandlerResponse::Handled;
        }
        return httplib::Server::HandlerResponse::Unhandled;
    });

    // ๊ณตํ†ต ๋ผ์šฐํŠธ (ํ˜ธ์ŠคํŠธ ๋ชป ๋งž์ถ˜ ๊ฒฝ์šฐ) / Common route (if host does not match)
    svr.Get("/", [](const auto& req, auto& res) {
        res.set_content("Default vhost", "text/plain");
    });

    std::cout << "Server Listening: https://0.0.0.0:5080" << std::endl;
    svr.listen("0.0.0.0", 5080);
}

โœ”๏ธ ์ปดํŒŒ์ผ / Compiling

— ์ปดํŒŒ์ผ์‹œ httplib.h (v0.42.0)๋ฒ„์ „์˜ ์—…๋ฐ์ดํŠธ๋กœ ์ธํ•ด c++17๋กœ ์ปดํŒŒ์ผํ•ด์•ผ ๊ฒฝ๊ณ ๋‚˜ ์˜ค๋ฅ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
Due to the update of httplib.h (v0.42.0) during compilation, you must compile in C++17 to avoid warnings or errors.

g++ -o server server.cpp -std=c++17 -pthread -lssl -lcrypto

โœ”๏ธ ์‹คํ–‰ / Run

./server

โœ”๏ธ ํ˜ธ์ŠคํŠธ๋ณ„ ์ •์ ํŒŒ์ผ ์„œ๋น™ / Serving static files per host

โœ”๏ธserver_static.cpp ์ฝ”๋“œ / code

#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "httplib.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <unordered_map>

static std::string read_file(const std::string& path) {
    std::ifstream ifs(path, std::ios::binary);
    if (!ifs) return {};
    std::ostringstream ss;
    ss << ifs.rdbuf();
    return ss.str();
}

static std::string sanitize_path(const std::string& raw) {
    if (raw.empty() || raw[0] != '/') return "/index.html";
    if (raw.find("..") != std::string::npos) return "/index.html";
    if (raw.find('\\') != std::string::npos) return "/index.html";
    if (raw.back() == '/') return raw + "index.html";
    if (raw == "/") return "/index.html";
    return raw;
}

static std::string mime_type(const std::string& path) {
    auto pos = path.find_last_of('.');
    if (pos == std::string::npos) return "application/octet-stream";
    auto ext = path.substr(pos + 1);
    if (ext == "html") return "text/html; charset=utf-8";
    if (ext == "css") return "text/css; charset=utf-8";
    if (ext == "js") return "application/javascript; charset=utf-8";
    if (ext == "json") return "application/json; charset=utf-8";
    if (ext == "png") return "image/png";
    if (ext == "jpg" || ext == "jpeg") return "image/jpeg";
    if (ext == "svg") return "image/svg+xml";
    if (ext == "ico") return "image/x-icon";
    if (ext == "txt") return "text/plain; charset=utf-8";
    return "application/octet-stream";
}

int main() {
    // ์ธ์ฆ์„œ ๊ฒฝ๋กœ / certificate path
    httplib::SSLServer svr("./fullchain.pem", "./privkey.pem");

    if (!svr.is_valid()) {
        std::cerr << "SSL server setup failed\n";
        return 1;
    }

    // ํ˜ธ์ŠคํŠธ๋ณ„ ๋ฃจํŠธ ํด๋” ๋งคํ•‘ (์ ˆ๋Œ€๊ฒฝ๋กœ ๊ถŒ์žฅ)
    // Mapping root folders by host (absolute paths recommended)
    std::unordered_map<std::string, std::string> roots = {
        {"host1.domain.org", "/home/username/http_lib_server_virtual/www"},
        {"silverhand.domain.org", "/home/username/http_lib_server_virtual/www2"}
    };

    svr.set_pre_routing_handler([&](const auto& req, auto& res) {
        std::string host = req.get_header_value("Host");
        if (auto pos = host.find(':'); pos != std::string::npos) {
            host = host.substr(0, pos); 
        }

        auto it = roots.find(host);
        if (it == roots.end()) {
            res.status = 404;
            res.set_content("Unknown host", "text/plain; charset=utf-8");
            return httplib::Server::HandlerResponse::Handled;
        }

        std::string rel = sanitize_path(req.path);
        std::string full = it->second + rel;

        auto body = read_file(full);
        if (body.empty()) {
            res.status = 404;
            res.set_content("Not Found", "text/plain; charset=utf-8");
        } else {
            res.set_content(body, mime_type(full).c_str());
        }
        return httplib::Server::HandlerResponse::Handled;
    });

    std::cout << "Listening on https://0.0.0.0:5080\n";
    svr.listen("0.0.0.0", 5080);
}

โœ”๏ธ ์ปดํŒŒ์ผ / Compiling

g++ -o server_static server_static.cpp -std=c++17 -pthread -lssl -lcrypto

โœ”๏ธ ์‹คํ–‰ / Run

./server_static

Leave a Reply