10 #ifndef SUPPORTLIB_HTTPSERVER_H
11 #define SUPPORTLIB_HTTPSERVER_H
16 #include <boost/asio/ip/tcp.hpp>
17 #include <boost/beast/core.hpp>
18 #include <boost/beast/http.hpp>
19 #include <boost/beast/version.hpp>
20 #include <boost/asio/bind_executor.hpp>
21 #include <boost/asio/ssl/stream.hpp>
22 #include <boost/asio/strand.hpp>
23 #include <boost/config.hpp>
35 using tcp = boost::asio::ip::tcp;
36 namespace ssl = boost::asio::ssl;
37 namespace http = boost::beast::http;
46 using SPtr = std::shared_ptr<HTTPServerException>;
47 using UPtr = std::unique_ptr<HTTPServerException>;
48 using WPtr = std::weak_ptr<HTTPServerException>;
72 explicit HTTPSession(tcp::socket socket,
const std::filesystem::path& docRoot,
const std::map<std::string, std::string>& mimeTypes,
const std::string& indexFile,
const std::string& serverString,
bool ssl,
const std::filesystem::path& cert,
const std::filesystem::path& key, boost::asio::io_context& ioc) :
73 m_Socket(
std::move(socket)),
75 m_MimeTypes(mimeTypes),
76 m_IndexFile(indexFile),
77 m_ServerString(serverString),
79 m_Strand(boost::asio::make_strand(ioc))
83 m_Ctx.set_options(boost::asio::ssl::context::default_workarounds |
84 boost::asio::ssl::context::no_sslv2 |
85 boost::asio::ssl::context::no_sslv3 |
86 boost::asio::ssl::context::no_tlsv1 |
87 boost::asio::ssl::context::single_dh_use);
88 m_Ctx.use_certificate_chain_file(cert.string());
89 m_Ctx.use_private_key_file(key.string(), boost::asio::ssl::context::file_format::pem);
90 m_Stream = std::make_shared< ssl::stream<tcp::socket&> >(m_Socket, m_Ctx);
91 m_Stream->set_verify_mode(ssl::verify_none);
100 m_Stream->async_handshake(ssl::stream_base::server, boost::asio::bind_executor(m_Strand, std::bind(&HTTPSession::on_handshake, this->shared_from_this(), std::placeholders::_1)));
120 return m_Socket.remote_endpoint().address().to_string();
126 return std::to_string(m_Socket.remote_endpoint().port());
137 http::response<http::vector_body<char>>
getResult()
const {
156 return m_ServerString;
168 void setResult(
const http::response<http::vector_body<char>> &res) {
190 m_ServerString = servstr;
196 void addMimeTypes(
const std::map<std::string, std::string>& mimeTypes) {
197 m_MimeTypes.insert(mimeTypes.begin(), mimeTypes.end());
205 {
if(m_Socket.is_open()){m_Stream->async_shutdown(boost::asio::bind_executor(m_Strand,std::bind(&HTTPSession::on_shutdown,this->shared_from_this(),std::placeholders::_1)));}}
207 {
if(m_Socket.is_open()){m_Socket.shutdown(tcp::socket::shutdown_send, m_Ec);}}
209 using SPtr = std::shared_ptr<HTTPSession>;
210 using UPtr = std::unique_ptr<HTTPSession>;
211 using WPtr = std::weak_ptr<HTTPSession>;
213 void on_read(boost::system::error_code ec, std::size_t bytes_transferred) {
214 boost::ignore_unused(bytes_transferred);
218 if(m_Request.method() != http::verb::get && m_Request.method() != http::verb::head){
219 m_Result = {http::status::bad_request, m_Request.version()};
220 m_Result.set(http::field::server, m_ServerString);
221 m_Result.set(http::field::content_type,
"text/html");
222 m_Result.keep_alive(m_Request.keep_alive());
223 std::string msg =
"Unknown HTTP-method";
224 m_Result.body().assign(msg.begin(), msg.end());
225 m_Result.prepare_payload();
227 else if(m_Request.target().empty() || m_Request.target()[0] !=
'/' || m_Request.target().find(
"..") != boost::beast::string_view::npos) {
228 m_Result = {http::status::bad_request, m_Request.version()};
229 m_Result.set(http::field::server, m_ServerString);
230 m_Result.set(http::field::content_type,
"text/html");
231 m_Result.keep_alive(m_Request.keep_alive());
232 std::string msg =
"Illegal request-target";
233 m_Result.body().assign(msg.begin(), msg.end());
234 m_Result.prepare_payload();
238 std::filesystem::path path = m_DocRoot;
239 path += std::string{m_Request.target()};
240 if(m_Request.target().back() ==
'/')
241 path.append(m_IndexFile);
242 else if((m_Request.target().back() !=
'/') && std::filesystem::is_directory(path, fEc)) {
244 path.append(m_IndexFile);
246 if(!std::filesystem::exists(path, fEc)){
247 m_Result = {http::status::not_found, m_Request.version()};
248 m_Result.set(http::field::server, m_ServerString);
249 m_Result.set(http::field::content_type,
"text/html");
250 m_Result.keep_alive(m_Request.keep_alive());
251 std::string msg =
"The resource '" + std::string{m_Request.target()} +
"' was not found.";
252 m_Result.body().assign(msg.begin(), msg.end());
253 m_Result.prepare_payload();
257 m_Result = {http::status::internal_server_error, m_Request.version()};
258 m_Result.set(http::field::server, m_ServerString);
259 m_Result.set(http::field::content_type,
"text/html");
260 m_Result.keep_alive(m_Request.keep_alive());
261 std::string msg =
"An error occurred: '" + fEc.message() +
"'";
262 m_Result.body().assign(msg.begin(), msg.end());
263 m_Result.prepare_payload();
268 if(m_Request.method() == http::verb::head)
269 m_Result = {http::status::ok, m_Request.version()};
271 m_Result = {http::status::ok, m_Request.version()};
272 m_Result.body() = sfile;
274 m_MimeTypes.try_emplace(path.extension().string(),
"application/text");
275 m_Result.set(http::field::server, m_ServerString);
276 m_Result.set(http::field::content_type, m_MimeTypes[path.extension().string()]);
277 m_Result.keep_alive(m_Request.keep_alive());
278 m_Result.prepare_payload();
280 catch(
const ExceptionBase& e)
282 m_Result = {http::status::internal_server_error, m_Request.version()};
283 m_Result.set(http::field::server, m_ServerString);
284 m_Result.set(http::field::content_type,
"text/html");
285 m_Result.keep_alive(m_Request.keep_alive());
286 std::string msg =
"An error occurred: '" + e.getMessage() +
"'";
287 m_Result.body().assign(msg.begin(), msg.end());
288 m_Result.prepare_payload();
292 m_Result = {http::status::internal_server_error, m_Request.version()};
293 m_Result.set(http::field::server, m_ServerString);
294 m_Result.set(http::field::content_type,
"text/html");
295 m_Result.keep_alive(m_Request.keep_alive());
296 std::string msg =
"An unknown error occurred.";
297 m_Result.body().assign(msg.begin(), msg.end());
298 m_Result.prepare_payload();
303 else if(ec == http::error::end_of_stream)
306 m_Result = {http::status::internal_server_error, m_Request.version()};
307 m_Result.set(http::field::server, m_ServerString);
308 m_Result.set(http::field::content_type,
"text/html");
309 m_Result.keep_alive(m_Request.keep_alive());
310 std::string msg =
"An error occurred: '" + ec.message() +
"'";
311 m_Result.body().assign(msg.begin(), msg.end());
312 m_Result.prepare_payload();
314 m_Buffer.consume(m_Buffer.size());
317 http::async_write(*m_Stream, m_Result, boost::asio::bind_executor(m_Strand, std::bind(&HTTPSession::on_write, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2, m_Result.need_eof())));
319 http::async_write(m_Socket, m_Result, boost::asio::bind_executor(m_Strand, std::bind(&HTTPSession::on_write, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2, m_Result.need_eof())));
324 {
if(m_Socket.is_open()){http::async_read(*m_Stream, m_Buffer, m_Request, boost::asio::bind_executor(m_Strand, std::bind(&HTTPSession::on_read, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2)));}}
326 {
if(m_Socket.is_open()){http::async_read(m_Socket, m_Buffer, m_Request, boost::asio::bind_executor(m_Strand, std::bind(&HTTPSession::on_read, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2)));}}
328 void on_write(boost::system::error_code ec, std::size_t bytes_transferred,
bool close) {
329 boost::ignore_unused(bytes_transferred);
330 if(ec == boost::beast::errc::broken_pipe)
331 return this->
close();
333 throw HTTPServerException(
"Write: " + ec.message());
335 return this->
close();
339 void on_handshake(boost::system::error_code ec) {
341 m_Result = {http::status::internal_server_error, m_Request.version()};
342 m_Result.set(http::field::server, m_ServerString);
343 m_Result.set(http::field::content_type,
"text/html");
344 m_Result.keep_alive(m_Request.keep_alive());
345 std::string msg =
"An error occurred during SSL handshake: '" + ec.message() +
"'";
346 m_Result.body().assign(msg.begin(), msg.end());
347 m_Result.prepare_payload();
348 http::async_write(m_Socket, m_Result, boost::asio::bind_executor(m_Strand, std::bind(&HTTPSession::on_write, this->shared_from_this(), std::placeholders::_1, std::placeholders::_2, m_Result.need_eof())));
354 void on_shutdown(boost::system::error_code ec) {
355 if(ec && ec != boost::beast::errc::broken_pipe)
356 throw HTTPServerException(
"Shutdown: " + ec.message());
358 tcp::socket m_Socket;
359 std::filesystem::path m_DocRoot;
361 std::shared_ptr< ssl::stream<tcp::socket&> > m_Stream;
362 boost::asio::strand<boost::asio::io_context::executor_type> m_Strand;
363 boost::beast::flat_buffer m_Buffer;
364 http::request<http::string_body> m_Request;
365 ssl::context m_Ctx{ssl::context::sslv23};
366 boost::system::error_code m_Ec;
367 http::response<http::vector_body<char>> m_Result;
368 std::string m_ServerString;
369 std::string m_IndexFile;
370 std::map<std::string, std::string> m_MimeTypes;
449 HTTPServer(
const std::string& address =
"0.0.0.0",
const std::string& port =
"80",
const std::filesystem::path& docRoot =
"./",
const size_t numThreads = 1,
bool ssl =
false,
const std::filesystem::path& cert =
"",
const std::filesystem::path& key =
"",
const std::map<std::string, std::string>& mimeTypes = {},
const std::string& indexFile =
"index.html",
const std::string& serverString =
"giris_supportlib_http_server") :
450 m_Endpoint(boost::asio::ip::make_address(address),
451 std::atoi(port.c_str())),
453 m_NumThreads(numThreads),
457 m_MimeTypes(mimeTypes),
458 m_IndexFile(indexFile),
459 m_ServerString(serverString),
464 boost::system::error_code ec;
465 m_Acceptor.open(m_Endpoint.protocol(), ec);
468 m_Acceptor.set_option(boost::asio::socket_base::reuse_address(
true));
471 m_Acceptor.bind(m_Endpoint, ec);
474 m_Acceptor.listen(boost::asio::socket_base::max_listen_connections, ec);
477 m_MimeTypes.try_emplace(
".htm",
"text/html");
478 m_MimeTypes.try_emplace(
".html",
"text/html");
479 m_MimeTypes.try_emplace(
".php",
"text/html");
480 m_MimeTypes.try_emplace(
".txt",
"text/plain");
481 m_MimeTypes.try_emplace(
".css",
"text/css");
482 m_MimeTypes.try_emplace(
".map",
"text/map");
483 m_MimeTypes.try_emplace(
".js",
"application/javascript");
484 m_MimeTypes.try_emplace(
".json",
"application/json");
485 m_MimeTypes.try_emplace(
".xml",
"application/xml");
486 m_MimeTypes.try_emplace(
".swf",
"application/x-shockwave-flash");
487 m_MimeTypes.try_emplace(
".flv",
"video/x-flv");
488 m_MimeTypes.try_emplace(
".png",
"image/png");
489 m_MimeTypes.try_emplace(
".jpg",
"image/jpeg");
490 m_MimeTypes.try_emplace(
".jpe",
"image/jpeg");
491 m_MimeTypes.try_emplace(
".jpeg",
"image/jpeg");
492 m_MimeTypes.try_emplace(
".bmp",
"image/bmp");
493 m_MimeTypes.try_emplace(
".ico",
"image/vnd.microsoft.icon");
494 m_MimeTypes.try_emplace(
".svg",
"image/svg+xml");
495 m_MimeTypes.try_emplace(
".svgz",
"image/svg+xml");
496 m_MimeTypes.try_emplace(
".woff",
"text/plain");
497 m_MimeTypes.try_emplace(
".woff2",
"text/plain");
498 m_MimeTypes.try_emplace(
".ttf",
"text/plain");
499 m_MimeTypes.try_emplace(
".m3u8",
"application/x-mpegURL");
500 m_MimeTypes.try_emplace(
".m3u",
"audio/x-mpegurl");
501 m_MimeTypes.try_emplace(
".wav",
"audio/x-wav");
502 m_MimeTypes.try_emplace(
".mp3",
"audio/mpeg");
503 m_MimeTypes.try_emplace(
".m4a",
"audio/mpeg");
504 m_MimeTypes.try_emplace(
".mpeg",
"video/mpeg");
505 m_MimeTypes.try_emplace(
".mpg",
"video/mpeg");
506 m_MimeTypes.try_emplace(
".ts",
"video/MP2T");
507 m_MimeTypes.try_emplace(
".gif",
"image/gif");
508 m_MimeTypes.try_emplace(
".tiff",
"image/tiff");
509 m_MimeTypes.try_emplace(
".tif",
"image/tiff");
517 if(!m_Acceptor.is_open())
return;
520 m_Threads.reserve(m_NumThreads);
521 for(
auto i = m_NumThreads; i > 0; --i)
522 m_Threads.emplace_back([
this]{ m_Ioc.run();});
564 return m_ServerString;
591 m_ServerString = servstr;
597 void addMimeTypes(
const std::map<std::string, std::string>& mimeTypes) {
598 m_MimeTypes.insert(mimeTypes.begin(), mimeTypes.end());
600 using SPtr = std::shared_ptr<HTTPServer>;
601 using UPtr = std::unique_ptr<HTTPServer>;
602 using WPtr = std::weak_ptr<HTTPServer>;
605 m_Acceptor.async_accept(m_Socket, std::bind(&HTTPServer::on_accept, this->shared_from_this(), std::placeholders::_1));
607 void on_accept(boost::system::error_code ec) {
609 throw HTTPServerException(
"Accept: " + ec.message());
610 m_NewSession = std::make_shared<HTTPSession>(std::move(m_Socket), m_DocRoot, m_MimeTypes, m_IndexFile, m_ServerString, m_SSL, m_Cert, m_Key, m_Ioc);
615 tcp::endpoint m_Endpoint;
616 boost::asio::io_context m_Ioc;
617 std::filesystem::path m_DocRoot;
620 std::filesystem::path m_Cert;
621 std::filesystem::path m_Key;
622 std::map<std::string, std::string> m_MimeTypes;
623 std::string m_IndexFile;
624 std::string m_ServerString;
625 std::vector<std::thread> m_Threads;
626 tcp::acceptor m_Acceptor;
627 tcp::socket m_Socket;
628 HTTPSession::SPtr m_NewSession;
Base exception to inherit custom exceptions from.
Contains FileSystem I/O functions.
Base class of all classes.
Observer/Obersvable Pattern implementation.
Base exception to inherit custom exceptions from.
Definition: Exception.h:48
ExceptionBase(const std::string &msg="")
Definition: Exception.h:54
Exception to be thrown on http server errors.
Definition: HTTPServer.h:43
Class representing a HTTP Server.
Definition: HTTPServer.h:433
std::filesystem::path getDocRoot() const
Definition: HTTPServer.h:551
void setServerString(const std::string &servstr)
Definition: HTTPServer.h:590
void run()
Definition: HTTPServer.h:516
std::string getServerString() const
Definition: HTTPServer.h:563
HTTPSession::SPtr getSession() const
Definition: HTTPServer.h:527
void setIndexFile(const std::string &indx)
Definition: HTTPServer.h:583
std::filesystem::path getKey() const
Definition: HTTPServer.h:545
std::string getIndexFile() const
Definition: HTTPServer.h:557
HTTPServer(const std::string &address="0.0.0.0", const std::string &port="80", const std::filesystem::path &docRoot="./", const size_t numThreads=1, bool ssl=false, const std::filesystem::path &cert="", const std::filesystem::path &key="", const std::map< std::string, std::string > &mimeTypes={}, const std::string &indexFile="index.html", const std::string &serverString="giris_supportlib_http_server")
Definition: HTTPServer.h:449
bool getSSL() const
Definition: HTTPServer.h:533
std::filesystem::path getCert() const
Definition: HTTPServer.h:539
std::map< std::string, std::string > getMimeTypes() const
Definition: HTTPServer.h:569
void setDocRoot(const std::filesystem::path &path)
Definition: HTTPServer.h:576
void addMimeTypes(const std::map< std::string, std::string > &mimeTypes)
Definition: HTTPServer.h:597
Class representing one session/connection.
Definition: HTTPServer.h:57
void setDocRoot(const std::filesystem::path &path)
Definition: HTTPServer.h:175
void setServerString(const std::string &servstr)
Definition: HTTPServer.h:189
std::string getClientIP() const
Definition: HTTPServer.h:119
void run()
Definition: HTTPServer.h:98
void setResult(const http::response< http::vector_body< char >> &res)
Definition: HTTPServer.h:168
void addMimeTypes(const std::map< std::string, std::string > &mimeTypes)
Definition: HTTPServer.h:196
boost::system::error_code getError() const
Definition: HTTPServer.h:113
http::response< http::vector_body< char > > getResult() const
Definition: HTTPServer.h:137
bool getSSL() const
Definition: HTTPServer.h:107
std::string getClientPort() const
Definition: HTTPServer.h:125
void setIndexFile(const std::string &indx)
Definition: HTTPServer.h:182
std::string getIndexFile() const
Definition: HTTPServer.h:149
std::map< std::string, std::string > getMimeTypes() const
Definition: HTTPServer.h:161
http::request< http::string_body > getRequest() const
Definition: HTTPServer.h:131
HTTPSession(tcp::socket socket, const std::filesystem::path &docRoot, const std::map< std::string, std::string > &mimeTypes, const std::string &indexFile, const std::string &serverString, bool ssl, const std::filesystem::path &cert, const std::filesystem::path &key, boost::asio::io_context &ioc)
Definition: HTTPServer.h:72
void close()
Definition: HTTPServer.h:202
std::string getServerString() const
Definition: HTTPServer.h:155
std::filesystem::path getDocRoot() const
Definition: HTTPServer.h:143
Observable class. Inherited classes can notify all classes which inherit from Observer.
Definition: Observer.h:99
void notify()
Definition: Observer.h:130
std::vector< char > LoadFile(const std::filesystem::path &file)
Definition: FileSystem.h:44
Namespace for giri's C++ support library.
Definition: Base64.h:47