blob: f7c818e6e16310213d4293d2f4f4ebefb3e0d0cd [file] [log] [blame]
Ed Tanous7045c8d2017-04-03 10:04:37 -07001#pragma once
Ed Tanous911ac312017-08-15 09:37:42 -07002#include <array>
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +02003#include <async_resp.hpp>
Ed Tanous911ac312017-08-15 09:37:42 -07004#include <boost/algorithm/string/predicate.hpp>
Ed Tanous609145a2018-09-05 16:27:36 -07005#include <boost/asio/buffer.hpp>
Ed Tanous1b0044b2018-08-03 14:30:05 -07006#include <boost/beast/websocket.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07007#include <functional>
8
Ed Tanousc94ad492019-10-10 15:39:33 -07009#include "http_request.h"
Ed Tanous1b0044b2018-08-03 14:30:05 -070010
11#ifdef BMCWEB_ENABLE_SSL
12#include <boost/beast/websocket/ssl.hpp>
13#endif
Ed Tanous7045c8d2017-04-03 10:04:37 -070014
Ed Tanous1abe55e2018-09-05 08:30:59 -070015namespace crow
16{
17namespace websocket
18{
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020019
Ed Tanous1abe55e2018-09-05 08:30:59 -070020struct Connection : std::enable_shared_from_this<Connection>
21{
22 public:
Jan Sowinskiee52ae12020-01-09 16:28:32 +000023 explicit Connection(const crow::Request& reqIn) :
24 req(reqIn.req), userdataPtr(nullptr){};
Ed Tanous911ac312017-08-15 09:37:42 -070025
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050026 virtual void sendBinary(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070027 virtual void sendBinary(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050028 virtual void sendText(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070029 virtual void sendText(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050030 virtual void close(const std::string_view msg = "quit") = 0;
Ed Tanousceac6f72018-12-02 11:58:47 -080031 virtual boost::asio::io_context& get_io_context() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070032 virtual ~Connection() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -070033
Ed Tanous1abe55e2018-09-05 08:30:59 -070034 void userdata(void* u)
35 {
36 userdataPtr = u;
37 }
38 void* userdata()
39 {
40 return userdataPtr;
41 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070042
Jan Sowinskiee52ae12020-01-09 16:28:32 +000043 boost::beast::http::request<boost::beast::http::string_body> req;
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020044 crow::Response res;
Ed Tanous911ac312017-08-15 09:37:42 -070045
Ed Tanous1abe55e2018-09-05 08:30:59 -070046 private:
47 void* userdataPtr;
Ed Tanous7045c8d2017-04-03 10:04:37 -070048};
49
Ed Tanous1abe55e2018-09-05 08:30:59 -070050template <typename Adaptor> class ConnectionImpl : public Connection
51{
52 public:
53 ConnectionImpl(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000054 const crow::Request& reqIn, Adaptor adaptorIn,
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020055 std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
56 open_handler,
Ed Tanous1abe55e2018-09-05 08:30:59 -070057 std::function<void(Connection&, const std::string&, bool)>
58 message_handler,
59 std::function<void(Connection&, const std::string&)> close_handler,
60 std::function<void(Connection&)> error_handler) :
Jan Sowinskiee52ae12020-01-09 16:28:32 +000061 Connection(reqIn),
Adriana Kobylak69664cf2019-03-12 10:49:48 -050062 ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088),
Ed Tanousceac6f72018-12-02 11:58:47 -080063 openHandler(std::move(open_handler)),
Ed Tanous55c7b7a2018-05-22 15:27:24 -070064 messageHandler(std::move(message_handler)),
65 closeHandler(std::move(close_handler)),
Ed Tanous1abe55e2018-09-05 08:30:59 -070066 errorHandler(std::move(error_handler))
67 {
68 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070069 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070070
Ed Tanousceac6f72018-12-02 11:58:47 -080071 boost::asio::io_context& get_io_context() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070072 {
Ed Tanous271584a2019-07-09 16:24:22 -070073 return static_cast<boost::asio::io_context&>(
74 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070075 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070076
Ed Tanous1abe55e2018-09-05 08:30:59 -070077 void start()
78 {
79 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070080
Ed Tanousfe5b2162019-05-22 14:28:16 -070081 using bf = boost::beast::http::field;
82
Jan Sowinskiee52ae12020-01-09 16:28:32 +000083 std::string_view protocol = req[bf::sec_websocket_protocol];
Ed Tanous7045c8d2017-04-03 10:04:37 -070084
Ed Tanous1abe55e2018-09-05 08:30:59 -070085 // Perform the websocket upgrade
86 ws.async_accept_ex(
Jan Sowinskiee52ae12020-01-09 16:28:32 +000087 req,
Ed Tanous1abe55e2018-09-05 08:30:59 -070088 [protocol{std::string(protocol)}](
89 boost::beast::websocket::response_type& m) {
90 if (!protocol.empty())
91 {
Ed Tanousfe5b2162019-05-22 14:28:16 -070092 m.insert(bf::sec_websocket_protocol, protocol);
Ed Tanous1abe55e2018-09-05 08:30:59 -070093 }
Ed Tanousfe5b2162019-05-22 14:28:16 -070094
95 m.insert(bf::strict_transport_security, "max-age=31536000; "
96 "includeSubdomains; "
97 "preload");
98 m.insert(bf::pragma, "no-cache");
99 m.insert(bf::cache_control, "no-Store,no-Cache");
100 m.insert("Content-Security-Policy", "default-src 'self'");
101 m.insert("X-XSS-Protection", "1; "
102 "mode=block");
103 m.insert("X-Content-Type-Options", "nosniff");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700104 },
105 [this, self(shared_from_this())](boost::system::error_code ec) {
106 if (ec)
107 {
108 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
109 return;
110 }
111 acceptDone();
112 });
113 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700114
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500115 void sendBinary(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700116 {
117 ws.binary(true);
118 outBuffer.emplace_back(msg);
119 doWrite();
120 }
121
122 void sendBinary(std::string&& msg) override
123 {
124 ws.binary(true);
125 outBuffer.emplace_back(std::move(msg));
126 doWrite();
127 }
128
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500129 void sendText(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700130 {
131 ws.text(true);
132 outBuffer.emplace_back(msg);
133 doWrite();
134 }
135
136 void sendText(std::string&& msg) override
137 {
138 ws.text(true);
139 outBuffer.emplace_back(std::move(msg));
140 doWrite();
141 }
142
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500143 void close(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700144 {
145 ws.async_close(
146 boost::beast::websocket::close_code::normal,
Ed Tanous43b761d2019-02-13 20:10:56 -0800147 [self(shared_from_this())](boost::system::error_code ec) {
Ed Tanousceac6f72018-12-02 11:58:47 -0800148 if (ec == boost::asio::error::operation_aborted)
149 {
150 return;
151 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700152 if (ec)
153 {
154 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
155 return;
156 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700157 });
158 }
159
160 void acceptDone()
161 {
162 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
163
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200164 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
165 res, [this, self(shared_from_this())]() { doRead(); });
166
167 asyncResp->res.result(boost::beast::http::status::ok);
168
Ed Tanous1abe55e2018-09-05 08:30:59 -0700169 if (openHandler)
170 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200171 openHandler(*this, asyncResp);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700172 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700173 }
174
175 void doRead()
176 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500177 ws.async_read(inBuffer,
178 [this, self(shared_from_this())](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700179 boost::beast::error_code ec, std::size_t bytes_read) {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500180 if (ec)
181 {
182 if (ec != boost::beast::websocket::error::closed)
183 {
184 BMCWEB_LOG_ERROR << "doRead error " << ec;
185 }
186 if (closeHandler)
187 {
188 std::string_view reason = ws.reason().reason;
189 closeHandler(*this, std::string(reason));
190 }
191 return;
192 }
193 if (messageHandler)
194 {
195 messageHandler(*this, inString, ws.got_text());
196 }
197 inBuffer.consume(bytes_read);
198 inString.clear();
199 doRead();
200 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700201 }
202
203 void doWrite()
204 {
205 // If we're already doing a write, ignore the request, it will be picked
206 // up when the current write is complete
207 if (doingWrite)
208 {
209 return;
210 }
211
212 if (outBuffer.empty())
213 {
214 // Done for now
215 return;
216 }
217 doingWrite = true;
218 ws.async_write(
219 boost::asio::buffer(outBuffer.front()),
220 [this, self(shared_from_this())](boost::beast::error_code ec,
221 std::size_t bytes_written) {
222 doingWrite = false;
223 outBuffer.erase(outBuffer.begin());
224 if (ec == boost::beast::websocket::error::closed)
225 {
226 // Do nothing here. doRead handler will call the
227 // closeHandler.
228 close("Write error");
229 return;
230 }
231 if (ec)
232 {
233 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
234 return;
235 }
236 doWrite();
237 });
238 }
239
240 private:
Ed Tanousceac6f72018-12-02 11:58:47 -0800241 boost::beast::websocket::stream<Adaptor> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700242
Ed Tanous609145a2018-09-05 16:27:36 -0700243 std::string inString;
244 boost::asio::dynamic_string_buffer<std::string::value_type,
245 std::string::traits_type,
246 std::string::allocator_type>
247 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700248 std::vector<std::string> outBuffer;
249 bool doingWrite = false;
250
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200251 std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
252 openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700253 std::function<void(Connection&, const std::string&, bool)> messageHandler;
254 std::function<void(Connection&, const std::string&)> closeHandler;
255 std::function<void(Connection&)> errorHandler;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700256};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700257} // namespace websocket
258} // namespace crow