Giri's C++ Support Library
C++ library providing everything you need to quickly create awesome applications.
HTTPServer.h
Go to the documentation of this file.
1 
10 #ifndef SUPPORTLIB_HTTPSERVER_H
11 #define SUPPORTLIB_HTTPSERVER_H
12 #include "Object.h"
13 #include "Observer.h"
14 #include "Exception.h"
15 #include "FileSystem.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>
24 #include <algorithm>
25 #include <cstdlib>
26 #include <functional>
27 #include <iostream>
28 #include <memory>
29 #include <string>
30 #include <thread>
31 #include <vector>
32 #include <map>
33 
34 namespace giri {
35  using tcp = boost::asio::ip::tcp;
36  namespace ssl = boost::asio::ssl;
37  namespace http = boost::beast::http;
38 
43  {
44  public:
45  HTTPServerException(const std::string &msg) : ExceptionBase(msg) {};
46  using SPtr = std::shared_ptr<HTTPServerException>;
47  using UPtr = std::unique_ptr<HTTPServerException>;
48  using WPtr = std::weak_ptr<HTTPServerException>;
49  };
50 
56  class HTTPSession : public Observable<HTTPSession>
57  {
58  public:
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)),
74  m_DocRoot(docRoot),
75  m_MimeTypes(mimeTypes),
76  m_IndexFile(indexFile),
77  m_ServerString(serverString),
78  m_SSL(ssl),
79  m_Strand(boost::asio::make_strand(ioc))
80  {
81  if(m_SSL)
82  {
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);
92  }
93  }
98  void run() {
99  if(m_SSL)
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)));
101  else
102  do_read();
103  }
107  bool getSSL() const {
108  return m_SSL;
109  }
113  boost::system::error_code getError() const {
114  return m_Ec;
115  }
119  std::string getClientIP() const {
120  return m_Socket.remote_endpoint().address().to_string();
121  }
125  std::string getClientPort() const {
126  return std::to_string(m_Socket.remote_endpoint().port());
127  }
131  http::request<http::string_body> getRequest() const {
132  return m_Request;
133  }
137  http::response<http::vector_body<char>> getResult() const {
138  return m_Result;
139  }
143  std::filesystem::path getDocRoot() const {
144  return m_DocRoot;
145  }
149  std::string getIndexFile() const {
150  return m_IndexFile;
151  }
155  std::string getServerString() const {
156  return m_ServerString;
157  }
161  std::map<std::string, std::string> getMimeTypes() const {
162  return m_MimeTypes;
163  }
168  void setResult(const http::response<http::vector_body<char>> &res) {
169  m_Result = res;
170  }
175  void setDocRoot(const std::filesystem::path& path) {
176  m_DocRoot = path;
177  }
182  void setIndexFile(const std::string& indx) {
183  m_IndexFile = indx;
184  }
189  void setServerString(const std::string& servstr) {
190  m_ServerString = servstr;
191  }
196  void addMimeTypes(const std::map<std::string, std::string>& mimeTypes) {
197  m_MimeTypes.insert(mimeTypes.begin(), mimeTypes.end());
198  }
202  void close() {
203  if(!m_Ec)
204  if(m_SSL)
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)));}}
206  else
207  {if(m_Socket.is_open()){m_Socket.shutdown(tcp::socket::shutdown_send, m_Ec);}}
208  }
209  using SPtr = std::shared_ptr<HTTPSession>;
210  using UPtr = std::unique_ptr<HTTPSession>;
211  using WPtr = std::weak_ptr<HTTPSession>;
212  private:
213  void on_read(boost::system::error_code ec, std::size_t bytes_transferred) {
214  boost::ignore_unused(bytes_transferred);
215  m_Ec = ec;
216  if(!ec)
217  {
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();
226  }
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();
235  }
236  else {
237  std::error_code fEc;
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)) {
243  path += "/";
244  path.append(m_IndexFile);
245  }
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();
254  }
255  else if(fEc)
256  {
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();
264  }
265  else {
266  try{
267  std::vector<char> sfile = FileSystem::LoadFile(path);
268  if(m_Request.method() == http::verb::head)
269  m_Result = {http::status::ok, m_Request.version()};
270  else {
271  m_Result = {http::status::ok, m_Request.version()};
272  m_Result.body() = sfile;
273  }
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();
279  }
280  catch(const ExceptionBase& e)
281  {
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();
289  }
290  catch(...)
291  {
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();
299  }
300  }
301  }
302  }
303  else if(ec == http::error::end_of_stream)
304  return close();
305  else {
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();
313  }
314  m_Buffer.consume(m_Buffer.size()); // clear buffer
315  notify(); // notify all subscribed observers
316  if(m_SSL)
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())));
318  else
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())));
320  }
321  void do_read() {
322  m_Request = {};
323  if(m_SSL)
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)));}}
325  else
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)));}}
327  }
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();
332  else if(ec)
333  throw HTTPServerException("Write: " + ec.message());
334  if(close)
335  return this->close();
336  m_Result.clear();
337  do_read();
338  }
339  void on_handshake(boost::system::error_code ec) {
340  if(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())));
349  }
350  else{
351  do_read();
352  }
353  }
354  void on_shutdown(boost::system::error_code ec) {
355  if(ec && ec != boost::beast::errc::broken_pipe)
356  throw HTTPServerException("Shutdown: " + ec.message());
357  }
358  tcp::socket m_Socket;
359  std::filesystem::path m_DocRoot;
360  bool m_SSL;
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;
371  };
372 
432  class HTTPServer : public Observable<HTTPServer>
433  {
434  public:
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())),
452  m_DocRoot(docRoot),
453  m_NumThreads(numThreads),
454  m_SSL(ssl),
455  m_Cert(cert),
456  m_Key(key),
457  m_MimeTypes(mimeTypes),
458  m_IndexFile(indexFile),
459  m_ServerString(serverString),
460  m_Ioc(numThreads),
461  m_Acceptor(m_Ioc),
462  m_Socket(m_Ioc)
463  {
464  boost::system::error_code ec;
465  m_Acceptor.open(m_Endpoint.protocol(), ec);
466  if(ec)
467  throw HTTPServerException("Open: " + ec.message());
468  m_Acceptor.set_option(boost::asio::socket_base::reuse_address(true));
469  if(ec)
470  throw HTTPServerException("Set Option: " + ec.message());
471  m_Acceptor.bind(m_Endpoint, ec);
472  if(ec)
473  throw HTTPServerException("Bind: " + ec.message());
474  m_Acceptor.listen(boost::asio::socket_base::max_listen_connections, ec);
475  if(ec)
476  throw HTTPServerException("Listen: " + ec.message());
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");
510  }
516  void run() {
517  if(!m_Acceptor.is_open()) return;
518  do_accept();
519 
520  m_Threads.reserve(m_NumThreads);
521  for(auto i = m_NumThreads; i > 0; --i)
522  m_Threads.emplace_back([this]{ m_Ioc.run();});
523  }
527  HTTPSession::SPtr getSession() const {
528  return m_NewSession;
529  }
533  bool getSSL() const {
534  return m_SSL;
535  }
539  std::filesystem::path getCert() const {
540  return m_Cert;
541  }
545  std::filesystem::path getKey() const {
546  return m_Key;
547  }
551  std::filesystem::path getDocRoot() const {
552  return m_DocRoot;
553  }
557  std::string getIndexFile() const {
558  return m_IndexFile;
559  }
563  std::string getServerString() const {
564  return m_ServerString;
565  }
569  std::map<std::string, std::string> getMimeTypes() const {
570  return m_MimeTypes;
571  }
576  void setDocRoot(const std::filesystem::path& path) {
577  m_DocRoot = path;
578  }
583  void setIndexFile(const std::string& indx) {
584  m_IndexFile = indx;
585  }
590  void setServerString(const std::string& servstr) {
591  m_ServerString = servstr;
592  }
597  void addMimeTypes(const std::map<std::string, std::string>& mimeTypes) {
598  m_MimeTypes.insert(mimeTypes.begin(), mimeTypes.end());
599  }
600  using SPtr = std::shared_ptr<HTTPServer>;
601  using UPtr = std::unique_ptr<HTTPServer>;
602  using WPtr = std::weak_ptr<HTTPServer>;
603  private:
604  void do_accept() {
605  m_Acceptor.async_accept(m_Socket, std::bind(&HTTPServer::on_accept, this->shared_from_this(), std::placeholders::_1));
606  }
607  void on_accept(boost::system::error_code ec) {
608  if(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);
611  m_NewSession->run();
612  notify(); // notify all subscribed observers
613  do_accept(); // Accept another connection
614  }
615  tcp::endpoint m_Endpoint;
616  boost::asio::io_context m_Ioc;
617  std::filesystem::path m_DocRoot;
618  size_t m_NumThreads;
619  bool m_SSL;
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;
629  };
630 }
631 #endif //SUPPORTLIB_HTTPSERVER_H
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
Definition: JSON.h:123