Skip to content

Commit 09d5447

Browse files
Changes headers to use a Python Dict rather than a List of Tuples (#28)
* change headers to python dict Signed-off-by: Jason Washburn <[email protected]> * update examples for dict headers Signed-off-by: Jason Washburn <[email protected]> * update template and test app for dict headers Signed-off-by: Jason Washburn <[email protected]> * adds parsing of repeated header names Signed-off-by: Jason Washburn <[email protected]> * adds error handling to header parsing Signed-off-by: Jason Washburn <[email protected]> * adds space between multiple header values Signed-off-by: Jason Washburn <[email protected]> * adds duplicate header names test Signed-off-by: Jason Washburn <[email protected]> --------- Signed-off-by: Jason Washburn <[email protected]>
1 parent 425d476 commit 09d5447

File tree

10 files changed

+73
-31
lines changed

10 files changed

+73
-31
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
uses: engineerd/[email protected]
8484
with:
8585
name: "spin"
86-
url: "https://github.com/fermyon/spin/releases/download/v0.9.0/spin-v0.9.0-linux-amd64.tar.gz"
86+
url: "https://github.com/fermyon/spin/releases/download/canary/spin-canary-linux-amd64.tar.gz"
8787
pathInArchive: "spin"
8888

8989
- name: Install pipenv

crates/spin-python-engine/src/lib.rs

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use {
1616
key_value, outbound_http,
1717
redis::{self, RedisParameter, RedisResult},
1818
},
19-
std::{env, ops::Deref, str, sync::Arc},
19+
std::{collections::HashMap, env, ops::Deref, str, sync::Arc},
2020
};
2121

2222
thread_local! {
@@ -44,7 +44,7 @@ struct HttpRequest {
4444
#[pyo3(get, set)]
4545
uri: String,
4646
#[pyo3(get, set)]
47-
headers: Vec<(String, String)>,
47+
headers: HashMap<String, String>,
4848
#[pyo3(get, set)]
4949
body: Option<Py<PyBytes>>,
5050
}
@@ -55,7 +55,7 @@ impl HttpRequest {
5555
fn new(
5656
method: String,
5757
uri: String,
58-
headers: Vec<(String, String)>,
58+
headers: HashMap<String, String>,
5959
body: Option<Py<PyBytes>>,
6060
) -> Self {
6161
Self {
@@ -74,15 +74,15 @@ struct HttpResponse {
7474
#[pyo3(get, set)]
7575
status: u16,
7676
#[pyo3(get, set)]
77-
headers: Vec<(String, String)>,
77+
headers: HashMap<String, String>,
7878
#[pyo3(get, set)]
7979
body: Option<Py<PyBytes>>,
8080
}
8181

8282
#[pyo3::pymethods]
8383
impl HttpResponse {
8484
#[new]
85-
fn new(status: u16, headers: Vec<(String, String)>, body: Option<Py<PyBytes>>) -> Self {
85+
fn new(status: u16, headers: HashMap<String, String>, body: Option<Py<PyBytes>>) -> Self {
8686
Self {
8787
status,
8888
headers,
@@ -179,7 +179,7 @@ fn http_send(module: &PyModule, request: HttpRequest) -> PyResult<HttpResponse>
179179
.to_owned(),
180180
))
181181
})
182-
.collect::<PyResult<_>>()?,
182+
.collect::<PyResult<HashMap<_, _>>>()?,
183183
body: response
184184
.into_body()
185185
.as_deref()
@@ -396,15 +396,23 @@ fn handle(request: Request) -> Result<Response> {
396396
headers: request
397397
.headers()
398398
.iter()
399-
.map(|(k, v)| {
400-
Ok((
401-
k.as_str().to_owned(),
402-
str::from_utf8(v.as_bytes())
399+
.try_fold::<_, _, PyResult<HashMap<_, _>>>(
400+
HashMap::new(),
401+
|mut acc: HashMap<String, String>, (k, v): (&HeaderName, &HeaderValue)| {
402+
let key = k.as_str().to_owned();
403+
let value = str::from_utf8(v.as_bytes())
403404
.map_err(Anyhow::from)?
404-
.to_owned(),
405-
))
406-
})
407-
.collect::<PyResult<_>>()?,
405+
.to_owned();
406+
acc.entry(key)
407+
.and_modify(|existing_value| {
408+
existing_value.push_str(", ");
409+
existing_value.push_str(&value);
410+
})
411+
.or_insert(value);
412+
Ok(acc)
413+
},
414+
)
415+
.map_err(Anyhow::from)?,
408416
body: request
409417
.body()
410418
.as_deref()

examples/KV/app.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ def handle_request(request):
1313
status = 200
1414
else:
1515
status = 404
16-
return Response(status, [("content-type", "text/plain")], value)
16+
return Response(status, {"content-type": "text/plain"}, value)
1717
case "POST":
1818
store.set(request.uri, request.body)
19-
return Response(200, [("content-type", "text/plain")])
19+
return Response(200, {"content-type": "text/plain"})
2020
case "DELETE":
2121
store.delete(request.uri)
22-
return Response(200, [("content-type", "text/plain")])
22+
return Response(200, {"content-type": "text/plain"})
2323
case "HEAD":
2424
if store.exists(request.uri):
25-
return Response(200, [("content-type", "text/plain")])
26-
return Response(404, [("content-type", "text/plain")])
25+
return Response(200, {"content-type": "text/plain"})
26+
return Response(404, {"content-type": "text/plain"})
2727
case default:
28-
return Response(405, [("content-type", "text/plain")])
28+
return Response(405, {"content-type": "text/plain"})

examples/external_lib/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ def handle_request(request):
1111
"""
1212

1313
return Response(200,
14-
[("content-type", "text/plain")],
14+
{"content-type": "text/plain"},
1515
bytes(f"Toml content:{toml.loads(some_toml)}", "utf-8"))

examples/hello_world/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
def handle_request(request):
55

66
return Response(200,
7-
[("content-type", "text/plain")],
7+
{"content-type": "text/plain"},
88
bytes(f"Hello from Python!", "utf-8"))

examples/outbound_http/app.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
def handle_request(request):
55

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

99
return Response(200,
10-
[("content-type", "text/plain")],
10+
{"content-type": "text/plain"},
1111
bytes(f"Here is a dog fact: {str(response.body, 'utf-8')}", "utf-8"))

examples/outbound_redis/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,5 @@ def handle_request(request):
2020
assert value == b"bar", f"expected \"bar\", got \"{str(value, 'utf-8')}\""
2121

2222
return Response(200,
23-
[("content-type", "text/plain")],
23+
{"content-type": "text/plain"},
2424
bytes(f"Executed outbound Redis commands", "utf-8"))

templates/http-py/content/app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44
def handle_request(request):
55

66
return Response(200,
7-
[("content-type", "text/plain")],
7+
{"content-type": "text/plain"},
88
bytes(f"Hello from the Python SDK", "utf-8"))

test/test-app/app.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,45 @@
44
from os import environ
55
import toml
66

7+
78
def handle_request(request):
89
if request.uri == "/foo":
910
return Response(200,
10-
[("content-type", "text/plain")],
11+
{"content-type": "text/plain"},
1112
bytes(f"foo indeed", "utf-8"))
1213

14+
if request.uri == "/duplicateheadertest":
15+
test_headers = [("spin-header-test-key1", "value1"), ("spin-header-test-key2", "value2"),
16+
("spin-header-test-key1", "value3"), ("spin-header-test-key2", "value4")]
17+
18+
key1_test_passes = request.headers.get(
19+
"spin-header-test-key1") == "value1, value3"
20+
key2_test_passes = request.headers.get(
21+
"spin-header-test-key2") == "value2, value4"
22+
if key1_test_passes and key2_test_passes:
23+
response_content = "Duplicate Header Name Test: Pass"
24+
response_code = 200
25+
26+
else:
27+
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"'
28+
example_curl_request = f'curl {example_curl_headers} http://127.0.0.1:3000/duplicateheadertest'
29+
required_headers = "\n".join(
30+
[str(header) for header in test_headers])
31+
response_content = f"""
32+
---------------------- Duplicate Header Name Test -------------------------------------
33+
To make this test pass, you must include the following headers in your request:
34+
{required_headers}
35+
36+
Example Passing Curl Request: {example_curl_request}
37+
38+
Actual Headers Received:
39+
{request.headers}
40+
---------------------------------------------------------------------------------------
41+
"""
42+
response_code = 404
43+
44+
return Response(response_code, {"content-type": "text/plain"}, bytes(response_content, "utf-8"))
45+
1346
print(f"Got request URI: {request.uri}")
1447

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

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

3265
return Response(200,
33-
[("content-type", "text/plain")],
66+
{"content-type": "text/plain"},
3467
bytes(f"Hello from Python! Got request URI: {request.uri}", "utf-8"))

test/test.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ echo "built the test app successfully"
1212

1313
# Start the spin app in the background
1414
echo "Starting Spin app"
15-
spin up --follow-all &
15+
spin up &
1616

1717
# wait for app to be up and running
1818
echo "Waiting for Spin app to be ready"
@@ -21,6 +21,7 @@ timeout 60s bash -c 'until curl --silent -f http://localhost:3000/health > /dev/
2121
# start the test
2222
echo -e "Starting test\n"
2323
curl -f http://localhost:3000 || isFailed=true
24+
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
2425
echo -e "\n\nTest completed"
2526

2627
# kill the spin app

0 commit comments

Comments
 (0)