| Ed Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 1 | #pragma once | 
| Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 2 | #include <array> | 
| Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 3 | #include <boost/algorithm/string/predicate.hpp> | 
| Ed Tanous | 1b0044b | 2018-08-03 14:30:05 -0700 | [diff] [blame] | 4 | #include <boost/beast/websocket.hpp> | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 5 | #include <functional> | 
 | 6 |  | 
 | 7 | #include "crow/http_request.h" | 
 | 8 | #include "crow/socket_adaptors.h" | 
| Ed Tanous | 1b0044b | 2018-08-03 14:30:05 -0700 | [diff] [blame] | 9 |  | 
 | 10 | #ifdef BMCWEB_ENABLE_SSL | 
 | 11 | #include <boost/beast/websocket/ssl.hpp> | 
 | 12 | #endif | 
| Ed Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 13 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 14 | namespace crow | 
 | 15 | { | 
 | 16 | namespace websocket | 
 | 17 | { | 
 | 18 | struct Connection : std::enable_shared_from_this<Connection> | 
 | 19 | { | 
 | 20 |   public: | 
 | 21 |     explicit Connection(const crow::Request& req) : | 
 | 22 |         req(req), userdataPtr(nullptr){}; | 
| Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 23 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 24 |     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 Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 31 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 32 |     void userdata(void* u) | 
 | 33 |     { | 
 | 34 |         userdataPtr = u; | 
 | 35 |     } | 
 | 36 |     void* userdata() | 
 | 37 |     { | 
 | 38 |         return userdataPtr; | 
 | 39 |     } | 
| Ed Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 40 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 41 |     crow::Request req; | 
| Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 42 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 43 |   private: | 
 | 44 |     void* userdataPtr; | 
| Ed Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 45 | }; | 
 | 46 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 47 | template <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 Tanous | 55c7b7a | 2018-05-22 15:27:24 -0700 | [diff] [blame] | 59 |         openHandler(std::move(open_handler)), | 
 | 60 |         messageHandler(std::move(message_handler)), | 
 | 61 |         closeHandler(std::move(close_handler)), | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 62 |         errorHandler(std::move(error_handler)) | 
 | 63 |     { | 
 | 64 |         BMCWEB_LOG_DEBUG << "Creating new connection " << this; | 
| Ed Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 65 |     } | 
| Ed Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 66 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 67 |     boost::asio::io_service& getIoService() override | 
 | 68 |     { | 
 | 69 |         return adaptor.getIoService(); | 
| Ed Tanous | 911ac31 | 2017-08-15 09:37:42 -0700 | [diff] [blame] | 70 |     } | 
| Ed Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 71 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 72 |     void start() | 
 | 73 |     { | 
 | 74 |         BMCWEB_LOG_DEBUG << "starting connection " << this; | 
| Ed Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 75 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 76 |         boost::string_view protocol = req.getHeaderValue( | 
 | 77 |             boost::beast::http::field::sec_websocket_protocol); | 
| Ed Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 78 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 79 |         // 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 Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 99 |  | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 100 |     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 Tanous | 7045c8d | 2017-04-03 10:04:37 -0700 | [diff] [blame] | 238 | }; | 
| Ed Tanous | 1abe55e | 2018-09-05 08:30:59 -0700 | [diff] [blame^] | 239 | } // namespace websocket | 
 | 240 | } // namespace crow |