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 server support for push #327

Merged
merged 11 commits into from
Sep 16, 2019
Merged

Add server support for push #327

merged 11 commits into from
Sep 16, 2019

Conversation

michaelbeaumont
Copy link
Contributor

Closes #291, closes #185

@michaelbeaumont
Copy link
Contributor Author

michaelbeaumont commented Oct 21, 2018

The test I added doesn't pass.
What happens:

Prioritize.queue_frame(PushPromise(1, 2), .. , Stream(1) ..)
  Stream(1).pending_send.push_back(.., PushPromise(1,2))
  Prioritize.schedule_send(Stream(1))
    Prioritize.pending_send.push(Stream(1))
Prioritize.queue_frame(PushPromise(1, 4), .. , Stream(1) ..)
  Stream(1).pending_send.push_back(.., PushPromise(1,4))
  Prioritize.schedule_send(Stream(1))
    Prioritize.pending_send.push(Stream(1))
.queue_frame(Headers(4), .. , Stream(4) ..)
  Stream(4).pending_send.push_back(.., Headers(4))
  Prioritize.schedule_send(Stream(4))
    Prioritize.pending_send.push(Stream(4))

pending_send doesn't add duplicates, so we have:
pending_send: [1,4]

in Prioritize.pop_frame loop:

1: Prioritize.pending_send.pop() -> Stream(1)
      Stream(1).pending_send.pop_front -> PushPromise(1,2)
      !Stream(1).pending_send.is_empty() -> Prioritize.pending_send.push(Stream(1))
2: Prioritize.pending_send.pop() -> Stream(4)
      Stream(4).pending_send.pop_front -> Headers(4)
3: Prioritize.pending_send.pop() -> Stream(1)
      Stream(1).pending_send.pop_front -> PushPromise(1,4)

So we write [PushPromise(1,2), Headers(4), PushPromise(1,4)] to our buffer.

When what we really want is [PushPromise(1,2), PushPromise(1,4), Headers(4)]

Obvious option would I guess be to only call pop from Prioritize.pending_send when there's no more Frames left on Stream.pending_send, otherwise peek. Not sure what that effect that might have though.

@carllerche
Copy link
Collaborator

@michaelbeaumont thanks for doing this <3 I just got home after traveling. I'm going to review this in more depth shortly.

Regarding the issue you described, it sounds tricky indeed.

The required behavior is to not send any frames for a push promise stream until the push promise is sent. It sounds like this behavior needs to be coded in.

One option would be to not schedule a push promise stream for send if the push promise frame has not been written. This would be tracked in the stream state.

So, if the headers frame is pushed, but the push promise was not written, then the stream is not scheduled for send. Once the push promise frame is written, at that point, the stream's pendign frames is checked to see if there is anything. If there is a frame, then the stream is scheduled for send at that point.

Thoughts?

Copy link
Collaborator

@carllerche carllerche left a comment

Choose a reason for hiding this comment

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

Great start 👍 I left some comments inline.

src/server.rs Outdated
pub fn push_request(
&mut self,
request: Request<()>,
) -> Result<SendResponse<B>, ::Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this returns a SendResponse, it looks like we will have a similar problem as with the client half.

Only "root" streams may have push promises. Calling push_request on the returned SendResponse should fail. Is there a test for this?

Or should this function return SendPushedResponse, a type similar to SendResponse but without a push_request function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My plan was definitely the SendPushedResponse! I think that's the cleanest option.

@@ -283,9 +283,86 @@ impl fmt::Debug for Headers {
}
}

// ===== util =====

pub fn parse_u64(src: &[u8]) -> Result<u64, ()> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this is used in multiple locations now (I believe), I would consider creating a private util module at the root and moving this there.

}
}

pub fn validate_request(req: &Request<()>) -> bool {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This cleanup work is good standalone. It might be worth extracting it to a dedicated PR in order to simplify this PR (and make reviewing easier). Up to you though! Whatever makes it easier for you.

@carllerche
Copy link
Collaborator

Unrelated, I'm thinking that, when writing frames, prioritize should keep writing frames from the same stream until after a data frame is reached...

@michaelbeaumont
Copy link
Contributor Author

So, if the headers frame is pushed, but the push promise was not written, then the stream is not scheduled for send. Once the push promise frame is written, at that point, the stream's pendign frames is checked to see if there is anything. If there is a frame, then the stream is scheduled for send at that point.

I've actually fixed the issue I mentioned in my first comment, in the second commit. By pushing the PushPromise(1,4) (which technically belongs to Stream(1)) onto Stream(4)'s pending_send (i.e. before Headers(4)) it's ensured that they don't get sent out of order.

But then there's unexpected behavior, as evidened by the test (which actually runs successfully, not sure why travis didn't build that commit).
https://github.com/carllerche/h2/blob/287c36bb547e20edb2c7f8e760d86956f8827a7e/tests/h2-tests/tests/server.rs#L145-L151

Although we send the headers for Stream(4) "first", the headers for Stream(1) are received first. Definitely not the best ordering for most use cases of push.

https://github.com/carllerche/h2/blob/287c36bb547e20edb2c7f8e760d86956f8827a7e/tests/h2-tests/tests/server.rs#L126-L129

Unrelated, I'm thinking that, when writing frames, prioritize should keep writing frames from the same stream until after a data frame is reached...

Exactly, I think this is the crux of the issue. This seems the only way to enforce the ordering of frames like this and would fix the issue with the 2nd commit.

@carllerche
Copy link
Collaborator

I've actually fixed the issue I mentioned in my first comment, in the second commit. By pushing the PushPromise(1,4) (which technically belongs to Stream(1)) onto Stream(4)'s pending_send (i.e. before Headers(4)) it's ensured that they don't get sent out of order.

It seems to me that it would be possible for Stream(1) to be closed before the push promise is sent out? The order in which frames are sent out for any given stream should not be assumed. The prioritization logic is free to change.

@michaelbeaumont
Copy link
Contributor Author

It seems to me that it would be possible for Stream(1) to be closed before the push promise is sent out? The order in which frames are sent out for any given stream should not be assumed. The prioritization logic is free to change.

True, not a good solution.
I reverted it and am now tracking pending_push explicitly. That prevents the early sending of pushed response headers.
Added a few other tests for more complicated scenarios/bugs run into along the way.

There will need to be backpressure support for pushed requests similar to Client::send_request I think, right?

@michaelbeaumont
Copy link
Contributor Author

@carllerche do you have time to take another look?

src/frame/headers.rs Outdated Show resolved Hide resolved
src/server.rs Show resolved Hide resolved
src/proto/streams/streams.rs Outdated Show resolved Hide resolved
@ivanceras
Copy link

I need to use HTTP/2 server push. How much of the work is done for a bare minimum functional server push? Is resolving the conflicts here still applicable?

@le-invoker
Copy link

Any progress on this? I also needed the HTTP/2 server_push extension. What's good for an HTTP/2 without support for the server_push extension.

@michaelbeaumont
Copy link
Contributor Author

Tests still need to be rewritten, I'll work on it

@AlexandroPixel22

This comment has been minimized.

@LimitlessCloud
Copy link

LimitlessCloud commented Sep 12, 2019

Any news on this? I think thousands of people are expecting this crate to finally work since months but still nothing :/ Please anyone fork it and make it work, because HTTP2 is only useful because of SERVERPUSH... Save the rust community please

It's kinda mean to say that this is useless without this option...
It looks like the developer just abandoned the library that's it, I might work on a fork guys to help you, might be done in a few weeks.

@seanmonstar
Copy link
Member

We haven't abandoned the library. We've just so far not needed server push, and so haven't spent time working on it. However, Michael has done some excellent work here, hopefully we can get this functionality in soon!

src/server.rs Outdated Show resolved Hide resolved
use http::request::Parts;

if let Err(_) = frame::PushPromise::validate_request(&request) {
return Err(UserError::MalformedHeaders);
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we should log the PushPromiseError, like at debug level.

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.

Looks good to me, thanks!

@seanmonstar
Copy link
Member

Looks like there's some merge conflicts?

@michaelbeaumont
Copy link
Contributor Author

@seanmonstar The branch is up to date as far as I can tell

@seanmonstar seanmonstar merged commit fac165e into hyperium:master Sep 16, 2019
@seanmonstar
Copy link
Member

itshappening.gif

@NN-Binary
Copy link

Wow amazing job guys! Congrats, you are the first Rust crate to handle HTTP2 Server push!

@le-invoker
Copy link

Thank you all for the hardwork.
Now, if someone can pinpoint me the exact function to use for me to tell the webserver to create a server_push response for each corresponding future request.

@le-invoker
Copy link

hi @michaelbeaumont , there is a posted example/issue on how to use h2 to do server_push. I have the same problem, can you pinpoint what the code does wrong here?

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.

State of HTTP/2 push Provide a way for the server to send push promises to the client
9 participants