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

feat(server): support upgrading to other protocols #1287

Closed
wants to merge 5 commits into from

Conversation

spinda
Copy link
Contributor

@spinda spinda commented Aug 12, 2017

The Http type learns a new method, bind_upgradable_connection, which returns a Future. This method behaves like bind_connection, except the Service supplied must return an UpgradableResponse containing either an HTTP Response or some information describing a protocol switch. If an Upgrade response is returned, the server task is shut down (without closing the underlying io) and this information is forwarded to the Future, along with the underlying io and any remaining buffered data that has already been read from it.

If the server task shuts down normally, the Future completes with a None output value. In the case of an error handling the connection, the error is also forwarded to the Future.

This allows for implementing servers with hyper that can upgrade their connections to different protocols, such as WebSockets, in response to client requests. Such protocols can be implemented outside of hyper itself. The API is designed to allow for potentially multiple upgrade paths out of an HTTP exchange, with the upgrade information attached to the UpgradableResponse::Upgrade signal determining which is used.

A companion crate implementing WebSocket upgrades can be found here, with sample code here.

Importantly, the code path for normal, non-upgradable connections is entirely untouched by this addition.

Allow unwrapping the connection to its inner I/O object and read buffer, if the
write buffer is empty.
The Http type learns a new method, bind_upgradable_connection, which returns a
Future. This method behaves like bind_connection, except the Service supplied
must return an UpgradableResponse containing either an HTTP Response or some
information describing a protocol switch. If an Upgrade response is returned,
the server task is shut down (without closing the underlying io) and this
information is forwarded to the Future, along with the underlying io and any
remaining buffered data that has already been read from it.

If the server task shuts down normally, the Future completes with a None output
value. In the case of an error handling the connection, the error is also
forwarded to the Future.

This allows for implementing servers with hyper that can upgrade their
connections to different protocols, such as WebSockets, in response to client
requests. Such protocols can be implemented outside of hyper itself. The API is
designed to allow for potentially multiple upgrade paths out of an HTTP
exchange, with the upgrade information attached to the
UpgradableResponse::Upgrade signal determining which is used.

Importantly, the code path for normal, non-upgradable connections is entirely
untouched by this addition.
This makes it easier to send a "101 Switching Protocols" before upgrading from
HTTP to another protocol.
@seanmonstar
Copy link
Member

This looks really impressive! I apologize I haven't had time to review it, I've been trying to wrap up changes to the http crate before RustConf. I'll take a look soon, I promise!

@spinda
Copy link
Contributor Author

spinda commented Aug 18, 2017

No problem, and thanks!

@spinda
Copy link
Contributor Author

spinda commented Aug 31, 2017

Hi again, have you had a chance to look at this since RustConf?

@seanmonstar
Copy link
Member

@spinda I'm so sorry, I don't have particularly good reasons other than forgetting. I'd like to look at this this week. I think upgrading protocols is necessary, and so hyper should definitely support it.

cc @carllerche

Instead of printing

    Server { core: "..." }

we print

    Server { core: ... }

This clarifies the situation, that a field is unprintable and hence has been omitted, instead of
looking like the `core` field contains a string.
Copy link
Member

@seanmonstar seanmonstar left a comment

Choose a reason for hiding this comment

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

Alright, I got a chance to look through this. This is solid work, thanks!

I like that it's generic over the upgrade protocol: it doesn't matter what is actually being upgraded to, the user just receives the socket back and can do whatever they want.

I started thinking about if there were alternative ways to do this, and this is something I came up with:

Instead of needing to use a special method on Http, and a special enum return type, I wondered if this could be done while still using a normal Service, and still returning a Response. What if a method or constructor of Response were just used, with a sort of callback like interface.

It could use a futures::sync::oneshot channel internally (but perhaps newtyped to hide that detail), and a user can send the completion future anywhere they want. For example:

impl Service for HttpUpgrades {
    fn call(&self, req: Request) -> Self::Future {
        // omitting inspection of Request, details irrelevant
        let (resp, on_upgraded) = Response::upgrade();
        self.ws_queue.send((details, on_upgraded));
        future::ok(resp)
    }
}

The on_upgraded argument would be a impl Future<Item=(T, Bytes)> of some kind.

Perhaps a constructor doesn't make sense, and it should instead be a mutator of the Response, that's fine.

What do you think of an API like this?


type UpgradableResponseHead<P> = Result<__ProtoResponse, (P, Option<__ProtoResponse>)>;

impl<T, B, P> ServerProto<(T, UpgradableSender<T, B, P>)> for UpgradableHttp<P, B>
Copy link
Member

Choose a reason for hiding this comment

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

That all of these boilerplate impls are needed is a bummer. Sorry to have to write that XD

}

#[test]
fn test_upgrade() {
Copy link
Member

Choose a reason for hiding this comment

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

❤️

Response(Response<B>),
/// A protocol upgrade signal with accompanying information and optional
/// "switching protocols" HTTP response head.
Upgrade(P, Option<Response<()>>),
Copy link
Member

Choose a reason for hiding this comment

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

So, what exactly is the P? From what I can tell, is it just some arbitrary thing that might be created when inspected a Request, and useful once upgraded?

@spinda
Copy link
Contributor Author

spinda commented Sep 8, 2017

Thanks for taking a look! Not sure how much time I'm going to have over the next few days (ending an internship and moving back home). I'll address these comments as soon as I can.

@seanmonstar
Copy link
Member

I'd be especially interested in discussing the design before spending time writing code :D

@seanmonstar
Copy link
Member

I put more details of the proposal I suggested in #1323.

@blueridanus
Copy link

How's progress on this? Can I help?

@nayato
Copy link

nayato commented Nov 21, 2017

is this now blocked on moving away from tokio-proto?

@seanmonstar
Copy link
Member

The internal change to not use tokio-proto is already merged, just not on by default while bugs are still found. It should probably be easier to make upgrades work, however, because of this work.

I'd say the blocker for this feature is settling on an API to expose to a user.

@seanmonstar
Copy link
Member

I'm closing this due to inactivity. I recommend discussing the feature more in #1323.

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