Expand description
§Bevy Simplenet
Provides a bi-directional server/client channel implemented over websockets. This crate is suitable for user authentication, talking to a matchmaking service, communicating between micro-services, games that don’t have strict latency requirements, etc.
- Client/server channel includes one-shot messages and a request/response API.
- Client message statuses can be tracked.
- Clients automatically work on native and WASM targets.
- Clients can be authenticated by the server.
- Provides optional server TLS.
Check out the example for a demonstration of how to build a Bevy client using this crate.
Check out bevy_simplenet_events for an event-based framework for networking that builds on this crate.
§Features
default
: includesbevy
,client
,server
featuresbevy
: derivesResource
onClient
andServer
client
: enables clients (native and WASM targets)server
: enables servers (native-only targets)tls-rustls
: enables TLS for servers viarustls
tls-openssl
: enables TLS for servers viaOpenSSL
§WASM
On WASM targets the client backend will not update while any other tasks are running. You must either build an IO-oriented application that naturally spends a lot of time polling tasks, or manually release the main thread periodically (e.g. with web_sys::Window::set_timeout_with_callback_and_timeout_and_arguments_0()
). For Bevy apps the latter happens automatically at the end of every app update/tick (see the bevy::app::ScheduleRunnerPlugin
implementation).
§Usage notes
- Servers and clients must be created with enfync runtimes. The backend is ezsockets.
- A client’s
AuthRequest
type must match the corresponding server’sAuthenticator
type. - Client ids are defined by clients via their
AuthRequest
when connecting to a server. Connections will be rejected if an id is already connected. - Client connect messages will be cloned for all reconnect attempts, so they should be treated as static data.
- Server or client messages may fail to send if the underlying connection is broken. Clients can use the signals returned from
Client::send()
andClient::request()
to track the status of a message. Client request results will always be emitted byClient::next()
. Message tracking is not available for servers. - Tracing levels assume the server is trusted and clients are not trusted.
§Example
An example using bevy
to manage client and server resources.
§Setup
Common
Specify all the message types in a ChannelPack
. Messages are automatically serialized and deserialized, so concrete types are required.
/// Clients send these when connecting to the server.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestConnectMsg(pub String);
/// Clients can send these at any time.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestClientMsg(pub u64);
/// Client requests are special messages that expect a response from the server.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestClientRequest(pub u64);
/// Servers can send these at any time.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestServerMsg(pub u64);
/// Servers send these in response to client requests.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TestServerResponse(pub u64);
#[derive(Debug, Clone)]
pub struct TestChannel;
impl ChannelPack for TestChannel
{
type ConnectMsg = TestConnectMsg;
type ClientMsg = TestClientMsg;
type ClientRequest = TestClientRequest;
type ServerMsg = TestServerMsg;
type ServerResponse = TestServerResponse;
}
Server
Prepare to make servers. We need a separate ServerFactory
to embed the channel’s protocol version in a centralized location.
fn server_factory() -> ServerFactory<TestChannel>
{
// It is recommended to make server/client factories with baked-in protocol versions (e.g.
// with env!("CARGO_PKG_VERSION")).
ServerFactory::<TestChannel>::new("test")
}
Make a server and insert it into an app.
fn setup_server(mut commands: Commands)
{
let server = server_factory().new_server(
enfync::builtin::native::TokioHandle::default(),
"127.0.0.1:0",
AcceptorConfig::Default,
Authenticator::None,
ServerConfig::default(),
);
commands.insert_resource(server);
}
Client
Prepare to make clients. We need a separate ClientFactory
to embed the channel’s protocol version, as with the server factory.
fn client_factory() -> ClientFactory<TestChannel>
{
// You must use the same protocol version string as the server factory.
ClientFactory::<TestChannel>::new("test")
}
Make a client and insert it into an app.
fn setup_client(mut commands: Commands)
{
let client_id = 0u128;
let client = client_factory().new_client(
enfync::builtin::Handle::default(), //automatically selects native/WASM runtime
server.url(),
AuthRequest::None{ client_id },
ClientConfig::default(),
TestConnectMsg(String::from("hello"))
);
commands.insert_resource(client);
}
§Sending from the client
Send a message. The send
method returns a MessageSignal
that can be used to track the message status.
fn send_client_message(client: Res<Client<TestChannel>>)
{
let message_signal = client.send(TestClientMsg(42));
}
Send a request. The request
method returns a RequestSignal
that can be used to track the request status.
fn send_client_request(client: Res<Client<TestChannel>>)
{
let request_signal = client.request(TestClientRequest(24));
}
§Sending from the Server
Send a message.
fn send_server_message(server: Res<Server<TestChannel>>)
{
server.send(0u128, TestServerMsg(111));
}
Send a response. The RequestToken
allows us to correctly associate the response with the original request. If you drop the token then the client will automatically receive a ClientEvent::Reject
event and their RequestSignal
will return RequestStatus::Rejected
.
fn send_server_response(In(token): In<RequestToken>, server: Res<Server<TestChannel>>)
{
server.respond(token, TestServerResponse(1));
}
§Reading on the client
All client events are synchronized to ensure deterministic, unambiguous behavior.
type TestClientEvent = ClientEventFrom<TestChannel>;
fn read_on_client(mut client: ResMut<Client<TestChannel>>)
{
while let Some(client_event) = client.next()
{
match client_event
{
TestClientEvent::Report(connection_report) => match connection_report
{
ClientReport::Connected => todo!(),
ClientReport::Disconnected => todo!(),
ClientReport::ClosedByServer(reason) => todo!(),
ClientReport::ClosedBySelf => todo!(),
ClientReport::IsDead(pending_requests) => todo!(),
}
TestClientEvent::Msg(message) => todo!(),
TestClientEvent::Response(response, request_id) => todo!(),
TestClientEvent::Ack(request_id) => todo!(),
TestClientEvent::Reject(request_id) => todo!(),
TestClientEvent::SendFailed(request_id) => todo!(),
TestClientEvent::ResponseLost(request_id) => todo!(),
}
}
}
§Reading on the server
All server events are synchronized to ensure deterministic, unambiguous behavior.
type TestServerEvent = ServerEventFrom<TestChannel>;
fn read_on_server(mut server: ResMut<Server<TestChannel>>)
{
while let Some((client_id, server_event)) = server.next()
{
match server_event
{
TestServerEvent::Report(connection_report) => match connection_report
{
ServerReport::Connected(env, message) => todo!(),
ServerReport::Disconnected => todo!(),
}
TestServerEvent::Msg(message) => todo!(),
TestServerEvent::Request(token, request) => todo!(),
}
}
}
§Client authentication
Servers have three options for authenticating clients:
Authentication::None
: All connections are valid.Authentication::Secret
: A connection is valid if the client providesAuthRequest::Secret
with a matching secret.Authentication::Token
: A connection is valid if the client providesAuthRequest::Token
with a token produced by your backend.
Generating and managing auth tokens is very simple.
- Generate auth keys in your backend and setup a server.
let (privkey, pubkey) = bevy_simplenet::generate_auth_token_keys();
let server = server_factory().new_server(
enfync::builtin::native::TokioHandle::default(),
"127.0.0.1:0",
AcceptorConfig::Default,
Authenticator::Token{pubkey},
ServerConfig::default(),
);
Typically the keypair will be generated on one frontend server, and the pubkey
will be sent to other servers for running bevy_simplenet
servers that will authenticate tokens.
-
Client sends their credentials (e.g. login name and password) to your frontend.
-
Your frontend validates the credentials and produces an auth token allowing the user’s client id
123u128
to connect to your backend.
// This token expires after 10 seconds.
let token = bevy_simplenet::make_auth_token_from_lifetime(&privkey, 10, 123u128);
You send this token to the client.
- Client makes a
bevy_simplenet
client using the received token.
let client = client_factory().new_client(
enfync::builtin::Handle::default(),
server_url,
AuthRequest::Token{ token },
ClientConfig::default(),
TestConnectMsg(String::from("hello"))
);
Note that the server_url
can be transmitted alongside the token for convenience. This way clients only need to know the auth token endpoint, and you can provision backend servers as needed.
When the token expires, bevy_simplenet
clients will stop automatically reconnecting on network error and instead shut down and emit ClientEvent::Report(ClientReport::IsDead(_))
. You can request a new auth token and set up a new client in that event.
It is recommended to set a relatively low auth token expiry if you are concerned about DoS from clients clogging up the server’s capacity, or if you have a force-disconnect/blacklist mechanism in your backend (which presumably communicates with the auth-token-producing endpoint).
§TODOs
- Add server shut down procedure.
- Use const generics to bake protocol versions into
Server
andClient
directly, instead of relying on factories (currently blocked by lack of robust compiler support).
§Bevy compatability
bevy | bevy_simplenet |
---|---|
0.15 | v0.14 - master |
0.14 | v0.12 - v0.13 |
0.13 | v0.9 - v0.11 |
0.12 | v0.5 - v0.8 |
0.11 | v0 - v0.4 |
Structs§
- Auth
Token - Client id authenticated by auth key.
- Client
client
- A client for communicating with a
Server
. - Client
Config client
- Config for the
Client
. - Client
Factory client
- Factory for producing
Client
s that all bake in the same protocol version. - Rate
Limit Config - Configuration for rate limiter. Defaults to 10 messages per 100 millisconds.
- Rate
Limit Tracker - Tracks and limits the rate that messages are accepted.
- Request
Signal client
- Tracks the current status of a client request.
- Request
Token server
- Represents a client request on the server.
- Server
server
- A server for communicating with
Client
s. - Server
Config server
- Config for the
Server
. - Server
Factory server
- Factory for producing servers that all bake in the same protocol version.
Enums§
- Acceptor
Config server
- Configuration for accepting connections to the
Server
. Defaults to non-TLS. - Auth
Request Client
authentication for connecting to aServer
.- Authenticator
Non- target_family="wasm"
andauth
- Used by the
Server
to authenticateClient
connections. - Client
Error client
- Errors emitted by the internal client handler.
- Client
Event client
- An event received by a client.
- Client
Report client
- Emitted by clients when they connect/disconnect/shut down.
- Connection
Error server
- Errors emitted by the internal connection handler.
- EnvType
- Environment type of a binary.
- Request
Status client
- Indicates the current status of a client request.
- Server
Event server
- An event received by the server.
- Server
Report server
- Emitted by servers when a client connects/disconnects.
Constants§
- AUTH_
PRIVKEY_ BYTES Non- target_family="wasm"
andauth
- Byte length of
AuthToken
authentication privkeys. - AUTH_
PUBKEY_ BYTES Non- target_family="wasm"
andauth
- Byte length of
AuthToken
authentication pubkeys. - AUTH_
TOKEN_ PROTOCOL_ VERSION Non- target_family="wasm"
andauth
- The current protocol version for
AuthToken
construction and validation. - AUTH_
TOKEN_ SIGNATURE_ BYTES - Byte length of a signature in
AuthToken
. - SECRET_
AUTH_ BYTES - Secret size for the
Authenticator::Secret
authentication type.
Traits§
- Channel
Pack - Represents the message types that can be sent between a client and server.
Functions§
- env_
type - Get the binary’s target environment.
- env_
type_ as_ str - Convert
EnvType
to a string. - env_
type_ from_ str - Get an
EnvType
from a string. - generate_
auth_ token_ keys Non- target_family="wasm"
andauth
- Generates a privkey/pubkey pair for use in creating and verifying
AuthTokens
. - make_
auth_ token_ from_ expiry Non- target_family="wasm"
andauth
- Makes an
AuthToken
from an expiration time in seconds sinceUNIX_EPOCH
. - make_
auth_ token_ from_ lifetime Non- target_family="wasm"
andauth
- Makes an
AuthToken
from a token lifetime. - make_
websocket_ url - Make a websocket url:
{ws, wss}://[ip:port]/ws
.
Type Aliases§
- Client
Event From client
- Get a
ClientEvent
from aChannelPack
. - Client
Id - Client id specified in client
AuthTokens
. - Close
Frame - Message
Signal client
- Re-exports
ezsockets::MessageSignal
. - Message
Status client
- Re-exports
ezsockets::MessageStatus
. - Server
Event From server
- Get a
ServerEvent
from aChannelPack
.