-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Conversation
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.
This looks really impressive! I apologize I haven't had time to review it, I've been trying to wrap up changes to the |
No problem, and thanks! |
Hi again, have you had a chance to look at this since RustConf? |
@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.
There was a problem hiding this 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> |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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<()>>), |
There was a problem hiding this comment.
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?
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. |
I'd be especially interested in discussing the design before spending time writing code :D |
I put more details of the proposal I suggested in #1323. |
How's progress on this? Can I help? |
is this now blocked on moving away from tokio-proto? |
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. |
I'm closing this due to inactivity. I recommend discussing the feature more in #1323. |
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.