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

fix(client): drop connection if body of a response was not consumed, to avoid bad state #1177

Merged
merged 1 commit into from
Jan 30, 2025

Conversation

joelwurtz
Copy link
Contributor

Pr is not finished, i only added a test.

Actually if a body is not readed on the client, but the reponse is dropped, conn will go back to the pool with existing data of the response not consumed, which will fail the next request since it will try to decode the header on the previous response body

This may happens if a client send a request, gets an error with a status code and a message in the body by example, and when there is a specific status code (in this case the error), will not bother to consume the body.

Not sure how to handle this well, i assume we should force the body to be consumed before going back to the pool but i'm not sure how to do this

@joelwurtz joelwurtz marked this pull request as ready for review January 11, 2025 14:05
@joelwurtz
Copy link
Contributor Author

Not sure this is best way, but at least it's safer, i'm not sure how to consume the body if dropped so for the moment i destroy the connection in this case.

I think it will require a specific state of the connection so it will ready the previous body before the next request in this case, but maybe you will have a better feedback for this.

@joelwurtz joelwurtz force-pushed the fix/invalid-conn-state branch 2 times, most recently from f2c8bfc to ad51cce Compare January 11, 2025 14:44
@joelwurtz joelwurtz changed the title fix(client): add a test to show bad behavior if body not consumed fix(client): drop connection if body of a response was not consumed, to avoid bad state Jan 11, 2025
@joelwurtz joelwurtz force-pushed the fix/invalid-conn-state branch from ad51cce to 8a82b9d Compare January 28, 2025 14:17
@fakeshadow
Copy link
Collaborator

fakeshadow commented Jan 28, 2025

I believe using TransferCoding::is_eof as the condition for destroy connection on drop would be enough.

In xitca-http a body with content-length: 0 header would be converted to TransferCoding::Eof in place(Not exactly the described process in real but very close) to hint user can avoid polling beforehand. And there should be no need to handle it specially. Related code:

// skip set when the request body is zero length.

As for body with transfer-encoding: chunked I feel the current code be exposed as not handling eof properly. (The body should explicitly branch on ChunkResult::OnEof by polling the stream one more time recursively and enter ChunkResult::AlreadyEof branch). Something like this should be enough:

impl<C> Stream for ResponseBody<C> {
    type Item = Result<Bytes, BodyError>;

    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        let this = self.get_mut();

        loop {
            match this.decoder.decode(&mut this.buf) {
                ChunkResult::OnEof => {
                    // this does not really impact the state of TransferCoding other than hint who polling the ResponseBody should not skip the OnEof state.
                },
            }
        }
    }
}

@fakeshadow
Copy link
Collaborator

fakeshadow commented Jan 28, 2025

There could be edge case where user weirdly decide to ignore body state and stop polling the stream when OnEof state appear. But I personally don't believe this is case we should burden ourselves to support. The worst case is connection can be closed prematurely due to user not polling body until it reaches None state.

@joelwurtz joelwurtz force-pushed the fix/invalid-conn-state branch from 8a82b9d to ca11fca Compare January 29, 2025 15:05
@joelwurtz
Copy link
Contributor Author

I updated the PR to use is_eof instead

@fakeshadow
Copy link
Collaborator

Sorry I forgot to mention that it could be better to impl the Drop on h1::body::ResponseBody to reduce feature gated code. The change and test are good.

@joelwurtz
Copy link
Contributor Author

joelwurtz commented Jan 30, 2025

It's not possible to drop it on h1::body::ResponseBody due to the generic data type i cannot enforce a drop on every possible one (but given how it's used we could now drop this generic data type since there is no difference between owned and not owned anymore, if you are fine with that i can make the change)

@fakeshadow
Copy link
Collaborator

Yea it's Ok to remove generic type from the h1::body::ResponseBody and use connection::H1ConnectionWithKey as concrete type. body::ResponseBody::H1Owned variant can be completely remove to make this happen. They are mostly legacy artifact right now.

@joelwurtz joelwurtz force-pushed the fix/invalid-conn-state branch 2 times, most recently from 66531d8 to c4712ec Compare January 30, 2025 10:33
@joelwurtz
Copy link
Contributor Author

Should be good then, clippy failure seems unrelated

@joelwurtz joelwurtz force-pushed the fix/invalid-conn-state branch from c4712ec to b5ac294 Compare January 30, 2025 10:41
@fakeshadow
Copy link
Collaborator

Looks good

@fakeshadow fakeshadow merged commit 3698103 into HFQR:main Jan 30, 2025
12 of 13 checks passed
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.

2 participants