Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
uses: engineerd/configurator@v0.0.8
with:
name: "spin"
url: "https://github.com/fermyon/spin/releases/download/v0.9.0/spin-v0.9.0-linux-amd64.tar.gz"
url: "https://github.com/fermyon/spin/releases/download/canary/spin-canary-linux-amd64.tar.gz"
pathInArchive: "spin"

- name: Install pipenv
Expand Down
36 changes: 22 additions & 14 deletions crates/spin-python-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use {
key_value, outbound_http,
redis::{self, RedisParameter, RedisResult},
},
std::{env, ops::Deref, str, sync::Arc},
std::{collections::HashMap, env, ops::Deref, str, sync::Arc},
};

thread_local! {
Expand Down Expand Up @@ -44,7 +44,7 @@ struct HttpRequest {
#[pyo3(get, set)]
uri: String,
#[pyo3(get, set)]
headers: Vec<(String, String)>,
headers: HashMap<String, String>,
#[pyo3(get, set)]
body: Option<Py<PyBytes>>,
}
Expand All @@ -55,7 +55,7 @@ impl HttpRequest {
fn new(
method: String,
uri: String,
headers: Vec<(String, String)>,
headers: HashMap<String, String>,
body: Option<Py<PyBytes>>,
) -> Self {
Self {
Expand All @@ -74,15 +74,15 @@ struct HttpResponse {
#[pyo3(get, set)]
status: u16,
#[pyo3(get, set)]
headers: Vec<(String, String)>,
headers: HashMap<String, String>,
#[pyo3(get, set)]
body: Option<Py<PyBytes>>,
}

#[pyo3::pymethods]
impl HttpResponse {
#[new]
fn new(status: u16, headers: Vec<(String, String)>, body: Option<Py<PyBytes>>) -> Self {
fn new(status: u16, headers: HashMap<String, String>, body: Option<Py<PyBytes>>) -> Self {
Self {
status,
headers,
Expand Down Expand Up @@ -179,7 +179,7 @@ fn http_send(module: &PyModule, request: HttpRequest) -> PyResult<HttpResponse>
.to_owned(),
))
})
.collect::<PyResult<_>>()?,
.collect::<PyResult<HashMap<_, _>>>()?,
body: response
.into_body()
.as_deref()
Expand Down Expand Up @@ -396,15 +396,23 @@ fn handle(request: Request) -> Result<Response> {
headers: request
.headers()
.iter()
.map(|(k, v)| {
Ok((
k.as_str().to_owned(),
str::from_utf8(v.as_bytes())
.try_fold::<_, _, PyResult<HashMap<_, _>>>(
HashMap::new(),
|mut acc: HashMap<String, String>, (k, v): (&HeaderName, &HeaderValue)| {
let key = k.as_str().to_owned();
let value = str::from_utf8(v.as_bytes())
.map_err(Anyhow::from)?
.to_owned(),
))
})
.collect::<PyResult<_>>()?,
.to_owned();
acc.entry(key)
.and_modify(|existing_value| {
existing_value.push_str(", ");
existing_value.push_str(&value);
})
.or_insert(value);
Ok(acc)
},
)
.map_err(Anyhow::from)?,
body: request
.body()
.as_deref()
Expand Down
12 changes: 6 additions & 6 deletions examples/KV/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ def handle_request(request):
status = 200
else:
status = 404
return Response(status, [("content-type", "text/plain")], value)
return Response(status, {"content-type": "text/plain"}, value)
case "POST":
store.set(request.uri, request.body)
return Response(200, [("content-type", "text/plain")])
return Response(200, {"content-type": "text/plain"})
case "DELETE":
store.delete(request.uri)
return Response(200, [("content-type", "text/plain")])
return Response(200, {"content-type": "text/plain"})
case "HEAD":
if store.exists(request.uri):
return Response(200, [("content-type", "text/plain")])
return Response(404, [("content-type", "text/plain")])
return Response(200, {"content-type": "text/plain"})
return Response(404, {"content-type": "text/plain"})
case default:
return Response(405, [("content-type", "text/plain")])
return Response(405, {"content-type": "text/plain"})
2 changes: 1 addition & 1 deletion examples/external_lib/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ def handle_request(request):
"""

return Response(200,
[("content-type", "text/plain")],
{"content-type": "text/plain"},
bytes(f"Toml content:{toml.loads(some_toml)}", "utf-8"))
2 changes: 1 addition & 1 deletion examples/hello_world/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
def handle_request(request):

return Response(200,
[("content-type", "text/plain")],
{"content-type": "text/plain"},
bytes(f"Hello from Python!", "utf-8"))
4 changes: 2 additions & 2 deletions examples/outbound_http/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
def handle_request(request):

response = http_send(
Request("GET", "https://some-random-api.ml/facts/dog", [], None))
Request("GET", "https://some-random-api.ml/facts/dog", {}, None))

return Response(200,
[("content-type", "text/plain")],
{"content-type": "text/plain"},
bytes(f"Here is a dog fact: {str(response.body, 'utf-8')}", "utf-8"))
2 changes: 1 addition & 1 deletion examples/outbound_redis/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ def handle_request(request):
assert value == b"bar", f"expected \"bar\", got \"{str(value, 'utf-8')}\""

return Response(200,
[("content-type", "text/plain")],
{"content-type": "text/plain"},
bytes(f"Executed outbound Redis commands", "utf-8"))
2 changes: 1 addition & 1 deletion templates/http-py/content/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
def handle_request(request):

return Response(200,
[("content-type", "text/plain")],
{"content-type": "text/plain"},
bytes(f"Hello from the Python SDK", "utf-8"))
39 changes: 36 additions & 3 deletions test/test-app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,45 @@
from os import environ
import toml


def handle_request(request):
if request.uri == "/foo":
return Response(200,
[("content-type", "text/plain")],
{"content-type": "text/plain"},
bytes(f"foo indeed", "utf-8"))

if request.uri == "/duplicateheadertest":
test_headers = [("spin-header-test-key1", "value1"), ("spin-header-test-key2", "value2"),
("spin-header-test-key1", "value3"), ("spin-header-test-key2", "value4")]

key1_test_passes = request.headers.get(
"spin-header-test-key1") == "value1, value3"
key2_test_passes = request.headers.get(
"spin-header-test-key2") == "value2, value4"
if key1_test_passes and key2_test_passes:
response_content = "Duplicate Header Name Test: Pass"
response_code = 200

else:
example_curl_headers = '-H "spin-header-test-key1: value1" -H "spin-header-test-key2: value2" -H "spin-header-test-key1: value3" -H "spin-header-test-key2: value4"'
example_curl_request = f'curl {example_curl_headers} http://127.0.0.1:3000/duplicateheadertest'
required_headers = "\n".join(
[str(header) for header in test_headers])
response_content = f"""
---------------------- Duplicate Header Name Test -------------------------------------
To make this test pass, you must include the following headers in your request:
{required_headers}

Example Passing Curl Request: {example_curl_request}

Actual Headers Received:
{request.headers}
---------------------------------------------------------------------------------------
"""
response_code = 404

return Response(response_code, {"content-type": "text/plain"}, bytes(response_content, "utf-8"))

print(f"Got request URI: {request.uri}")

print(f"Here's my environment: {environ}")
Expand All @@ -26,9 +59,9 @@ def handle_request(request):
my_file = open("/foo.txt", "r")
print(f"And here's the content of foo.txt: {my_file.read()}")

response = http_send(Request("GET", "http://localhost:3000/foo", [], None))
response = http_send(Request("GET", "http://localhost:3000/foo", {}, None))
print(f"Got foo: {str(response.body, 'utf-8')}")

return Response(200,
[("content-type", "text/plain")],
{"content-type": "text/plain"},
bytes(f"Hello from Python! Got request URI: {request.uri}", "utf-8"))
3 changes: 2 additions & 1 deletion test/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ echo "built the test app successfully"

# Start the spin app in the background
echo "Starting Spin app"
spin up --follow-all &
spin up &

# wait for app to be up and running
echo "Waiting for Spin app to be ready"
Expand All @@ -21,6 +21,7 @@ timeout 60s bash -c 'until curl --silent -f http://localhost:3000/health > /dev/
# start the test
echo -e "Starting test\n"
curl -f http://localhost:3000 || isFailed=true
curl -f -H "spin-header-test-key1: value1" -H "spin-header-test-key2: value2" -H "spin-header-test-key1: value3" -H "spin-header-test-key2: value4" http://127.0.0.1:3000/duplicateheadertest || isFailed=true
echo -e "\n\nTest completed"

# kill the spin app
Expand Down