mirror of
https://github.com/RGBCube/serenity
synced 2025-07-28 08:37:35 +00:00
Services: Add a WebSocket service
The WebSocket service isolates communication with a WebSocket to its own isolated process. Similar to other isolating services, it has its own user and group.
This commit is contained in:
parent
c11ca9df33
commit
62ed26164b
16 changed files with 582 additions and 1 deletions
|
@ -16,4 +16,5 @@ add_subdirectory(Taskbar)
|
|||
add_subdirectory(TelnetServer)
|
||||
add_subdirectory(WebContent)
|
||||
add_subdirectory(WebServer)
|
||||
add_subdirectory(WebSocket)
|
||||
add_subdirectory(WindowServer)
|
||||
|
|
12
Userland/Services/WebSocket/CMakeLists.txt
Normal file
12
Userland/Services/WebSocket/CMakeLists.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
compile_ipc(WebSocketServer.ipc WebSocketServerEndpoint.h)
|
||||
compile_ipc(WebSocketClient.ipc WebSocketClientEndpoint.h)
|
||||
|
||||
set(SOURCES
|
||||
ClientConnection.cpp
|
||||
main.cpp
|
||||
WebSocketClientEndpoint.h
|
||||
WebSocketServerEndpoint.h
|
||||
)
|
||||
|
||||
serenity_bin(WebSocket)
|
||||
target_link_libraries(WebSocket LibCore LibIPC LibWebSocket)
|
144
Userland/Services/WebSocket/ClientConnection.cpp
Normal file
144
Userland/Services/WebSocket/ClientConnection.cpp
Normal file
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Dex♪ <dexes.ttp@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/Badge.h>
|
||||
#include <LibWebSocket/ConnectionInfo.h>
|
||||
#include <LibWebSocket/Message.h>
|
||||
#include <WebSocket/ClientConnection.h>
|
||||
#include <WebSocket/WebSocketClientEndpoint.h>
|
||||
|
||||
namespace WebSocket {
|
||||
|
||||
static HashMap<int, RefPtr<ClientConnection>> s_connections;
|
||||
|
||||
ClientConnection::ClientConnection(NonnullRefPtr<Core::LocalSocket> socket, int client_id)
|
||||
: IPC::ClientConnection<WebSocketClientEndpoint, WebSocketServerEndpoint>(*this, move(socket), client_id)
|
||||
{
|
||||
s_connections.set(client_id, *this);
|
||||
}
|
||||
|
||||
ClientConnection::~ClientConnection()
|
||||
{
|
||||
}
|
||||
|
||||
void ClientConnection::die()
|
||||
{
|
||||
s_connections.remove(client_id());
|
||||
if (s_connections.is_empty())
|
||||
Core::EventLoop::current().quit(0);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WebSocketServer::GreetResponse> ClientConnection::handle(const Messages::WebSocketServer::Greet&)
|
||||
{
|
||||
return make<Messages::WebSocketServer::GreetResponse>();
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WebSocketServer::ConnectResponse> ClientConnection::handle(const Messages::WebSocketServer::Connect& message)
|
||||
{
|
||||
const auto& url = message.url();
|
||||
if (!url.is_valid()) {
|
||||
dbgln("WebSocket::Connect: Invalid URL requested: '{}'", url);
|
||||
return make<Messages::WebSocketServer::ConnectResponse>(-1);
|
||||
}
|
||||
|
||||
ConnectionInfo connection_info(url);
|
||||
connection_info.set_origin(message.origin());
|
||||
connection_info.set_protocols(message.protocols());
|
||||
connection_info.set_extensions(message.extensions());
|
||||
|
||||
Vector<ConnectionInfo::Header> headers;
|
||||
for (const auto& header : message.additional_request_headers().entries()) {
|
||||
headers.append({ header.key, header.value });
|
||||
}
|
||||
connection_info.set_headers(headers);
|
||||
|
||||
VERIFY(m_connection_ids < NumericLimits<i32>::max());
|
||||
auto id = ++m_connection_ids;
|
||||
auto connection = WebSocket::create(move(connection_info));
|
||||
connection->on_open = [this, id]() {
|
||||
did_connect(id);
|
||||
};
|
||||
connection->on_message = [this, id](auto message) {
|
||||
did_receive_message(id, move(message));
|
||||
};
|
||||
connection->on_error = [this, id](auto message) {
|
||||
did_error(id, (i32)message);
|
||||
};
|
||||
connection->on_close = [this, id](u16 code, String reason, bool was_clean) {
|
||||
did_close(id, code, move(reason), was_clean);
|
||||
};
|
||||
|
||||
connection->start();
|
||||
m_connections.set(id, move(connection));
|
||||
return make<Messages::WebSocketServer::ConnectResponse>(id);
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WebSocketServer::ReadyStateResponse> ClientConnection::handle(const Messages::WebSocketServer::ReadyState& message)
|
||||
{
|
||||
RefPtr<WebSocket> connection = m_connections.get(message.connection_id()).value_or({});
|
||||
if (connection) {
|
||||
return make<Messages::WebSocketServer::ReadyStateResponse>((u32)connection->ready_state());
|
||||
}
|
||||
return make<Messages::WebSocketServer::ReadyStateResponse>((u32)ReadyState::Closed);
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WebSocketServer::Send& message)
|
||||
{
|
||||
RefPtr<WebSocket> connection = m_connections.get(message.connection_id()).value_or({});
|
||||
if (connection && connection->ready_state() == ReadyState::Open) {
|
||||
Message websocket_message(message.data(), message.is_text());
|
||||
connection->send(websocket_message);
|
||||
}
|
||||
}
|
||||
|
||||
void ClientConnection::handle(const Messages::WebSocketServer::Close& message)
|
||||
{
|
||||
RefPtr<WebSocket> connection = m_connections.get(message.connection_id()).value_or({});
|
||||
if (connection && connection->ready_state() == ReadyState::Open)
|
||||
connection->close(message.code(), message.reason());
|
||||
}
|
||||
|
||||
OwnPtr<Messages::WebSocketServer::SetCertificateResponse> ClientConnection::handle(const Messages::WebSocketServer::SetCertificate& message)
|
||||
{
|
||||
RefPtr<WebSocket> connection = m_connections.get(message.connection_id()).value_or({});
|
||||
bool success = false;
|
||||
if (connection) {
|
||||
// NO OP here
|
||||
// connection->set_certificate(message.certificate(), message.key());
|
||||
success = true;
|
||||
}
|
||||
return make<Messages::WebSocketServer::SetCertificateResponse>(success);
|
||||
}
|
||||
|
||||
void ClientConnection::did_connect(i32 connection_id)
|
||||
{
|
||||
post_message(Messages::WebSocketClient::Connected(connection_id));
|
||||
}
|
||||
|
||||
void ClientConnection::did_receive_message(i32 connection_id, Message message)
|
||||
{
|
||||
post_message(Messages::WebSocketClient::Received(connection_id, message.is_text(), message.data()));
|
||||
}
|
||||
|
||||
void ClientConnection::did_error(i32 connection_id, i32 message)
|
||||
{
|
||||
post_message(Messages::WebSocketClient::Errored(connection_id, message));
|
||||
}
|
||||
|
||||
void ClientConnection::did_close(i32 connection_id, u16 code, String reason, bool was_clean)
|
||||
{
|
||||
post_message(Messages::WebSocketClient::Closed(connection_id, code, reason, was_clean));
|
||||
deferred_invoke([this, connection_id] {
|
||||
m_connections.remove(connection_id);
|
||||
});
|
||||
}
|
||||
|
||||
void ClientConnection::did_request_certificates(i32 connection_id)
|
||||
{
|
||||
post_message(Messages::WebSocketClient::CertificateRequested(connection_id));
|
||||
}
|
||||
|
||||
}
|
46
Userland/Services/WebSocket/ClientConnection.h
Normal file
46
Userland/Services/WebSocket/ClientConnection.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Dex♪ <dexes.ttp@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/HashMap.h>
|
||||
#include <LibIPC/ClientConnection.h>
|
||||
#include <LibWebSocket/WebSocket.h>
|
||||
#include <WebSocket/WebSocketClientEndpoint.h>
|
||||
#include <WebSocket/WebSocketServerEndpoint.h>
|
||||
|
||||
namespace WebSocket {
|
||||
|
||||
class ClientConnection final
|
||||
: public IPC::ClientConnection<WebSocketClientEndpoint, WebSocketServerEndpoint>
|
||||
, public WebSocketServerEndpoint {
|
||||
C_OBJECT(ClientConnection);
|
||||
|
||||
public:
|
||||
explicit ClientConnection(NonnullRefPtr<Core::LocalSocket>, int client_id);
|
||||
~ClientConnection() override;
|
||||
|
||||
virtual void die() override;
|
||||
|
||||
private:
|
||||
virtual OwnPtr<Messages::WebSocketServer::GreetResponse> handle(const Messages::WebSocketServer::Greet&) override;
|
||||
virtual OwnPtr<Messages::WebSocketServer::ConnectResponse> handle(const Messages::WebSocketServer::Connect&) override;
|
||||
virtual OwnPtr<Messages::WebSocketServer::ReadyStateResponse> handle(const Messages::WebSocketServer::ReadyState&) override;
|
||||
virtual void handle(const Messages::WebSocketServer::Send&) override;
|
||||
virtual void handle(const Messages::WebSocketServer::Close&) override;
|
||||
virtual OwnPtr<Messages::WebSocketServer::SetCertificateResponse> handle(const Messages::WebSocketServer::SetCertificate&) override;
|
||||
|
||||
void did_connect(i32);
|
||||
void did_receive_message(i32, Message);
|
||||
void did_error(i32, i32 message);
|
||||
void did_close(i32, u16 code, String reason, bool was_clean);
|
||||
void did_request_certificates(i32);
|
||||
|
||||
i32 m_connection_ids { 0 };
|
||||
HashMap<i32, RefPtr<WebSocket>> m_connections;
|
||||
};
|
||||
|
||||
}
|
11
Userland/Services/WebSocket/WebSocketClient.ipc
Normal file
11
Userland/Services/WebSocket/WebSocketClient.ipc
Normal file
|
@ -0,0 +1,11 @@
|
|||
endpoint WebSocketClient
|
||||
{
|
||||
// Connection API
|
||||
Connected(i32 connection_id) =|
|
||||
Received(i32 connection_id, bool is_text, ByteBuffer data) =|
|
||||
Errored(i32 connection_id, i32 message) =|
|
||||
Closed(i32 connection_id, u16 code, String reason, bool clean) =|
|
||||
|
||||
// Certificate requests
|
||||
CertificateRequested(i32 connection_id) =|
|
||||
}
|
13
Userland/Services/WebSocket/WebSocketServer.ipc
Normal file
13
Userland/Services/WebSocket/WebSocketServer.ipc
Normal file
|
@ -0,0 +1,13 @@
|
|||
endpoint WebSocketServer
|
||||
{
|
||||
// Basic protocol
|
||||
Greet() => ()
|
||||
|
||||
// Connection API
|
||||
Connect(URL url, String origin, Vector<String> protocols, Vector<String> extensions, IPC::Dictionary additional_request_headers) => (i32 connection_id)
|
||||
ReadyState(i32 connection_id) => (u32 ready_state)
|
||||
Send(i32 connection_id, bool is_text, ByteBuffer data) =|
|
||||
Close(i32 connection_id, u16 code, String reason) =|
|
||||
|
||||
SetCertificate(i32 connection_id, String certificate, String key) => (bool success)
|
||||
}
|
42
Userland/Services/WebSocket/main.cpp
Normal file
42
Userland/Services/WebSocket/main.cpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Dex♪ <dexes.ttp@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/LocalServer.h>
|
||||
#include <LibIPC/ClientConnection.h>
|
||||
#include <LibTLS/Certificate.h>
|
||||
#include <WebSocket/ClientConnection.h>
|
||||
|
||||
int main(int, char**)
|
||||
{
|
||||
if (pledge("stdio inet accept unix rpath cpath fattr sendfd recvfd", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Ensure the certificates are read out here.
|
||||
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
|
||||
|
||||
Core::EventLoop event_loop;
|
||||
// FIXME: Establish a connection to LookupServer and then drop "unix"?
|
||||
if (pledge("stdio inet accept unix sendfd recvfd", nullptr) < 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
if (unveil("/tmp/portal/lookup", "rw") < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
if (unveil(nullptr, nullptr) < 0) {
|
||||
perror("unveil");
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto socket = Core::LocalSocket::take_over_accepted_socket_from_system_server();
|
||||
VERIFY(socket);
|
||||
IPC::new_client_connection<WebSocket::ClientConnection>(socket.release_nonnull(), 1);
|
||||
return event_loop.exec();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue