From c11ef442fb17371df0e066bc8b02a4c08db8d31e Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Sun, 5 Jul 2020 22:10:30 -0700 Subject: [PATCH] [wpiutil] Add HttpWebSocketServerConnection (#2505) This is a derived class of HttpServerConnection that implements the WebSocket upgrade pieces. This combination is pretty common so is worth refactoring here. --- .../wpi/HttpWebSocketServerConnection.h | 94 +++++++++++++++++++ .../wpi/HttpWebSocketServerConnection.inl | 54 +++++++++++ .../cpp/HttpWebSocketServerConnectionTest.cpp | 28 ++++++ 3 files changed, 176 insertions(+) create mode 100644 wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.h create mode 100644 wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inl create mode 100644 wpiutil/src/test/native/cpp/HttpWebSocketServerConnectionTest.cpp diff --git a/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.h b/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.h new file mode 100644 index 0000000000..d662471bc6 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.h @@ -0,0 +1,94 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2020 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_H_ +#define WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_H_ + +#include +#include +#include + +#include "wpi/ArrayRef.h" +#include "wpi/HttpServerConnection.h" +#include "wpi/SmallVector.h" +#include "wpi/StringRef.h" +#include "wpi/WebSocket.h" +#include "wpi/WebSocketServer.h" +#include "wpi/uv/Stream.h" + +namespace wpi { + +/** + * A server-side HTTP connection that also accepts WebSocket upgrades. + * + * @tparam Derived derived class for std::enable_shared_from_this. + */ +template +class HttpWebSocketServerConnection + : public HttpServerConnection, + public std::enable_shared_from_this { + public: + /** + * Constructor. + * + * @param stream network stream + * @param protocols Acceptable subprotocols + */ + HttpWebSocketServerConnection(std::shared_ptr stream, + ArrayRef protocols); + + /** + * Constructor. + * + * @param stream network stream + * @param protocols Acceptable subprotocols + */ + HttpWebSocketServerConnection(std::shared_ptr stream, + std::initializer_list protocols) + : HttpWebSocketServerConnection( + stream, makeArrayRef(protocols.begin(), protocols.end())) {} + + protected: + /** + * Check that an incoming WebSocket upgrade is okay. This is called prior + * to accepting the upgrade (so prior to ProcessWsUpgrade()). + * + * The implementation should check other headers and return true if the + * WebSocket connection should be accepted. + * + * @param protocol negotiated subprotocol + */ + virtual bool IsValidWsUpgrade(StringRef protocol) { return true; } + + /** + * Process an incoming WebSocket upgrade. This is called after the header + * reader has been disconnected and the websocket has been accepted. + * + * The implementation should set up appropriate callbacks on the websocket + * object to continue communication. + * + * @note When a WebSocket upgrade occurs, the stream user data is replaced + * with the websocket, and the websocket user data points to "this". + * Replace the websocket user data with caution! + */ + virtual void ProcessWsUpgrade() = 0; + + /** + * WebSocket connection; not valid until ProcessWsUpgrade is called. + */ + WebSocket* m_websocket = nullptr; + + private: + WebSocketServerHelper m_helper; + SmallVector m_protocols; +}; + +} // namespace wpi + +#include "HttpWebSocketServerConnection.inl" + +#endif // WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_H_ diff --git a/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inl b/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inl new file mode 100644 index 0000000000..586f189b7c --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/HttpWebSocketServerConnection.inl @@ -0,0 +1,54 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2020 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INL_ +#define WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INL_ + +#include + +namespace wpi { + +template +HttpWebSocketServerConnection::HttpWebSocketServerConnection( + std::shared_ptr stream, ArrayRef protocols) + : HttpServerConnection{stream}, + m_helper{m_request}, + m_protocols{protocols.begin(), protocols.end()} { + // Handle upgrade event + m_helper.upgrade.connect([this] { + // Negotiate sub-protocol + SmallVector protocols{m_protocols.begin(), m_protocols.end()}; + StringRef protocol = m_helper.MatchProtocol(protocols).second; + + // Check that the upgrade is valid + if (!IsValidWsUpgrade(protocol)) return; + + // Disconnect HttpServerConnection header reader + m_dataConn.disconnect(); + m_messageCompleteConn.disconnect(); + + // Accepting the stream may destroy this (as it replaces the stream user + // data), so grab a shared pointer first. + auto self = this->shared_from_this(); + + // Accept the upgrade + auto ws = m_helper.Accept(m_stream, protocol); + + // Set this as the websocket user data to keep it around + ws->SetData(self); + + // Store in member + m_websocket = ws.get(); + + // Call derived class function + ProcessWsUpgrade(); + }); +} + +} // namespace wpi + +#endif // WPIUTIL_WPI_HTTPWEBSOCKETSERVERCONNECTION_INL_ diff --git a/wpiutil/src/test/native/cpp/HttpWebSocketServerConnectionTest.cpp b/wpiutil/src/test/native/cpp/HttpWebSocketServerConnectionTest.cpp new file mode 100644 index 0000000000..8022cae6dc --- /dev/null +++ b/wpiutil/src/test/native/cpp/HttpWebSocketServerConnectionTest.cpp @@ -0,0 +1,28 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2020 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "wpi/HttpWebSocketServerConnection.h" // NOLINT(build/include_order) + +#include + +namespace wpi { + +class HttpWebSocketServerConnectionTest + : public HttpWebSocketServerConnection { + public: + HttpWebSocketServerConnectionTest(std::shared_ptr stream, + ArrayRef protocols) + : HttpWebSocketServerConnection{stream, protocols} {} + + void ProcessRequest() override { ++gotRequest; } + void ProcessWsUpgrade() override { ++gotUpgrade; } + + int gotRequest = 0; + int gotUpgrade = 0; +}; + +} // namespace wpi