Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Making Soketto easier to use with http types (and hyper) #48

Merged
merged 13 commits into from
Sep 23, 2021

Conversation

jsdw
Copy link
Contributor

@jsdw jsdw commented Sep 14, 2021

This is an attempt to integrate some code behind an http feature flag which (hopefully) makes it much simpler to turn http::Requests (such as those hyper hands out) into Soketto WebSocket connections.

Have a look at the hyper example to see what making use of this would look like :)

Todo:

  • code
  • document feature flag
  • tests?

@@ -40,7 +40,7 @@ jobs:
uses: actions-rs/cargo@v1.0.3
with:
command: check
args: --all-targets
args: --all-targets --all-features
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that we have a feature, make sure CI checks the code behind it


[[example]]
name = "hyper_server"
required-features = ["http"]
Copy link
Contributor Author

@jsdw jsdw Sep 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just makes the error messages nicer when trying to run the example.

@jsdw
Copy link
Contributor Author

jsdw commented Sep 14, 2021

I'd definitely appreciate some feedback on the API design here (and whether we want it at all)! Part of me likes the idea of making it easier to use Soketto with the common http types and hyper because they are quite dominant in the ecosystem, but I don't like adding too much complexity/maintenance overhead either..

/// This is handed back on a successful call to [`upgrade_request`].
pub struct Upgraded {
/// This should be handed back to the client to complete the upgrade.
pub response: http::Response<()>,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are actually only returning a set of headers, so I could conceivably return just those, and let the caller construct a Response themselves. Headers would be a more precise type, but also add a little more overhead to using (I expect people should just return this as-is pretty much)

Copy link
Contributor

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is useful; it's likely a common usage scenario and I think it's good to have a nice example for it. Left a few questions and suggestions.

src/handshake/http.rs Outdated Show resolved Hide resolved
examples/hyper_server.rs Show resolved Hide resolved
src/handshake/http.rs Outdated Show resolved Hide resolved
examples/hyper_server.rs Outdated Show resolved Hide resolved
src/handshake.rs Outdated Show resolved Hide resolved
@@ -126,6 +132,16 @@ impl<'a, T: AsyncRead + AsyncWrite + Unpin> Server<'a, T> {
self.socket
}

/// Configure the server based on the [`ServerConfiguration`] payload provided. This
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be explicit about that it is the extensions that can be configured this way, and perhaps even mention that the deflate extension is the only one available?

Copy link
Contributor Author

@jsdw jsdw Sep 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My initial aim here was to have a fairly opaque object that may just be used for extensions or may be handy for other bits (because I wasn't sure whether we might want it to handle more), but I've been reading more about extensions, and have realised that:

  1. I think being more specific here does make sense; it's all about Extension configuration and I don't think anything else will come into play!
  2. I missed a piece of the puzzle! I realise (and it makes a lot of sense) that the server needs to respond with infomration on which extensions are agreed on, based in its configuration and the requested extensions, and that's a little awkward given the way things are currently split, so I'll need to think about that a little!

src/handshake/server.rs Outdated Show resolved Hide resolved
@jsdw
Copy link
Contributor Author

jsdw commented Sep 16, 2021

I actually ended up significantly reworking the code, because it turned out that my handling of extensions wasn't good enough!

Now, there is a handshake::http::Server which somewhat resembles the current handshake::Server, but is made to be given http::Requests and hand back appropriate http::Responses, rather than doing this dance internally.

The API is different where necessary so that it's possible to actually do the relevant dance (because the thing returned from hyper::upgrade::on doesn't resolve until a response is returned to the client, but I need it to be resolved if the Server requires a socket from the word go).

@jsdw jsdw changed the title Making Soketto easier to use with http::Request types (and hyper) Making Soketto easier to use with http types (and hyper) Sep 17, 2021
Copy link
Contributor

@dvdplm dvdplm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. :)

examples/hyper_server.rs Outdated Show resolved Hide resolved
Comment on lines +87 to +88
// Note: awaiting this won't succeed until the handshake response has been returned to the client, so this must be
// spawned on a separate task so as not to block that response being handed back.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good, without this comment I'd have missed how this works.

Copy link
Contributor

@maciejhirsz maciejhirsz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks clean, just some notes on the accept key encoding :)

src/handshake.rs Outdated
// writes the response that's expected to be handed back in the response header `Sec-WebSocket-Accept`.
//
// See https://datatracker.ietf.org/doc/html/rfc6455#section-1.3 for more information on this.
fn generate_accept_key<'k>(key_base64: &[u8; 24], output_buf: &'k mut [u8; 32]) -> &'k [u8] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: there is a WebSocketKey type alias defined here ([u8; 24]): the hash is always 16 bytes, and base64 encoding of it is always 24 bytes. You could skip the output_buf and just return WebSocketKey by value here.

Copy link
Contributor Author

@jsdw jsdw Sep 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pointer! I am now returning a 28 (not 32) byte array rather than taking in a buffer. Unless I'm mistaken, base64 encoding a 160bit hash will always use exactly 28 bytes!

}

// Pull out the Sec-WebSocket-Key and generate the appropriate response to it.
let key: &[u8; 24] = match key.as_bytes().try_into() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also use WebSocketKey alias here.

@jsdw jsdw merged commit 921d031 into develop Sep 23, 2021
@jsdw jsdw deleted the jsdw-hyper-soketto branch September 23, 2021 11:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants