The current state Rump library can connect to a WAMP router and perform publish and subscribe events asynchronously. It has support for encoding heterogeneous positional arguments and keyword arguments when Rust's strongly typed system, while still adhering to WAMP's robust spec. The limitations of the Rump library lies in it's lack of error handling and fine grained control over events and event options.
There were three tiers of goals: minimal, expected, and stretch goals.
All minimals goals were met.
- ✓ Simple connect with WAMP Router
- ✓ Use WebSocket as default socket type
- ✓ Support JSON Serialization
- ✓ Publish Events
I was able to implement simple versions of my expected goals.
- ✓ Subscribe Events
- ✓ Trait-based transport layer
- ✓ Positional arguments and Keyword arguments
- My original goal stated using rust macros, but in fact a more elegant solution using traits and a custom enum type was used.
- ✕ Remote Procedure calls
- This involved a lot of handshaking between the client and the router, and so wasn't done in favor of implementing the pub/sub paradigm. However, RPC calls are very similar to Subscribe calls, so the knowledge gained from implementing that feature could be leveraged in the future.
- ✕ More complex connection with WAMP Router, and support of advanced features.
- In pushing to get the pub/sub paradigm working for a demo, I've neglected some basic error handling features, and verification of the integrity of the connection with the WAMP router. Furthermore, I've also decided not to implement a lot of WAMP's v2 advanced feature spec.
There are four main modules in the project, they are: Client, Message, Transport, and Options.
The client module is the user-facing part of the library, it provides endpoints to interact with the WAMP router and perform events and publishing and subscribing. There are two main structs in the Client module, the Client struct and the Session struct.
The Client struct is responsible for holding all the relevant information before connecting to the WAMP router, like the router's url and the application's realm. In the future, the client can be given user facing options on particular features (like serialization type, or socket type). Furthermore, it would be where callback functions would be given for events involved in the connection to a Router (i.e. OnOpen, OnClose, OnError). After building this struct the user can connect to the WAMP router, returning a Session.
The Session struct represents a currently opened and valid WAMP session between the client and the router. It is responsible for performing publish/subscribe/rpc events on the user's behalf (forming the correct message, sending it through the transport layer). It also keeps track of the user's subscriptions and calls the proper callbacks for such events. In the future, it will include more complex error handling features to let the user know about the state of the connection.
The message module provides structs and functions for encoding and decoding messages to and from the router. The complication involved in adhering with the WAMP spec while maintaining an abstract way of performing the encoding and decoding.
Aside from WAMP messages, the Message module has two user facing structs: Payload and WampType. When a user subscribes to a topic, they receive the payload struct in their callback. WampType provides a easy way to pass heterogeneous types to a Session event.
The transport module abstracts the mechanism for sending/receiving messages over the network for the library. There are two main components here: The serialization layer, and the socket layer. The serialization layer defines how messages should be encoded and decoded. The WAMP spec supports two main methods of serialization, JSON (data passed as a string) and MSGPACK (data passed as binary). Currently only JSON is implemented, as its human readable and a popular JSON serializer is already available for Rust. The socket layer describes the underlying protocol used to send messages over the network. The WAMP spec supports WebSockets, TCP, and Simple Polling. Currently only WebSockets is implemented, as that's the most popular socket type used with WAMP. Traits are used to enforce these abstractions.
The WAMP spec defines a fair bit of advanced usage features and options that can be passed with each event. This module holds the definitions for these options.
Unfortunately, since this project is fundamentally for communication over a network much of the integrated testing was performed using 'visual inspection', that is printing key variables to the console which would indicate success or failure. While this kind of testing isn't exhaustive, it does model real world usage, and provides a good indication of the behavior of the library under normal usage.
Testing has the following dependencies: Rust, Crossbar, Python3.*, Autobahn|Python.
Once those dependencies are installed perform the following:
- Start a local Crossbar node (a WAMP router)
crossbar init; crossbar start
- Navigate to the Rump project directory.
- Start the python WAMP client to receive/send events
python3 examples/subscribe.py
orpython3 examples/publish.py
- Run the respective Rump test
cargo test client_loop_publish -- --nocapture --ignored
orcargo test client_loop_subscribe -- --nocapture --ignored
For future testing, running against the Autobahn|Testsuite would be prudent since it is the standard test suite for exhaustively testing against the WAMP spec.
While testing wasn't done to benchmark certain features of the project (testing was done for a binary works, does not works), there are a couple of potential associated benchmarks. Namely was could be benchmarked is volume of messages that could be sent, and the overhead delay between receiving a message from the socket and calling user code.
Whenever a message is sent over WAMP, the router acknowledges the message and would reply if any error occured. Currently, my client doesn't perform any of these checks and only performs the bare essentials to send a message to the router. A major limitation of the library is how it handles errors in general. If a panic occurs on one of the threads, the user is not informed. This includes errors like the connection to the router closing unexpectedly. Going forward this limitation will have to be dealt with.
Sending and receiving messages on the transport layers occurs on their own thread (two in total). Using a thread pool for communicating over the socket layer may be helpful for applications that have a high volume of messages.
Generally, I would change my approach with dealing with complex issues. Initially, I just spent a lot of time debugging until I hit a wall where my working knowledge of Rust couldn't help me get past. After hitting this wall, I found the Rust community, on both stackoverflow and IRC very helpful. A lot of the debugging would be easier if I spent less time trying to debug a problem I didn't have much grasp on, and more time seeking help in the community. That being said, a lot of the initial debugging is important in order to get a sense of the problem I was facing.
When approaching the project I decided to reverse engineer my implementation from an existing project, the Autobahn|Javascript implementation. While the Javascript implementation was a good point of comparison, some language features got in the way. Referring to the WAMP spec early on, and giving a closer examination would have helped guide in certain design decisions early on, and limited the scope of parts of the project (like limiting the types that could be sent to/from the router).
I would have also spent more time looking into serde as an alternative to rustc_serialize to perform encoding and decoding. It seems to be gaining popularity in the rust community as the de facto serialization engine.
I feel that my scoping of the project was just enough to be a stretch or ambitious, but enough to be obtainable, which lead to a satisfactory conclusion of the project.
I felt my initial design decisions in separating parts of the project into Client, Session, Messages, and Transport was a natural and a logical way to modularize. It provided a clear separation of code, and give me a set of different smaller ‘tasks’ to work towards.