jsonrpc/http/
minreq_http.rs1#[cfg(jsonrpc_fuzz)]
7use std::io::{self, Read, Write};
8#[cfg(jsonrpc_fuzz)]
9use std::sync::Mutex;
10use std::time::Duration;
11use std::{error, fmt};
12
13use crate::client::Transport;
14use crate::{Request, Response};
15
16const DEFAULT_URL: &str = "http://localhost";
17const DEFAULT_PORT: u16 = 8332; #[cfg(not(jsonrpc_fuzz))]
19const DEFAULT_TIMEOUT_SECONDS: u64 = 15;
20#[cfg(jsonrpc_fuzz)]
21const DEFAULT_TIMEOUT_SECONDS: u64 = 1;
22
23#[derive(Clone, Debug)]
25pub struct MinreqHttpTransport {
26 url: String,
28 timeout: Duration,
30 basic_auth: Option<String>,
32}
33
34impl Default for MinreqHttpTransport {
35 fn default() -> Self {
36 MinreqHttpTransport {
37 url: format!("{}:{}", DEFAULT_URL, DEFAULT_PORT),
38 timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECONDS),
39 basic_auth: None,
40 }
41 }
42}
43
44impl MinreqHttpTransport {
45 pub fn new() -> Self {
47 MinreqHttpTransport::default()
48 }
49
50 pub fn builder() -> Builder {
52 Builder::new()
53 }
54
55 fn request<R>(&self, req: impl serde::Serialize) -> Result<R, Error>
56 where
57 R: for<'a> serde::de::Deserialize<'a>,
58 {
59 let req = match &self.basic_auth {
60 Some(auth) => minreq::Request::new(minreq::Method::Post, &self.url)
61 .with_timeout(self.timeout.as_secs())
62 .with_header("Authorization", auth)
63 .with_json(&req)?,
64 None => minreq::Request::new(minreq::Method::Post, &self.url)
65 .with_timeout(self.timeout.as_secs())
66 .with_json(&req)?,
67 };
68
69 let resp = req.send()?;
73 match resp.json() {
74 Ok(json) => Ok(json),
75 Err(minreq_err) => {
76 if resp.status_code != 200 {
77 Err(Error::Http(HttpError {
78 status_code: resp.status_code,
79 body: resp.as_str().unwrap_or("").to_string(),
80 }))
81 } else {
82 Err(Error::Minreq(minreq_err))
83 }
84 }
85 }
86 }
87}
88
89impl Transport for MinreqHttpTransport {
90 fn send_request(&self, req: Request) -> Result<Response, crate::Error> {
91 Ok(self.request(req)?)
92 }
93
94 fn send_batch(&self, reqs: &[Request]) -> Result<Vec<Response>, crate::Error> {
95 Ok(self.request(reqs)?)
96 }
97
98 fn fmt_target(&self, f: &mut fmt::Formatter) -> fmt::Result {
99 write!(f, "{}", self.url)
100 }
101}
102
103#[derive(Clone, Debug)]
105pub struct Builder {
106 tp: MinreqHttpTransport,
107}
108
109impl Builder {
110 pub fn new() -> Builder {
112 Builder {
113 tp: MinreqHttpTransport::new(),
114 }
115 }
116
117 pub fn timeout(mut self, timeout: Duration) -> Self {
119 self.tp.timeout = timeout;
120 self
121 }
122
123 pub fn url(mut self, url: &str) -> Result<Self, Error> {
125 self.tp.url = url.to_owned();
126 Ok(self)
127 }
128
129 pub fn basic_auth(mut self, user: String, pass: Option<String>) -> Self {
131 let mut s = user;
132 s.push(':');
133 if let Some(ref pass) = pass {
134 s.push_str(pass.as_ref());
135 }
136 self.tp.basic_auth = Some(format!("Basic {}", &base64::encode(s.as_bytes())));
137 self
138 }
139
140 pub fn cookie_auth<S: AsRef<str>>(mut self, cookie: S) -> Self {
157 self.tp.basic_auth = Some(format!("Basic {}", &base64::encode(cookie.as_ref().as_bytes())));
158 self
159 }
160
161 pub fn build(self) -> MinreqHttpTransport {
163 self.tp
164 }
165}
166
167impl Default for Builder {
168 fn default() -> Self {
169 Builder::new()
170 }
171}
172
173#[derive(Debug)]
175pub struct HttpError {
176 pub status_code: i32,
178 pub body: String,
180}
181
182impl fmt::Display for HttpError {
183 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
184 write!(f, "status: {}, body: {}", self.status_code, self.body)
185 }
186}
187
188impl error::Error for HttpError {}
189
190#[non_exhaustive]
194#[derive(Debug)]
195pub enum Error {
196 Json(serde_json::Error),
198 Minreq(minreq::Error),
200 Http(HttpError),
202}
203
204impl fmt::Display for Error {
205 fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
206 match *self {
207 Error::Json(ref e) => write!(f, "parsing JSON failed: {}", e),
208 Error::Minreq(ref e) => write!(f, "minreq: {}", e),
209 Error::Http(ref e) => write!(f, "http ({})", e),
210 }
211 }
212}
213
214impl error::Error for Error {
215 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
216 use self::Error::*;
217
218 match *self {
219 Json(ref e) => Some(e),
220 Minreq(ref e) => Some(e),
221 Http(ref e) => Some(e),
222 }
223 }
224}
225
226impl From<serde_json::Error> for Error {
227 fn from(e: serde_json::Error) -> Self {
228 Error::Json(e)
229 }
230}
231
232impl From<minreq::Error> for Error {
233 fn from(e: minreq::Error) -> Self {
234 Error::Minreq(e)
235 }
236}
237
238impl From<Error> for crate::Error {
239 fn from(e: Error) -> crate::Error {
240 match e {
241 Error::Json(e) => crate::Error::Json(e),
242 e => crate::Error::Transport(Box::new(e)),
243 }
244 }
245}
246
247#[cfg(jsonrpc_fuzz)]
249pub static FUZZ_TCP_SOCK: Mutex<Option<io::Cursor<Vec<u8>>>> = Mutex::new(None);
250
251#[cfg(jsonrpc_fuzz)]
252#[derive(Clone, Debug)]
253struct TcpStream;
254
255#[cfg(jsonrpc_fuzz)]
256mod impls {
257 use super::*;
258
259 impl Read for TcpStream {
260 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
261 match *FUZZ_TCP_SOCK.lock().unwrap() {
262 Some(ref mut cursor) => io::Read::read(cursor, buf),
263 None => Ok(0),
264 }
265 }
266 }
267 impl Write for TcpStream {
268 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
269 io::sink().write(buf)
270 }
271 fn flush(&mut self) -> io::Result<()> {
272 Ok(())
273 }
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::Client;
281
282 #[test]
283 fn construct() {
284 let tp = Builder::new()
285 .timeout(Duration::from_millis(100))
286 .url("http://localhost:22")
287 .unwrap()
288 .basic_auth("user".to_string(), None)
289 .build();
290 let _ = Client::with_transport(tp);
291 }
292}