Skip to content

Commit 0dee603

Browse files
committed
add outbound HTTP support
Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 38145b9 commit 0dee603

2 files changed

Lines changed: 87 additions & 8 deletions

File tree

py/app.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
from spin_http import Response
1+
from spin_http import Request, Response, send
22

33
def handle_request(request):
44
print(f"Got request URI: {request.uri}")
5+
response = send(Request("GET", "https://some-random-api.ml/facts/dog", [], None))
6+
print(f"Got dog fact: {response.body}")
57
return Response(200, [("content-type", "text/plain")], f"Hello from Python! Got request URI: {request.uri}")

src/lib.rs

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,63 @@
22

33
use {
44
anyhow::{Error, Result},
5-
http::{header::HeaderName, HeaderValue},
5+
http::{header::HeaderName, request, HeaderValue},
66
once_cell::unsync::OnceCell,
7-
pyo3::{types::PyModule, PyObject, PyResult, Python},
8-
spin_sdk::http::{Request, Response},
7+
pyo3::{exceptions::PyAssertionError, types::PyModule, PyErr, PyObject, PyResult, Python},
8+
spin_sdk::{
9+
http::{Request, Response},
10+
outbound_http,
11+
},
912
std::{ops::Deref, str},
1013
};
1114

1215
thread_local! {
1316
static HANDLE_REQUEST: OnceCell<PyObject> = OnceCell::new();
1417
}
1518

19+
#[derive(Clone)]
1620
#[pyo3::pyclass]
1721
#[pyo3(name = "Request")]
1822
struct HttpRequest {
19-
#[pyo3(get)]
23+
#[pyo3(get, set)]
2024
method: String,
21-
#[pyo3(get)]
25+
#[pyo3(get, set)]
2226
uri: String,
23-
#[pyo3(get)]
27+
#[pyo3(get, set)]
2428
headers: Vec<(String, String)>,
2529
// todo: this should be a byte slice, but make sure it gets converted to/from Python correctly
26-
#[pyo3(get)]
30+
#[pyo3(get, set)]
2731
body: Option<String>,
2832
}
2933

34+
#[pyo3::pymethods]
35+
impl HttpRequest {
36+
#[new]
37+
fn new(
38+
method: String,
39+
uri: String,
40+
headers: Vec<(String, String)>,
41+
body: Option<String>,
42+
) -> Self {
43+
Self {
44+
method,
45+
uri,
46+
headers,
47+
body,
48+
}
49+
}
50+
}
51+
3052
#[derive(Clone)]
3153
#[pyo3::pyclass]
3254
#[pyo3(name = "Response")]
3355
struct HttpResponse {
56+
#[pyo3(get, set)]
3457
status: u16,
58+
#[pyo3(get, set)]
3559
headers: Vec<(String, String)>,
3660
// todo: this should be a byte slice, but make sure it gets converted to/from Python correctly
61+
#[pyo3(get, set)]
3762
body: Option<String>,
3863
}
3964

@@ -49,9 +74,61 @@ impl HttpResponse {
4974
}
5075
}
5176

77+
struct Anyhow(Error);
78+
79+
impl From<Anyhow> for PyErr {
80+
fn from(Anyhow(error): Anyhow) -> Self {
81+
PyAssertionError::new_err(format!("{error:?}"))
82+
}
83+
}
84+
85+
impl<T: std::error::Error + Send + Sync + 'static> From<T> for Anyhow {
86+
fn from(error: T) -> Self {
87+
Self(error.into())
88+
}
89+
}
90+
91+
#[pyo3::pyfunction]
92+
fn send(request: HttpRequest) -> Result<HttpResponse, Anyhow> {
93+
let mut builder = request::Builder::new()
94+
.method(request.method.deref())
95+
.uri(request.uri.deref());
96+
97+
if let Some(headers) = builder.headers_mut() {
98+
for (key, value) in &request.headers {
99+
headers.insert(
100+
HeaderName::from_bytes(key.as_bytes())?,
101+
HeaderValue::from_bytes(value.as_bytes())?,
102+
);
103+
}
104+
}
105+
106+
let response = outbound_http::send_request(
107+
builder.body(request.body.map(|buffer| buffer.into_bytes().into()))?,
108+
)?;
109+
110+
Ok(HttpResponse {
111+
status: response.status().as_u16(),
112+
headers: response
113+
.headers()
114+
.iter()
115+
.map(|(key, value)| {
116+
Ok((
117+
key.as_str().to_owned(),
118+
str::from_utf8(value.as_bytes())?.to_owned(),
119+
))
120+
})
121+
.collect::<Result<_, Anyhow>>()?,
122+
body: response
123+
.into_body()
124+
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned()),
125+
})
126+
}
127+
52128
#[pyo3::pymodule]
53129
#[pyo3(name = "spin_http")]
54130
fn spin_http_module(_py: Python<'_>, module: &PyModule) -> PyResult<()> {
131+
module.add_function(pyo3::wrap_pyfunction!(send, module)?)?;
55132
module.add_class::<HttpRequest>()?;
56133
module.add_class::<HttpResponse>()
57134
}

0 commit comments

Comments
 (0)