use crate::transport::http;
use crate::{HttpBody, HttpRequest, HttpResponse};
use futures_util::{FutureExt, TryFutureExt};
use http_body_util::BodyExt;
use hyper::body::Bytes;
use hyper::header::{ACCEPT, CONTENT_TYPE};
use hyper::http::HeaderValue;
use hyper::{Method, StatusCode, Uri};
use jsonrpsee_core::BoxError;
use jsonrpsee_types::{ErrorCode, ErrorObject, Id, Request};
use std::collections::HashMap;
use std::future::Future;
use std::pin::Pin;
use std::str::FromStr;
use std::sync::Arc;
use std::task::{Context, Poll};
use tower::{Layer, Service};
#[derive(Debug, thiserror::Error)]
pub enum ProxyGetRequestError {
#[error("ProxyGetRequestLayer path must be unique, got duplicated `{0}`")]
DuplicatedPath(String),
#[error("ProxyGetRequestLayer path must start with `/`, got `{0}`")]
InvalidPath(String),
}
#[derive(Debug, Clone)]
pub struct ProxyGetRequestLayer {
methods: Arc<HashMap<String, String>>,
}
impl ProxyGetRequestLayer {
pub fn new<P, M>(pairs: impl IntoIterator<Item = (P, M)>) -> Result<Self, ProxyGetRequestError>
where
P: Into<String>,
M: Into<String>,
{
let mut methods = HashMap::new();
for (path, method) in pairs {
let path = path.into();
let method = method.into();
if !path.starts_with('/') {
return Err(ProxyGetRequestError::InvalidPath(path));
}
if let Some(path) = methods.insert(path, method) {
return Err(ProxyGetRequestError::DuplicatedPath(path));
}
}
Ok(Self { methods: Arc::new(methods) })
}
}
impl<S> Layer<S> for ProxyGetRequestLayer {
type Service = ProxyGetRequest<S>;
fn layer(&self, inner: S) -> Self::Service {
ProxyGetRequest { inner, methods: self.methods.clone() }
}
}
#[derive(Debug, Clone)]
pub struct ProxyGetRequest<S> {
inner: S,
methods: Arc<HashMap<String, String>>,
}
impl<S, B> Service<HttpRequest<B>> for ProxyGetRequest<S>
where
S: Service<HttpRequest, Response = HttpResponse>,
S::Response: 'static,
S::Error: Into<BoxError> + 'static,
S::Future: Send + 'static,
B: http_body::Body<Data = Bytes> + Send + 'static,
B::Data: Send,
B::Error: Into<BoxError>,
{
type Response = S::Response;
type Error = BoxError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
#[inline]
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, mut req: HttpRequest<B>) -> Self::Future {
let path = req.uri().path();
let method = self.methods.get(path);
match (method, req.method()) {
(Some(method), &Method::GET) => {
*req.method_mut() = Method::POST;
*req.uri_mut() = if let Some(query) = req.uri().query() {
Uri::from_str(&format!("/?{}", query)).expect("The query comes from a valid URI; qed")
} else {
Uri::from_static("/")
};
req.headers_mut().insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
req.headers_mut().insert(ACCEPT, HeaderValue::from_static("application/json"));
let bytes =
serde_json::to_vec(&Request::borrowed(method, None, Id::Number(0))).expect("Valid request; qed");
let req = req.map(|_| HttpBody::from(bytes));
let fut = self.inner.call(req);
async move {
let res = fut.await.map_err(Into::into)?;
let (parts, body) = res.into_parts();
let mut body = http_body_util::BodyStream::new(body);
let mut bytes = Vec::new();
while let Some(frame) = body.frame().await {
let data = frame?.into_data().map_err(|e| format!("{e:?}"))?;
bytes.extend(data);
}
#[derive(serde::Deserialize)]
struct SuccessResponse<'a> {
#[serde(borrow)]
result: &'a serde_json::value::RawValue,
}
let mut response = if let Ok(payload) = serde_json::from_slice::<SuccessResponse>(&bytes) {
http::response::ok_response(payload.result.to_string())
} else {
internal_proxy_error(&bytes)
};
response.extensions_mut().extend(parts.extensions);
Ok(response)
}
.boxed()
}
_ => {
let req = req.map(HttpBody::new);
self.inner.call(req).map_err(Into::into).boxed()
}
}
}
}
fn internal_proxy_error(bytes: &[u8]) -> HttpResponse {
#[derive(serde::Deserialize)]
struct ErrorResponse<'a> {
#[serde(borrow)]
error: ErrorObject<'a>,
}
let error = serde_json::from_slice::<ErrorResponse>(bytes)
.map(|payload| payload.error)
.unwrap_or_else(|_| ErrorObject::from(ErrorCode::InternalError));
http::response::from_template(
StatusCode::INTERNAL_SERVER_ERROR,
serde_json::to_string(&error).expect("JSON serialization infallible; qed"),
"application/json; charset=utf-8",
)
}