tgbot-cpp
Loading...
Searching...
No Matches
HttpServer.h
Go to the documentation of this file.
1#pragma once
2
3#include <boost/asio.hpp>
4#include <boost/asio/signal_set.hpp>
5#include <boost/beast/core/flat_buffer.hpp>
6#include <boost/beast/http.hpp>
7
8#include <csignal>
9#include <exception>
10#include <functional>
11#include <initializer_list>
12#include <memory>
13#include <optional>
14#include <stdexcept>
15#include <string>
16#include <string_view>
17#include <unordered_map>
18#include <utility>
19
20namespace TgBot {
21
27template<typename Protocol>
29public:
31 = std::function<std::string(const std::string&, const std::unordered_map<std::string, std::string>&)>;
32 using ErrorHandler = std::function<void(const std::exception&)>;
34 HttpServer(const typename boost::asio::basic_socket_acceptor<Protocol>::endpoint_type& endpoint,
35 ServerHandler handler)
36 : _acceptor(_ioContext, endpoint)
37 , _handler(std::move(handler)) {
38 }
39
44 */
45 void start(const ErrorHandler& errorHandler = { }, std::initializer_list<int> signals = { SIGINT, SIGTERM }) {
46 _errorHandler = errorHandler;
47 try {
48 _ioContext.restart();
49 if (signals.size() != 0) {
50 _signals.emplace(_ioContext);
51 for (const int signal : signals) {
52 _signals->add(signal);
53 }
54 _signals->async_wait([this](const boost::system::error_code& error, int) {
55 if (!error) {
56 stop();
57 }
58 });
59 }
60 accept();
61 _ioContext.run();
62 } catch (...) {
63 _signals.reset();
64 _errorHandler = { };
65 throw;
66 }
67 _signals.reset();
68 _errorHandler = { };
69 }
70
73 */
74 void stop() {
75 boost::asio::post(_ioContext, [this] {
76 boost::system::error_code error;
77 if (_signals) {
78 _signals->cancel(error);
79 }
80 _acceptor.close(error);
81 });
82 }
83
84private:
85 class Connection : public std::enable_shared_from_this<Connection> {
86 public:
87 Connection(boost::asio::basic_stream_socket<Protocol> socket, ServerHandler handler, ErrorHandler errorHandler)
88 : _socket(std::move(socket))
89 , _handler(std::move(handler))
90 , _errorHandler(std::move(errorHandler)) {
91 }
92
93 void start() {
94 auto self = this->shared_from_this();
95 boost::beast::http::async_read(_socket, _buffer, _request,
96 [self](const boost::system::error_code& error, std::size_t) {
97 self->handleRequest(error);
98 });
99 }
100
101 private:
102 void handleRequest(const boost::system::error_code& error) {
103 namespace http = boost::beast::http;
104
105 if (error == http::error::end_of_stream) {
106 close();
107 return;
108 }
109 if (error) {
110 reportError(_errorHandler, "error reading HTTP request: " + error.message());
111 close();
112 return;
113 }
114
115 std::unordered_map<std::string, std::string> headers;
116 headers.emplace("_method", std::string(_request.method_string()));
117 headers.emplace("_path", std::string(_request.target()));
118 for (const auto& field : _request) {
119 headers.emplace(std::string(field.name_string()), std::string(field.value()));
120 }
121
122 _response = { http::status::ok, _request.version() };
123 _response.set(http::field::content_type, "text/plain");
124 _response.keep_alive(false);
125 try {
126 _response.body() = _handler(_request.body(), headers);
127 } catch (const std::exception& exception) {
128 reportError(_errorHandler, "error handling HTTP request: " + std::string(exception.what()));
129 _response.result(http::status::internal_server_error);
130 _response.body() = "Internal server error";
131 }
132 _response.prepare_payload();
133
134 auto self = this->shared_from_this();
135 http::async_write(_socket, _response, [self](const boost::system::error_code& writeError, std::size_t) {
136 if (writeError) {
137 reportError(self->_errorHandler, "error writing HTTP response: " + writeError.message());
138 }
139 self->close();
140 });
141 }
142
143 void close() {
144 boost::system::error_code error;
145 _socket.shutdown(boost::asio::socket_base::shutdown_both, error);
146 _socket.close(error);
147 }
148
149 boost::asio::basic_stream_socket<Protocol> _socket;
150 const ServerHandler _handler;
151 const ErrorHandler _errorHandler;
152 boost::beast::flat_buffer _buffer;
153 boost::beast::http::request<boost::beast::http::string_body> _request;
154 boost::beast::http::response<boost::beast::http::string_body> _response;
155 };
156
157 void accept() {
158 _acceptor.async_accept(
159 [this](const boost::system::error_code& error, boost::asio::basic_stream_socket<Protocol> socket) {
160 if (error == boost::asio::error::operation_aborted) {
161 return;
162 }
163 if (error) {
164 reportError(_errorHandler, "error accepting HTTP connection: " + error.message());
165 if (_acceptor.is_open()) {
166 accept();
167 }
168 } else {
169 accept();
170 std::make_shared<Connection>(std::move(socket), _handler, _errorHandler)->start();
171 }
172 });
173 }
174
175 static void reportError(const ErrorHandler& errorHandler, std::string_view message) {
176 const std::runtime_error error { std::string(message) };
177 if (errorHandler) {
178 errorHandler(error);
179 }
180 }
181
182 boost::asio::io_context _ioContext;
183 std::optional<boost::asio::signal_set> _signals;
184 boost::asio::basic_socket_acceptor<Protocol> _acceptor;
185 const ServerHandler _handler;
186 ErrorHandler _errorHandler;
187};
188
189} // namespace TgBot
HttpServer(const typename boost::asio::basic_socket_acceptor< Protocol >::endpoint_type &endpoint, ServerHandler handler)
Definition HttpServer.h:33
std::function< void(const std::exception &)> ErrorHandler
Definition HttpServer.h:31
void start(const ErrorHandler &errorHandler={ }, std::initializer_list< int > signals={ SIGINT, SIGTERM })
Starts receiving connections until stop() is called or one of the configured signals is received.
Definition HttpServer.h:44
void stop()
Stops receiving new connections. Connections already accepted are completed.
Definition HttpServer.h:73
std::function< std::string(const std::string &, const std::unordered_map< std::string, std::string > &)> ServerHandler
Definition HttpServer.h:30
Definition Api.h:18