Skip to content

Commit cd21856

Browse files
authored
Merge pull request #15 from G-Core/feat/wasi-http
WASI HTTP Support
2 parents 9df20b0 + 145fe46 commit cd21856

File tree

18 files changed

+889
-544
lines changed

18 files changed

+889
-544
lines changed

.github/workflows/ci.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@ jobs:
3131
with:
3232
token: ${{ secrets.GITHUB_TOKEN }}
3333

34-
- name: Release Build
35-
run: cargo build --release --all-features
34+
- name: Build
35+
run: cargo build --all-features
36+
37+
- name: Unit Tests
38+
run: cargo test --all --exclude http-backend
3639

3740
- name: Run Clippy
3841
run: cargo clippy --all-targets --all-features

Cargo.toml

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = ["crates/*"]
33
resolver = "2"
44

55
[workspace.package]
6-
version = "0.4.2"
6+
version = "0.5.0"
77
edition = "2021"
88
publish = false
99
authors = ["FastEdge Development Team"]
@@ -12,13 +12,13 @@ authors = ["FastEdge Development Team"]
1212
tokio = { version = "1", features = ["full"] }
1313
tokio-util = { version = "0.7", features = ["codec"] }
1414
tracing = "0.1"
15-
hyper = { version = "0.14", features = ["full"] }
16-
http = "0.2.9"
15+
hyper = { version = "1", features = ["full"] }
16+
http = "1.1.0"
1717
async-trait = "0.1"
18-
wasmtime = { version = "20.0" }
19-
wasmtime-wasi = { version = "20.0" }
20-
wasi-common = { version = "20.0" }
21-
wasmtime-wasi-nn = { version = "20.0" }
18+
wasmtime = { version = "20.0.2" }
19+
wasmtime-wasi = { version = "20.0.2" }
20+
wasi-common = { version = "20.0.2" }
21+
wasmtime-wasi-nn = { version = "20.0.2" }
2222
clap = { version = "4", features = ["derive"] }
2323
moka = { version = "0.12", features = ["sync"] }
2424
smol_str = { version = "0.2.1", features = ["serde"] }
@@ -51,10 +51,15 @@ tokio-util = { workspace = true }
5151
wasmtime = { workspace = true }
5252
wasmtime-wasi = { workspace = true }
5353
smol_str = { workspace = true }
54-
clap = { version = "4.5.0", features = ["derive"] }
55-
pretty_env_logger = "0.5.0"
54+
async-trait = {workspace = true}
55+
clap = { version = "4.5", features = ["derive"] }
56+
pretty_env_logger = "0.5"
5657
runtime = { path = "crates/runtime", default-features = false }
5758
http-service = { path = "crates/http-service" }
5859
http-backend = { path = "crates/http-backend" }
59-
hyper-tls = "0.5.0"
60+
hyper-tls = "0.6"
61+
hyper-util = { version = "0.1", features = ["client", "client-legacy", "http1", "tokio"] }
62+
http-body-util = "0.1"
6063
shellflip = {workspace = true}
64+
bytesize = "1.3.0"
65+

crates/candle-wasi-nn/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ edition.workspace = true
55
publish.workspace = true
66
authors.workspace = true
77

8+
[features]
9+
metal = []
10+
cuda = []
11+
812
[dependencies]
913
wasmtime-wasi-nn = { workspace = true }
1014
tracing = { workspace = true }

crates/http-backend/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ anyhow = {workspace = true}
1414
tracing = {workspace = true}
1515
hyper = { workspace = true }
1616
tokio = { workspace = true }
17+
hyper-util = { version = "0.1.3", features = ["client", "client-legacy", "http1", "tokio"] }
18+
http-body-util = "0.1.1"
1719
pin-project = "1.1.3"
1820
log = "0.4.20"
1921
url = "2.5.0"
22+
tower-service = "0.3.2"
23+
smol_str = {workspace = true}
2024

2125
[dev-dependencies]
2226
claims = "0.7"

crates/http-backend/src/lib.rs

Lines changed: 81 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,17 @@ use std::time::Duration;
66

77
use anyhow::{anyhow, Error, Result};
88
use async_trait::async_trait;
9-
use http::{uri::Scheme, HeaderMap, HeaderValue, Uri};
10-
use hyper::client::connect::Connect;
11-
use hyper::{client::HttpConnector, service::Service, Client};
9+
use http::{header, uri::Scheme, HeaderMap, HeaderName, Uri};
10+
use http_body_util::{BodyExt, Full};
11+
use hyper::body::Bytes;
12+
use hyper::rt::ReadBufCursor;
13+
use hyper_util::client::legacy::connect::{Connect, HttpConnector};
14+
use hyper_util::client::legacy::Client;
15+
use hyper_util::rt::TokioExecutor;
1216
use pin_project::pin_project;
13-
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
17+
use smol_str::{SmolStr, ToSmolStr};
1418
use tokio::net::TcpStream;
19+
use tower_service::Service;
1520
use tracing::{debug, trace, warn};
1621

1722
use reactor::gcore::fastedge::http::Headers;
@@ -20,9 +25,7 @@ use reactor::gcore::fastedge::{
2025
http_client::Host,
2126
};
2227

23-
const HOST_HEADER_NAME: &str = "host";
24-
25-
type HeaderList = Vec<(String, String)>;
28+
type HeaderNameList = Vec<SmolStr>;
2629

2730
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2831
pub enum BackendStrategy {
@@ -47,17 +50,17 @@ pub struct FastEdgeConnector {
4750

4851
#[derive(Clone, Debug)]
4952
pub struct Backend<C> {
50-
client: Client<C>,
53+
client: Client<C, Full<Bytes>>,
5154
uri: Uri,
52-
propagate_headers: HeaderList,
53-
propagate_header_names: Vec<String>,
55+
propagate_headers: HeaderMap,
56+
propagate_header_names: HeaderNameList,
5457
max_sub_requests: usize,
5558
strategy: BackendStrategy,
5659
}
5760

5861
pub struct Builder {
5962
uri: Uri,
60-
propagate_header_names: Vec<String>,
63+
propagate_header_names: HeaderNameList,
6164
max_sub_requests: usize,
6265
strategy: BackendStrategy,
6366
}
@@ -67,7 +70,7 @@ impl Builder {
6770
self.uri = uri;
6871
self
6972
}
70-
pub fn propagate_headers_names(&mut self, propagate: Vec<String>) -> &mut Self {
73+
pub fn propagate_headers_names(&mut self, propagate: HeaderNameList) -> &mut Self {
7174
self.propagate_header_names = propagate;
7275
self
7376
}
@@ -80,15 +83,15 @@ impl Builder {
8083
where
8184
C: Connect + Clone,
8285
{
83-
let client = hyper::Client::builder()
86+
let client = hyper_util::client::legacy::Client::builder(TokioExecutor::new())
8487
.set_host(false)
8588
.pool_idle_timeout(Duration::from_secs(30))
8689
.build(connector);
8790

8891
Backend {
8992
client,
9093
uri: self.uri.to_owned(),
91-
propagate_headers: vec![],
94+
propagate_headers: HeaderMap::new(),
9295
propagate_header_names: self.propagate_header_names.to_owned(),
9396
max_sub_requests: self.max_sub_requests,
9497
strategy: self.strategy,
@@ -106,43 +109,57 @@ impl<C> Backend<C> {
106109
}
107110
}
108111

109-
pub fn uri(&self) -> &Uri {
110-
&self.uri
112+
pub fn uri(&self) -> Uri {
113+
self.uri.to_owned()
114+
}
115+
116+
pub fn propagate_header_names(&self) -> Vec<SmolStr> {
117+
self.propagate_header_names.to_owned()
111118
}
112119

113120
/// Propagate filtered headers from original requests
114-
pub fn propagate_headers(&mut self, headers: &HeaderMap<HeaderValue>) -> Result<()> {
121+
pub fn propagate_headers(&mut self, headers: HeaderMap) -> Result<()> {
115122
self.propagate_headers.clear();
116123

117124
if self.strategy == BackendStrategy::FastEdge {
118125
let server_name = headers
119-
.get("Server_Name")
126+
.get("server_name")
120127
.and_then(|v| v.to_str().ok())
121128
.ok_or(anyhow!("header Server_name is missing"))?;
122-
self.propagate_headers
123-
.push(("Host".to_string(), be_base_domain(server_name)));
129+
self.propagate_headers.insert(
130+
HeaderName::from_static("host"),
131+
be_base_domain(server_name).parse()?,
132+
);
124133
}
125-
126-
for header_name in self.propagate_header_names.iter() {
127-
if let Some(value) = headers.get(header_name).and_then(|v| v.to_str().ok()) {
128-
trace!("add original request header: {}={}", header_name, value);
129-
self.propagate_headers
130-
.push((header_name.to_string(), value.to_string()));
134+
let headers = headers.into_iter().filter(|(k, _)| {
135+
if let Some(name) = k {
136+
self.propagate_header_names.contains(&name.to_smolstr())
137+
} else {
138+
false
131139
}
132-
}
140+
});
141+
self.propagate_headers.extend(headers);
142+
133143
Ok(())
134144
}
135145

136-
fn make_request(&self, req: Request) -> Result<http::Request<hyper::Body>> {
146+
fn propagate_headers_vec(&self) -> Vec<(String, String)> {
147+
self.propagate_headers
148+
.iter()
149+
.filter_map(|(k, v)| v.to_str().ok().map(|v| (k.to_string(), v.to_string())))
150+
.collect::<Vec<(String, String)>>()
151+
}
152+
153+
fn make_request(&self, req: Request) -> Result<http::Request<Full<Bytes>>> {
137154
trace!("strategy: {:?}", self.strategy);
138155
let builder = match self.strategy {
139156
BackendStrategy::Direct => {
140157
let mut headers = req.headers.into_iter().collect::<Vec<(String, String)>>();
141-
headers.extend(self.propagate_headers.clone());
158+
headers.extend(self.propagate_headers_vec());
142159
// CLI has to set Host header from URL, if it is not set already by the request
143160
if !headers
144161
.iter()
145-
.any(|(k, _)| k.eq_ignore_ascii_case(HOST_HEADER_NAME))
162+
.any(|(k, _)| k.eq_ignore_ascii_case(header::HOST.as_str()))
146163
{
147164
if let Ok(uri) = req.uri.parse::<Uri>() {
148165
if let Some(host) = uri.authority().map(|a| {
@@ -152,7 +169,7 @@ impl<C> Backend<C> {
152169
a.host().to_string()
153170
}
154171
}) {
155-
headers.push((HOST_HEADER_NAME.to_string(), host))
172+
headers.push((header::HOST.as_str().to_string(), host))
156173
}
157174
}
158175
}
@@ -216,8 +233,12 @@ impl<C> Backend<C> {
216233
})
217234
.collect::<Vec<(String, String)>>();
218235

219-
headers.extend(backend_headers(&original_url, original_host));
220-
headers.extend(self.propagate_headers.clone());
236+
headers.push(("fastedge-hostname".to_string(), original_host));
237+
headers.push((
238+
"fastedge-scheme".to_string(),
239+
original_url.scheme_str().unwrap_or("http").to_string(),
240+
));
241+
headers.extend(self.propagate_headers_vec());
221242

222243
let host = canonical_host_name(&headers, &original_url)?;
223244
let url = canonical_url(&original_url, &host, self.uri.path())?;
@@ -240,7 +261,7 @@ impl<C> Backend<C> {
240261
};
241262
debug!("request builder: {:?}", builder);
242263
let body = req.body.unwrap_or_default();
243-
builder.body(hyper::Body::from(body)).map_err(Error::msg)
264+
Ok(builder.body(Full::new(Bytes::from(body)))?)
244265
}
245266
}
246267

@@ -280,7 +301,7 @@ where
280301
None
281302
};
282303

283-
let body_bytes = hyper::body::to_bytes(body).await?;
304+
let body_bytes = body.collect().await?.to_bytes();
284305
let body = Some(body_bytes.to_vec());
285306

286307
trace!(?status, ?headers, len = body_bytes.len(), "reply");
@@ -350,16 +371,6 @@ fn canonical_url(original_url: &Uri, canonical_host: &str, backend_path: &str) -
350371
.map_err(Error::msg)
351372
}
352373

353-
fn backend_headers(original_url: &Uri, original_host: String) -> HeaderList {
354-
vec![("Fastedge-Hostname".to_string(), original_host),(
355-
"Fastedge-Scheme".to_string(),
356-
original_url
357-
.scheme_str()
358-
.unwrap_or( "http")
359-
.to_string(),
360-
)]
361-
}
362-
363374
impl FastEdgeConnector {
364375
pub fn new(backend: Uri) -> Self {
365376
let mut inner = HttpConnector::new();
@@ -385,54 +396,63 @@ impl Service<Uri> for FastEdgeConnector {
385396
Box::pin(async move {
386397
let conn = connect_fut
387398
.await
388-
.map(|inner| Connection { inner })
399+
.map(|inner| Connection {
400+
inner: inner.into_inner(),
401+
})
389402
.map_err(Box::new)?;
390403
Ok(conn)
391404
})
392405
}
393406
}
394407

395-
impl AsyncRead for Connection {
408+
impl hyper::rt::Read for Connection {
396409
fn poll_read(
397410
self: Pin<&mut Self>,
398411
cx: &mut Context<'_>,
399-
buf: &mut ReadBuf<'_>,
400-
) -> Poll<std::io::Result<()>> {
401-
let this = self.project();
402-
this.inner.poll_read(cx, buf)
412+
mut buf: ReadBufCursor<'_>,
413+
) -> Poll<std::result::Result<(), std::io::Error>> {
414+
let n = unsafe {
415+
let mut tbuf = tokio::io::ReadBuf::uninit(buf.as_mut());
416+
match tokio::io::AsyncRead::poll_read(self.project().inner, cx, &mut tbuf) {
417+
Poll::Ready(Ok(())) => tbuf.filled().len(),
418+
other => return other,
419+
}
420+
};
421+
422+
unsafe {
423+
buf.advance(n);
424+
}
425+
Poll::Ready(Ok(()))
403426
}
404427
}
405428

406-
impl AsyncWrite for Connection {
429+
impl hyper::rt::Write for Connection {
407430
fn poll_write(
408431
self: Pin<&mut Self>,
409432
cx: &mut Context<'_>,
410433
buf: &[u8],
411434
) -> Poll<std::result::Result<usize, std::io::Error>> {
412-
let this = self.project();
413-
this.inner.poll_write(cx, buf)
435+
tokio::io::AsyncWrite::poll_write(self.project().inner, cx, buf)
414436
}
415437

416438
fn poll_flush(
417439
self: Pin<&mut Self>,
418440
cx: &mut Context<'_>,
419441
) -> Poll<std::result::Result<(), std::io::Error>> {
420-
let this = self.project();
421-
this.inner.poll_flush(cx)
442+
tokio::io::AsyncWrite::poll_flush(self.project().inner, cx)
422443
}
423444

424445
fn poll_shutdown(
425446
self: Pin<&mut Self>,
426447
cx: &mut Context<'_>,
427448
) -> Poll<std::result::Result<(), std::io::Error>> {
428-
let this = self.project();
429-
this.inner.poll_shutdown(cx)
449+
tokio::io::AsyncWrite::poll_shutdown(self.project().inner, cx)
430450
}
431451
}
432452

433-
impl hyper::client::connect::Connection for Connection {
434-
fn connected(&self) -> hyper::client::connect::Connected {
435-
hyper::client::connect::Connected::new()
453+
impl hyper_util::client::legacy::connect::Connection for Connection {
454+
fn connected(&self) -> hyper_util::client::legacy::connect::Connected {
455+
hyper_util::client::legacy::connect::Connected::new()
436456
}
437457
}
438458

0 commit comments

Comments
 (0)