Skip to content

Extract hyper-independent part of rust-websocket into a separate crate. #222

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

Merged
merged 17 commits into from
Nov 7, 2019

Conversation

vi
Copy link
Member

@vi vi commented Apr 30, 2019

rust-websocket still depends on legacy hyper 0.10.

This may be the first step to migrate it to something else.

@vi vi changed the title Extract hyper-intependent part of rust-websocket into a separate crate. Extract hyper-independent part of rust-websocket into a separate crate. Apr 30, 2019
@kpp
Copy link
Contributor

kpp commented Apr 30, 2019

Are you by the way interested in #222 (and in general in migration away from hyper 0.10)?

@vi why move from hyper 0.10?

@vi
Copy link
Member Author

vi commented Apr 30, 2019

Because of it is non-last version of hyper, making it auto-deprecated.

For example, Debian package suites are supposed to contain only one package per crate.

If Hyper 0.10 continues to live, it should be published with some new separate name.

@kpp
Copy link
Contributor

kpp commented Apr 30, 2019

I will help if you need to.

@kpp
Copy link
Contributor

kpp commented May 1, 2019

What exactly do you want to achieve? What is your plan?

}
}

pub(crate) fn towse<E>(e: E) -> WebSocketError
Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry but the name sux. How about using .map_err(WebSocketOtherError::from)?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure about how should I do error handing after splitting the crate in general. This function is basically to avoid typing .map_err(WebSocketOtherError::from) and making lines really longer (or introducing additional lines).

Making websocket-lowlevel's error type primary error type also for high-level websocket is dubious, but I'm not sure how to do better: there are traits that are re-exported from websocket-lowlevel by websocket and those rely on lowlevel's error type.

use bytes::BytesMut;
use tokio_codec::Decoder;
use tokio_codec::Encoder;
use self::bytes::BufMut;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why self::?

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably some edition 2015 shenanigans.

pub fn new() -> WebSocketKey {
let key: [u8; 16] = unsafe {
// Much faster than calling random() several times
mem::transmute(rand::random::<(u64, u64)>())
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

This code was only transferred from the parent crate, not invented by me.

Maybe at the time the code was written, rand had fewer functions.

Copy link
Member Author

Choose a reason for hiding this comment

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

Do you expect "drive by" refactorings to be in scope for splitting pull request?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, I can do that in another PR, and you can rebase on master to get these changes

let mut array = [0u8; 20];
let mut iter = vec.into_iter();
for i in &mut array {
*i = iter.next().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

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

How about:

		match base64::decode(accept) {
			Ok(vec) => {
				use std::convert::TryInto;
				vec[..].try_into()
					.map(WebSocketAccept)
					.map_err(|_e| WebSocketError::ProtocolError(
						"Sec-WebSocket-Accept must be 20 bytes",
					))
			}
			Err(_) => Err(WebSocketError::ProtocolError(
				"Invalid Sec-WebSocket-Accept ",
			)),
		}

@vi
Copy link
Member Author

vi commented May 1, 2019

The plan is mostly this:

  1. Split away Hyper_0.10-independent part away to separate crate websocket-lowlevel;
  2. Try implementing new, maybe API-incompatible async-only crate based on websocket-lowlevel, either using hyper 0.12 or using something else.
  3. Use it for my project websocat (which is why I got involved in rust-websocket project in the first place), removing obstacle for publishing to Debian.
  4. Maybe mark websocket as officially deprecated; maybe rewire it to depend on some successor of hyper 0.10 if there is any.

I'm not sure about this although.

/// Return the Base64 encoding of this WebSocketAccept
pub fn serialize(&self) -> String {
let WebSocketAccept(accept) = *self;
base64::encode(&accept)
Copy link
Contributor

Choose a reason for hiding this comment

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

	pub fn serialize(&self) -> String {
		base64::encode(&self.0)
	}

Copy link
Member Author

Choose a reason for hiding this comment

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

This code was only transferred from the parent crate, not invented by me.

@@ -365,6 +368,8 @@ mod tests {

#[test]
fn message_codec_client_send_receive() {
extern crate tokio;
Copy link
Contributor

Choose a reason for hiding this comment

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

Import this crate only in lib.rs with cfg test

Copy link
Member Author

Choose a reason for hiding this comment

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

Doesn't #[test] imply #[cfg(test)]?

What's wrong with non-root crate import?

Copy link
Contributor

Choose a reason for hiding this comment

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


/// Represents a Sec-WebSocket-Accept header
#[derive(PartialEq, Clone, Copy)]
pub struct WebSocketAccept([u8; 20]);
pub struct WebSocketAccept(WebSocketAcceptLL);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why? pub use websocket_lowlevel::header::WebSocketAccept would be enough

Copy link
Member Author

Choose a reason for hiding this comment

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

It does not implement hyper_0.10's Header and HeaderFormat traits.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see

@kpp
Copy link
Contributor

kpp commented May 2, 2019

So looks like you want to move all core parts and create some kind of https://github.com/snapview/tungstenite-rs/tree/master/src ?

@vi
Copy link
Member Author

vi commented May 2, 2019

tungstenite? I haven't yet considered it. It seems to be relatively new project.

Can it do async? How does it compare to rust-websocket?

@kpp
Copy link
Contributor

kpp commented May 2, 2019

Can it do async? How does it compare to rust-websocket?

I don't know, but I have just discovered it in https://github.com/seanmonstar/warp/blob/master/src/filters/ws.rs when googled how to set up hyper with websockets.

@ahmedcharles
Copy link

Is this change still being considered?

I'd like to update websockets to the new futures api, but doing that would conflict with this change heavily.

@vi
Copy link
Member Author

vi commented Aug 14, 2019

Yes, I'm going to base websocat 2.0 on it (in order to drop hyper 0.10 dependency).

You can look at split_in_two branch and point a pull request to there.

But expect the pull request to hang around for rather long time. Maybe it would make sense to release it to crates.io with other name (like websocket-async + websocket-async-ll) in meantime. README of this project may include link to the fork.

@ahmedcharles
Copy link

Is there any way I can help with getting it merged?

@vi
Copy link
Member Author

vi commented Aug 14, 2019

Is there any way I can help with getting it merged?

Review it and state opinions:

  • Is quality of the pull request good enough so better to merge than to wait more?
  • How hacky / unmanageable error handling becomes? Error handling is the trickiest part of the split. Now it's somewhat "reverse": lowlevel's error is main error type and highlevel's error is dyn-boxed away.
  • Are there any breaks of dependent crates introduced by this split?

@ahmedcharles
Copy link

I'm curious, why not just have the crate use a newer version of hyper? After reading the comments here, it seems like the motivation for the split is remaining on hyper 0.10, but no one says why.

@vi
Copy link
Member Author

vi commented Aug 14, 2019

why not just have the crate use a newer version of hyper?

Hyper 0.12 is drastically different from 0.10. Also it does not support non-async at all. It may be easier to rewrite websocket entirely based on hyper 0.12 and websocket-lowlevel rather than trying to migrate it evolutionally.

@ahmedcharles
Copy link

Note, reqwest implements both sync and async apis on top of hyper's async api's. That seems reasonable?

@kpp
Copy link
Contributor

kpp commented Aug 14, 2019

That seems reasonable?

You have to have an executor (futures/tokio/romio) to block on a async future. And this library is executor-free.

@ahmedcharles
Copy link

hyper depends on tokio, tokio has an executor, so a sync version of websockets can be implemented on top of hyper by blocking on the tokio executor?

@kpp
Copy link
Contributor

kpp commented Aug 15, 2019

Hyper 0.12 is drastically different from 0.10. Also it does not support non-async at all. I

@vi Are you sure?

@vi
Copy link
Member Author

vi commented Aug 31, 2019

Is there any way I can help with getting it merged?

Review it and state opinions.

So, do you have any opinions? Shall websocket-lowlevel + websocket be published or there are blockers?

@najamelan
Copy link
Contributor

I have said it before on tungstenite and tide, but I think what would make sense for the rust websocket ecosystem is to have a separate websocket_handshake crate that everyone can agree on.

It would deal with http handshake and websocket crates could take it from when the handshake is finished. It could rely just on http crate, take in something that implements std::Read + std::Write and return that once the handshake is complete. It would deal with protocols, extensions, http2 etc.

All web frameworks could depend on it for the handshake before switching to a websocket phase 2 crate, or implementing their own phase 2 API (like warp)

All websocket crates could depend on it for standalone websocket server.

This would reduce all of the duplicated work, as well as decrease risk of bugs and security vulnerabilities and improve interoperability.

@vi
Copy link
Member Author

vi commented Oct 21, 2019

@hyyking , Do you have an opinion about this pull request? Shall I release it already (as 0.24.0 or 0.25.0) or something needs to be addressed before that?

@hyyking
Copy link
Contributor

hyyking commented Oct 21, 2019

Rust wise LGTM, I don't like the name websocket-lowlevel (maybe websocket-basis/base). I think it's good for now.

But like said above we could split the crate even more it would make feature developping much easier (rn it's hard to differentiate what needs what). Maybe have a codec workspace for async and a true base crate with handshake/frame like said above.

@vi
Copy link
Member Author

vi commented Oct 21, 2019

@hyyking Which names do you suggest for:

  • rust-websocket part that does not depend on any HTTP or async library. Just encoding/decoding messages, base64, key, some type definitions, etc.
  • rust-websocket part that can wrap an async connection to provide a WebSocket message-based async connection, but without negotiation / http upgrade part. That would depend on futures.
  • rust-websocket part that depends on legacy outdated hyper 0.10 library? Shall it provide fully-fledged WebSocket experience or just HTTP upgrade without the channel itself?

@hyyking
Copy link
Contributor

hyyking commented Oct 22, 2019

I believe the project should be split in 3 parts (I'm ok with helping btw)

  1. rust-websocket-encoding (ws-encoding workspace) for message encoding
  2. rust-websocket-upgrade (ws-upgrade workspace) for the http upgrade protocol (deps: hyper and ws-encoding)
  3. rust-websocket should provide work-ready sockets with sync/async/tls features like it does right now

@vi
Copy link
Member Author

vi commented Oct 22, 2019

Shall special effort be dedicated to prevent / minimize API breakage during the split?
Is API preservation worth complicating the error handling?

@hyyking
Copy link
Contributor

hyyking commented Oct 22, 2019

Shall special effort be dedicated to prevent / minimize API breakage during the split?
Is API preservation worth complicating the error handling?

That's up to you, if we achieve the changes above it's a major release-worthy so breaking changes may occur. Current API is fine imho but maybe things will be uncovered during the split. If these changes go through we should be able to handle errors in a clearer way but that should be another release I think.

@vi vi merged commit e1b811c into master Nov 7, 2019
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.

5 participants