Skip to content

Commit 04d86a0

Browse files
authored
refactor(http/upgrade): remove HttpConnect extension (#3779)
this branch is motivated by [review feedback](#3504 (comment)) from #3504. see linkerd/linkerd2#8733 for more information on upgrading `hyper`. there, we asked: > I wonder if we should be a little more defensive about cloning [`HttpConnect`]. What does cloning it mean? When handling a CONNECT request, we can't clone the request, really. (Technically, we can't clone the body, but practically, it means we can't clone the request). Can we easily track whether this was accidentally cloned (i.e. with a custom Clone impl or Arc or some such) and validate at runtime (i.e., in proxy::http::h1) that everything is copacetic? `linkerd-http-upgrade` provides a `HttpConnect` type that is intended for use as a response extension. this commit performs a refactor, removing this type. we use this extension in a single piece of tower middleware. typically, these sorts of extensions are intended for e.g. passing state between distinct layers of tower middleware, or otherwise facilitating extensions to the HTTP family of protocols. this extension is only constructed and subsequently referenced within a single file, in the `linkerd_proxy_http::http::h1::Client`. we can perform the same task by using the `is_http_connect` boolean we use to conditionally insert this extension. then, this branch removes a helper function for a computation whose amortization is no longer as helpful. now that we are passing `is_http_connect` down into this function, we are no longer inspecting the response's extensions. because of that, the only work to do is to check the status code, which is a very cheap comparison. this also restates an `if version != HTTP_11 { .. }` conditional block as a match statement. this is a code motion change, none of the inner blocks are changed. reviewers are encouraged to examine this branch commit-by-commit; because of the sensitivity of this change, this refactor is performed in small, methodical changes. for posterity, i've run the linkerd/linkerd2 test suite against this branch, as of linkerd/linkerd2@57dd7f4. --- * refactor(http/upgrade): remove `HttpConnect` extension `linkerd-http-upgrade` provides a `HttpConnect` type that is intended for use as a response extension. this commit performs a refactor, removing this type. we use this extension in a single piece of tower middleware. typically, these sorts of extensions are intended for e.g. passing state between distinct layers of tower middleware, or otherwise facilitating extensions to the HTTP family of protocols. this extension is only constructed and subsequently referenced within a single file, in the `linkerd_proxy_http::http::h1::Client`. we can perform the same task by using the `is_http_connect` boolean we use to conditionally insert this extension. Signed-off-by: katelyn martin <kate@buoyant.io> * refactor(proxy/http): fold helper function this removes a helper function for a computation whose amortization is no longer as helpful. now that we are passing `is_http_connect` down into this function, we are no longer inspecting the response's extensions. because of that, the only work to do is to check the status code, which is a very cheap comparison. Signed-off-by: katelyn martin <kate@buoyant.io> * refactor(proxy/http): match on response status this commit refactors a sequence of conditional blocks in a helper function used to identity HTTP/1.1 upgrades. this commit replaces this sequence of conditional blocks with a match statement. Signed-off-by: katelyn martin <kate@buoyant.io> * nit(proxy/http): rename `res` to `rsp` we follow a convention where we tend to name responses `rsp`, not `res` or `resp`. this commit applies that convention to this helper function. Signed-off-by: katelyn martin <kate@buoyant.io> * nit(proxy/http): import `Version` Signed-off-by: katelyn martin <kate@buoyant.io> * refactor(proxy/http): match on http version this restates an `if version != HTTP_11 { .. }` conditional block as a match statement. this is a code motion change, none of the inner blocks are changed. Signed-off-by: katelyn martin <kate@buoyant.io> * refactor(proxy/http): add comments on http/1.1 this commit adds a brief comment noting that upgrades are a concept specific to http/1.1. Signed-off-by: katelyn martin <kate@buoyant.io> --------- Signed-off-by: katelyn martin <kate@buoyant.io>
1 parent 13478ae commit 04d86a0

File tree

2 files changed

+26
-44
lines changed

2 files changed

+26
-44
lines changed

linkerd/http/upgrade/src/upgrade.rs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,6 @@ struct Http11UpgradeHalves {
3737
client: Http11Upgrade,
3838
}
3939

40-
/// A marker type inserted into Extensions to signal it was an HTTP CONNECT
41-
/// request.
42-
#[derive(Debug)]
43-
pub struct HttpConnect;
44-
4540
struct Inner {
4641
server: TryLock<Option<OnUpgrade>>,
4742
client: TryLock<Option<OnUpgrade>>,

linkerd/proxy/http/src/h1.rs

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ use http::{
66
};
77
use linkerd_error::{Error, Result};
88
use linkerd_http_box::BoxBody;
9-
use linkerd_http_upgrade::{
10-
glue::HyperConnect,
11-
upgrade::{Http11Upgrade, HttpConnect},
12-
};
9+
use linkerd_http_upgrade::{glue::HyperConnect, upgrade::Http11Upgrade};
1310
use linkerd_stack::MakeConnection;
1411
use std::{pin::Pin, time::Duration};
1512
use tracing::{debug, trace};
@@ -139,12 +136,6 @@ where
139136
Box::pin(async move {
140137
let mut rsp = rsp_fut.await?;
141138
if is_http_connect {
142-
// Add an extension to indicate that this a response to a CONNECT request.
143-
debug_assert!(
144-
upgrade.is_some(),
145-
"Upgrade extension must be set on CONNECT requests"
146-
);
147-
rsp.extensions_mut().insert(HttpConnect);
148139
// Strip headers that may not be transmitted to the server, per RFC 9110:
149140
//
150141
// > A server MUST NOT send any `Transfer-Encoding` or `Content-Length` header
@@ -159,7 +150,7 @@ where
159150
}
160151
}
161152

162-
if is_upgrade(&rsp) {
153+
if is_upgrade(&rsp, is_http_connect) {
163154
trace!("Client response is HTTP/1.1 upgrade");
164155
if let Some(upgrade) = upgrade {
165156
upgrade.insert_half(hyper::upgrade::on(&mut rsp))?;
@@ -174,36 +165,32 @@ where
174165
}
175166

176167
/// Checks responses to determine if they are successful HTTP upgrades.
177-
fn is_upgrade<B>(res: &http::Response<B>) -> bool {
178-
#[inline]
179-
fn is_connect_success<B>(res: &http::Response<B>) -> bool {
180-
res.extensions().get::<HttpConnect>().is_some() && res.status().is_success()
181-
}
182-
183-
// Upgrades were introduced in HTTP/1.1
184-
if res.version() != http::Version::HTTP_11 {
185-
if is_connect_success(res) {
186-
tracing::warn!(
187-
"A successful response to a CONNECT request had an incorrect HTTP version \
188-
(expected HTTP/1.1, got {:?})",
189-
res.version()
190-
);
168+
fn is_upgrade<B>(rsp: &http::Response<B>, is_http_connect: bool) -> bool {
169+
use http::Version;
170+
171+
match rsp.version() {
172+
Version::HTTP_11 => match rsp.status() {
173+
// `101 Switching Protocols` indicates an upgrade.
174+
http::StatusCode::SWITCHING_PROTOCOLS => true,
175+
// CONNECT requests are complete if status code is 2xx.
176+
status if is_http_connect && status.is_success() => true,
177+
// Just a regular HTTP response...
178+
_ => false,
179+
},
180+
version => {
181+
// Upgrades are specific to HTTP/1.1. They are not included in HTTP/1.0, nor are they
182+
// supported in HTTP/2. If this response is associated with any protocol version
183+
// besides HTTP/1.1, it is not applicable to an upgrade.
184+
if is_http_connect && rsp.status().is_success() {
185+
tracing::warn!(
186+
"A successful response to a CONNECT request had an incorrect HTTP version \
187+
(expected HTTP/1.1, got {:?})",
188+
version
189+
);
190+
}
191+
false
191192
}
192-
return false;
193193
}
194-
195-
// 101 Switching Protocols
196-
if res.status() == http::StatusCode::SWITCHING_PROTOCOLS {
197-
return true;
198-
}
199-
200-
// CONNECT requests are complete if status code is 2xx.
201-
if is_connect_success(res) {
202-
return true;
203-
}
204-
205-
// Just a regular HTTP response...
206-
false
207194
}
208195

209196
/// Returns if the request target is in `absolute-form`.

0 commit comments

Comments
 (0)