blob: b545b5abcc7be095927ec7ab01c52e3cd3ea7b84 [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:
21 explicit Connection(const crow::Request& req) :
22 req(req), userdataPtr(nullptr){};
Ed Tanous911ac312017-08-15 09:37:42 -070023
Ed Tanous1abe55e2018-09-05 08:30:59 -070024 virtual void sendBinary(const boost::beast::string_view msg) = 0;
25 virtual void sendBinary(std::string&& msg) = 0;
26 virtual void sendText(const boost::beast::string_view msg) = 0;
27 virtual void sendText(std::string&& msg) = 0;
28 virtual void close(const boost::beast::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 Tanousceac6f72018-12-02 11:58:47 -080051 const crow::Request& req, 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 Tanousceac6f72018-12-02 11:58:47 -080057 inString(),
58 inBuffer(inString, 4096), ws(std::move(adaptorIn)), Connection(req),
59 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 Tanousceac6f72018-12-02 11:58:47 -080069 return ws.get_executor().context();
Ed Tanous911ac312017-08-15 09:37:42 -070070 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070071
Ed Tanous1abe55e2018-09-05 08:30:59 -070072 void start()
73 {
74 BMCWEB_LOG_DEBUG << "starting connection " << this;
Ed Tanous7045c8d2017-04-03 10:04:37 -070075
Ed Tanous1abe55e2018-09-05 08:30:59 -070076 boost::string_view protocol = req.getHeaderValue(
77 boost::beast::http::field::sec_websocket_protocol);
Ed Tanous7045c8d2017-04-03 10:04:37 -070078
Ed Tanous1abe55e2018-09-05 08:30:59 -070079 // Perform the websocket upgrade
80 ws.async_accept_ex(
81 req.req,
82 [protocol{std::string(protocol)}](
83 boost::beast::websocket::response_type& m) {
84 if (!protocol.empty())
85 {
86 m.insert(boost::beast::http::field::sec_websocket_protocol,
87 protocol);
88 }
89 },
90 [this, self(shared_from_this())](boost::system::error_code ec) {
91 if (ec)
92 {
93 BMCWEB_LOG_ERROR << "Error in ws.async_accept " << ec;
94 return;
95 }
96 acceptDone();
97 });
98 }
Ed Tanous7045c8d2017-04-03 10:04:37 -070099
Ed Tanous1abe55e2018-09-05 08:30:59 -0700100 void sendBinary(const boost::beast::string_view msg) override
101 {
102 ws.binary(true);
103 outBuffer.emplace_back(msg);
104 doWrite();
105 }
106
107 void sendBinary(std::string&& msg) override
108 {
109 ws.binary(true);
110 outBuffer.emplace_back(std::move(msg));
111 doWrite();
112 }
113
114 void sendText(const boost::beast::string_view msg) override
115 {
116 ws.text(true);
117 outBuffer.emplace_back(msg);
118 doWrite();
119 }
120
121 void sendText(std::string&& msg) override
122 {
123 ws.text(true);
124 outBuffer.emplace_back(std::move(msg));
125 doWrite();
126 }
127
128 void close(const boost::beast::string_view msg) override
129 {
130 ws.async_close(
131 boost::beast::websocket::close_code::normal,
132 [this, self(shared_from_this())](boost::system::error_code ec) {
Ed Tanousceac6f72018-12-02 11:58:47 -0800133 if (ec == boost::asio::error::operation_aborted)
134 {
135 return;
136 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700137 if (ec)
138 {
139 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
140 return;
141 }
Ed Tanous1abe55e2018-09-05 08:30:59 -0700142 });
143 }
144
145 void acceptDone()
146 {
147 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
148
149 if (openHandler)
150 {
151 openHandler(*this);
152 }
153 doRead();
154 }
155
156 void doRead()
157 {
158 ws.async_read(
159 inBuffer, [this, self(shared_from_this())](
160 boost::beast::error_code ec, std::size_t bytes_read) {
161 if (ec)
162 {
163 if (ec != boost::beast::websocket::error::closed)
164 {
165 BMCWEB_LOG_ERROR << "doRead error " << ec;
166 }
167 if (closeHandler)
168 {
169 boost::beast::string_view reason = ws.reason().reason;
170 closeHandler(*this, std::string(reason));
171 }
172 return;
173 }
174 if (messageHandler)
175 {
Ed Tanous609145a2018-09-05 16:27:36 -0700176 messageHandler(*this, inString, ws.got_text());
Ed Tanous1abe55e2018-09-05 08:30:59 -0700177 }
Ed Tanous609145a2018-09-05 16:27:36 -0700178 inBuffer.consume(bytes_read);
179 inString.clear();
Ed Tanous1abe55e2018-09-05 08:30:59 -0700180 doRead();
181 });
182 }
183
184 void doWrite()
185 {
186 // If we're already doing a write, ignore the request, it will be picked
187 // up when the current write is complete
188 if (doingWrite)
189 {
190 return;
191 }
192
193 if (outBuffer.empty())
194 {
195 // Done for now
196 return;
197 }
198 doingWrite = true;
199 ws.async_write(
200 boost::asio::buffer(outBuffer.front()),
201 [this, self(shared_from_this())](boost::beast::error_code ec,
202 std::size_t bytes_written) {
203 doingWrite = false;
204 outBuffer.erase(outBuffer.begin());
205 if (ec == boost::beast::websocket::error::closed)
206 {
207 // Do nothing here. doRead handler will call the
208 // closeHandler.
209 close("Write error");
210 return;
211 }
212 if (ec)
213 {
214 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
215 return;
216 }
217 doWrite();
218 });
219 }
220
221 private:
Ed Tanousceac6f72018-12-02 11:58:47 -0800222 boost::beast::websocket::stream<Adaptor> ws;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700223
Ed Tanous609145a2018-09-05 16:27:36 -0700224 std::string inString;
225 boost::asio::dynamic_string_buffer<std::string::value_type,
226 std::string::traits_type,
227 std::string::allocator_type>
228 inBuffer;
Ed Tanous1abe55e2018-09-05 08:30:59 -0700229 std::vector<std::string> outBuffer;
230 bool doingWrite = false;
231
232 std::function<void(Connection&)> openHandler;
233 std::function<void(Connection&, const std::string&, bool)> messageHandler;
234 std::function<void(Connection&, const std::string&)> closeHandler;
235 std::function<void(Connection&)> errorHandler;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700236};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700237} // namespace websocket
238} // namespace crow