Skip to content

Commit a96db3a

Browse files
authored
feat: implement request/connection info interface (#441)
* Add additional example * Expose request info like HTTP method, host, protocol and path It needs to extend middleware logic (e.g. rewrite path) * Cosmetics * Use internal String to expose connection info * Use separate structure for uri components * Unify url structures to access the same interfaces * Use pydict instead of nested hashmap
1 parent 5d883ac commit a96db3a

File tree

4 files changed

+106
-2
lines changed

4 files changed

+106
-2
lines changed

integration_tests/base_routes.py

+25
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,31 @@ async def async_param(request):
204204
return id
205205

206206

207+
@app.get("/sync/extra/*extra")
208+
def sync_param_extra(request):
209+
extra = request["params"]["extra"]
210+
return extra
211+
212+
213+
@app.get("/async/extra/*extra")
214+
async def async_param_extra(request):
215+
extra = request["params"]["extra"]
216+
return extra
217+
218+
219+
# Request Info
220+
221+
222+
@app.get("/sync/http/param")
223+
def sync_http_param(request):
224+
return jsonify({"url": request["url"], "method": request["method"]})
225+
226+
227+
@app.get("/async/http/param")
228+
async def async_http_param(request):
229+
return jsonify({"url": request["url"], "method": request["method"]})
230+
231+
207232
# HTML serving
208233

209234

integration_tests/test_basic_routes.py

+35
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,38 @@ def test_json_get(route: str, expected_json: dict, session):
6262
for key in expected_json.keys():
6363
assert key in res.json()
6464
assert res.json()[key] == expected_json[key]
65+
66+
67+
@pytest.mark.benchmark
68+
@pytest.mark.parametrize(
69+
"route, expected_json",
70+
[
71+
(
72+
"/sync/http/param",
73+
{
74+
"method": "GET",
75+
"url": {
76+
"host": "127.0.0.1:8080",
77+
"path": "/sync/http/param",
78+
"scheme": "http",
79+
},
80+
},
81+
),
82+
(
83+
"/async/http/param",
84+
{
85+
"method": "GET",
86+
"url": {
87+
"host": "127.0.0.1:8080",
88+
"path": "/async/http/param",
89+
"scheme": "http",
90+
},
91+
},
92+
),
93+
],
94+
)
95+
def test_http_request_info_get(route: str, expected_json: dict, session):
96+
res = get(route)
97+
for key in expected_json.keys():
98+
assert key in res.json()
99+
assert res.json()[key] == expected_json[key]

integration_tests/test_get_requests.py

+9
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ def test_param(function_type: str, session):
1313
assert r.text == "12345"
1414

1515

16+
@pytest.mark.benchmark
17+
@pytest.mark.parametrize("function_type", ["sync", "async"])
18+
def test_param_suffix(function_type: str, session):
19+
r = get(f"/{function_type}/extra/foo/1/baz")
20+
assert r.text == "foo/1/baz"
21+
r = get(f"/{function_type}/extra/foo/bar/baz")
22+
assert r.text == "foo/bar/baz"
23+
24+
1625
@pytest.mark.benchmark
1726
@pytest.mark.parametrize("function_type", ["sync", "async"])
1827
def test_serve_html(function_type: str, session):

src/types.rs

+37-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ use actix_web::{http::Method, HttpRequest};
1515
use anyhow::Result;
1616
use dashmap::DashMap;
1717
use pyo3::exceptions::PyValueError;
18-
use pyo3::types::{PyBytes, PyString};
19-
use pyo3::{exceptions, prelude::*};
18+
use pyo3::types::{PyBytes, PyDict, PyString};
19+
use pyo3::{exceptions, intern, prelude::*};
2020

2121
use crate::io_helpers::read_file;
2222

@@ -108,13 +108,39 @@ impl FunctionInfo {
108108
}
109109
}
110110

111+
#[derive(Default)]
112+
pub struct Url {
113+
pub scheme: String,
114+
pub host: String,
115+
pub path: String,
116+
}
117+
118+
impl Url {
119+
fn new(scheme: &str, host: &str, path: &str) -> Self {
120+
Self {
121+
scheme: scheme.to_string(),
122+
host: host.to_string(),
123+
path: path.to_string(),
124+
}
125+
}
126+
127+
pub fn to_object(&self, py: Python<'_>) -> PyResult<PyObject> {
128+
let dict = PyDict::new(py);
129+
dict.set_item(intern!(py, "scheme"), self.scheme.as_str())?;
130+
dict.set_item(intern!(py, "host"), self.host.as_str())?;
131+
dict.set_item(intern!(py, "path"), self.path.as_str())?;
132+
Ok(dict.into_py(py))
133+
}
134+
}
135+
111136
#[derive(Default)]
112137
pub struct Request {
113138
pub queries: HashMap<String, String>,
114139
pub headers: HashMap<String, String>,
115140
pub method: Method,
116141
pub params: HashMap<String, String>,
117142
pub body: Bytes,
143+
pub url: Url,
118144
}
119145

120146
impl Request {
@@ -139,15 +165,24 @@ impl Request {
139165
method: req.method().clone(),
140166
params: HashMap::new(),
141167
body,
168+
url: Url::new(
169+
req.connection_info().scheme(),
170+
req.connection_info().host(),
171+
req.path(),
172+
),
142173
}
143174
}
144175

145176
pub fn to_hashmap(&self, py: Python<'_>) -> Result<HashMap<&str, Py<PyAny>>> {
146177
let mut result = HashMap::new();
178+
179+
result.insert("method", self.method.as_ref().to_object(py));
147180
result.insert("params", self.params.to_object(py));
148181
result.insert("queries", self.queries.to_object(py));
149182
result.insert("headers", self.headers.to_object(py));
150183
result.insert("body", self.body.to_object(py));
184+
result.insert("url", self.url.to_object(py)?);
185+
151186
Ok(result)
152187
}
153188
}

0 commit comments

Comments
 (0)