jsonrpsee_server/transport/
http.rs

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