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

Add a websocket transport #84

Merged
merged 6 commits into from Jan 15, 2018
Merged

Add a websocket transport #84

merged 6 commits into from Jan 15, 2018

Conversation

tomaka
Copy link
Member

@tomaka tomaka commented Jan 2, 2018

Based over #81
cc #23

Adds the libp2p-websocket crate that allows using websockets as a transport.

One things to note is that it provides two completely separate implementations: one for desktop platforms, and one for emscripten.

On emscripten, the transport is a stand-alone, and will handle multiaddresses that look like /ipN/.../tcp/.../ws. On desktop, the transport needs to be passed an underlying transport (such as a TcpConfig), and will only handle the /ws suffix.

The purpose of using websockets is to enable nodes that run in the browser. I have a working example in another branch, which I didn't push because it needs a lot of work.

You can already try to connect two nodes together through websockets by manually providing a dialing or listening address to the echo examples. For example, run echo-server /ip4/0.0.0.0/tcp/10333/ws, and echo-dialer /ip4/127.0.0.1/tcp/10333/ws.

Note that if you try to connect a node that expects websockets to a node that doesn't expect websockets, or vice-versa, you will get a "cryptic" error (something like "failed to negotiate handshake"). It is not possible to configure the dialer or listener to try websockets and fall back to not-using-websockets if that fails, but I don't think it is a problem.

Err((second, addr)) => Err((OrTransport(first, second), addr)),
}
#[inline]
fn next_incoming(self) -> Self::Incoming {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you forward the addr from first and second and then .map it away immediately?

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's not the address that is mapped away but a SelectNext object.

///
/// This function returns the next incoming substream. You are strongly encouraged to call it
/// if you have a muxed transport.
pub fn next_incoming(self) -> Box<Future<Item = (C::Output, Multiaddr), Error = IoError> + 'a>
Copy link
Contributor

Choose a reason for hiding this comment

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

We can do this using as fn(_) -> _ and then copying the giant resulting type signature out of the error message, although it's your call as to whether this is worth doing.

Copy link
Member Author

Choose a reason for hiding this comment

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

We can't because unfortunately one of the closures captures a value.


Box::new(dialed_fut) as _
})
Box::new(future) as Box<_>
Copy link
Contributor

Choose a reason for hiding this comment

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

Mixed tabs and spaces

Err(err) => Ok((Err(err), client_addr)),
}
})
.map(move |(connection, client_addr)| {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above, we can do this without Box

Copy link
Member Author

Choose a reason for hiding this comment

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

We also move stuff in the closure here :-/

// Pull out a stream of sockets for incoming connections
listener.incoming().map(|(sock, addr)| {
println!("incoming tcp stream");
Copy link
Contributor

Choose a reason for hiding this comment

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

Println left in

OwnedMessage::Text(data) => Ok(Some(data.into_bytes())),
OwnedMessage::Close(_) => Ok(None),
// TODO: pings and pongs ; freaking hard
_ => unimplemented!()
Copy link
Contributor

Choose a reason for hiding this comment

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

Either turn this into a real error with "pings and pongs are unimplemented" as the message or mark this struct as experimental (or something similar), we can't have the server explode if you send a ping.

.map_err(|err| IoError::new(IoErrorKind::Other, err))
.map(|(client, _http_headers)| {
// Plug our own API on top of the `websockets` API.
let framed_data = client
Copy link
Contributor

Choose a reason for hiding this comment

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

Mixed tabs and spaces

let dial = inner_dial.into_future().and_then(|connec| {
// We pass a dummy address to `ClientBuilder` because it is never used anywhere
// in the negotiation anyway, and we use `async_connect_on` to pass a stream.
ClientBuilder::new("ws://127.0.0.1:80")
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we fork the WS library to make this cleaner or is it not worth it?

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 would say it's not worth it. Since it's a breaking change they would have to bump their version, leading to more burden.

//! Listening on a websockets multiaddress isn't supported on emscripten. Dialing a multiaddress
//! which uses `ws` on top of TCP/IP will automatically use the `XMLHttpRequest` Javascript object.
//!
//! ```ignore
Copy link
Contributor

Choose a reason for hiding this comment

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

Why ignore and not no_run?

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 demonstrates the emscripten code, but the doctests are compiled for x86 and hence will fail to compile.

impl WsConfig {
/// Creates a new configuration object for websocket.
#[inline]
pub fn new() -> WsConfig {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't like how the signature for this method changes depending on your platform but otherwise everything acts the same, it seems like it's just asking for confusing error messages when you switch platform and expect everything to work the same. Maybe have BrowserWsConfig and WsConfig?

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 is also true that WsConfig (the desktop one) could be used from inside nodejs or another similar environment, which makes the name BrowserWsConfig more relevant.

@tomaka
Copy link
Member Author

tomaka commented Jan 11, 2018

Should be good now.

The ping/pong messages now produce an error and/or close the socket instead of panicking.
This seems like a lazy solution, but handling them is really really not easy. An issue should be opened after this PR is merged for that.

@tomaka tomaka merged commit b83ebe3 into libp2p:master Jan 15, 2018
@tomaka tomaka deleted the websockets branch January 15, 2018 11:01
mxinden pushed a commit to mxinden/rust-libp2p that referenced this pull request Nov 20, 2020
…ct (libp2p#84)

* also consider allow self origin when using a published message ids dictionary

* make allow_self_origin configurable via the config builder
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.

None yet

2 participants