Join GitHub today
Yeti Next has weird RPC requirements. Here's why.
A Persistent Hub
Yeti 0.1 was very simple: you simply connected browsers to the computer where your tests live. However, this approach has drawbacks:
- The remote browser must connect directly to your computer. Developers behind NAT must port-forward, and you may need to punch holes in your firewall.
- The remote browser is not easily shared with other developers. If a team of a dozen developers wanted to use a single VM, they would need to maintain a dozen windows pointed to their constantly running Yeti server. Faulty connections to the computer (e.g., putting your laptop to sleep) require you to re-establish the browser connection.
These shortcomings are the reason why Yeti Next introduces the Hub concept.
The Hub is the middleman between your computer and agents, née browsers. In the simple case, the Hub is built-in to Yeti, but when possible you can tell your Yeti to use a Yeti Hub.
Instead of pointing browsers to your computer, you point them to a shared, persistent Hub. Your computer then asks the Hub to test browsers for you.
- IT administrators only need to poke holes for the single Hub, instead of for every developer computer.
- Browsers can remain connected to a Hub, which is more stable than a transient developer's computer.
- More efficient use of test box resources: Browsers only need to open a single window to a Hub, where they can be used by many developers.
- Reporting and statistics: The Hub emits information about tests that other systems can use for reporting purposes.
The introduction of the Hub means that a lot of data needs to be transferred between the Hub and your computer.
- Test dependencies: CSS, binary data (images)
- Test results, status information: JSON
This channel must be bi-directional:
- JSON data is sent to the Hub representing the batch requested.
- The Hub requests the test files, including text and binary data.
- JSON data is sent from the Hub representing test results.
How You Can Help
Current Solution: Blizzard (a Protocol and API)
Currently Yeti uses the Blizzard system for RPC. This is comprised of two components:
- The Blizzard API, which provides rpc.* and request.* events for sending and receiving messages with EventEmitter2 events.
- The Blizzard protocol, the mechanism which Blizzard messages are sent.
Proposed Solution: WebSocket with Blizzard's Event API
The ws library provides a client and server implementation of the RFC 6455 WebSocket protocol.
Instead of writing a new Blizzard parser, I would like to replace Blizzard's protocol with the WebSocket protocol by using a performant RFC 6455 WebSocket client-server implementation for Node.js.
What's needed is to: replace use of the Blizzard protocol inside the Blizzard API implementation with WS equivalents. Also, figure out how to make ws work together with socket.io -- they both will attempt to handle http.Server upgrade events.
Fallback Solution: Write a better Blizzard protocol parser
It's possible to get much faster throughput for Blizzard by writing a better parser. I would prefer to replace the protocol all together with WebSocket, but if that's not possible, we can still improve performance by writing a better parser against the Blizzard protocol spec.
These solutions were discarded because they failed to meet Yeti's RPC goals stated earlier.
Solution A: HTTP Server on both sides
The first option explored was creating an HTTP server on the client. Making separate requests back and forth for bi-directional transfer imposes a high cost on the implementation and for speed. You would also need to open a firewall hole.
Solution B: dnode, hook.io
The second option explored was using dnode or hook.io to open a bi-directional communication channel. Hook.io was ruled out because of it's poor support for one-to-many connections and poor performance at high load.
Hook.io is built on dnode. The dnode project is more promising, but also has shortcomings:
- Poor handling of errors. If the port specified is an HTTP server instead of a DNode server, no error is emitted.
- No support for streaming data.
- JSON serialization of test files, including images, is not needed.
Solution C: Implement BERT-RPC
The third option explored was a system like GitHub's BERT-RPC. Using it here isn't possible because:
- Asynchronous RPC requests require an out-of-band callback socket connection, meaning the client has to open a server port.
- The reference implementation of BERT-RPC for Node.js is very old and does not take advantage of Node's Buffer APIs, a must for a binary protocol.
Modernizing BERT-RPC's reference implementation is a bit overkill. BERT introduces many data types that are unnecessary for JS-to-JS RPC.