Skip to content

Commit 059dff8

Browse files
authored
Merge pull request #107 from jbr/no-addr
No addr needed for accept
2 parents c1780d6 + 0999baf commit 059dff8

File tree

10 files changed

+157
-30
lines changed

10 files changed

+157
-30
lines changed

examples/server.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ async fn main() -> http_types::Result<()> {
1414
let mut incoming = listener.incoming();
1515
while let Some(stream) = incoming.next().await {
1616
let stream = stream?;
17-
let addr = addr.clone();
1817
task::spawn(async {
19-
if let Err(err) = accept(addr, stream).await {
18+
if let Err(err) = accept(stream).await {
2019
eprintln!("{}", err);
2120
}
2221
});
@@ -25,9 +24,9 @@ async fn main() -> http_types::Result<()> {
2524
}
2625

2726
// Take a TCP stream, and convert it into sequential HTTP request / response pairs.
28-
async fn accept(addr: String, stream: TcpStream) -> http_types::Result<()> {
27+
async fn accept(stream: TcpStream) -> http_types::Result<()> {
2928
println!("starting new connection from {}", stream.peer_addr()?);
30-
async_h1::accept(&addr, stream.clone(), |_req| async move {
29+
async_h1::accept(stream.clone(), |_req| async move {
3130
let mut res = Response::new(StatusCode::Ok);
3231
res.insert_header("Content-Type", "text/plain")?;
3332
res.set_body("Hello world");

src/lib.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,8 @@
6767
//! let mut incoming = listener.incoming();
6868
//! while let Some(stream) = incoming.next().await {
6969
//! let stream = stream?;
70-
//! let addr = addr.clone();
7170
//! task::spawn(async {
72-
//! if let Err(err) = accept(addr, stream).await {
71+
//! if let Err(err) = accept(stream).await {
7372
//! eprintln!("{}", err);
7473
//! }
7574
//! });
@@ -78,9 +77,9 @@
7877
//! }
7978
//!
8079
//! // Take a TCP stream, and convert it into sequential HTTP request / response pairs.
81-
//! async fn accept(addr: String, stream: TcpStream) -> http_types::Result<()> {
80+
//! async fn accept(stream: TcpStream) -> http_types::Result<()> {
8281
//! println!("starting new connection from {}", stream.peer_addr()?);
83-
//! async_h1::accept(&addr, stream.clone(), |_req| async move {
82+
//! async_h1::accept(stream.clone(), |_req| async move {
8483
//! let mut res = Response::new(StatusCode::Ok);
8584
//! res.insert_header("Content-Type", "text/plain")?;
8685
//! res.set_body("Hello");

src/server/decode.rs

Lines changed: 111 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use std::str::FromStr;
55
use async_std::io::BufReader;
66
use async_std::io::Read;
77
use async_std::prelude::*;
8-
use http_types::headers::{HeaderName, HeaderValue, CONTENT_LENGTH, TRANSFER_ENCODING};
8+
use http_types::headers::{HeaderName, HeaderValue, CONTENT_LENGTH, HOST, TRANSFER_ENCODING};
99
use http_types::{ensure, ensure_eq, format_err};
1010
use http_types::{Body, Method, Request};
1111

@@ -18,7 +18,7 @@ const LF: u8 = b'\n';
1818
const HTTP_1_1_VERSION: u8 = 1;
1919

2020
/// Decode an HTTP request on the server.
21-
pub(crate) async fn decode<R>(addr: &str, reader: R) -> http_types::Result<Option<Request>>
21+
pub(crate) async fn decode<R>(reader: R) -> http_types::Result<Option<Request>>
2222
where
2323
R: Read + Unpin + Send + Sync + 'static,
2424
{
@@ -57,21 +57,26 @@ where
5757
let method = httparse_req.method;
5858
let method = method.ok_or_else(|| format_err!("No method found"))?;
5959

60-
let uri = httparse_req.path;
61-
let uri = uri.ok_or_else(|| format_err!("No uri found"))?;
62-
let uri = url::Url::parse(&format!("{}{}", addr, uri))?;
60+
let path = httparse_req.path;
61+
let path = path.ok_or_else(|| format_err!("No uri found"))?;
6362

6463
let version = httparse_req.version;
6564
let version = version.ok_or_else(|| format_err!("No version found"))?;
6665
ensure_eq!(version, HTTP_1_1_VERSION, "Unsupported HTTP version");
6766

68-
let mut req = Request::new(Method::from_str(method)?, uri);
67+
let mut req = Request::new(
68+
Method::from_str(method)?,
69+
url::Url::parse("http://_").unwrap().join(path)?,
70+
);
71+
6972
for header in httparse_req.headers.iter() {
7073
let name = HeaderName::from_str(header.name)?;
7174
let value = HeaderValue::from_str(std::str::from_utf8(header.value)?)?;
7275
req.insert_header(name, value)?;
7376
}
7477

78+
set_url_and_port_from_host_header(&mut req)?;
79+
7580
let content_length = req.header(&CONTENT_LENGTH);
7681
let transfer_encoding = req.header(&TRANSFER_ENCODING);
7782

@@ -99,3 +104,103 @@ where
99104

100105
Ok(Some(req))
101106
}
107+
108+
fn set_url_and_port_from_host_header(req: &mut Request) -> http_types::Result<()> {
109+
let host = req
110+
.header(&HOST)
111+
.and_then(|header| header.last()) // There must only exactly one Host header, so this is permissive
112+
.ok_or_else(|| format_err!("Mandatory Host header missing"))?; // https://tools.ietf.org/html/rfc7230#section-5.4
113+
114+
let host = host.to_string();
115+
if let Some(colon) = host.find(":") {
116+
req.url_mut().set_host(Some(&host[0..colon]))?;
117+
req.url_mut()
118+
.set_port(host[colon + 1..].parse().ok())
119+
.unwrap();
120+
} else {
121+
req.url_mut().set_host(Some(&host))?;
122+
}
123+
124+
Ok(())
125+
}
126+
127+
#[cfg(test)]
128+
mod tests {
129+
use super::*;
130+
131+
fn request_with_host_header(host: &str) -> Request {
132+
let mut req = Request::new(
133+
Method::from_str("GET").unwrap(),
134+
url::Url::parse("http://_")
135+
.unwrap()
136+
.join("/some/path")
137+
.unwrap(),
138+
);
139+
140+
req.insert_header(HOST, host).unwrap();
141+
142+
req
143+
}
144+
145+
#[test]
146+
fn test_setting_host_with_no_port() {
147+
let mut request = request_with_host_header("subdomain.mydomain.tld");
148+
set_url_and_port_from_host_header(&mut request).unwrap();
149+
assert_eq!(
150+
request.url(),
151+
&url::Url::parse("http://subdomain.mydomain.tld/some/path").unwrap()
152+
);
153+
}
154+
155+
#[test]
156+
fn test_setting_host_with_a_port() {
157+
let mut request = request_with_host_header("subdomain.mydomain.tld:8080");
158+
set_url_and_port_from_host_header(&mut request).unwrap();
159+
assert_eq!(
160+
request.url(),
161+
&url::Url::parse("http://subdomain.mydomain.tld:8080/some/path").unwrap()
162+
);
163+
}
164+
165+
#[test]
166+
fn test_setting_host_with_an_ip_and_port() {
167+
let mut request = request_with_host_header("12.34.56.78:90");
168+
set_url_and_port_from_host_header(&mut request).unwrap();
169+
assert_eq!(
170+
request.url(),
171+
&url::Url::parse("http://12.34.56.78:90/some/path").unwrap()
172+
);
173+
}
174+
175+
#[test]
176+
fn test_malformed_nonnumeric_port_is_ignored() {
177+
let mut request = request_with_host_header("hello.world:uh-oh");
178+
set_url_and_port_from_host_header(&mut request).unwrap();
179+
assert_eq!(
180+
request.url(),
181+
&url::Url::parse("http://hello.world/some/path").unwrap()
182+
);
183+
}
184+
185+
#[test]
186+
fn test_malformed_trailing_colon_is_ignored() {
187+
let mut request = request_with_host_header("edge.cases:");
188+
set_url_and_port_from_host_header(&mut request).unwrap();
189+
assert_eq!(
190+
request.url(),
191+
&url::Url::parse("http://edge.cases/some/path").unwrap()
192+
);
193+
}
194+
195+
#[test]
196+
fn test_malformed_leading_colon_is_invalid_host_value() {
197+
let mut request = request_with_host_header(":300");
198+
assert!(set_url_and_port_from_host_header(&mut request).is_err());
199+
}
200+
201+
#[test]
202+
fn test_malformed_invalid_url_host_is_invalid_host_header_value() {
203+
let mut request = request_with_host_header(" ");
204+
assert!(set_url_and_port_from_host_header(&mut request).is_err());
205+
}
206+
}

src/server/mod.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,19 @@ impl Default for ServerOptions {
3131
/// Accept a new incoming HTTP/1.1 connection.
3232
///
3333
/// Supports `KeepAlive` requests by default.
34-
pub async fn accept<RW, F, Fut>(addr: &str, io: RW, endpoint: F) -> http_types::Result<()>
34+
pub async fn accept<RW, F, Fut>(io: RW, endpoint: F) -> http_types::Result<()>
3535
where
3636
RW: Read + Write + Clone + Send + Sync + Unpin + 'static,
3737
F: Fn(Request) -> Fut,
3838
Fut: Future<Output = http_types::Result<Response>>,
3939
{
40-
accept_with_opts(addr, io, endpoint, Default::default()).await
40+
accept_with_opts(io, endpoint, Default::default()).await
4141
}
4242

4343
/// Accept a new incoming HTTP/1.1 connection.
4444
///
4545
/// Supports `KeepAlive` requests by default.
4646
pub async fn accept_with_opts<RW, F, Fut>(
47-
addr: &str,
4847
mut io: RW,
4948
endpoint: F,
5049
opts: ServerOptions,
@@ -56,7 +55,7 @@ where
5655
{
5756
loop {
5857
// Decode a new request, timing out if this takes longer than the timeout duration.
59-
let fut = decode(addr, io.clone());
58+
let fut = decode(io.clone());
6059

6160
let req = if let Some(timeout_duration) = opts.headers_timeout {
6261
match timeout(timeout_duration, fut).await {

tests/fixtures/request-unexpected-eof.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
POST / HTTP/1.1
2+
host: example.com
23
content-type: text/plain
34
content-length: 11
45

tests/fixtures/request-with-host.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
GET /pub/WWW/TheProject.html HTTP/1.1
2+
Host: www.w3.org
3+
Content-Length: 0
4+

tests/fixtures/response-with-host.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
HTTP/1.1 200 OK
2+
content-length: 41
3+
date: {DATE}
4+
content-type: text/plain; charset=utf-8
5+
6+
http://www.w3.org/pub/WWW/TheProject.html

tests/server-chunked-encode-large.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ const RESPONSE: &'static str = concat![
145145
#[async_std::test]
146146
async fn server_chunked_large() {
147147
let case = TestCase::new(REQUEST, "").await;
148-
async_h1::accept("http://example.com", case.clone(), |_| async {
148+
async_h1::accept(case.clone(), |_| async {
149149
let mut res = Response::new(StatusCode::Ok);
150150
let body = Cursor::new(TEXT.to_owned());
151151
res.set_body(Body::from_reader(body, None));

tests/server-chunked-encode-small.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ const RESPONSE: &'static str = concat![
4141
#[async_std::test]
4242
async fn server_chunked_large() {
4343
let case = TestCase::new(REQUEST, "").await;
44-
async_h1::accept("http://example.com", case.clone(), |_| async {
44+
async_h1::accept(case.clone(), |_| async {
4545
let mut res = Response::new(StatusCode::Ok);
4646
let body = Cursor::new(TEXT.to_owned());
4747
res.set_body(Body::from_reader(body, None));

tests/server.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ async fn test_basic_request() {
1212
"fixtures/response-add-date.txt",
1313
)
1414
.await;
15-
let addr = "http://example.com";
1615

17-
async_h1::accept(addr, case.clone(), |_req| async {
16+
async_h1::accept(case.clone(), |_req| async {
1817
let mut res = Response::new(StatusCode::Ok);
1918
res.set_body("");
2019
Ok(res)
@@ -25,16 +24,34 @@ async fn test_basic_request() {
2524
case.assert().await;
2625
}
2726

27+
#[async_std::test]
28+
async fn test_host() {
29+
let case = TestCase::new_server(
30+
"fixtures/request-with-host.txt",
31+
"fixtures/response-with-host.txt",
32+
)
33+
.await;
34+
35+
async_h1::accept(case.clone(), |req| async move {
36+
let mut res = Response::new(StatusCode::Ok);
37+
res.set_body(req.url().as_str());
38+
Ok(res)
39+
})
40+
.await
41+
.unwrap();
42+
43+
case.assert().await;
44+
}
45+
2846
#[async_std::test]
2947
async fn test_chunked_basic() {
3048
let case = TestCase::new_server(
3149
"fixtures/request-chunked-basic.txt",
3250
"fixtures/response-chunked-basic.txt",
3351
)
3452
.await;
35-
let addr = "http://example.com";
3653

37-
async_h1::accept(addr, case.clone(), |_req| async {
54+
async_h1::accept(case.clone(), |_req| async {
3855
let mut res = Response::new(StatusCode::Ok);
3956
res.set_body(Body::from_reader(
4057
Cursor::new(b"Mozilla")
@@ -59,8 +76,7 @@ async fn test_chunked_echo() {
5976
)
6077
.await;
6178

62-
let addr = "http://example.com";
63-
async_h1::accept(addr, case.clone(), |req| async {
79+
async_h1::accept(case.clone(), |req| async {
6480
let ct = req.content_type();
6581
let body: Body = req.into();
6682

@@ -86,9 +102,8 @@ async fn test_unexpected_eof() {
86102
"fixtures/response-unexpected-eof.txt",
87103
)
88104
.await;
89-
let addr = "http://example.com";
90105

91-
async_h1::accept(addr, case.clone(), |req| async {
106+
async_h1::accept(case.clone(), |req| async {
92107
let mut res = Response::new(StatusCode::Ok);
93108
let ct = req.content_type();
94109
let body: Body = req.into();
@@ -112,9 +127,8 @@ async fn test_invalid_trailer() {
112127
"fixtures/response-invalid-trailer.txt",
113128
)
114129
.await;
115-
let addr = "http://example.com";
116130

117-
async_h1::accept(addr, case.clone(), |req| async {
131+
async_h1::accept(case.clone(), |req| async {
118132
let mut res = Response::new(StatusCode::Ok);
119133
let ct = req.content_type();
120134
let body: Body = req.into();

0 commit comments

Comments
 (0)