blob: 80d536a19d944f50e5fb494ab757162a5ecff175 [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:
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020023 explicit Connection(const crow::Request& reqIn, crow::Response& res) :
Ed Tanous271584a2019-07-09 16:24:22 -070024 req(reqIn), 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
Ed Tanous1abe55e2018-09-05 08:30:59 -070043 crow::Request 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(
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020054 const crow::Request& reqIn, crow::Response& res, Adaptor adaptorIn,
55 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) :
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +020061 Connection(reqIn, res),
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
83 std::string_view protocol =
84 req.getHeaderValue(bf::sec_websocket_protocol);
Ed Tanous7045c8d2017-04-03 10:04:37 -070085
Ed Tanous1abe55e2018-09-05 08:30:59 -070086 // Perform the websocket upgrade
87 ws.async_accept_ex(
88 req.req,
89 [protocol{std::string(protocol)}](
90 boost::beast::websocket::response_type& m) {
91 if (!protocol.empty())
92 {
Ed Tanousfe5b2162019-05-22 14:28:16 -070093 m.insert(bf::sec_websocket_protocol, protocol);
Ed Tanous1abe55e2018-09-05 08:30:59 -070094 }
Ed Tanousfe5b2162019-05-22 14:28:16 -070095
96 m.insert(bf::strict_transport_security, "max-age=31536000; "
97 "includeSubdomains; "
98 "preload");
99 m.insert(bf::pragma, "no-cache");
100 m.insert(bf::cache_control, "no-Store,no-Cache");
101 m.insert("Content-Security-Policy", "default-src 'self'");
102 m.insert("X-XSS-Protection", "1; "
103 "mode=block");
104 m.insert("X-Content-Type-Options", "nosniff");
Ed Tanous1abe55e2018-09-05 08:30:59 -0700105 },
106 [this, self(shared_from_this())](boost::system::error_code ec) {
107 if (ec)
108 {
109 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
110 return;
111 }
112 acceptDone();
113 });
114 }
Ed Tanous7045c8d2017-04-03 10:04:37 -0700115
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500116 void sendBinary(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700117 {
118 ws.binary(true);
119 outBuffer.emplace_back(msg);
120 doWrite();
121 }
122
123 void sendBinary(std::string&& msg) override
124 {
125 ws.binary(true);
126 outBuffer.emplace_back(std::move(msg));
127 doWrite();
128 }
129
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500130 void sendText(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700131 {
132 ws.text(true);
133 outBuffer.emplace_back(msg);
134 doWrite();
135 }
136
137 void sendText(std::string&& msg) override
138 {
139 ws.text(true);
140 outBuffer.emplace_back(std::move(msg));
141 doWrite();
142 }
143
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500144 void close(const std::string_view msg) override
Ed Tanous1abe55e2018-09-05 08:30:59 -0700145 {
146 ws.async_close(
147 boost::beast::websocket::close_code::normal,
Ed Tanous43b761d2019-02-13 20:10:56 -0800148 [self(shared_from_this())](boost::system::error_code ec) {
Ed Tanousceac6f72018-12-02 11:58:47 -0800149 if (ec == boost::asio::error::operation_aborted)
150 {
151 return;
152 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700153 if (ec)
154 {
155 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
156 return;
157 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700158 });
159 }
160
161 void acceptDone()
162 {
163 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
164
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200165 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(
166 res, [this, self(shared_from_this())]() { doRead(); });
167
168 asyncResp->res.result(boost::beast::http::status::ok);
169
Ed Tanous1abe55e2018-09-05 08:30:59 -0700170 if (openHandler)
171 {
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200172 openHandler(*this, asyncResp);
Ed Tanous1abe55e2018-09-05 08:30:59 -0700173 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700174 }
175
176 void doRead()
177 {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500178 ws.async_read(inBuffer,
179 [this, self(shared_from_this())](
Ed Tanous1abe55e2018-09-05 08:30:59 -0700180 boost::beast::error_code ec, std::size_t bytes_read) {
Adriana Kobylakae29b8c2019-04-24 11:19:18 -0500181 if (ec)
182 {
183 if (ec != boost::beast::websocket::error::closed)
184 {
185 BMCWEB_LOG_ERROR << "doRead error " << ec;
186 }
187 if (closeHandler)
188 {
189 std::string_view reason = ws.reason().reason;
190 closeHandler(*this, std::string(reason));
191 }
192 return;
193 }
194 if (messageHandler)
195 {
196 messageHandler(*this, inString, ws.got_text());
197 }
198 inBuffer.consume(bytes_read);
199 inString.clear();
200 doRead();
201 });
Ed Tanous1abe55e2018-09-05 08:30:59 -0700202 }
203
204 void doWrite()
205 {
206 // If we're already doing a write, ignore the request, it will be picked
207 // up when the current write is complete
208 if (doingWrite)
209 {
210 return;
211 }
212
213 if (outBuffer.empty())
214 {
215 // Done for now
216 return;
217 }
218 doingWrite = true;
219 ws.async_write(
220 boost::asio::buffer(outBuffer.front()),
221 [this, self(shared_from_this())](boost::beast::error_code ec,
222 std::size_t bytes_written) {
223 doingWrite = false;
224 outBuffer.erase(outBuffer.begin());
225 if (ec == boost::beast::websocket::error::closed)
226 {
227 // Do nothing here. doRead handler will call the
228 // closeHandler.
229 close("Write error");
230 return;
231 }
232 if (ec)
233 {
234 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
235 return;
236 }
237 doWrite();
238 });
239 }
240
241 private:
Ed Tanousceac6f72018-12-02 11:58:47 -0800242 boost::beast::websocket::stream<Adaptor> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700243
Ed Tanous609145a2018-09-05 16:27:36 -0700244 std::string inString;
245 boost::asio::dynamic_string_buffer<std::string::value_type,
246 std::string::traits_type,
247 std::string::allocator_type>
248 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700249 std::vector<std::string> outBuffer;
250 bool doingWrite = false;
251
Iwona Klimaszewskac0a1c8a2019-07-12 18:26:38 +0200252 std::function<void(Connection&, std::shared_ptr<bmcweb::AsyncResp>)>
253 openHandler;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700254 std::function<void(Connection&, const std::string&, bool)> messageHandler;
255 std::function<void(Connection&, const std::string&)> closeHandler;
256 std::function<void(Connection&)> errorHandler;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700257};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700258} // namespace websocket
259} // namespace crow