Custom async websocket client - API design and futures


#1

Hey there!

I’m trying to build a custom async websocket client using the websocket crate (cyderize/rust-websocket), which is using tokio and futures itself. I know there are several other crates, but it seems to me that async, tokio and futures are the way to go, so the exact websocket implementation probably doesn’t matter much.

What I’m having troubles with is how my client library API (let’s call it myclient) could look like. Bear with me as I’m new to Rust, futures and websockets :grimacing:

A rough overview about the websocket API that myclient would wrap:

  • It provides a stream of “events” for a certain PC game, e.g. “player id#xyz logged in”
  • One can manage the subscription of events, e.g. which game servers and player events one is interested in
  • Communication happens with custom JSON format inside websocket text messages

So what myclient should do is:

  • Wrap its internal websocket sink and stream
  • Provide functions to manage subscriptions (which send JSON websocket messages via sink)
  • Provide a stream of events as struct/enum (by parsing the JSON from the websocket stream)

To understand how the websocket API works, I fiddled a bit with rust-websocket/examples/ssl-client.rs as a CLI:

  • Once the websocket is connected (that is before you’ve setup any subscriptions), events start flowing in (e.g. which game servers are currently available as event sources)
  • After connecting, you can setup your subscription of events (e.g. “all player login events on all servers”)
  • The JSON format is … very custom :expressionless:

While me as a human (despite the username :stuck_out_tongue: ) fiddling with a CLI worked well, I wonder how to turn that into an API:

  • what exactly would a “connect” function return? An impl MyClient with functions to manage subscriptions and get the event stream? What happens with incoming websocket messages when there’s no consumer yet?
  • how do I provide a stream of one type by converting an internal stream of a different type?
    I can parse 1 websocket text message with serde_json into 1 struct just fine, but how do I glue this to the websocket stream?
  • the user needs to provide functionality for 2 things
  • setting up the initial subscriptions after connecting (while still being able to alter the descriptions lateron)
  • doing whatever with the incoming stream of events
    How can he provide these; as fns, by combining custom futures himself?
  • How does closing the websocket fit into this? According to the spec, you have to send a “close” message and should not just close the TCP socket. Would this be done with impl Drop for MyClient?
  • How does error handling come into play? Things that can go wrong are e.g. broken websocket connection, invalid incoming JSON. How could these stop the stream and close myclient?

I know this isn’t really a clear cut question, as I’m still trying to figure things out. I’d appreciate any API design suggestions, examples and further reading material :slight_smile: