๐๐ป ๊ฐ์ํธ์คํธ / 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

