I wanted to learn more about QUIC, the transport protocol behind HTTP/3. It's inspired by the learnings of HTTP/2. Some of the notable features:
I was thinking about what I could implement that could take advantage of these functionalities. I'm not too sure how I can take advantage of point 2 above, but point 3 made me think of another protocol that I am familiar with, the Advanced Message Queue Protocol (AMQP) as implemented by RabbitMQ.
AMQP has a channel abstraction, which is for multiplexed connections. This allows you to consume from multiple queues without having multiple connections open.
So, that's what I'm going to experiment with. Writing my own message broker in Rust using QUIC.
I'm going to use
quinn which is inspired by the implementation of
quinn makes strict assumptions about
rustls, I will also lean into it and use mTLS for authentication,
instead of using an additional username and password flow.
I am using
rcgen to generate the certifications that I know will work with
I want to make sure that my mTLS setup and
quinn works properly, so start off by experimenting with creating a connection and a stream, and writing data.
It wasn't too hard, you have to create an
Endpoint on both sides, one using a
ClientConfig and one using a
On the server, you can
accept() connection requests, which gives you a
Spawn that off to a
tokio task and await it to establish the TLS authenticated connection.
On the client, you can
connect to a socket address which similarly returns a
Await that, and if the TLS hostname is correct and the certificates are validated, the connection will be ready to use.
On both sides now, it's possible to use
open_bi to open new sub-connections, called 'streams' in QUIC. This returns on both sides a
SendStream and a
RecvStream which implement
AsyncRead respectively. What caught me out at first is that stream open requests are buffered.
If you open a bi-directional stream on the client, the server will not be notified of that stream until you
flush() the stream.
This is part of what makes streams so lightweight. Once I figured out that you had to use
finish(), I was able to get a quick echo example
You can follow along with the code in this post on GitHub