Skip to content

Commit 430d7bc

Browse files
committed
build(deps): migrate from hyper 0.14 to hyper 1.x
1 parent 6ed3cb7 commit 430d7bc

12 files changed

Lines changed: 248 additions & 376 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ regex = "1.12.3"
2727
serde = { version = "1.0.228", features = ["derive"] }
2828
cookie = "0.18.1"
2929
futures-lite = "2.6.1"
30-
hyper = { version = "0.14.31", features = ["full"] }
30+
hyper = { version = "1.9.0", features = ["http1", "server"] }
31+
http-body-util = "0.1.3"
32+
hyper-util = { version = "0.1.20", features = ["tokio"] }
33+
bytes = "1"
3134
percent-encoding = "2.3.2"
3235
route-recognizer = "0.3.1"
3336
serde_json = "1.0.149"

src/client.rs

Lines changed: 43 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
1-
use crate::dbg_msg;
2-
use crate::oauth::{force_refresh_token, token_daemon, Oauth, OauthBackendImpl};
3-
use crate::server::RequestExt;
4-
use crate::utils::{format_url, Post};
51
use arc_swap::ArcSwap;
62
use cached::proc_macro::cached;
73
use futures_lite::future::block_on;
84
use futures_lite::{future::Boxed, FutureExt};
9-
use hyper::{body::Buf, header, Body, Request as HyperRequest, Response as HyperResponse};
5+
use http_body_util::BodyExt;
6+
use hyper::{header, Method, Request, Response};
107
use log::{error, info, trace, warn};
118
use percent_encoding::{percent_encode, CONTROLS};
129
use serde_json::Value;
13-
use std::result::Result;
10+
use wreq::redirect::Policy;
11+
use wreq::{Client as WreqClient, EmulationFactory, header as wreq_header, Response as WreqResponse};
12+
use wreq_util::{Emulation, EmulationOS, EmulationOption};
13+
1414
use std::sync::atomic::Ordering;
1515
use std::sync::atomic::{AtomicBool, AtomicU16};
1616
use std::sync::LazyLock;
17-
use wreq::redirect::Policy;
18-
use wreq::{header as wreq_header, Client as WreqClient, EmulationFactory, Method, Response as WreqResponse};
19-
use wreq_util::{Emulation, EmulationOS, EmulationOption};
17+
use std::result::Result;
18+
19+
use crate::dbg_msg;
20+
use crate::oauth::{force_refresh_token, token_daemon, Oauth, OauthBackendImpl};
21+
use crate::server::RequestExt;
22+
use crate::utils::{format_url, Post};
2023

2124
const REDDIT_URL_BASE: &str = "https://oauth.reddit.com";
2225
const REDDIT_URL_BASE_HOST: &str = "oauth.reddit.com";
@@ -66,21 +69,19 @@ pub fn build_client() -> WreqClient {
6669
.expect("Should always be able to build a client")
6770
}
6871

72+
use crate::server::{full, Body};
73+
6974
/// Convert a wreq Response into a hyper Response<Body>.
70-
/// This bridge lets the rest of the codebase stay unchanged.
75+
/// wreq and hyper now both use http v1.x, so header types are directly compatible.
7176
trait IntoHyperResponse {
7277
async fn into_hyper(self) -> Result<Response<Body>, String>;
7378
}
7479

7580
impl IntoHyperResponse for wreq::Response {
7681
async fn into_hyper(self) -> Result<Response<Body>, String> {
77-
// wreq uses http v1.x; hyper uses http v0.2.x. Convert via primitives.
78-
let status_u16 = self.status().as_u16();
79-
let status = hyper::StatusCode::from_u16(status_u16).map_err(|e| e.to_string())?;
82+
let status = self.status();
8083

81-
// Snapshot headers before consuming self (bytes() moves self).
82-
// Use SmallVec-style stack storage: most responses have < 32 headers.
83-
let mut builder = hyper::Response::builder().status(status);
84+
let mut builder = hyper::Response::builder().status(status.as_u16());
8485
for (k, v) in self.headers() {
8586
// Skip headers that hyper rejects (e.g. non-ASCII bytes in CDN headers)
8687
// rather than aborting the entire response.
@@ -100,7 +101,7 @@ impl IntoHyperResponse for wreq::Response {
100101
h.insert(header::CONTENT_LENGTH, bytes.len().into());
101102
}
102103

103-
builder.body(Body::from(bytes)).map_err(|e| e.to_string())
104+
builder.body(full(bytes)).map_err(|e| e.to_string())
104105
}
105106
}
106107

@@ -191,7 +192,7 @@ pub async fn canonical_path(path: String, tries: i8) -> Result<Option<String>, S
191192
}
192193
}
193194

194-
pub async fn proxy(req: HyperRequest<Body>, format: &str) -> Result<HyperResponse<Body>, String> {
195+
pub async fn proxy(req: Request<Body>, format: &str) -> Result<Response<Body>, String> {
195196
let mut url = format!("{format}?{}", req.uri().query().unwrap_or_default());
196197

197198
// For each parameter in request
@@ -221,30 +222,24 @@ pub async fn proxy(req: HyperRequest<Body>, format: &str) -> Result<HyperRespons
221222
// This is needed or Reddit will redirect us to a /media landing page that just renders the image.
222223
builder = builder.header(wreq_header::ACCEPT, "*/*");
223224

224-
builder
225-
.send()
226-
.await
227-
.map(|mut res| {
228-
let headers = res.headers_mut();
229-
230-
let mut rm = |key: &str| headers.remove(key);
231-
232-
rm("access-control-expose-headers");
233-
rm("server");
234-
rm("vary");
235-
rm("etag");
236-
rm("x-cdn");
237-
rm("x-cdn-client-region");
238-
rm("x-cdn-name");
239-
rm("x-cdn-server-region");
240-
rm("x-reddit-cdn");
241-
rm("x-reddit-video-features");
242-
rm("Nel");
243-
rm("Report-To");
244-
245-
res.into_hyper_response()
246-
})
247-
.map_err(|e| e.to_string())
225+
let mut res = builder.send().await.map_err(|e| e.to_string())?;
226+
{
227+
let headers = res.headers_mut();
228+
let mut rm = |key: &str| headers.remove(key);
229+
rm("access-control-expose-headers");
230+
rm("server");
231+
rm("vary");
232+
rm("etag");
233+
rm("x-cdn");
234+
rm("x-cdn-client-region");
235+
rm("x-cdn-name");
236+
rm("x-cdn-server-region");
237+
rm("x-reddit-cdn");
238+
rm("x-reddit-video-features");
239+
rm("Nel");
240+
rm("Report-To");
241+
}
242+
res.into_hyper().await
248243
}
249244

250245
/// Makes a GET request to Reddit at `path`. By default, this will honor HTTP
@@ -399,13 +394,12 @@ pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
399394
None
400395
};
401396

402-
// asynchronously aggregate the chunks of the body
403-
match hyper::body::aggregate(response.into_hyper_response()).await {
404-
Ok(body) => {
405-
let has_remaining = body.has_remaining();
397+
// Collect the body bytes
398+
match response.collect().await {
399+
Ok(collected) => {
400+
let body_bytes = collected.to_bytes();
406401

407-
if !has_remaining {
408-
// Rate limited, so spawn a force_refresh_token()
402+
if body_bytes.is_empty() {
409403
tokio::spawn(force_refresh_token());
410404
return match reset {
411405
Some(val) => Err(format!(
@@ -416,8 +410,7 @@ pub async fn json(path: String, quarantine: bool) -> Result<Value, String> {
416410
};
417411
}
418412

419-
// Parse the response from Reddit as JSON
420-
match serde_json::from_reader(body.reader()) {
413+
match serde_json::from_slice(&body_bytes) {
421414
Ok(value) => {
422415
let json: Value = value;
423416

@@ -513,34 +506,6 @@ pub async fn rate_limit_check() -> Result<(), String> {
513506
Ok(())
514507
}
515508

516-
trait IntoHyperResponse {
517-
fn into_hyper_response(self) -> HyperResponse<Body>;
518-
}
519-
520-
impl IntoHyperResponse for WreqResponse {
521-
fn into_hyper_response(self) -> HyperResponse<Body> {
522-
let status = self.status();
523-
let version = self.version();
524-
525-
let mut builder = HyperResponse::builder().status(status.as_u16()).version(match version {
526-
wreq::Version::HTTP_09 => hyper::Version::HTTP_09,
527-
wreq::Version::HTTP_10 => hyper::Version::HTTP_10,
528-
wreq::Version::HTTP_11 => hyper::Version::HTTP_11,
529-
wreq::Version::HTTP_2 => hyper::Version::HTTP_2,
530-
wreq::Version::HTTP_3 => hyper::Version::HTTP_3,
531-
_ => hyper::Version::HTTP_11,
532-
});
533-
534-
for (name, value) in self.headers() {
535-
builder = builder.header(
536-
header::HeaderName::from_bytes(name.as_str().as_bytes()).unwrap(),
537-
header::HeaderValue::from_bytes(value.as_bytes()).unwrap(),
538-
);
539-
}
540-
541-
builder.body(Body::wrap_stream(self.bytes_stream())).unwrap()
542-
}
543-
}
544509

545510
#[cfg(test)]
546511
mod tests {

src/duplicates.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use crate::subreddit::{can_access_quarantine, quarantine};
66
use crate::utils::{error, filter_posts, nsfw_landing, parse_post, template, Post, Preferences};
77

88
use askama::Template;
9-
use hyper::{Body, Request, Response};
9+
use hyper::{Request, Response};
10+
use crate::server::Body;
1011
use serde_json::Value;
1112
use std::borrow::ToOwned;
1213
use std::collections::HashSet;

src/instance_info.rs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ use crate::{
55
};
66
use askama::Template;
77
use build_html::{Container, Html, HtmlContainer, Table};
8-
use hyper::{http::Error, Body, Request, Response};
8+
use hyper::{http::Error, Request, Response};
9+
use crate::server::{full, Body};
910
use serde::{Deserialize, Serialize};
1011
use std::sync::LazyLock;
1112
use time::OffsetDateTime;
@@ -33,42 +34,39 @@ pub async fn instance_info(req: Request<Body>) -> Result<Response<Body>, String>
3334
}
3435
.render()
3536
.unwrap();
36-
Response::builder().status(404).header("content-type", "text/html; charset=utf-8").body(error.into())
37+
Response::builder().status(404).header("content-type", "text/html; charset=utf-8").body(full(error))
3738
}
3839
};
3940
response.map_err(|err| format!("{err}"))
4041
}
4142

4243
fn info_json() -> Result<Response<Body>, Error> {
4344
if let Ok(body) = serde_json::to_string(&*INSTANCE_INFO) {
44-
Response::builder().status(200).header("content-type", "application/json").body(body.into())
45+
Response::builder().status(200).header("content-type", "application/json").body(full(body))
4546
} else {
4647
Response::builder()
4748
.status(500)
4849
.header("content-type", "text/plain")
49-
.body(Body::from("Error serializing JSON"))
50+
.body(full("Error serializing JSON"))
5051
}
5152
}
5253

5354
fn info_yaml() -> Result<Response<Body>, Error> {
5455
if let Ok(body) = serde_yaml_ng::to_string(&*INSTANCE_INFO) {
55-
// We can use `application/yaml` as media type, though there is no guarantee
56-
// that browsers will honor it. But we'll do it anyway. See:
57-
// https://github.com/ietf-wg-httpapi/mediatypes/blob/main/draft-ietf-httpapi-yaml-mediatypes.md#media-type-applicationyaml-application-yaml
58-
Response::builder().status(200).header("content-type", "application/yaml").body(body.into())
56+
Response::builder().status(200).header("content-type", "application/yaml").body(full(body))
5957
} else {
6058
Response::builder()
6159
.status(500)
6260
.header("content-type", "text/plain")
63-
.body(Body::from("Error serializing YAML."))
61+
.body(full("Error serializing YAML."))
6462
}
6563
}
6664

6765
fn info_txt() -> Result<Response<Body>, Error> {
6866
Response::builder()
6967
.status(200)
7068
.header("content-type", "text/plain")
71-
.body(Body::from(INSTANCE_INFO.to_string(&StringType::Raw)))
69+
.body(full(INSTANCE_INFO.to_string(&StringType::Raw)))
7270
}
7371
fn info_html(req: &Request<Body>) -> Result<Response<Body>, Error> {
7472
let message = MessageTemplate {
@@ -79,7 +77,7 @@ fn info_html(req: &Request<Body>) -> Result<Response<Body>, Error> {
7977
}
8078
.render()
8179
.unwrap();
82-
Response::builder().status(200).header("content-type", "text/html; charset=utf8").body(Body::from(message))
80+
Response::builder().status(200).header("content-type", "text/html; charset=utf8").body(full(message))
8381
}
8482
#[derive(Serialize, Deserialize, Default)]
8583
pub struct InstanceInfo {

src/main.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use clap::{Arg, ArgAction, Command};
77
use std::sync::LazyLock;
88

99
use futures_lite::FutureExt;
10-
use hyper::{header::HeaderValue, Body, Request, Response};
10+
use hyper::{header::HeaderValue, Request, Response};
11+
use redlib::server::{full, Body};
1112
use log::{info, warn};
1213
use redlib::client::{canonical_path, proxy, rate_limit_check, MEDIA_CLIENT};
1314
use redlib::server::{self, RequestExt};
@@ -24,7 +25,7 @@ async fn pwa_logo() -> Result<Response<Body>, String> {
2425
Response::builder()
2526
.status(200)
2627
.header("content-type", "image/png")
27-
.body(include_bytes!("../static/logo.png").as_ref().into())
28+
.body(full(include_bytes!("../static/logo.png").as_ref()))
2829
.unwrap_or_default(),
2930
)
3031
}
@@ -35,7 +36,7 @@ async fn iphone_logo() -> Result<Response<Body>, String> {
3536
Response::builder()
3637
.status(200)
3738
.header("content-type", "image/png")
38-
.body(include_bytes!("../static/apple-touch-icon.png").as_ref().into())
39+
.body(full(include_bytes!("../static/apple-touch-icon.png").as_ref()))
3940
.unwrap_or_default(),
4041
)
4142
}
@@ -46,7 +47,7 @@ async fn favicon() -> Result<Response<Body>, String> {
4647
.status(200)
4748
.header("content-type", "image/vnd.microsoft.icon")
4849
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
49-
.body(include_bytes!("../static/favicon.ico").as_ref().into())
50+
.body(full(include_bytes!("../static/favicon.ico").as_ref()))
5051
.unwrap_or_default(),
5152
)
5253
}
@@ -57,7 +58,7 @@ async fn font() -> Result<Response<Body>, String> {
5758
.status(200)
5859
.header("content-type", "font/woff2")
5960
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
60-
.body(include_bytes!("../static/Inter.var.woff2").as_ref().into())
61+
.body(full(include_bytes!("../static/Inter.var.woff2").as_ref()))
6162
.unwrap_or_default(),
6263
)
6364
}
@@ -68,7 +69,7 @@ async fn opensearch() -> Result<Response<Body>, String> {
6869
.status(200)
6970
.header("content-type", "application/opensearchdescription+xml")
7071
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
71-
.body(include_bytes!("../static/opensearch.xml").as_ref().into())
72+
.body(full(include_bytes!("../static/opensearch.xml").as_ref()))
7273
.unwrap_or_default(),
7374
)
7475
}
@@ -77,7 +78,7 @@ async fn resource(body: &str, content_type: &str, cache: bool) -> Result<Respons
7778
let mut res = Response::builder()
7879
.status(200)
7980
.header("content-type", content_type)
80-
.body(body.to_string().into())
81+
.body(full(body.to_string()))
8182
.unwrap_or_default();
8283

8384
if cache {
@@ -105,7 +106,7 @@ async fn style() -> Result<Response<Body>, String> {
105106
.status(200)
106107
.header("content-type", "text/css")
107108
.header("Cache-Control", "public, max-age=1209600, s-maxage=86400")
108-
.body(Body::from(STYLE_CSS.clone()))
109+
.body(full(STYLE_CSS.clone()))
109110
.unwrap_or_default(),
110111
)
111112
}
@@ -428,7 +429,7 @@ pub async fn proxy_commit_info() -> Result<Response<Body>, String> {
428429
Response::builder()
429430
.status(200)
430431
.header("content-type", "application/atom+xml")
431-
.body(Body::from(fetch_commit_info().await))
432+
.body(full(fetch_commit_info().await))
432433
.unwrap_or_default(),
433434
)
434435
}
@@ -452,7 +453,7 @@ pub async fn proxy_instances() -> Result<Response<Body>, String> {
452453
Response::builder()
453454
.status(200)
454455
.header("content-type", "application/json")
455-
.body(Body::from(fetch_instances().await))
456+
.body(full(fetch_instances().await))
456457
.unwrap_or_default(),
457458
)
458459
}

src/post.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use crate::subreddit::{can_access_quarantine, quarantine};
66
use crate::utils::{
77
error, format_num, nsfw_landing, param, parse_post, rewrite_emotes, setting, template, time, val, Author, Awards, Comment, Flair, FlairPart, Post, Preferences,
88
};
9+
use hyper::{Request, Response};
10+
use crate::server::Body;
911
use askama::Template;
10-
use hyper::{Body, Request, Response};
1112
use regex::Regex;
1213
use std::collections::HashSet;
1314
use std::sync::LazyLock;

src/search.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use crate::{
66
subreddit::{can_access_quarantine, quarantine},
77
};
88
use askama::Template;
9-
use hyper::{Body, Request, Response};
9+
use hyper::{Request, Response};
10+
use crate::server::Body;
1011
use regex::Regex;
1112
use std::sync::LazyLock;
1213

0 commit comments

Comments
 (0)