diff options
Diffstat (limited to 'rust/src/http/mod.rs')
-rw-r--r-- | rust/src/http/mod.rs | 149 |
1 files changed, 149 insertions, 0 deletions
diff --git a/rust/src/http/mod.rs b/rust/src/http/mod.rs new file mode 100644 index 0000000000..b533a3d36d --- /dev/null +++ b/rust/src/http/mod.rs @@ -0,0 +1,149 @@ +use std::collections::HashMap; + +use anyhow::Error; +use http::Request; +use hyper::Body; +use log::info; +use pyo3::{ + pyclass, pymethods, + types::{PyBytes, PyModule}, + IntoPy, PyAny, PyObject, PyResult, Python, ToPyObject, +}; + +use self::resolver::{MatrixConnector, MatrixResolver}; + +mod resolver; + +/// Called when registering modules with python. +pub fn register_module(py: Python<'_>, m: &PyModule) -> PyResult<()> { + let child_module = PyModule::new(py, "http")?; + child_module.add_class::<HttpClient>()?; + child_module.add_class::<MatrixResponse>()?; + + m.add_submodule(child_module)?; + + // We need to manually add the module to sys.modules to make `from + // synapse.synapse_rust import push` work. + py.import("sys")? + .getattr("modules")? + .set_item("synapse.synapse_rust.http", child_module)?; + + Ok(()) +} + +#[derive(Clone)] +struct Bytes(Vec<u8>); + +impl ToPyObject for Bytes { + fn to_object(&self, py: Python<'_>) -> pyo3::PyObject { + PyBytes::new(py, &self.0).into_py(py) + } +} + +impl IntoPy<PyObject> for Bytes { + fn into_py(self, py: Python<'_>) -> PyObject { + self.to_object(py) + } +} + +#[pyclass] +pub struct MatrixResponse { + #[pyo3(get)] + code: u16, + #[pyo3(get)] + phrase: &'static str, + #[pyo3(get)] + content: Bytes, + #[pyo3(get)] + headers: HashMap<String, Bytes>, +} + +#[pyclass] +#[derive(Clone)] +pub struct HttpClient { + client: hyper::Client<MatrixConnector>, +} + +impl HttpClient { + pub fn new() -> Result<Self, Error> { + let resolver = MatrixResolver::new()?; + + let client = hyper::Client::builder().build(MatrixConnector::with_resolver(resolver)); + + Ok(HttpClient { client }) + } + + pub async fn async_request( + &self, + url: String, + method: String, + headers: HashMap<Vec<u8>, Vec<Vec<u8>>>, + body: Option<Vec<u8>>, + ) -> Result<MatrixResponse, Error> { + let mut builder = Request::builder().method(&*method).uri(url); + + for (key, values) in headers { + for value in values { + builder = builder.header(key.clone(), value); + } + } + + let request = if let Some(body) = body { + builder.body(Body::from(body))? + } else { + builder.body(Body::empty())? + }; + + let response = self.client.request(request).await?; + + let code = response.status().as_u16(); + let phrase = response.status().canonical_reason().unwrap_or_default(); + + let headers = response + .headers() + .iter() + .map(|(k, v)| (k.to_string(), Bytes(v.as_bytes().to_owned()))) + .collect(); + + let body = response.into_body(); + + let bytes = hyper::body::to_bytes(body).await?; + let content = Bytes(bytes.to_vec()); + + info!("DONE"); + + Ok(MatrixResponse { + code, + phrase, + content, + headers, + }) + } +} + +#[pymethods] +impl HttpClient { + #[new] + fn py_new() -> Result<Self, Error> { + Self::new() + } + + fn request<'a>( + &'a self, + py: Python<'a>, + url: String, + method: String, + headers: HashMap<Vec<u8>, Vec<Vec<u8>>>, + body: Option<Vec<u8>>, + ) -> PyResult<&'a PyAny> { + pyo3::prepare_freethreaded_python(); + info!("REQUEST"); + + let client = self.clone(); + + pyo3_asyncio::tokio::future_into_py(py, async move { + let resp = client.async_request(url, method, headers, body).await?; + Ok(resp) + }) + } +} |