blob: f345223ebc1bdb55834ff69ae993b4654e7f8941 [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 Tanous1b0044b2018-08-03 14:30:05 -07004#include <boost/beast/websocket.hpp>
Ed Tanous1abe55e2018-09-05 08:30:59 -07005#include <functional>
6
7#include "crow/http_request.h"
8#include "crow/socket_adaptors.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;
29 virtual boost::asio::io_service& getIoService() = 0;
30 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(
51 const crow::Request& req, Adaptor&& adaptorIn,
52 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) :
57 adaptor(std::move(adaptorIn)),
58 ws(adaptor.socket()), Connection(req),
Ed Tanous55c7b7a2018-05-22 15:27:24 -070059 openHandler(std::move(open_handler)),
60 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 Tanous1abe55e2018-09-05 08:30:59 -070067 boost::asio::io_service& getIoService() override
68 {
69 return adaptor.getIoService();
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) {
133 if (ec)
134 {
135 BMCWEB_LOG_ERROR << "Error closing websocket " << ec;
136 return;
137 }
138 adaptor.close();
139 });
140 }
141
142 void acceptDone()
143 {
144 BMCWEB_LOG_DEBUG << "Websocket accepted connection";
145
146 if (openHandler)
147 {
148 openHandler(*this);
149 }
150 doRead();
151 }
152
153 void doRead()
154 {
155 ws.async_read(
156 inBuffer, [this, self(shared_from_this())](
157 boost::beast::error_code ec, std::size_t bytes_read) {
158 if (ec)
159 {
160 if (ec != boost::beast::websocket::error::closed)
161 {
162 BMCWEB_LOG_ERROR << "doRead error " << ec;
163 }
164 if (closeHandler)
165 {
166 boost::beast::string_view reason = ws.reason().reason;
167 closeHandler(*this, std::string(reason));
168 }
169 return;
170 }
171 if (messageHandler)
172 {
173 // TODO(Ed) There must be a more direct way to do this
174 // conversion, but I can't find it at the moment. It should
175 // get optimized away
176 boost::asio::const_buffer cb =
177 boost::beast::buffers_front(inBuffer.data());
178 boost::beast::string_view message(
179 reinterpret_cast<char const*>(cb.data()), cb.size());
180 messageHandler(*this, std::string(message), ws.got_text());
181 }
182 doRead();
183 });
184 }
185
186 void doWrite()
187 {
188 // If we're already doing a write, ignore the request, it will be picked
189 // up when the current write is complete
190 if (doingWrite)
191 {
192 return;
193 }
194
195 if (outBuffer.empty())
196 {
197 // Done for now
198 return;
199 }
200 doingWrite = true;
201 ws.async_write(
202 boost::asio::buffer(outBuffer.front()),
203 [this, self(shared_from_this())](boost::beast::error_code ec,
204 std::size_t bytes_written) {
205 doingWrite = false;
206 outBuffer.erase(outBuffer.begin());
207 if (ec == boost::beast::websocket::error::closed)
208 {
209 // Do nothing here. doRead handler will call the
210 // closeHandler.
211 close("Write error");
212 return;
213 }
214 if (ec)
215 {
216 BMCWEB_LOG_ERROR << "Error in ws.async_write " << ec;
217 return;
218 }
219 doWrite();
220 });
221 }
222
223 private:
224 Adaptor adaptor;
225
226 boost::beast::websocket::stream<
227 std::add_lvalue_reference_t<typename Adaptor::streamType>>
228 ws;
229
230 boost::beast::flat_static_buffer<4096> inBuffer;
231 std::vector<std::string> outBuffer;
232 bool doingWrite = false;
233
234 std::function<void(Connection&)> openHandler;
235 std::function<void(Connection&, const std::string&, bool)> messageHandler;
236 std::function<void(Connection&, const std::string&)> closeHandler;
237 std::function<void(Connection&)> errorHandler;
Ed Tanous7045c8d2017-04-03 10:04:37 -0700238};
Ed Tanous1abe55e2018-09-05 08:30:59 -0700239} // namespace websocket
240} // namespace crow