blob: 7166c826928e88257a9a08d72bfc81408cd02add [file] [log] [blame]
Ed Tanous7045c8d2017-04-03 10:04:37 -07001#pragma once
Ed Tanous911ac312017-08-15 09:37:42 -07002#include <array>
Ed Tanous911ac312017-08-15 09:37:42 -07003#include <boost/algorithm/string/predicate.hpp>
Ed Tanous609145a2018-09-05 16:27:36 -07004#include <boost/asio/buffer.hpp>
Ed Tanous1b0044b2018-08-03 14:30:05 -07005#include <boost/beast/websocket.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07006#include <functional>
7
8#include "crow/http_request.h"
Ed Tanous1b0044b2018-08-03 14:30:05 -07009
10#ifdef BMCWEB_ENABLE_SSL
11#include <boost/beast/websocket/ssl.hpp>
12#endif
Ed Tanous7045c8d2017-04-03 10:04:37 -070013
Ed Tanous1abe55e2018-09-05 08:30:59 -070014namespace crow
15{
16namespace websocket
17{
18struct Connection : std::enable_shared_from_this<Connection>
19{
20 public:
Ed Tanous271584a2019-07-09 16:24:22 -070021 explicit Connection(const crow::Request& reqIn) :
22 req(reqIn), userdataPtr(nullptr){};
Ed Tanous911ac312017-08-15 09:37:42 -070023
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050024 virtual void sendBinary(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070025 virtual void sendBinary(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050026 virtual void sendText(const std::string_view msg) = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070027 virtual void sendText(std::string&& msg) = 0;
Adriana Kobylakae29b8c2019-04-24 11:19:18 -050028 virtual void close(const std::string_view msg = "quit") = 0;
Ed Tanousceac6f72018-12-02 11:58:47 -080029 virtual boost::asio::io_context& get_io_context() = 0;
Ed Tanous1abe55e2018-09-05 08:30:59 -070030 virtual ~Connection() = default;
Ed Tanous7045c8d2017-04-03 10:04:37 -070031
Ed Tanous1abe55e2018-09-05 08:30:59 -070032 void userdata(void* u)
33 {
34 userdataPtr = u;
35 }
36 void* userdata()
37 {
38 return userdataPtr;
39 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070040
Ed Tanous1abe55e2018-09-05 08:30:59 -070041 crow::Request req;
Ed Tanous911ac312017-08-15 09:37:42 -070042
Ed Tanous1abe55e2018-09-05 08:30:59 -070043 private:
44 void* userdataPtr;
Ed Tanous7045c8d2017-04-03 10:04:37 -070045};
46
Ed Tanous1abe55e2018-09-05 08:30:59 -070047template <typename Adaptor> class ConnectionImpl : public Connection
48{
49 public:
50 ConnectionImpl(
Ed Tanous271584a2019-07-09 16:24:22 -070051 const crow::Request& reqIn, Adaptor adaptorIn,
Ed Tanous1abe55e2018-09-05 08:30:59 -070052 std::function<void(Connection&)> open_handler,
53 std::function<void(Connection&, const std::string&, bool)>
54 message_handler,
55 std::function<void(Connection&, const std::string&)> close_handler,
56 std::function<void(Connection&)> error_handler) :
Ed Tanous271584a2019-07-09 16:24:22 -070057 Connection(reqIn),
Adriana Kobylak69664cf2019-03-12 10:49:48 -050058 ws(std::move(adaptorIn)), inString(), inBuffer(inString, 131088),
Ed Tanousceac6f72018-12-02 11:58:47 -080059 openHandler(std::move(open_handler)),
Ed Tanous55c7b7a2018-05-22 15:27:24 -070060 messageHandler(std::move(message_handler)),
61 closeHandler(std::move(close_handler)),
Ed Tanous1abe55e2018-09-05 08:30:59 -070062 errorHandler(std::move(error_handler))
63 {
64 BMCWEB_LOG_DEBUG << "Creating new connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070065 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070066
Ed Tanousceac6f72018-12-02 11:58:47 -080067 boost::asio::io_context& get_io_context() override
Ed Tanous1abe55e2018-09-05 08:30:59 -070068 {
Ed Tanous271584a2019-07-09 16:24:22 -070069 return static_cast<boost::asio::io_context&>(
70 ws.get_executor().context());
Ed Tanous911ac312017-08-15 09:37:42 -070071 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070072
Ed Tanous1abe55e2018-09-05 08:30:59 -070073 void start()
74 {
75 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070076
Ed Tanousfe5b2162019-05-22 14:28:16 -070077 using bf = boost::beast::http::field;
78
79 std::string_view protocol =
80 req.getHeaderValue(bf::sec_websocket_protocol);
Ed Tanous7045c8d2017-04-03 10:04:37 -070081
Ed Tanous1abe55e2018-09-05 08:30:59 -070082 // Perform the websocket upgrade
83 ws.async_accept_ex(
84 req.req,
85 [protocol{std::string(protocol)}](
86 boost::beast::websocket::response_type& m) {
87 if (!protocol.empty())
88 {
Ed Tanousfe5b2162019-05-22 14:28:16 -070089 m.insert(bf::sec_websocket_protocol, protocol);
Ed Tanous1abe55e2018-09-05 08:30:59 -070090 }
Ed Tanousfe5b2162019-05-22 14:28:16 -070091
92 m.insert(bf::strict_transport_security, "max-age=31536000; "
93 "includeSubdomains; "
94 "preload");
95 m.insert(bf::pragma, "no-cache");
96 m.insert(bf::cache_control, "no-Store,no-Cache");
97 m.insert("Content-Security-Policy", "default-src 'self'");
98 m.insert("X-XSS-Protection", "1; "
99 "mode=block");
100 m.insert("X-Content-Type-Options", "nosniff");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700101 },
102 [this, self(shared_from_this())](boost::system::error_code ec) {
103 if (ec)
104 {
105 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
106 return;
107 }
108 acceptDone();
109 });
110 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700111
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500112 void sendBinary(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700113 {
114 ws.binary(true);
115 outBuffer.emplace_back(msg);
116 doWrite();
117 }
118
119 void sendBinary(std::string&& msg) override
120 {
121 ws.binary(true);
122 outBuffer.emplace_back(std::move(msg));
123 doWrite();
124 }
125
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500126 void sendText(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700127 {
128 ws.text(true);
129 outBuffer.emplace_back(msg);
130 doWrite();
131 }
132
133 void sendText(std::string&& msg) override
134 {
135 ws.text(true);
136 outBuffer.emplace_back(std::move(msg));
137 doWrite();
138 }
139
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500140 void close(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700141 {
142 ws.async_close(
143 boost::beast::websocket::close_code::normal,
Ed Tanous43b761d2019-02-13 20:10:56 -0800144 [self(shared_from_this())](boost::system::error_code ec) {
Ed Tanousceac6f72018-12-02 11:58:47 -0800145 if (ec == boost::asio::error::operation_aborted)
146 {
147 return;
148 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700149 if (ec)
150 {
151 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
152 return;
153 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700154 });
155 }
156
157 void acceptDone()
158 {
159 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
160
161 if (openHandler)
162 {
163 openHandler(*this);
164 }
165 doRead();
166 }
167
168 void doRead()
169 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500170 ws.async_read(inBuffer,
171 [this, self(shared_from_this())](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700172 boost::beast::error_code ec, std::size_t bytes_read) {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500173 if (ec)
174 {
175 if (ec != boost::beast::websocket::error::closed)
176 {
177 BMCWEB_LOG_ERROR << "doRead error " << ec;
178 }
179 if (closeHandler)
180 {
181 std::string_view reason = ws.reason().reason;
182 closeHandler(*this, std::string(reason));
183 }
184 return;
185 }
186 if (messageHandler)
187 {
188 messageHandler(*this, inString, ws.got_text());
189 }
190 inBuffer.consume(bytes_read);
191 inString.clear();
192 doRead();
193 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700194 }
195
196 void doWrite()
197 {
198 // If we're already doing a write, ignore the request, it will be picked
199 // up when the current write is complete
200 if (doingWrite)
201 {
202 return;
203 }
204
205 if (outBuffer.empty())
206 {
207 // Done for now
208 return;
209 }
210 doingWrite = true;
211 ws.async_write(
212 boost::asio::buffer(outBuffer.front()),
213 [this, self(shared_from_this())](boost::beast::error_code ec,
214 std::size_t bytes_written) {
215 doingWrite = false;
216 outBuffer.erase(outBuffer.begin());
217 if (ec == boost::beast::websocket::error::closed)
218 {
219 // Do nothing here. doRead handler will call the
220 // closeHandler.
221 close("Write error");
222 return;
223 }
224 if (ec)
225 {
226 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
227 return;
228 }
229 doWrite();
230 });
231 }
232
233 private:
Ed Tanousceac6f72018-12-02 11:58:47 -0800234 boost::beast::websocket::stream<Adaptor> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700235
Ed Tanous609145a2018-09-05 16:27:36 -0700236 std::string inString;
237 boost::asio::dynamic_string_buffer<std::string::value_type,
238 std::string::traits_type,
239 std::string::allocator_type>
240 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700241 std::vector<std::string> outBuffer;
242 bool doingWrite = false;
243
244 std::function<void(Connection&)> openHandler;
245 std::function<void(Connection&, const std::string&, bool)> messageHandler;
246 std::function<void(Connection&, const std::string&)> closeHandler;
247 std::function<void(Connection&)> errorHandler;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700248};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700249} // namespace websocket
250} // namespace crow