1#[cfg(feature = "proxy")]
8use socks::Socks5Stream;
9use std::io::{BufRead, BufReader, Read, Write};
10#[cfg(not(jsonrpc_fuzz))]
11use std::net::TcpStream;
12use std::net::{SocketAddr, ToSocketAddrs};
13use std::sync::{Arc, Mutex, MutexGuard};
14use std::time::Duration;
15use std::{error, fmt, io, net, num};
16
17use crate::client::Transport;
18use crate::http::DEFAULT_PORT;
19#[cfg(feature = "proxy")]
20use crate::http::DEFAULT_PROXY_PORT;
21use crate::{Request, Response};
22
23const FINAL_RESP_ALLOC: u64 = 1024 * 1024 * 1024;
25
26#[cfg(not(jsonrpc_fuzz))]
27const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15);
28
29#[cfg(jsonrpc_fuzz)]
30const DEFAULT_TIMEOUT: Duration = Duration::from_millis(1);
31
32#[derive(Clone, Debug)]
35pub struct SimpleHttpTransport {
36 addr: net::SocketAddr,
37 path: String,
38 timeout: Duration,
39 basic_auth: Option<String>,
41 #[cfg(feature = "proxy")]
42 proxy_addr: net::SocketAddr,
43 #[cfg(feature = "proxy")]
44 proxy_auth: Option<(String, String)>,
45 sock: Arc<Mutex<Option<BufReader<TcpStream>>>>,
46}
47
48impl Default for SimpleHttpTransport {
49 fn default() -> Self {
50 SimpleHttpTransport {
51 addr: net::SocketAddr::new(
52 net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)),
53 DEFAULT_PORT,
54 ),
55 path: "/".to_owned(),
56 timeout: DEFAULT_TIMEOUT,
57 basic_auth: None,
58 #[cfg(feature = "proxy")]
59 proxy_addr: net::SocketAddr::new(
60 net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)),
61 DEFAULT_PROXY_PORT,
62 ),
63 #[cfg(feature = "proxy")]
64 proxy_auth: None,
65 sock: Arc::new(Mutex::new(None)),
66 }
67 }
68}
69
70impl SimpleHttpTransport {
71 pub fn new() -> Self {
73 SimpleHttpTransport::default()
74 }
75
76 pub fn builder() -> Builder {
78 Builder::new()
79 }
80
81 pub fn set_url(&mut self, url: &str) -> Result<(), Error> {
83 let url = check_url(url)?;
84 self.addr = url.0;
85 self.path = url.1;
86 Ok(())
87 }
88
89 pub fn set_url_path(&mut self, path: String) {
91 self.path = path;
92 }
93
94 fn request<R>(&self, req: impl serde::Serialize) -> Result<R, Error>
95 where
96 R: for<'a> serde::de::Deserialize<'a>,
97 {
98 match self.try_request(req) {
99 Ok(response) => Ok(response),
100 Err(err) => {
101 *self.sock.lock().expect("poisoned mutex") = None;
103 Err(err)
104 }
105 }
106 }
107
108 #[cfg(feature = "proxy")]
109 fn fresh_socket(&self) -> Result<TcpStream, Error> {
110 let stream = if let Some((username, password)) = &self.proxy_auth {
111 Socks5Stream::connect_with_password(
112 self.proxy_addr,
113 self.addr,
114 username.as_str(),
115 password.as_str(),
116 )?
117 } else {
118 Socks5Stream::connect(self.proxy_addr, self.addr)?
119 };
120 Ok(stream.into_inner())
121 }
122
123 #[cfg(not(feature = "proxy"))]
124 fn fresh_socket(&self) -> Result<TcpStream, Error> {
125 let stream = TcpStream::connect_timeout(&self.addr, self.timeout)?;
126 stream.set_read_timeout(Some(self.timeout))?;
127 stream.set_write_timeout(Some(self.timeout))?;
128 Ok(stream)
129 }
130
131 fn try_request<R>(&self, req: impl serde::Serialize) -> Result<R, Error>
132 where
133 R: for<'a> serde::de::Deserialize<'a>,
134 {
135 let mut sock_lock: MutexGuard<Option<_>> = self.sock.lock().expect("poisoned mutex");
137 if sock_lock.is_none() {
138 *sock_lock = Some(BufReader::new(self.fresh_socket()?));
139 };
140 let sock: &mut BufReader<_> = sock_lock.as_mut().unwrap();
143
144 let body = serde_json::to_vec(&req)?;
146
147 let mut request_bytes = Vec::new();
148
149 request_bytes.write_all(b"POST ")?;
150 request_bytes.write_all(self.path.as_bytes())?;
151 request_bytes.write_all(b" HTTP/1.1\r\n")?;
152 request_bytes.write_all(b"host: ")?;
154 request_bytes.write_all(self.addr.to_string().as_bytes())?;
155 request_bytes.write_all(b"\r\n")?;
156 request_bytes.write_all(b"Content-Type: application/json\r\n")?;
157 request_bytes.write_all(b"Content-Length: ")?;
158 request_bytes.write_all(body.len().to_string().as_bytes())?;
159 request_bytes.write_all(b"\r\n")?;
160 if let Some(ref auth) = self.basic_auth {
161 request_bytes.write_all(b"Authorization: ")?;
162 request_bytes.write_all(auth.as_ref())?;
163 request_bytes.write_all(b"\r\n")?;
164 }
165 request_bytes.write_all(b"\r\n")?;
167 request_bytes.write_all(&body)?;
168
169 let write_success = sock.get_mut().write_all(request_bytes.as_slice()).is_ok()
171 && sock.get_mut().flush().is_ok();
172
173 if !write_success {
175 *sock.get_mut() = self.fresh_socket()?;
176 sock.get_mut().write_all(request_bytes.as_slice())?;
177 sock.get_mut().flush()?;
178 }
179
180 let mut header_buf = String::new();
182 let read_success = sock.read_line(&mut header_buf).is_ok();
183
184 if (!read_success || header_buf.is_empty()) && write_success {
187 *sock.get_mut() = self.fresh_socket()?;
188 sock.get_mut().write_all(request_bytes.as_slice())?;
189 sock.get_mut().flush()?;
190
191 sock.read_line(&mut header_buf)?;
192 }
193
194 if header_buf.len() < 12 {
195 return Err(Error::HttpResponseTooShort {
196 actual: header_buf.len(),
197 needed: 12,
198 });
199 }
200 if !header_buf.as_bytes()[..12].is_ascii() {
201 return Err(Error::HttpResponseNonAsciiHello(header_buf.as_bytes()[..12].to_vec()));
202 }
203 if !header_buf.starts_with("HTTP/1.1 ") {
204 return Err(Error::HttpResponseBadHello {
205 actual: header_buf[0..9].into(),
206 expected: "HTTP/1.1 ".into(),
207 });
208 }
209 let response_code = match header_buf[9..12].parse::<u16>() {
210 Ok(n) => n,
211 Err(e) => return Err(Error::HttpResponseBadStatus(header_buf[9..12].into(), e)),
212 };
213
214 let mut content_length = None;
216 loop {
217 header_buf.clear();
218 sock.read_line(&mut header_buf)?;
219 if header_buf == "\r\n" {
220 break;
221 }
222 header_buf.make_ascii_lowercase();
223
224 const CONTENT_LENGTH: &str = "content-length: ";
225 if let Some(s) = header_buf.strip_prefix(CONTENT_LENGTH) {
226 content_length = Some(
227 s.trim()
228 .parse::<u64>()
229 .map_err(|e| Error::HttpResponseBadContentLength(s.into(), e))?,
230 );
231 }
232 }
233
234 if response_code == 401 {
235 return Err(Error::HttpErrorCode(response_code));
237 }
238
239 let mut reader = match content_length {
243 None => sock.take(FINAL_RESP_ALLOC),
244 Some(n) if n > FINAL_RESP_ALLOC => {
245 return Err(Error::HttpResponseContentLengthTooLarge {
246 length: n,
247 max: FINAL_RESP_ALLOC,
248 });
249 }
250 Some(n) => sock.take(n),
251 };
252
253 match serde_json::from_reader(&mut reader) {
257 Ok(s) => {
258 if content_length.is_some() {
259 reader.bytes().count(); }
261 Ok(s)
262 }
263 Err(e) => {
264 if response_code != 200 {
266 Err(Error::HttpErrorCode(response_code))
267 } else {
268 Err(e.into())
270 }
271 }
272 }
273 }
274}
275
276fn check_url(url: &str) -> Result<(SocketAddr, String), Error> {
279 let mut fallback_port = DEFAULT_PORT;
282
283 let after_scheme = {
286 let mut split = url.splitn(2, "://");
287 let s = split.next().unwrap();
288 match split.next() {
289 None => s, Some(after) => {
291 if s == "http" {
293 fallback_port = 80;
294 } else if s == "https" {
295 fallback_port = 443;
296 } else {
297 return Err(Error::url(url, "scheme should be http or https"));
298 }
299 after
300 }
301 }
302 };
303 let (before_path, path) = {
305 if let Some(slash) = after_scheme.find('/') {
306 (&after_scheme[0..slash], &after_scheme[slash..])
307 } else {
308 (after_scheme, "/")
309 }
310 };
311 let after_auth = {
313 let mut split = before_path.splitn(2, '@');
314 let s = split.next().unwrap();
315 split.next().unwrap_or(s)
316 };
317
318 let mut addr = match after_auth.to_socket_addrs() {
322 Ok(addr) => addr,
323 Err(_) => {
324 format!("{}:{}", after_auth, fallback_port).to_socket_addrs()?
326 }
327 };
328
329 match addr.next() {
330 Some(a) => Ok((a, path.to_owned())),
331 None => Err(Error::url(url, "invalid hostname: error extracting socket address")),
332 }
333}
334
335impl Transport for SimpleHttpTransport {
336 fn send_request(&self, req: Request) -> Result<Response, crate::Error> {
337 Ok(self.request(req)?)
338 }
339
340 fn send_batch(&self, reqs: &[Request]) -> Result<Vec<Response>, crate::Error> {
341 Ok(self.request(reqs)?)
342 }
343
344 fn fmt_target(&self, f: &mut fmt::Formatter) -> fmt::Result {
345 write!(f, "http://{}:{}{}", self.addr.ip(), self.addr.port(), self.path)
346 }
347}
348
349#[derive(Clone, Debug)]
351pub struct Builder {
352 tp: SimpleHttpTransport,
353}
354
355impl Builder {
356 pub fn new() -> Builder {
358 Builder {
359 tp: SimpleHttpTransport::new(),
360 }
361 }
362
363 pub fn timeout(mut self, timeout: Duration) -> Self {
365 self.tp.timeout = timeout;
366 self
367 }
368
369 pub fn url(mut self, url: &str) -> Result<Self, Error> {
371 self.tp.set_url(url)?;
372 Ok(self)
373 }
374
375 pub fn auth<S: AsRef<str>>(mut self, user: S, pass: Option<S>) -> Self {
377 let mut auth = user.as_ref().to_owned();
378 auth.push(':');
379 if let Some(ref pass) = pass {
380 auth.push_str(pass.as_ref());
381 }
382 self.tp.basic_auth = Some(format!("Basic {}", &base64::encode(auth.as_bytes())));
383 self
384 }
385
386 pub fn cookie_auth<S: AsRef<str>>(mut self, cookie: S) -> Self {
388 self.tp.basic_auth = Some(format!("Basic {}", &base64::encode(cookie.as_ref().as_bytes())));
389 self
390 }
391
392 #[cfg(feature = "proxy")]
394 pub fn proxy_addr<S: AsRef<str>>(mut self, proxy_addr: S) -> Result<Self, Error> {
395 self.tp.proxy_addr = check_url(proxy_addr.as_ref())?.0;
397 Ok(self)
398 }
399
400 #[cfg(feature = "proxy")]
402 pub fn proxy_auth<S: AsRef<str>>(mut self, user: S, pass: S) -> Self {
403 self.tp.proxy_auth =
404 Some((user, pass)).map(|(u, p)| (u.as_ref().to_string(), p.as_ref().to_string()));
405 self
406 }
407
408 pub fn build(self) -> SimpleHttpTransport {
410 self.tp
411 }
412}
413
414impl Default for Builder {
415 fn default() -> Self {
416 Builder::new()
417 }
418}
419
420impl crate::Client {
421 pub fn simple_http(
423 url: &str,
424 user: Option<String>,
425 pass: Option<String>,
426 ) -> Result<crate::Client, Error> {
427 let mut builder = Builder::new().url(url)?;
428 if let Some(user) = user {
429 builder = builder.auth(user, pass);
430 }
431 Ok(crate::Client::with_transport(builder.build()))
432 }
433
434 #[cfg(feature = "proxy")]
436 pub fn http_proxy(
437 url: &str,
438 user: Option<String>,
439 pass: Option<String>,
440 proxy_addr: &str,
441 proxy_auth: Option<(&str, &str)>,
442 ) -> Result<crate::Client, Error> {
443 let mut builder = Builder::new().url(url)?;
444 if let Some(user) = user {
445 builder = builder.auth(user, pass);
446 }
447 builder = builder.proxy_addr(proxy_addr)?;
448 if let Some((user, pass)) = proxy_auth {
449 builder = builder.proxy_auth(user, pass);
450 }
451 let tp = builder.build();
452 Ok(crate::Client::with_transport(tp))
453 }
454}
455
456#[derive(Debug)]
458pub enum Error {
459 InvalidUrl {
461 url: String,
463 reason: &'static str,
465 },
466 SocketError(io::Error),
468 HttpResponseTooShort {
470 actual: usize,
472 needed: usize,
474 },
475 HttpResponseNonAsciiHello(Vec<u8>),
477 HttpResponseBadHello {
479 actual: String,
481 expected: String,
483 },
484 HttpResponseBadStatus(String, num::ParseIntError),
486 HttpResponseBadContentLength(String, num::ParseIntError),
488 HttpResponseContentLengthTooLarge {
490 length: u64,
492 max: u64,
494 },
495 HttpErrorCode(u16),
497 IncompleteResponse {
499 content_length: u64,
501 n_read: u64,
503 },
504 Json(serde_json::Error),
506}
507
508impl Error {
509 fn url<U: Into<String>>(url: U, reason: &'static str) -> Error {
511 Error::InvalidUrl {
512 url: url.into(),
513 reason,
514 }
515 }
516}
517
518impl fmt::Display for Error {
519 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
520 use Error::*;
521
522 match *self {
523 InvalidUrl {
524 ref url,
525 ref reason,
526 } => write!(f, "invalid URL '{}': {}", url, reason),
527 SocketError(ref e) => write!(f, "Couldn't connect to host: {}", e),
528 HttpResponseTooShort {
529 ref actual,
530 ref needed,
531 } => {
532 write!(f, "HTTP response too short: length {}, needed {}.", actual, needed)
533 }
534 HttpResponseNonAsciiHello(ref bytes) => {
535 write!(f, "HTTP response started with non-ASCII {:?}", bytes)
536 }
537 HttpResponseBadHello {
538 ref actual,
539 ref expected,
540 } => {
541 write!(f, "HTTP response started with `{}`; expected `{}`.", actual, expected)
542 }
543 HttpResponseBadStatus(ref status, ref err) => {
544 write!(f, "HTTP response had bad status code `{}`: {}.", status, err)
545 }
546 HttpResponseBadContentLength(ref len, ref err) => {
547 write!(f, "HTTP response had bad content length `{}`: {}.", len, err)
548 }
549 HttpResponseContentLengthTooLarge {
550 length,
551 max,
552 } => {
553 write!(f, "HTTP response content length {} exceeds our max {}.", length, max)
554 }
555 HttpErrorCode(c) => write!(f, "unexpected HTTP code: {}", c),
556 IncompleteResponse {
557 content_length,
558 n_read,
559 } => {
560 write!(
561 f,
562 "read {} bytes but HTTP response content-length header was {}.",
563 n_read, content_length
564 )
565 }
566 Json(ref e) => write!(f, "JSON error: {}", e),
567 }
568 }
569}
570
571impl error::Error for Error {
572 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
573 use self::Error::*;
574
575 match *self {
576 InvalidUrl {
577 ..
578 }
579 | HttpResponseTooShort {
580 ..
581 }
582 | HttpResponseNonAsciiHello(..)
583 | HttpResponseBadHello {
584 ..
585 }
586 | HttpResponseBadStatus(..)
587 | HttpResponseBadContentLength(..)
588 | HttpResponseContentLengthTooLarge {
589 ..
590 }
591 | HttpErrorCode(_)
592 | IncompleteResponse {
593 ..
594 } => None,
595 SocketError(ref e) => Some(e),
596 Json(ref e) => Some(e),
597 }
598 }
599}
600
601impl From<io::Error> for Error {
602 fn from(e: io::Error) -> Self {
603 Error::SocketError(e)
604 }
605}
606
607impl From<serde_json::Error> for Error {
608 fn from(e: serde_json::Error) -> Self {
609 Error::Json(e)
610 }
611}
612
613impl From<Error> for crate::Error {
614 fn from(e: Error) -> crate::Error {
615 match e {
616 Error::Json(e) => crate::Error::Json(e),
617 e => crate::Error::Transport(Box::new(e)),
618 }
619 }
620}
621
622#[cfg(jsonrpc_fuzz)]
624pub static FUZZ_TCP_SOCK: Mutex<Option<io::Cursor<Vec<u8>>>> = Mutex::new(None);
625
626#[cfg(jsonrpc_fuzz)]
627#[derive(Clone, Debug)]
628struct TcpStream;
629
630#[cfg(jsonrpc_fuzz)]
631mod impls {
632 use super::*;
633 impl Read for TcpStream {
634 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
635 match *FUZZ_TCP_SOCK.lock().unwrap() {
636 Some(ref mut cursor) => io::Read::read(cursor, buf),
637 None => Ok(0),
638 }
639 }
640 }
641 impl Write for TcpStream {
642 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
643 io::sink().write(buf)
644 }
645 fn flush(&mut self) -> io::Result<()> {
646 Ok(())
647 }
648 }
649
650 impl TcpStream {
651 pub fn connect_timeout(_: &SocketAddr, _: Duration) -> io::Result<Self> {
652 Ok(TcpStream)
653 }
654 pub fn set_read_timeout(&self, _: Option<Duration>) -> io::Result<()> {
655 Ok(())
656 }
657 pub fn set_write_timeout(&self, _: Option<Duration>) -> io::Result<()> {
658 Ok(())
659 }
660 }
661}
662
663#[cfg(test)]
664mod tests {
665 use std::net;
666 #[cfg(feature = "proxy")]
667 use std::str::FromStr;
668
669 use super::*;
670 use crate::Client;
671
672 #[test]
673 fn test_urls() {
674 let addr: net::SocketAddr = ("localhost", 22).to_socket_addrs().unwrap().next().unwrap();
675 let urls = [
676 "localhost:22",
677 "http://localhost:22/",
678 "https://localhost:22/walletname/stuff?it=working",
679 "http://me:weak@localhost:22/wallet",
680 ];
681 for u in &urls {
682 let tp = Builder::new().url(u).unwrap().build();
683 assert_eq!(tp.addr, addr);
684 }
685
686 let addr: net::SocketAddr = ("localhost", 80).to_socket_addrs().unwrap().next().unwrap();
688 let tp = Builder::new().url("http://localhost/").unwrap().build();
689 assert_eq!(tp.addr, addr);
690 let addr: net::SocketAddr = ("localhost", 443).to_socket_addrs().unwrap().next().unwrap();
691 let tp = Builder::new().url("https://localhost/").unwrap().build();
692 assert_eq!(tp.addr, addr);
693 let addr: net::SocketAddr =
694 ("localhost", super::DEFAULT_PORT).to_socket_addrs().unwrap().next().unwrap();
695 let tp = Builder::new().url("localhost").unwrap().build();
696 assert_eq!(tp.addr, addr);
697
698 let valid_urls = [
699 "localhost",
700 "127.0.0.1:8080",
701 "http://127.0.0.1:8080/",
702 "http://127.0.0.1:8080/rpc/test",
703 "https://127.0.0.1/rpc/test",
704 "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8300",
705 "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
706 ];
707 for u in &valid_urls {
708 let (addr, path) = check_url(u).unwrap();
709 let builder = Builder::new().url(u).unwrap_or_else(|_| panic!("error for: {}", u));
710 assert_eq!(builder.tp.addr, addr);
711 assert_eq!(builder.tp.path, path);
712 assert_eq!(builder.tp.timeout, DEFAULT_TIMEOUT);
713 assert_eq!(builder.tp.basic_auth, None);
714 #[cfg(feature = "proxy")]
715 assert_eq!(builder.tp.proxy_addr, SocketAddr::from_str("127.0.0.1:9050").unwrap());
716 }
717
718 let invalid_urls = [
719 "127.0.0.1.0:8080",
720 "httpx://127.0.0.1:8080/",
721 "ftp://127.0.0.1:8080/rpc/test",
722 "http://127.0.0./rpc/test",
723 ];
725 for u in &invalid_urls {
726 if let Ok(b) = Builder::new().url(u) {
727 let tp = b.build();
728 panic!("expected error for url {}, got {:?}", u, tp);
729 }
730 }
731 }
732
733 #[test]
734 fn construct() {
735 let tp = Builder::new()
736 .timeout(Duration::from_millis(100))
737 .url("localhost:22")
738 .unwrap()
739 .auth("user", None)
740 .build();
741 let _ = Client::with_transport(tp);
742
743 let _ = Client::simple_http("localhost:22", None, None).unwrap();
744 }
745
746 #[cfg(feature = "proxy")]
747 #[test]
748 fn construct_with_proxy() {
749 let tp = Builder::new()
750 .timeout(Duration::from_millis(100))
751 .url("localhost:22")
752 .unwrap()
753 .auth("user", None)
754 .proxy_addr("127.0.0.1:9050")
755 .unwrap()
756 .build();
757 let _ = Client::with_transport(tp);
758
759 let _ = Client::http_proxy(
760 "localhost:22",
761 None,
762 None,
763 "127.0.0.1:9050",
764 Some(("user", "password")),
765 )
766 .unwrap();
767 }
768
769 #[cfg(all(not(feature = "proxy"), not(jsonrpc_fuzz)))]
772 #[test]
773 fn request_to_closed_socket() {
774 use serde_json::{Number, Value};
775 use std::net::{Shutdown, TcpListener};
776 use std::sync::mpsc;
777 use std::thread;
778
779 let (tx, rx) = mpsc::sync_channel(1);
780
781 thread::spawn(move || {
782 let server = TcpListener::bind("localhost:0").expect("Binding a Tcp Listener");
783 tx.send(server.local_addr().unwrap().port()).unwrap();
784 for (request_id, stream) in server.incoming().enumerate() {
785 let mut stream = stream.unwrap();
786
787 let buf_reader = BufReader::new(&mut stream);
788
789 let _http_request: Vec<_> = buf_reader
790 .lines()
791 .map(|result| result.unwrap())
792 .take_while(|line| !line.is_empty())
793 .collect();
794
795 let response = Response {
796 result: None,
797 error: None,
798 id: Value::Number(Number::from(request_id)),
799 jsonrpc: Some(String::from("2.0")),
800 };
801 let response_str = serde_json::to_string(&response).unwrap();
802
803 stream.write_all(b"HTTP/1.1 200\r\n").unwrap();
804 stream.write_all(b"Content-Length: ").unwrap();
805 stream.write_all(response_str.len().to_string().as_bytes()).unwrap();
806 stream.write_all(b"\r\n").unwrap();
807 stream.write_all(b"\r\n").unwrap();
808 stream.write_all(response_str.as_bytes()).unwrap();
809 stream.flush().unwrap();
810
811 stream.shutdown(Shutdown::Both).unwrap();
812 }
813 });
814
815 thread::sleep(Duration::from_secs(1));
817
818 let port = rx.recv().unwrap();
819 let client =
820 Client::simple_http(format!("localhost:{}", port).as_str(), None, None).unwrap();
821 let request = client.build_request("test_request", &[]);
822 let result = client.send_request(request).unwrap();
823 assert_eq!(result.id, Value::Number(Number::from(0)));
824 thread::sleep(Duration::from_secs(1));
825 let request = client.build_request("test_request2", &[]);
826 let result2 = client.send_request(request)
827 .expect("This second request should not be an Err like `Err(Transport(HttpResponseTooShort { actual: 0, needed: 12 }))`");
828 assert_eq!(result2.id, Value::Number(Number::from(1)));
829 }
830}