Skip to content

Commit deb92db

Browse files
cratelynsfleen
andauthored
refactor: move away from legacy hyper::body::HttpBody (#3467)
* refactor: move away from legacy `hyper::body::HttpBody` this is an incremental step away from hyper 0.14's request and response body interfaces, and towards the 1.0 body types. see <linkerd/linkerd2#8733> for more information about upgrading to hyper 1.0. hyper 0.14 provides a `hyper::body::Body` that is removed in the 1.0 interface. `hyper-util` now provides a workable default body type. hyper 0.14 reëxports `http_body::Body` as `HttpBody`. hyper 1.0 reëxports this trait as `hyper::body::Body` without any renaming. this commit moves application code away from hyper's legacy `Body` type and the `HttpBody` trait alias. this commit moves assorted interfaces towards the boxed `BoxBody` type instead. when possible, code is tweaked such that it refers to the reëxport in `linkerd-proxy-http`, rather than directly through `hyper`. NB: this commit is based upon #3466. Signed-off-by: katelyn martin <[email protected]> * feat(http/box): `BoxBody::from_static` constructor this commit relates to review feedback offered here: #3467 (comment) it is slightly ungainly to place a static string into a BoxBody, something that is a fairly common pattern throughout e.g. our admin server. to help nudge the compiler to infer a `B: Body` of `String`, various code follows a common convention of calling e.g. `BoxBody::new::<String>("ready\n".into())` or, `BoxBody::new("ready\n".to_string())`. this commit helps reduce that boilerplate by adding a `from_static(..)` constructor that accepts a static string. ```rust /// Returns a [`BoxBody`] with the contents of a static string. pub fn from_static(body: &'static str) -> Self { Self::new(body.to_string()) } ``` Co-authored-by: Scott Fleener <[email protected]> Signed-off-by: katelyn martin <[email protected]> * refactor(app/admin): outline json bytes body construction see <#3467 (comment)>. @sfleen points out this unfortunate part of our diff: ```diff - .body(json.into()) + .body(BoxBody::new(http_body::Full::new(bytes::Bytes::from(json)))) ``` this *is* a bit of an unfortunate edge, where boxing up a body feels especially cumbersome. this commit takes an attempt at tweaking the function in question so that this large nested expression reads a bit nicer. first, to justify why this gets a little more involved: hyper will no longer provide the `Body` type after 1.0, so we are tasked with providing our own body. for our purposes, `Full` works because we have a single chunk of bytes in hand. in order to create a `Full`, we must provide a `D: Buf`, which can be satisfied by the following types: <https://docs.rs/bytes/1.6.0/bytes/buf/trait.Buf.html#foreign-impls> most simply, we can cast our vector of bytes into a `Bytes`. with all of this in hand, we can hoist this creation of the body up out of the `match` arm, and use `Result::map` to apply these constructors in sequential order: ```rust // Serialize the value into JSON, and then place the bytes in a boxed response body. let json = serde_json::to_vec(val) .map(Bytes::from) .map(http_body::Full::new) .map(BoxBody::new); ``` Signed-off-by: katelyn martin <[email protected]> --------- Signed-off-by: katelyn martin <[email protected]> Co-authored-by: Scott Fleener <[email protected]>
1 parent 35e7316 commit deb92db

File tree

45 files changed

+202
-172
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+202
-172
lines changed

Cargo.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,7 @@ version = "0.1.0"
969969
dependencies = [
970970
"futures",
971971
"http",
972+
"http-body",
972973
"hyper",
973974
"pin-project",
974975
"tokio",
@@ -1312,6 +1313,7 @@ dependencies = [
13121313
name = "linkerd-app-admin"
13131314
version = "0.1.0"
13141315
dependencies = [
1316+
"bytes",
13151317
"deflate",
13161318
"futures",
13171319
"http",
@@ -1999,7 +2001,9 @@ version = "0.1.0"
19992001
dependencies = [
20002002
"deflate",
20012003
"http",
2004+
"http-body",
20022005
"hyper",
2006+
"linkerd-http-box",
20032007
"linkerd-stack",
20042008
"linkerd-system",
20052009
"parking_lot",
@@ -2335,8 +2339,10 @@ dependencies = [
23352339
name = "linkerd-proxy-tap"
23362340
version = "0.1.0"
23372341
dependencies = [
2342+
"bytes",
23382343
"futures",
23392344
"http",
2345+
"http-body",
23402346
"hyper",
23412347
"ipnet",
23422348
"linkerd-conditional",

hyper-balance/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ publish = false
99
[dependencies]
1010
futures = { version = "0.3", default-features = false }
1111
http = { workspace = true }
12+
http-body = { workspace = true }
1213
hyper = { workspace = true, features = ["deprecated"] }
1314
pin-project = "1"
1415
tower = { version = "0.4", default-features = false, features = ["load"] }

hyper-balance/src/lib.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#![deny(rust_2018_idioms, clippy::disallowed_methods, clippy::disallowed_types)]
22
#![forbid(unsafe_code)]
33

4-
use hyper::body::HttpBody;
4+
use http_body::Body;
55
use pin_project::pin_project;
66
use std::pin::Pin;
77
use std::task::{Context, Poll};
@@ -38,7 +38,7 @@ pub struct PendingUntilEosBody<T, B> {
3838

3939
impl<T, B> TrackCompletion<T, http::Response<B>> for PendingUntilFirstData
4040
where
41-
B: HttpBody,
41+
B: Body,
4242
{
4343
type Output = http::Response<PendingUntilFirstDataBody<T, B>>;
4444

@@ -59,7 +59,7 @@ where
5959

6060
impl<T, B> TrackCompletion<T, http::Response<B>> for PendingUntilEos
6161
where
62-
B: HttpBody,
62+
B: Body,
6363
{
6464
type Output = http::Response<PendingUntilEosBody<T, B>>;
6565

@@ -80,7 +80,7 @@ where
8080

8181
impl<T, B> Default for PendingUntilFirstDataBody<T, B>
8282
where
83-
B: HttpBody + Default,
83+
B: Body + Default,
8484
{
8585
fn default() -> Self {
8686
Self {
@@ -90,9 +90,9 @@ where
9090
}
9191
}
9292

93-
impl<T, B> HttpBody for PendingUntilFirstDataBody<T, B>
93+
impl<T, B> Body for PendingUntilFirstDataBody<T, B>
9494
where
95-
B: HttpBody,
95+
B: Body,
9696
T: Send + 'static,
9797
{
9898
type Data = B::Data;
@@ -138,7 +138,7 @@ where
138138

139139
impl<T, B> Default for PendingUntilEosBody<T, B>
140140
where
141-
B: HttpBody + Default,
141+
B: Body + Default,
142142
{
143143
fn default() -> Self {
144144
Self {
@@ -148,7 +148,7 @@ where
148148
}
149149
}
150150

151-
impl<T: Send + 'static, B: HttpBody> HttpBody for PendingUntilEosBody<T, B> {
151+
impl<T: Send + 'static, B: Body> Body for PendingUntilEosBody<T, B> {
152152
type Data = B::Data;
153153
type Error = B::Error;
154154

@@ -198,7 +198,7 @@ impl<T: Send + 'static, B: HttpBody> HttpBody for PendingUntilEosBody<T, B> {
198198
mod tests {
199199
use super::{PendingUntilEos, PendingUntilFirstData};
200200
use futures::future::poll_fn;
201-
use hyper::body::HttpBody;
201+
use http_body::Body;
202202
use std::collections::VecDeque;
203203
use std::io::Cursor;
204204
use std::pin::Pin;
@@ -429,7 +429,7 @@ mod tests {
429429

430430
#[derive(Default)]
431431
struct TestBody(VecDeque<&'static str>, Option<http::HeaderMap>);
432-
impl HttpBody for TestBody {
432+
impl Body for TestBody {
433433
type Data = Cursor<&'static str>;
434434
type Error = &'static str;
435435

@@ -456,7 +456,7 @@ mod tests {
456456

457457
#[derive(Default)]
458458
struct ErrBody(Option<&'static str>);
459-
impl HttpBody for ErrBody {
459+
impl Body for ErrBody {
460460
type Data = Cursor<&'static str>;
461461
type Error = &'static str;
462462

linkerd/app/admin/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ pprof = ["deflate", "dep:pprof"]
1515
log-streaming = ["linkerd-tracing/stream"]
1616

1717
[dependencies]
18+
bytes = "1"
1819
deflate = { version = "1", optional = true, features = ["gzip"] }
1920
http = { workspace = true }
2021
http-body = { workspace = true }

linkerd/app/admin/src/server.rs

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,9 @@
1212
1313
use futures::future::{self, TryFutureExt};
1414
use http::StatusCode;
15-
use hyper::{
16-
body::{Body, HttpBody},
17-
Request, Response,
18-
};
1915
use linkerd_app_core::{
2016
metrics::{self as metrics, FmtMetrics},
21-
proxy::http::ClientHandle,
17+
proxy::http::{Body, BoxBody, ClientHandle, Request, Response},
2218
trace, Error, Result,
2319
};
2420
use std::{
@@ -45,7 +41,7 @@ pub struct Admin<M> {
4541
pprof: Option<crate::pprof::Pprof>,
4642
}
4743

48-
pub type ResponseFuture = Pin<Box<dyn Future<Output = Result<Response<Body>>> + Send + 'static>>;
44+
pub type ResponseFuture = Pin<Box<dyn Future<Output = Result<Response<BoxBody>>> + Send + 'static>>;
4945

5046
impl<M> Admin<M> {
5147
pub fn new(
@@ -73,30 +69,30 @@ impl<M> Admin<M> {
7369
self
7470
}
7571

76-
fn ready_rsp(&self) -> Response<Body> {
72+
fn ready_rsp(&self) -> Response<BoxBody> {
7773
if self.ready.is_ready() {
7874
Response::builder()
7975
.status(StatusCode::OK)
8076
.header(http::header::CONTENT_TYPE, "text/plain")
81-
.body("ready\n".into())
77+
.body(BoxBody::from_static("ready\n"))
8278
.expect("builder with known status code must not fail")
8379
} else {
8480
Response::builder()
8581
.status(StatusCode::SERVICE_UNAVAILABLE)
86-
.body("not ready\n".into())
82+
.body(BoxBody::from_static("not ready\n"))
8783
.expect("builder with known status code must not fail")
8884
}
8985
}
9086

91-
fn live_rsp() -> Response<Body> {
87+
fn live_rsp() -> Response<BoxBody> {
9288
Response::builder()
9389
.status(StatusCode::OK)
9490
.header(http::header::CONTENT_TYPE, "text/plain")
95-
.body("live\n".into())
91+
.body(BoxBody::from_static("live\n"))
9692
.expect("builder with known status code must not fail")
9793
}
9894

99-
fn env_rsp<B>(req: Request<B>) -> Response<Body> {
95+
fn env_rsp<B>(req: Request<B>) -> Response<BoxBody> {
10096
use std::{collections::HashMap, env, ffi::OsString};
10197

10298
if req.method() != http::Method::GET {
@@ -142,56 +138,58 @@ impl<M> Admin<M> {
142138
json::json_rsp(&env)
143139
}
144140

145-
fn shutdown(&self) -> Response<Body> {
141+
fn shutdown(&self) -> Response<BoxBody> {
146142
if !self.enable_shutdown {
147143
return Response::builder()
148144
.status(StatusCode::NOT_FOUND)
149145
.header(http::header::CONTENT_TYPE, "text/plain")
150-
.body("shutdown endpoint is not enabled\n".into())
146+
.body(BoxBody::from_static("shutdown endpoint is not enabled\n"))
151147
.expect("builder with known status code must not fail");
152148
}
153149
if self.shutdown_tx.send(()).is_ok() {
154150
Response::builder()
155151
.status(StatusCode::OK)
156152
.header(http::header::CONTENT_TYPE, "text/plain")
157-
.body("shutdown\n".into())
153+
.body(BoxBody::from_static("shutdown\n"))
158154
.expect("builder with known status code must not fail")
159155
} else {
160156
Response::builder()
161157
.status(StatusCode::INTERNAL_SERVER_ERROR)
162158
.header(http::header::CONTENT_TYPE, "text/plain")
163-
.body("shutdown listener dropped\n".into())
159+
.body(BoxBody::from_static("shutdown listener dropped\n"))
164160
.expect("builder with known status code must not fail")
165161
}
166162
}
167163

168-
fn internal_error_rsp(error: impl ToString) -> http::Response<Body> {
164+
fn internal_error_rsp(error: impl ToString) -> http::Response<BoxBody> {
169165
http::Response::builder()
170166
.status(http::StatusCode::INTERNAL_SERVER_ERROR)
171167
.header(http::header::CONTENT_TYPE, "text/plain")
172-
.body(error.to_string().into())
168+
.body(BoxBody::new(error.to_string()))
173169
.expect("builder with known status code should not fail")
174170
}
175171

176-
fn not_found() -> Response<Body> {
172+
fn not_found() -> Response<BoxBody> {
177173
Response::builder()
178174
.status(http::StatusCode::NOT_FOUND)
179-
.body(Body::empty())
175+
.body(BoxBody::new(hyper::Body::empty()))
180176
.expect("builder with known status code must not fail")
181177
}
182178

183-
fn method_not_allowed() -> Response<Body> {
179+
fn method_not_allowed() -> Response<BoxBody> {
184180
Response::builder()
185181
.status(http::StatusCode::METHOD_NOT_ALLOWED)
186-
.body(Body::empty())
182+
.body(BoxBody::new(hyper::Body::empty()))
187183
.expect("builder with known status code must not fail")
188184
}
189185

190-
fn forbidden_not_localhost() -> Response<Body> {
186+
fn forbidden_not_localhost() -> Response<BoxBody> {
191187
Response::builder()
192188
.status(http::StatusCode::FORBIDDEN)
193189
.header(http::header::CONTENT_TYPE, "text/plain")
194-
.body("Requests are only permitted from localhost.".into())
190+
.body(BoxBody::new::<String>(
191+
"Requests are only permitted from localhost.".into(),
192+
))
195193
.expect("builder with known status code must not fail")
196194
}
197195

@@ -215,11 +213,11 @@ impl<M> Admin<M> {
215213
impl<M, B> tower::Service<http::Request<B>> for Admin<M>
216214
where
217215
M: FmtMetrics,
218-
B: HttpBody + Send + 'static,
216+
B: Body + Send + 'static,
219217
B::Error: Into<Error>,
220218
B::Data: Send,
221219
{
222-
type Response = http::Response<Body>;
220+
type Response = http::Response<BoxBody>;
223221
type Error = Error;
224222
type Future = ResponseFuture;
225223

@@ -331,7 +329,7 @@ mod tests {
331329
let r = Request::builder()
332330
.method(Method::GET)
333331
.uri("http://0.0.0.0/ready")
334-
.body(Body::empty())
332+
.body(hyper::Body::empty())
335333
.unwrap();
336334
let f = admin.clone().oneshot(r);
337335
timeout(TIMEOUT, f).await.expect("timeout").expect("call")

linkerd/app/admin/src/server/json.rs

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
static JSON_MIME: &str = "application/json";
22
pub(in crate::server) static JSON_HEADER_VAL: HeaderValue = HeaderValue::from_static(JSON_MIME);
33

4+
use bytes::Bytes;
45
use hyper::{
56
header::{self, HeaderValue},
6-
Body, StatusCode,
7+
StatusCode,
78
};
9+
use linkerd_app_core::proxy::http::BoxBody;
10+
811
pub(crate) fn json_error_rsp(
912
error: impl ToString,
1013
status: http::StatusCode,
11-
) -> http::Response<Body> {
14+
) -> http::Response<BoxBody> {
1215
mk_rsp(
1316
status,
1417
&serde_json::json!({
@@ -18,11 +21,12 @@ pub(crate) fn json_error_rsp(
1821
)
1922
}
2023

21-
pub(crate) fn json_rsp(val: &impl serde::Serialize) -> http::Response<Body> {
24+
pub(crate) fn json_rsp(val: &impl serde::Serialize) -> http::Response<BoxBody> {
2225
mk_rsp(StatusCode::OK, val)
2326
}
2427

25-
pub(crate) fn accepts_json<B>(req: &http::Request<B>) -> Result<(), http::Response<Body>> {
28+
#[allow(clippy::result_large_err)]
29+
pub(crate) fn accepts_json<B>(req: &http::Request<B>) -> Result<(), http::Response<BoxBody>> {
2630
if let Some(accept) = req.headers().get(header::ACCEPT) {
2731
let accept = match std::str::from_utf8(accept.as_bytes()) {
2832
Ok(accept) => accept,
@@ -41,26 +45,34 @@ pub(crate) fn accepts_json<B>(req: &http::Request<B>) -> Result<(), http::Respon
4145
tracing::warn!(?accept, "Accept header will not accept 'application/json'");
4246
return Err(http::Response::builder()
4347
.status(StatusCode::NOT_ACCEPTABLE)
44-
.body(JSON_MIME.into())
48+
.body(BoxBody::from_static(JSON_MIME))
4549
.expect("builder with known status code must not fail"));
4650
}
4751
}
4852

4953
Ok(())
5054
}
5155

52-
fn mk_rsp(status: StatusCode, val: &impl serde::Serialize) -> http::Response<Body> {
53-
match serde_json::to_vec(val) {
54-
Ok(json) => http::Response::builder()
56+
fn mk_rsp(status: StatusCode, val: &impl serde::Serialize) -> http::Response<BoxBody> {
57+
// Serialize the value into JSON, and then place the bytes in a boxed response body.
58+
let json = serde_json::to_vec(val)
59+
.map(Bytes::from)
60+
.map(http_body::Full::new)
61+
.map(BoxBody::new);
62+
63+
match json {
64+
Ok(body) => http::Response::builder()
5565
.status(status)
5666
.header(header::CONTENT_TYPE, JSON_HEADER_VAL.clone())
57-
.body(json.into())
67+
.body(body)
5868
.expect("builder with known status code must not fail"),
5969
Err(error) => {
6070
tracing::warn!(?error, "failed to serialize JSON value");
6171
http::Response::builder()
6272
.status(StatusCode::INTERNAL_SERVER_ERROR)
63-
.body(format!("failed to serialize JSON value: {error}").into())
73+
.body(BoxBody::new(format!(
74+
"failed to serialize JSON value: {error}"
75+
)))
6476
.expect("builder with known status code must not fail")
6577
}
6678
}

0 commit comments

Comments
 (0)