aboutsummaryrefslogtreecommitdiffhomepage
path: root/pingora-proxy/src/proxy_h2.rs
diff options
context:
space:
mode:
authorEdward Wang <[email protected]>2024-10-30 17:03:59 -0700
committerEdward Wang <[email protected]>2024-11-01 16:14:36 -0700
commit4e4eb5a362fe8dc5f8bff8bc4deabf549c7f8ac5 (patch)
tree1ea2f69624f3ce16d42eb5ba63b830a971f173ec /pingora-proxy/src/proxy_h2.rs
parent2228bfb3518dd6451261a6e319a41b7eb4604c22 (diff)
downloadpingora-4e4eb5a362fe8dc5f8bff8bc4deabf549c7f8ac5.tar.gz
pingora-4e4eb5a362fe8dc5f8bff8bc4deabf549c7f8ac5.zip
Check h2 stream end for error
Certain APIs consuming HTTPTasks want to know if the response has finished. However, in cases such as protocol errors the h2 RecvStream will return `is_end_stream` which may be associated with an HTTPTask that was not actually the end of stream. This affects systems such as caching which wants to know when the response has properly finished for finishing a cache miss. This change attempts to check if the stream has ended due to an h2 error or not to forward a successful or Failed HTTPTask as appropriate.
Diffstat (limited to 'pingora-proxy/src/proxy_h2.rs')
-rw-r--r--pingora-proxy/src/proxy_h2.rs44
1 files changed, 32 insertions, 12 deletions
diff --git a/pingora-proxy/src/proxy_h2.rs b/pingora-proxy/src/proxy_h2.rs
index 53b2c14..fad91c2 100644
--- a/pingora-proxy/src/proxy_h2.rs
+++ b/pingora-proxy/src/proxy_h2.rs
@@ -569,9 +569,21 @@ pub(crate) async fn pipe_2to1_response(
let resp_header = Box::new(client.response_header().expect("just read").clone());
- tx.send(HttpTask::Header(resp_header, client.response_finished()))
- .await
- .or_err(InternalError, "sending h2 headers to pipe")?;
+ match client.check_response_end_or_error() {
+ Ok(eos) => {
+ tx.send(HttpTask::Header(resp_header, eos))
+ .await
+ .or_err(InternalError, "sending h2 headers to pipe")?;
+ }
+ Err(e) => {
+ // If upstream errored, then push error to downstream and then quit
+ // Don't care if send fails (which means downstream already gone)
+ // we were still able to retrieve the headers, so try sending
+ let _ = tx.send(HttpTask::Header(resp_header, false)).await;
+ let _ = tx.send(HttpTask::Failed(e.into_up())).await;
+ return Ok(());
+ }
+ }
while let Some(chunk) = client
.read_response_body()
@@ -583,21 +595,29 @@ pub(crate) async fn pipe_2to1_response(
Ok(d) => d,
Err(e) => {
// Push the error to downstream and then quit
- // Don't care if send fails: downstream already gone
let _ = tx.send(HttpTask::Failed(e.into_up())).await;
// Downstream should consume all remaining data and handle the error
return Ok(());
}
};
- if data.is_empty() && !client.response_finished() {
- /* it is normal to get 0 bytes because of multi-chunk
- * don't write 0 bytes to downstream since it will be
- * misread as the terminating chunk */
- continue;
+ match client.check_response_end_or_error() {
+ Ok(eos) => {
+ if data.is_empty() && !eos {
+ /* it is normal to get 0 bytes because of multi-chunk
+ * don't write 0 bytes to downstream since it will be
+ * misread as the terminating chunk */
+ continue;
+ }
+ tx.send(HttpTask::Body(Some(data), eos))
+ .await
+ .or_err(InternalError, "sending h2 body to pipe")?;
+ }
+ Err(e) => {
+ // Similar to above, push the error to downstream and then quit
+ let _ = tx.send(HttpTask::Failed(e.into_up())).await;
+ return Ok(());
+ }
}
- tx.send(HttpTask::Body(Some(data), client.response_finished()))
- .await
- .or_err(InternalError, "sending h2 body to pipe")?;
}
// attempt to get trailers