Skip to content

Commit 6afcbe1

Browse files
authored
Update test code to use Hyper 1.x and Axum instead of Warp (#178)
1 parent db454f6 commit 6afcbe1

File tree

8 files changed

+257
-443
lines changed

8 files changed

+257
-443
lines changed

Cargo.toml

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
authors = ["Simon Bernier St-Pierre <git@sbstp.ca>"]
2+
authors = ["Simon Bernier St-Pierre <git.sbstp.ca@gmail.com>"]
33
edition = "2018"
44
license = "MPL-2.0"
55
name = "attohttpc"
@@ -38,24 +38,30 @@ rustls-opt-dep = { package = "rustls", version = "0.23.22", default-features = f
3838
serde = { version = "1.0.143", optional = true }
3939
serde_json = { version = "1.0.83", optional = true }
4040
serde_urlencoded = { version = "0.7.1", optional = true }
41+
tokio-rustls = "0.26.1"
4142
url = "2.2.2"
4243
webpki-roots = { version = "0.26.8", optional = true }
4344

4445
[dev-dependencies]
4546
anyhow = "1.0.61"
47+
axum = { version = "0.8.1", features = ["multipart"] }
48+
axum-server = { version = "0.7.1", features = ["tls-rustls"] }
49+
bytes = "1.10.0"
4650
env_logger = "0.11.0"
47-
futures-util = { version = "0.3.23", default-features = false }
48-
http02 = { package = "http", version = "0.2" }
49-
hyper = "0.14.20"
51+
http-body-util = "0.1.2"
52+
hyper = { version = "1.6.0", features = ["full"] }
53+
hyper-util = "0.1.10"
5054
lazy_static = "1.4.0"
5155
multipart = { version = "0.18.0", default-features = false, features = [
5256
"server",
5357
] }
58+
rustls-opt-dep = { package = "rustls", version = "0.23.22", default-features = false, features = [
59+
"ring",
60+
"std",
61+
"tls12",
62+
] }
5463
rustls-pemfile = "2"
5564
tokio = { version = "1.20.1", features = ["full"] }
56-
tokio-rustls = "0.25.0"
57-
tokio-stream = { version = "0.1.9", features = ["net"] }
58-
warp = "0.3.2"
5965

6066
[features]
6167
basic-auth = ["base64"]

tests/test_multipart.rs

Lines changed: 62 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,85 @@
1-
use std::io::{Cursor, Read};
2-
use std::net::SocketAddr;
3-
use std::sync::mpsc::{sync_channel, Receiver};
4-
use std::thread;
1+
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
2+
use std::time::Duration;
53

6-
use mime::Mime;
7-
use multipart::server::Multipart;
8-
use tokio::runtime::Builder;
9-
use warp::Filter;
4+
use axum::extract::{DefaultBodyLimit, Multipart, State};
5+
use axum::routing::post;
6+
use axum::Router;
7+
use bytes::Bytes;
108

11-
fn start_server() -> (u16, Receiver<Option<String>>) {
9+
#[derive(Debug, PartialEq, Eq)]
10+
struct Part {
11+
name: Option<String>,
12+
file_name: Option<String>,
13+
content_type: Option<String>,
14+
data: Bytes,
15+
}
16+
17+
async fn start_server() -> (u16, Receiver<Vec<Part>>) {
1218
let (send, recv) = sync_channel(1);
13-
let rt = Builder::new_multi_thread().enable_io().enable_time().build().unwrap();
14-
// ported from warp::multipart, which has a length limit (and we're generic over Read)
15-
let filter = warp::path("multipart")
16-
.and(
17-
warp::header::<Mime>("content-type")
18-
.and_then(|ct: Mime| async move {
19-
ct.get_param("boundary")
20-
.map(|mime| mime.to_string())
21-
.ok_or_else(warp::reject::reject)
22-
})
23-
.and(warp::body::bytes())
24-
.map(|boundary, bytes| Multipart::with_body(Cursor::new(bytes), boundary)),
25-
)
26-
.map(move |mut form: Multipart<_>| {
27-
let mut found_text = false;
28-
let mut found_file = false;
29-
let mut err = false;
30-
let mut buf = String::new();
31-
form.foreach_entry(|mut entry| {
32-
if err {
33-
return;
34-
}
35-
entry.data.read_to_string(&mut buf).unwrap();
36-
if !found_text && &*entry.headers.name == "Hello" && buf == "world!" {
37-
found_text = true;
38-
} else if !found_file
39-
&& &*entry.headers.name == "file"
40-
&& entry.headers.filename.as_deref() == Some("hello.txt")
41-
&& entry.headers.content_type.as_ref().map(|x| x.as_ref() == "text/plain") == Some(true)
42-
&& buf == "Hello, world!"
43-
{
44-
found_file = true;
45-
} else {
46-
send.send(Some(format!("Unexpected entry {:?} = {:?}", entry.headers, buf)))
47-
.unwrap();
48-
err = true;
49-
}
50-
buf.clear();
51-
})
52-
.unwrap();
53-
if err {
54-
return "ERR";
55-
}
56-
send.send(Some(
57-
match (found_text, found_file) {
58-
(false, false) => "Missing both fields!",
59-
(true, false) => "Missing file field!",
60-
(false, true) => "Missing text field!",
61-
(true, true) => {
62-
send.send(None).unwrap();
63-
return "OK";
64-
}
65-
}
66-
.to_string(),
67-
))
68-
.unwrap();
69-
"ERR"
70-
});
71-
let (addr, fut) =
72-
rt.block_on(async { warp::serve(filter).bind_ephemeral("0.0.0.0:0".parse::<SocketAddr>().unwrap()) });
73-
let port = addr.port();
74-
thread::spawn(move || {
75-
rt.block_on(fut);
19+
20+
async fn accept_form(State(send): State<SyncSender<Vec<Part>>>, mut multipart: Multipart) -> &'static str {
21+
let mut parts = Vec::new();
22+
while let Some(field) = multipart.next_field().await.unwrap() {
23+
parts.push(Part {
24+
name: field.name().map(|s| s.to_string()),
25+
file_name: field.file_name().map(|s| s.to_string()),
26+
content_type: field.content_type().map(|s| s.to_string()),
27+
data: field.bytes().await.unwrap(),
28+
});
29+
}
30+
send.send(parts).unwrap();
31+
"OK"
32+
}
33+
34+
let app = Router::new()
35+
.route("/multipart", post(accept_form))
36+
.layer(DefaultBodyLimit::disable())
37+
.with_state(send);
38+
39+
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
40+
let port = listener.local_addr().unwrap().port();
41+
tokio::spawn(async move {
42+
axum::serve(listener, app).await.unwrap();
7643
});
7744
(port, recv)
7845
}
7946

80-
#[test]
81-
fn test_multipart_default() -> attohttpc::Result<()> {
82-
let file = attohttpc::MultipartFile::new("file", b"Hello, world!")
47+
#[tokio::test(flavor = "multi_thread")]
48+
async fn test_multipart_default() -> attohttpc::Result<()> {
49+
let file = attohttpc::MultipartFile::new("file", b"abc123")
8350
.with_type("text/plain")?
8451
.with_filename("hello.txt");
8552
let form = attohttpc::MultipartBuilder::new()
8653
.with_text("Hello", "world!")
8754
.with_file(file)
8855
.build()?;
8956

90-
let (port, recv) = start_server();
57+
let (port, recv) = start_server().await;
9158

9259
attohttpc::post(format!("http://localhost:{port}/multipart"))
9360
.body(form)
9461
.send()?
9562
.text()?;
9663

97-
if let Some(err) = recv.recv().unwrap() {
98-
panic!("{}", err);
99-
}
64+
let parts = recv.recv_timeout(Duration::from_secs(5)).unwrap();
65+
assert_eq!(parts.len(), 2);
66+
assert_eq!(
67+
parts,
68+
vec![
69+
Part {
70+
name: Some("Hello".to_string()),
71+
file_name: None,
72+
content_type: None,
73+
data: Bytes::from(&b"world!"[..])
74+
},
75+
Part {
76+
name: Some("file".to_string()),
77+
file_name: Some("hello.txt".to_string()),
78+
content_type: Some("text/plain".to_string()),
79+
data: Bytes::from(&b"abc123"[..])
80+
}
81+
]
82+
);
10083

10184
Ok(())
10285
}

tests/test_proxy.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,6 @@ async fn test_http_url_with_https_proxy_refusal() -> Result<(), anyhow::Error> {
179179
#[cfg(any(feature = "tls-native", feature = "__rustls"))]
180180
#[tokio::test(flavor = "multi_thread")]
181181
async fn test_https_url_with_https_proxy_refusal() -> Result<(), anyhow::Error> {
182-
// env_logger::init();
183-
184182
let proxy_port = tools::start_refusing_proxy_server(true).await?;
185183
let proxy_url = Url::parse(&format!("https://localhost:{proxy_port}")).unwrap();
186184

tests/test_redirection.rs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
11
use std::net::SocketAddr;
22

33
use attohttpc::ErrorKind;
4-
use http02 as http;
5-
use tokio_stream::wrappers::TcpListenerStream;
6-
use warp::Filter;
4+
use axum::body::Body;
5+
use axum::response::Response;
6+
use axum::routing::get;
7+
use axum::Router;
8+
use http::StatusCode;
79

810
async fn make_server() -> Result<u16, anyhow::Error> {
911
let addr = SocketAddr::from(([127, 0, 0, 1], 0));
1012
let incoming = tokio::net::TcpListener::bind(&addr).await?;
1113
let local_addr = incoming.local_addr()?;
1214

13-
let a = warp::path("301").map(|| warp::redirect::redirect(http::Uri::from_static("/301")));
14-
let b = warp::path("304").map(|| {
15-
http::Response::builder()
16-
.header("Location", "/304")
17-
.status(http::StatusCode::NOT_MODIFIED)
18-
.body("")
19-
});
15+
async fn x301() -> Response {
16+
Response::builder()
17+
.status(StatusCode::MOVED_PERMANENTLY)
18+
.header("Location", "/301")
19+
.body(Body::from(""))
20+
.unwrap()
21+
}
22+
23+
async fn x304() -> Response {
24+
Response::builder()
25+
.status(StatusCode::NOT_MODIFIED)
26+
.body(Body::from(""))
27+
.unwrap()
28+
}
29+
30+
let app = Router::new().route("/301", get(x301)).route("/304", get(x304));
2031

21-
let server = warp::serve(a.or(b)).serve_incoming(TcpListenerStream::new(incoming));
22-
tokio::spawn(server);
32+
tokio::spawn(async move {
33+
axum::serve(incoming, app).await.unwrap();
34+
});
2335

2436
Ok(local_addr.port())
2537
}

tests/tools/mod.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
mod proxy;
22
mod servers;
3-
mod tls;
43

54
pub use proxy::*;
65
pub use servers::*;

0 commit comments

Comments
 (0)