1 //! Simple HTTP implementation which supports both async and traditional execution environments
2 //! with minimal dependencies. This is used as the basis for REST and RPC clients.
7 use std::convert::TryFrom;
8 #[cfg(not(feature = "tokio"))]
10 use std::net::ToSocketAddrs;
11 use std::time::Duration;
13 #[cfg(feature = "tokio")]
14 use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt};
15 #[cfg(feature = "tokio")]
16 use tokio::net::TcpStream;
18 #[cfg(not(feature = "tokio"))]
21 #[cfg(not(feature = "tokio"))]
22 use std::net::TcpStream;
24 /// Timeout for operations on TCP streams.
25 const TCP_STREAM_TIMEOUT: Duration = Duration::from_secs(5);
27 /// Maximum HTTP message header size in bytes.
28 const MAX_HTTP_MESSAGE_HEADER_SIZE: usize = 8192;
30 /// Maximum HTTP message body size in bytes. Enough for a hex-encoded block in JSON format and any
31 /// overhead for HTTP chunked transfer encoding.
32 const MAX_HTTP_MESSAGE_BODY_SIZE: usize = 2 * 4_000_000 + 32_000;
34 /// Endpoint for interacting with an HTTP-based API.
36 pub struct HttpEndpoint {
43 /// Creates an endpoint for the given host and default HTTP port.
44 pub fn for_host(host: String) -> Self {
48 path: String::from("/"),
52 /// Specifies a port to use with the endpoint.
53 pub fn with_port(mut self, port: u16) -> Self {
54 self.port = Some(port);
58 /// Specifies a path to use with the endpoint.
59 pub fn with_path(mut self, path: String) -> Self {
64 /// Returns the endpoint host.
65 pub fn host(&self) -> &str {
69 /// Returns the endpoint port.
70 pub fn port(&self) -> u16 {
77 /// Returns the endpoint path.
78 pub fn path(&self) -> &str {
83 impl<'a> std::net::ToSocketAddrs for &'a HttpEndpoint {
84 type Iter = <(&'a str, u16) as std::net::ToSocketAddrs>::Iter;
86 fn to_socket_addrs(&self) -> std::io::Result<Self::Iter> {
87 (self.host(), self.port()).to_socket_addrs()
91 /// Client for making HTTP requests.
92 pub(crate) struct HttpClient {
97 /// Opens a connection to an HTTP endpoint.
98 pub fn connect<E: ToSocketAddrs>(endpoint: E) -> std::io::Result<Self> {
99 let address = match endpoint.to_socket_addrs()?.next() {
101 return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "could not resolve to any addresses"));
103 Some(address) => address,
105 let stream = std::net::TcpStream::connect_timeout(&address, TCP_STREAM_TIMEOUT)?;
106 stream.set_read_timeout(Some(TCP_STREAM_TIMEOUT))?;
107 stream.set_write_timeout(Some(TCP_STREAM_TIMEOUT))?;
109 #[cfg(feature = "tokio")]
111 stream.set_nonblocking(true)?;
112 TcpStream::from_std(stream)?
118 /// Sends a `GET` request for a resource identified by `uri` at the `host`.
120 /// Returns the response body in `F` format.
122 pub async fn get<F>(&mut self, uri: &str, host: &str) -> std::io::Result<F>
123 where F: TryFrom<Vec<u8>, Error = std::io::Error> {
124 let request = format!(
125 "GET {} HTTP/1.1\r\n\
127 Connection: keep-alive\r\n\
129 let response_body = self.send_request_with_retry(&request).await?;
130 F::try_from(response_body)
133 /// Sends a `POST` request for a resource identified by `uri` at the `host` using the given HTTP
134 /// authentication credentials.
136 /// The request body consists of the provided JSON `content`. Returns the response body in `F`
139 pub async fn post<F>(&mut self, uri: &str, host: &str, auth: &str, content: serde_json::Value) -> std::io::Result<F>
140 where F: TryFrom<Vec<u8>, Error = std::io::Error> {
141 let content = content.to_string();
142 let request = format!(
143 "POST {} HTTP/1.1\r\n\
145 Authorization: {}\r\n\
146 Connection: keep-alive\r\n\
147 Content-Type: application/json\r\n\
148 Content-Length: {}\r\n\
150 {}", uri, host, auth, content.len(), content);
151 let response_body = self.send_request_with_retry(&request).await?;
152 F::try_from(response_body)
155 /// Sends an HTTP request message and reads the response, returning its body. Attempts to
156 /// reconnect and retry if the connection has been closed.
157 async fn send_request_with_retry(&mut self, request: &str) -> std::io::Result<Vec<u8>> {
158 let endpoint = self.stream.peer_addr().unwrap();
159 match self.send_request(request).await {
160 Ok(bytes) => Ok(bytes),
162 // Reconnect and retry on fail. This can happen if the connection was closed after
163 // the keep-alive limits are reached, or generally if the request timed out due to
164 // Bitcoin Core being stuck on a long-running operation or its RPC queue being
166 // Block 100ms before retrying the request as in many cases the source of the error
167 // may be persistent for some time.
168 #[cfg(feature = "tokio")]
169 tokio::time::sleep(Duration::from_millis(100)).await;
170 #[cfg(not(feature = "tokio"))]
171 std::thread::sleep(Duration::from_millis(100));
172 *self = Self::connect(endpoint)?;
173 self.send_request(request).await
178 /// Sends an HTTP request message and reads the response, returning its body.
179 async fn send_request(&mut self, request: &str) -> std::io::Result<Vec<u8>> {
180 self.write_request(request).await?;
181 self.read_response().await
184 /// Writes an HTTP request message.
185 async fn write_request(&mut self, request: &str) -> std::io::Result<()> {
186 #[cfg(feature = "tokio")]
188 self.stream.write_all(request.as_bytes()).await?;
189 self.stream.flush().await
191 #[cfg(not(feature = "tokio"))]
193 self.stream.write_all(request.as_bytes())?;
198 /// Reads an HTTP response message.
199 async fn read_response(&mut self) -> std::io::Result<Vec<u8>> {
200 #[cfg(feature = "tokio")]
201 let stream = self.stream.split().0;
202 #[cfg(not(feature = "tokio"))]
203 let stream = std::io::Read::by_ref(&mut self.stream);
205 let limited_stream = stream.take(MAX_HTTP_MESSAGE_HEADER_SIZE as u64);
207 #[cfg(feature = "tokio")]
208 let mut reader = tokio::io::BufReader::new(limited_stream);
209 #[cfg(not(feature = "tokio"))]
210 let mut reader = std::io::BufReader::new(limited_stream);
212 macro_rules! read_line { () => { {
213 let mut line = String::new();
214 #[cfg(feature = "tokio")]
215 let bytes_read = reader.read_line(&mut line).await?;
216 #[cfg(not(feature = "tokio"))]
217 let bytes_read = reader.read_line(&mut line)?;
222 // Remove trailing CRLF
223 if line.ends_with('\n') { line.pop(); if line.ends_with('\r') { line.pop(); } }
229 // Read and parse status line
230 let status_line = read_line!()
231 .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "no status line"))?;
232 let status = HttpStatus::parse(&status_line)?;
234 // Read and parse relevant headers
235 let mut message_length = HttpMessageLength::Empty;
237 let line = read_line!()
238 .ok_or(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "no headers"))?;
239 if line.is_empty() { break; }
241 let header = HttpHeader::parse(&line)?;
242 if header.has_name("Content-Length") {
243 let length = header.value.parse()
244 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
245 if let HttpMessageLength::Empty = message_length {
246 message_length = HttpMessageLength::ContentLength(length);
251 if header.has_name("Transfer-Encoding") {
252 message_length = HttpMessageLength::TransferEncoding(header.value.into());
258 // TODO: Handle 3xx redirection responses.
259 return Err(std::io::Error::new(std::io::ErrorKind::NotFound, "not found"));
263 let read_limit = MAX_HTTP_MESSAGE_BODY_SIZE - reader.buffer().len();
264 reader.get_mut().set_limit(read_limit as u64);
265 match message_length {
266 HttpMessageLength::Empty => { Ok(Vec::new()) },
267 HttpMessageLength::ContentLength(length) => {
268 if length == 0 || length > MAX_HTTP_MESSAGE_BODY_SIZE {
269 Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "out of range"))
271 let mut content = vec![0; length];
272 #[cfg(feature = "tokio")]
273 reader.read_exact(&mut content[..]).await?;
274 #[cfg(not(feature = "tokio"))]
275 reader.read_exact(&mut content[..])?;
279 HttpMessageLength::TransferEncoding(coding) => {
280 if !coding.eq_ignore_ascii_case("chunked") {
281 Err(std::io::Error::new(
282 std::io::ErrorKind::InvalidInput, "unsupported transfer coding"))
284 let mut content = Vec::new();
285 #[cfg(feature = "tokio")]
287 // Since chunked_transfer doesn't have an async interface, only use it to
288 // determine the size of each chunk to read.
290 // TODO: Replace with an async interface when available.
291 // https://github.com/frewsxcv/rust-chunked-transfer/issues/7
293 // Read the chunk header which contains the chunk size.
294 let mut chunk_header = String::new();
295 reader.read_line(&mut chunk_header).await?;
296 if chunk_header == "0\r\n" {
297 // Read the terminator chunk since the decoder consumes the CRLF
298 // immediately when this chunk is encountered.
299 reader.read_line(&mut chunk_header).await?;
302 // Decode the chunk header to obtain the chunk size.
303 let mut buffer = Vec::new();
304 let mut decoder = chunked_transfer::Decoder::new(chunk_header.as_bytes());
305 decoder.read_to_end(&mut buffer)?;
307 // Read the chunk body.
308 let chunk_size = match decoder.remaining_chunks_size() {
310 Some(chunk_size) => chunk_size,
312 let chunk_offset = content.len();
313 content.resize(chunk_offset + chunk_size + "\r\n".len(), 0);
314 reader.read_exact(&mut content[chunk_offset..]).await?;
315 content.resize(chunk_offset + chunk_size, 0);
319 #[cfg(not(feature = "tokio"))]
321 let mut decoder = chunked_transfer::Decoder::new(reader);
322 decoder.read_to_end(&mut content)?;
331 /// HTTP response status code as defined by [RFC 7231].
333 /// [RFC 7231]: https://tools.ietf.org/html/rfc7231#section-6
334 struct HttpStatus<'a> {
338 impl<'a> HttpStatus<'a> {
339 /// Parses an HTTP status line as defined by [RFC 7230].
341 /// [RFC 7230]: https://tools.ietf.org/html/rfc7230#section-3.1.2
342 fn parse(line: &'a String) -> std::io::Result<HttpStatus<'a>> {
343 let mut tokens = line.splitn(3, ' ');
345 let http_version = tokens.next()
346 .ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "no HTTP-Version"))?;
347 if !http_version.eq_ignore_ascii_case("HTTP/1.1") &&
348 !http_version.eq_ignore_ascii_case("HTTP/1.0") {
349 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid HTTP-Version"));
352 let code = tokens.next()
353 .ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "no Status-Code"))?;
354 if code.len() != 3 || !code.chars().all(|c| c.is_ascii_digit()) {
355 return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid Status-Code"));
358 let _reason = tokens.next()
359 .ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "no Reason-Phrase"))?;
364 /// Returns whether the status is successful (i.e., 2xx status class).
365 fn is_ok(&self) -> bool {
366 self.code.starts_with('2')
370 /// HTTP response header as defined by [RFC 7231].
372 /// [RFC 7231]: https://tools.ietf.org/html/rfc7231#section-7
373 struct HttpHeader<'a> {
378 impl<'a> HttpHeader<'a> {
379 /// Parses an HTTP header field as defined by [RFC 7230].
381 /// [RFC 7230]: https://tools.ietf.org/html/rfc7230#section-3.2
382 fn parse(line: &'a String) -> std::io::Result<HttpHeader<'a>> {
383 let mut tokens = line.splitn(2, ':');
384 let name = tokens.next()
385 .ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "no header name"))?;
386 let value = tokens.next()
387 .ok_or(std::io::Error::new(std::io::ErrorKind::InvalidData, "no header value"))?
389 Ok(Self { name, value })
392 /// Returns whether the header field has the given name.
393 fn has_name(&self, name: &str) -> bool {
394 self.name.eq_ignore_ascii_case(name)
398 /// HTTP message body length as defined by [RFC 7230].
400 /// [RFC 7230]: https://tools.ietf.org/html/rfc7230#section-3.3.3
401 enum HttpMessageLength {
403 ContentLength(usize),
404 TransferEncoding(String),
407 /// An HTTP response body in binary format.
408 pub struct BinaryResponse(pub Vec<u8>);
410 /// An HTTP response body in JSON format.
411 pub struct JsonResponse(pub serde_json::Value);
413 /// Interprets bytes from an HTTP response body as binary data.
414 impl TryFrom<Vec<u8>> for BinaryResponse {
415 type Error = std::io::Error;
417 fn try_from(bytes: Vec<u8>) -> std::io::Result<Self> {
418 Ok(BinaryResponse(bytes))
422 /// Interprets bytes from an HTTP response body as a JSON value.
423 impl TryFrom<Vec<u8>> for JsonResponse {
424 type Error = std::io::Error;
426 fn try_from(bytes: Vec<u8>) -> std::io::Result<Self> {
427 Ok(JsonResponse(serde_json::from_slice(&bytes)?))
433 use super::HttpEndpoint;
436 fn with_default_port() {
437 let endpoint = HttpEndpoint::for_host("foo.com".into());
438 assert_eq!(endpoint.host(), "foo.com");
439 assert_eq!(endpoint.port(), 80);
443 fn with_custom_port() {
444 let endpoint = HttpEndpoint::for_host("foo.com".into()).with_port(8080);
445 assert_eq!(endpoint.host(), "foo.com");
446 assert_eq!(endpoint.port(), 8080);
451 let endpoint = HttpEndpoint::for_host("foo.com".into()).with_path("/path".into());
452 assert_eq!(endpoint.host(), "foo.com");
453 assert_eq!(endpoint.path(), "/path");
457 fn without_uri_path() {
458 let endpoint = HttpEndpoint::for_host("foo.com".into());
459 assert_eq!(endpoint.host(), "foo.com");
460 assert_eq!(endpoint.path(), "/");
464 fn convert_to_socket_addrs() {
465 let endpoint = HttpEndpoint::for_host("foo.com".into());
466 let host = endpoint.host();
467 let port = endpoint.port();
469 use std::net::ToSocketAddrs;
470 match (&endpoint).to_socket_addrs() {
471 Err(e) => panic!("Unexpected error: {:?}", e),
472 Ok(mut socket_addrs) => {
473 match socket_addrs.next() {
474 None => panic!("Expected socket address"),
476 assert_eq!(addr, (host, port).to_socket_addrs().unwrap().next().unwrap());
477 assert!(socket_addrs.next().is_none());
486 pub(crate) mod client_tests {
488 use std::io::BufRead;
491 /// Server for handling HTTP client requests with a stock response.
492 pub struct HttpServer {
493 address: std::net::SocketAddr,
494 handler: std::thread::JoinHandle<()>,
495 shutdown: std::sync::Arc<std::sync::atomic::AtomicBool>,
498 /// Body of HTTP response messages.
499 pub enum MessageBody<T: ToString> {
506 pub fn responding_with_ok<T: ToString>(body: MessageBody<T>) -> Self {
507 let response = match body {
508 MessageBody::Empty => "HTTP/1.1 200 OK\r\n\r\n".to_string(),
509 MessageBody::Content(body) => {
510 let body = body.to_string();
512 "HTTP/1.1 200 OK\r\n\
513 Content-Length: {}\r\n\
515 {}", body.len(), body)
517 MessageBody::ChunkedContent(body) => {
518 let mut chuncked_body = Vec::new();
520 use chunked_transfer::Encoder;
521 let mut encoder = Encoder::with_chunks_size(&mut chuncked_body, 8);
522 encoder.write_all(body.to_string().as_bytes()).unwrap();
525 "HTTP/1.1 200 OK\r\n\
526 Transfer-Encoding: chunked\r\n\
528 {}", String::from_utf8(chuncked_body).unwrap())
531 HttpServer::responding_with(response)
534 pub fn responding_with_not_found() -> Self {
535 let response = "HTTP/1.1 404 Not Found\r\n\r\n".to_string();
536 HttpServer::responding_with(response)
539 fn responding_with(response: String) -> Self {
540 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
541 let address = listener.local_addr().unwrap();
543 let shutdown = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
544 let shutdown_signaled = std::sync::Arc::clone(&shutdown);
545 let handler = std::thread::spawn(move || {
546 for stream in listener.incoming() {
547 let mut stream = stream.unwrap();
548 stream.set_write_timeout(Some(TCP_STREAM_TIMEOUT)).unwrap();
550 let lines_read = std::io::BufReader::new(&stream)
552 .take_while(|line| !line.as_ref().unwrap().is_empty())
554 if lines_read == 0 { continue; }
556 for chunk in response.as_bytes().chunks(16) {
557 if shutdown_signaled.load(std::sync::atomic::Ordering::SeqCst) {
560 if let Err(_) = stream.write(chunk) { break; }
561 if let Err(_) = stream.flush() { break; }
567 Self { address, handler, shutdown }
571 self.shutdown.store(true, std::sync::atomic::Ordering::SeqCst);
572 self.handler.join().unwrap();
575 pub fn endpoint(&self) -> HttpEndpoint {
576 HttpEndpoint::for_host(self.address.ip().to_string()).with_port(self.address.port())
581 fn connect_to_unresolvable_host() {
582 match HttpClient::connect(("example.invalid", 80)) {
583 Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::Other),
584 Ok(_) => panic!("Expected error"),
589 fn connect_with_no_socket_address() {
590 match HttpClient::connect(&vec![][..]) {
591 Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput),
592 Ok(_) => panic!("Expected error"),
597 fn connect_with_unknown_server() {
598 match HttpClient::connect(("::", 80)) {
599 #[cfg(target_os = "windows")]
600 Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::AddrNotAvailable),
601 #[cfg(not(target_os = "windows"))]
602 Err(e) => assert_eq!(e.kind(), std::io::ErrorKind::ConnectionRefused),
603 Ok(_) => panic!("Expected error"),
608 async fn connect_with_valid_endpoint() {
609 let server = HttpServer::responding_with_ok::<String>(MessageBody::Empty);
611 match HttpClient::connect(&server.endpoint()) {
612 Err(e) => panic!("Unexpected error: {:?}", e),
618 async fn read_empty_message() {
619 let server = HttpServer::responding_with("".to_string());
621 let mut client = HttpClient::connect(&server.endpoint()).unwrap();
622 match client.get::<BinaryResponse>("/foo", "foo.com").await {
624 assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof);
625 assert_eq!(e.get_ref().unwrap().to_string(), "no status line");
627 Ok(_) => panic!("Expected error"),
632 async fn read_incomplete_message() {
633 let server = HttpServer::responding_with("HTTP/1.1 200 OK".to_string());
635 let mut client = HttpClient::connect(&server.endpoint()).unwrap();
636 match client.get::<BinaryResponse>("/foo", "foo.com").await {
638 assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof);
639 assert_eq!(e.get_ref().unwrap().to_string(), "no headers");
641 Ok(_) => panic!("Expected error"),
646 async fn read_too_large_message_headers() {
647 let response = format!(
648 "HTTP/1.1 302 Found\r\n\
650 \r\n", "Z".repeat(MAX_HTTP_MESSAGE_HEADER_SIZE));
651 let server = HttpServer::responding_with(response);
653 let mut client = HttpClient::connect(&server.endpoint()).unwrap();
654 match client.get::<BinaryResponse>("/foo", "foo.com").await {
656 assert_eq!(e.kind(), std::io::ErrorKind::UnexpectedEof);
657 assert_eq!(e.get_ref().unwrap().to_string(), "no headers");
659 Ok(_) => panic!("Expected error"),
664 async fn read_too_large_message_body() {
665 let body = "Z".repeat(MAX_HTTP_MESSAGE_BODY_SIZE + 1);
666 let server = HttpServer::responding_with_ok::<String>(MessageBody::Content(body));
668 let mut client = HttpClient::connect(&server.endpoint()).unwrap();
669 match client.get::<BinaryResponse>("/foo", "foo.com").await {
671 assert_eq!(e.kind(), std::io::ErrorKind::InvalidData);
672 assert_eq!(e.get_ref().unwrap().to_string(), "out of range");
674 Ok(_) => panic!("Expected error"),
680 async fn read_message_with_unsupported_transfer_coding() {
681 let response = String::from(
682 "HTTP/1.1 200 OK\r\n\
683 Transfer-Encoding: gzip\r\n\
686 let server = HttpServer::responding_with(response);
688 let mut client = HttpClient::connect(&server.endpoint()).unwrap();
689 match client.get::<BinaryResponse>("/foo", "foo.com").await {
691 assert_eq!(e.kind(), std::io::ErrorKind::InvalidInput);
692 assert_eq!(e.get_ref().unwrap().to_string(), "unsupported transfer coding");
694 Ok(_) => panic!("Expected error"),
699 async fn read_empty_message_body() {
700 let server = HttpServer::responding_with_ok::<String>(MessageBody::Empty);
702 let mut client = HttpClient::connect(&server.endpoint()).unwrap();
703 match client.get::<BinaryResponse>("/foo", "foo.com").await {
704 Err(e) => panic!("Unexpected error: {:?}", e),
705 Ok(bytes) => assert_eq!(bytes.0, Vec::<u8>::new()),
710 async fn read_message_body_with_length() {
711 let body = "foo bar baz qux".repeat(32);
712 let content = MessageBody::Content(body.clone());
713 let server = HttpServer::responding_with_ok::<String>(content);
715 let mut client = HttpClient::connect(&server.endpoint()).unwrap();
716 match client.get::<BinaryResponse>("/foo", "foo.com").await {
717 Err(e) => panic!("Unexpected error: {:?}", e),
718 Ok(bytes) => assert_eq!(bytes.0, body.as_bytes()),
723 async fn read_chunked_message_body() {
724 let body = "foo bar baz qux".repeat(32);
725 let chunked_content = MessageBody::ChunkedContent(body.clone());
726 let server = HttpServer::responding_with_ok::<String>(chunked_content);
728 let mut client = HttpClient::connect(&server.endpoint()).unwrap();
729 match client.get::<BinaryResponse>("/foo", "foo.com").await {
730 Err(e) => panic!("Unexpected error: {:?}", e),
731 Ok(bytes) => assert_eq!(bytes.0, body.as_bytes()),
736 async fn reconnect_closed_connection() {
737 let server = HttpServer::responding_with_ok::<String>(MessageBody::Empty);
739 let mut client = HttpClient::connect(&server.endpoint()).unwrap();
740 assert!(client.get::<BinaryResponse>("/foo", "foo.com").await.is_ok());
741 match client.get::<BinaryResponse>("/foo", "foo.com").await {
742 Err(e) => panic!("Unexpected error: {:?}", e),
743 Ok(bytes) => assert_eq!(bytes.0, Vec::<u8>::new()),
748 fn from_bytes_into_binary_response() {
750 match BinaryResponse::try_from(bytes.to_vec()) {
751 Err(e) => panic!("Unexpected error: {:?}", e),
752 Ok(response) => assert_eq!(&response.0, bytes),
757 fn from_invalid_bytes_into_json_response() {
758 let json = serde_json::json!({ "result": 42 });
759 match JsonResponse::try_from(json.to_string().as_bytes()[..5].to_vec()) {
761 Ok(_) => panic!("Expected error"),
766 fn from_valid_bytes_into_json_response() {
767 let json = serde_json::json!({ "result": 42 });
768 match JsonResponse::try_from(json.to_string().as_bytes().to_vec()) {
769 Err(e) => panic!("Unexpected error: {:?}", e),
770 Ok(response) => assert_eq!(response.0, json),