Skip to main content

jsonrpsee_server/transport/
http.rs

1use crate::{server::handle_rpc_call, BatchRequestConfig, HttpRequest, HttpResponse, LOG_TARGET};
2use http::Method;
3use hyper::body::Bytes;
4use jsonrpsee_core::{
5    http_helpers::{read_body, HttpError},
6    middleware::RpcServiceT,
7    BoxError,
8};
9
10/// Returns true if the `content_type` header indicates a valid JSON message.
11fn is_json(content_type: Option<&hyper::header::HeaderValue>) -> bool {
12    content_type.and_then(|val| val.to_str().ok()).is_some_and(|content| {
13        content.eq_ignore_ascii_case("application/json") ||
14            content.eq_ignore_ascii_case("application/json; charset=utf-8") ||
15            content.eq_ignore_ascii_case("application/json;charset=utf-8") ||
16            content.eq_ignore_ascii_case("application/json-rpc") ||
17            content.eq_ignore_ascii_case("application/json-rpc;charset=utf-8") ||
18            content.eq_ignore_ascii_case("application/json-rpc; charset=utf-8")
19    })
20}
21
22/// Make JSON-RPC HTTP call with a service [`RpcServiceT`]
23///
24/// Fails if the HTTP request was a malformed JSON-RPC request.
25pub async fn call_with_service<S, B>(
26    request: HttpRequest<B>,
27    batch_config: BatchRequestConfig,
28    max_request_size: u32,
29    rpc_service: S,
30) -> HttpResponse
31where
32    B: http_body::Body<Data = Bytes> + Send + 'static,
33    B::Data: Send,
34    B::Error: Into<BoxError>,
35    S: RpcServiceT + Send,
36{
37    // Only the `POST` method is allowed.
38    match *request.method() {
39        Method::POST if is_json(request.headers().get(hyper::header::CONTENT_TYPE)) => {
40            let (parts, body) = request.into_parts();
41
42            let (body, is_single) = match read_body(&parts.headers, body, max_request_size).await {
43                Ok(r) => r,
44                Err(HttpError::TooLarge) => return response::too_large(max_request_size),
45                Err(HttpError::Malformed) => return response::malformed(),
46                Err(HttpError::Stream(e)) => {
47                    tracing::warn!(target: LOG_TARGET, "Internal error reading request body: {}", e);
48                    return response::internal_error();
49                }
50            };
51
52            let rp = handle_rpc_call(&body, is_single, batch_config, &rpc_service).await;
53
54            // If the response is empty it means that it was a notification or empty batch.
55            // For HTTP these are just ACK:ed with a empty body.
56            response::from_method_response(rp)
57        }
58        // Error scenarios:
59        Method::POST => response::unsupported_content_type(),
60        _ => response::method_not_allowed(),
61    }
62}
63
64/// HTTP response helpers.
65pub mod response {
66    use jsonrpsee_core::server::MethodResponse;
67    use jsonrpsee_types::{
68        error::{reject_too_big_request, ErrorCode},
69        ErrorObjectOwned, Id, Response, ResponsePayload,
70    };
71
72    use crate::{HttpBody, HttpResponse};
73
74    const JSON: &str = "application/json; charset=utf-8";
75    const TEXT: &str = "text/plain";
76
77    /// Create a response for json internal error.
78    pub fn internal_error() -> HttpResponse {
79        let err = ResponsePayload::<()>::error(ErrorObjectOwned::from(ErrorCode::InternalError));
80        let rp = Response::new(err, Id::Null);
81        let error = serde_json::to_string(&rp).expect("built from known-good data; qed");
82
83        from_template(hyper::StatusCode::INTERNAL_SERVER_ERROR, error, JSON)
84    }
85
86    /// Create a text/plain response for disallowed method used.
87    pub fn method_not_allowed() -> HttpResponse {
88        from_template(
89            hyper::StatusCode::METHOD_NOT_ALLOWED,
90            "Used HTTP Method is not allowed. POST is required\n",
91            TEXT,
92        )
93    }
94
95    /// Create a json response for oversized requests (413)
96    pub fn too_large(limit: u32) -> HttpResponse {
97        let err = ResponsePayload::<()>::error(reject_too_big_request(limit));
98        let rp = Response::new(err, Id::Null);
99        let error = serde_json::to_string(&rp).expect("JSON serialization infallible; qed");
100
101        from_template(hyper::StatusCode::PAYLOAD_TOO_LARGE, error, JSON)
102    }
103
104    /// Create a json response for empty or malformed requests (400)
105    pub fn malformed() -> HttpResponse {
106        let rp = Response::new(ResponsePayload::<()>::error(ErrorCode::ParseError), Id::Null);
107        let error = serde_json::to_string(&rp).expect("JSON serialization infallible; qed");
108
109        from_template(hyper::StatusCode::BAD_REQUEST, error, JSON)
110    }
111
112    /// Create a response body.
113    pub(crate) fn from_template(
114        status: hyper::StatusCode,
115        body: impl Into<HttpBody>,
116        content_type: &'static str,
117    ) -> HttpResponse {
118        HttpResponse::builder()
119            .status(status)
120            .header("content-type", hyper::header::HeaderValue::from_static(content_type))
121            .body(body.into())
122            // Parsing `StatusCode` and `HeaderValue` is infallible but
123            // parsing body content is not.
124            .expect("Unable to parse response body for type conversion")
125    }
126
127    /// Create a response from a method response.
128    pub fn from_method_response(rp: MethodResponse) -> HttpResponse {
129        let body = rp.into_json();
130        from_template(hyper::StatusCode::OK, body, JSON)
131    }
132
133    /// Create a response for unsupported content type.
134    pub fn unsupported_content_type() -> HttpResponse {
135        from_template(
136            hyper::StatusCode::UNSUPPORTED_MEDIA_TYPE,
137            "Supplied content type is not allowed. Content-Type: application/json is required\n",
138            TEXT,
139        )
140    }
141
142    /// Create a response for when the server is busy and can't accept more requests.
143    pub fn too_many_requests() -> HttpResponse {
144        from_template(
145            hyper::StatusCode::TOO_MANY_REQUESTS,
146            "Too many connections. Please try again later.",
147            TEXT,
148        )
149    }
150
151    /// Create a response for when the server denied the request.
152    pub fn denied() -> HttpResponse {
153        from_template(hyper::StatusCode::FORBIDDEN, HttpBody::default(), TEXT)
154    }
155}