2020-12-26 14:12:05 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2018-08-24 20:54:23 -07:00
|
|
|
|
|
|
|
|
#include "wpi/WebSocketServer.h"
|
|
|
|
|
|
2020-12-28 10:12:52 -08:00
|
|
|
#include <utility>
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
#include "wpi/StringExtras.h"
|
|
|
|
|
#include "wpi/fmt/raw_ostream.h"
|
2018-08-24 20:54:23 -07:00
|
|
|
#include "wpi/raw_uv_ostream.h"
|
|
|
|
|
#include "wpi/uv/Buffer.h"
|
|
|
|
|
#include "wpi/uv/Stream.h"
|
|
|
|
|
|
|
|
|
|
using namespace wpi;
|
|
|
|
|
|
|
|
|
|
WebSocketServerHelper::WebSocketServerHelper(HttpParser& req) {
|
2021-06-06 16:13:58 -07:00
|
|
|
req.header.connect([this](std::string_view name, std::string_view value) {
|
|
|
|
|
if (equals_lower(name, "host")) {
|
2018-08-24 20:54:23 -07:00
|
|
|
m_gotHost = true;
|
2021-06-06 16:13:58 -07:00
|
|
|
} else if (equals_lower(name, "upgrade")) {
|
|
|
|
|
if (equals_lower(value, "websocket")) {
|
2020-12-28 12:58:06 -08:00
|
|
|
m_websocket = true;
|
|
|
|
|
}
|
2021-06-06 16:13:58 -07:00
|
|
|
} else if (equals_lower(name, "sec-websocket-key")) {
|
2018-08-24 20:54:23 -07:00
|
|
|
m_key = value;
|
2021-06-06 16:13:58 -07:00
|
|
|
} else if (equals_lower(name, "sec-websocket-version")) {
|
2018-08-24 20:54:23 -07:00
|
|
|
m_version = value;
|
2021-06-06 16:13:58 -07:00
|
|
|
} else if (equals_lower(name, "sec-websocket-protocol")) {
|
2018-08-24 20:54:23 -07:00
|
|
|
// Protocols are comma delimited, repeated headers add to list
|
2021-06-06 16:13:58 -07:00
|
|
|
SmallVector<std::string_view, 2> protocols;
|
|
|
|
|
split(value, protocols, ",", -1, false);
|
2018-08-24 20:54:23 -07:00
|
|
|
for (auto protocol : protocols) {
|
2021-06-06 16:13:58 -07:00
|
|
|
protocol = trim(protocol);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!protocol.empty()) {
|
|
|
|
|
m_protocols.emplace_back(protocol);
|
|
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
req.headersComplete.connect([&req, this](bool) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (req.IsUpgrade() && IsUpgrade()) {
|
|
|
|
|
upgrade();
|
|
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
std::pair<bool, std::string_view> WebSocketServerHelper::MatchProtocol(
|
|
|
|
|
ArrayRef<std::string_view> protocols) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (protocols.empty() && m_protocols.empty()) {
|
2021-06-06 16:13:58 -07:00
|
|
|
return {true, {}};
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
for (auto protocol : protocols) {
|
|
|
|
|
for (auto&& clientProto : m_protocols) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (protocol == clientProto) {
|
2021-06-06 16:13:58 -07:00
|
|
|
return {true, protocol};
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
}
|
|
|
|
|
}
|
2021-06-06 16:13:58 -07:00
|
|
|
return {false, {}};
|
2018-08-24 20:54:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WebSocketServer::WebSocketServer(uv::Stream& stream,
|
2021-06-06 16:13:58 -07:00
|
|
|
ArrayRef<std::string_view> protocols,
|
2020-12-28 10:12:52 -08:00
|
|
|
ServerOptions options, const private_init&)
|
2018-08-24 20:54:23 -07:00
|
|
|
: m_stream{stream},
|
|
|
|
|
m_helper{m_req},
|
|
|
|
|
m_protocols{protocols.begin(), protocols.end()},
|
2020-12-28 10:12:52 -08:00
|
|
|
m_options{std::move(options)} {
|
2018-08-24 20:54:23 -07:00
|
|
|
// Header handling
|
2021-06-06 16:13:58 -07:00
|
|
|
m_req.header.connect([this](std::string_view name, std::string_view value) {
|
|
|
|
|
if (equals_lower(name, "host")) {
|
2018-08-24 20:54:23 -07:00
|
|
|
if (m_options.checkHost) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!m_options.checkHost(value)) {
|
|
|
|
|
Abort(401, "Unrecognized Host");
|
|
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
2021-06-06 16:13:58 -07:00
|
|
|
m_req.url.connect([this](std::string_view name) {
|
2018-08-24 20:54:23 -07:00
|
|
|
if (m_options.checkUrl) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!m_options.checkUrl(name)) {
|
|
|
|
|
Abort(404, "Not Found");
|
|
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
m_req.headersComplete.connect([this](bool) {
|
|
|
|
|
// We only accept websocket connections
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!m_helper.IsUpgrade() || !m_req.IsUpgrade()) {
|
2018-08-24 20:54:23 -07:00
|
|
|
Abort(426, "Upgrade Required");
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Handle upgrade event
|
|
|
|
|
m_helper.upgrade.connect([this] {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (m_aborted) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
|
|
|
|
|
// Negotiate sub-protocol
|
2021-06-06 16:13:58 -07:00
|
|
|
SmallVector<std::string_view, 2> protocols{m_protocols.begin(),
|
|
|
|
|
m_protocols.end()};
|
|
|
|
|
std::string_view protocol = m_helper.MatchProtocol(protocols).second;
|
2018-08-24 20:54:23 -07:00
|
|
|
|
|
|
|
|
// Disconnect our header reader
|
2018-12-09 01:36:36 -08:00
|
|
|
m_dataConn.disconnect();
|
2018-08-24 20:54:23 -07:00
|
|
|
|
|
|
|
|
// Accepting the stream may destroy this (as it replaces the stream user
|
|
|
|
|
// data), so grab a shared pointer first.
|
|
|
|
|
auto self = shared_from_this();
|
|
|
|
|
|
|
|
|
|
// Accept the upgrade
|
|
|
|
|
auto ws = m_helper.Accept(m_stream, protocol);
|
|
|
|
|
|
|
|
|
|
// Connect the websocket open event to our connected event.
|
2021-06-06 16:13:58 -07:00
|
|
|
ws->open.connect_extended(
|
|
|
|
|
[self, s = ws.get()](auto conn, std::string_view) {
|
|
|
|
|
self->connected(self->m_req.GetUrl(), *s);
|
|
|
|
|
conn.disconnect(); // one-shot
|
|
|
|
|
});
|
2018-08-24 20:54:23 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Set up stream
|
|
|
|
|
stream.StartRead();
|
2018-12-09 01:36:36 -08:00
|
|
|
m_dataConn =
|
2018-08-24 20:54:23 -07:00
|
|
|
stream.data.connect_connection([this](uv::Buffer& buf, size_t size) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (m_aborted) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2021-06-06 16:13:58 -07:00
|
|
|
m_req.Execute(std::string_view{buf.base, size});
|
2020-12-28 12:58:06 -08:00
|
|
|
if (m_req.HasError()) {
|
|
|
|
|
Abort(400, "Bad Request");
|
|
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
});
|
2018-12-09 01:36:36 -08:00
|
|
|
m_errorConn =
|
|
|
|
|
stream.error.connect_connection([this](uv::Error) { m_stream.Close(); });
|
|
|
|
|
m_endConn = stream.end.connect_connection([this] { m_stream.Close(); });
|
2018-08-24 20:54:23 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::shared_ptr<WebSocketServer> WebSocketServer::Create(
|
2021-06-06 16:13:58 -07:00
|
|
|
uv::Stream& stream, ArrayRef<std::string_view> protocols,
|
2018-08-24 20:54:23 -07:00
|
|
|
const ServerOptions& options) {
|
|
|
|
|
auto server = std::make_shared<WebSocketServer>(stream, protocols, options,
|
|
|
|
|
private_init{});
|
|
|
|
|
stream.SetData(server);
|
|
|
|
|
return server;
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-06 16:13:58 -07:00
|
|
|
void WebSocketServer::Abort(uint16_t code, std::string_view reason) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (m_aborted) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
m_aborted = true;
|
|
|
|
|
|
|
|
|
|
// Build response
|
|
|
|
|
SmallVector<uv::Buffer, 4> bufs;
|
|
|
|
|
raw_uv_ostream os{bufs, 1024};
|
|
|
|
|
|
|
|
|
|
// Handle unsupported version
|
2021-06-06 16:13:58 -07:00
|
|
|
fmt::print(os, "HTTP/1.1 {} {}\r\n", code, reason);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (code == 426) {
|
|
|
|
|
os << "Upgrade: WebSocket\r\n";
|
|
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
os << "\r\n";
|
|
|
|
|
m_stream.Write(bufs, [this](auto bufs, uv::Error) {
|
2020-12-28 12:58:06 -08:00
|
|
|
for (auto& buf : bufs) {
|
|
|
|
|
buf.Deallocate();
|
|
|
|
|
}
|
2018-08-24 20:54:23 -07:00
|
|
|
m_stream.Shutdown([this] { m_stream.Close(); });
|
|
|
|
|
});
|
|
|
|
|
}
|