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