[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