use std::{fmt, io, net, thread};
use std::io::{BufRead, BufReader, Write};
use std::net::{ToSocketAddrs, TcpStream};
use std::time::{Instant, Duration};
use base64;
use serde;
use serde_json;
use ::client::Transport;
use ::{Request, Response};
pub const DEFAULT_PORT: u16 = 8332;
#[derive(Clone, Debug)]
pub struct SimpleHttpTransport {
addr: net::SocketAddr,
path: String,
timeout: Duration,
basic_auth: Option<String>,
}
impl Default for SimpleHttpTransport {
fn default() -> Self {
SimpleHttpTransport {
addr: net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), DEFAULT_PORT),
path: "/".to_owned(),
timeout: Duration::from_secs(15),
basic_auth: None,
}
}
}
impl SimpleHttpTransport {
pub fn new() -> Self {
SimpleHttpTransport::default()
}
pub fn builder() -> Builder {
Builder::new()
}
fn request<R>(&self, req: impl serde::Serialize) -> Result<R, Error>
where R: for<'a> serde::de::Deserialize<'a>
{
let request_deadline = Instant::now() + self.timeout;
let mut sock = TcpStream::connect_timeout(&self.addr, self.timeout)?;
let body = serde_json::to_vec(&req)?;
sock.write_all(b"POST ")?;
sock.write_all(self.path.as_bytes())?;
sock.write_all(b" HTTP/1.1\r\n")?;
sock.write_all(b"Content-Type: application/json-rpc\r\n")?;
sock.write_all(b"Content-Length: ")?;
sock.write_all(body.len().to_string().as_bytes())?;
sock.write_all(b"\r\n")?;
if let Some(ref auth) = self.basic_auth {
sock.write_all(b"Authorization: ")?;
sock.write_all(auth.as_ref())?;
sock.write_all(b"\r\n")?;
}
sock.write_all(b"\r\n")?;
sock.write_all(&body)?;
sock.flush()?;
let mut reader = BufReader::new(sock);
let http_response = get_line(&mut reader, request_deadline)?;
if http_response.len() < 12 || !http_response.starts_with("HTTP/1.1 ") {
return Err(Error::HttpParseError);
}
let response_code = match http_response[9..12].parse::<u16>() {
Ok(n) => n,
Err(_) => return Err(Error::HttpParseError),
};
while get_line(&mut reader, request_deadline)? != "\r\n" {}
let resp_body = get_line(&mut reader, request_deadline)?;
match serde_json::from_str(&resp_body) {
Ok(s) => Ok(s),
Err(e) => {
if response_code != 200 {
Err(Error::HttpErrorCode(response_code))
} else {
Err(e.into())
}
}
}
}
}
#[derive(Debug)]
pub enum Error {
InvalidUrl {
url: String,
reason: &'static str,
},
SocketError(io::Error),
HttpParseError,
HttpErrorCode(u16),
Timeout,
Json(serde_json::Error),
}
impl Error {
fn url<U: Into<String>>(url: U, reason: &'static str) -> Error {
Error::InvalidUrl {
url: url.into(),
reason: reason,
}
}
}
impl ::std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Error::InvalidUrl{ref url, ref reason} => write!(f, "invalid URL '{}': {}", url, reason),
Error::SocketError(ref e) => write!(f, "Couldn't connect to host: {}", e),
Error::HttpParseError => f.write_str("Couldn't parse response header."),
Error::HttpErrorCode(c) => write!(f, "unexpected HTTP code: {}", c),
Error::Timeout => f.write_str("Didn't receive response data in time, timed out."),
Error::Json(ref e) => write!(f, "JSON error: {}", e),
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::SocketError(e)
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::Json(e)
}
}
impl From<Error> for ::Error {
fn from(e: Error) -> ::Error {
match e {
Error::Json(e) => ::Error::Json(e),
e => ::Error::Transport(Box::new(e))
}
}
}
fn get_line<R: BufRead>(reader: &mut R, deadline: Instant) -> Result<String, Error> {
let mut line = String::new();
while deadline > Instant::now() {
match reader.read_line(&mut line) {
Ok(0) => thread::sleep(Duration::from_millis(5)),
Ok(_) => return Ok(line),
Err(e) => return Err(Error::SocketError(e)),
}
}
Err(Error::Timeout)
}
impl Transport for SimpleHttpTransport {
fn send_request(&self, req: Request) -> Result<Response, ::Error> {
Ok(self.request(req)?)
}
fn send_batch(&self, reqs: &[Request]) -> Result<Vec<Response>, ::Error> {
Ok(self.request(reqs)?)
}
fn fmt_target(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "http://{}:{}{}", self.addr.ip(), self.addr.port(), self.path)
}
}
#[derive(Clone, Debug)]
pub struct Builder {
tp: SimpleHttpTransport,
}
impl Builder {
pub fn new() -> Builder {
Builder {
tp: SimpleHttpTransport::new(),
}
}
pub fn timeout(mut self, timeout: Duration) -> Self {
self.tp.timeout = timeout;
self
}
pub fn url(mut self, url: &str) -> Result<Self, Error> {
let mut fallback_port = DEFAULT_PORT;
let after_scheme = {
let mut split = url.splitn(2, "://");
let s = split.next().unwrap();
match split.next() {
None => s,
Some(after) => {
if s == "http" {
fallback_port = 80;
} else if s == "https" {
fallback_port = 443;
} else {
return Err(Error::url(url, "scheme schould be http or https"));
}
after
}
}
};
let (before_path, path) = {
if let Some(slash) = after_scheme.find("/") {
(&after_scheme[0..slash], &after_scheme[slash..])
} else {
(after_scheme, "/")
}
};
let after_auth = {
let mut split = before_path.splitn(2, "@");
let s = split.next().unwrap();
split.next().unwrap_or(s)
};
let mut split = after_auth.split(":");
let hostname = split.next().unwrap();
let port: u16 = match split.next() {
Some(port_str) => match port_str.parse() {
Ok(port) => port,
Err(_) => return Err(Error::url(url, "invalid port")),
},
None => fallback_port,
};
if split.next().is_some() {
return Err(Error::url(url, "unexpected extra colon"));
}
self.tp.addr = match (hostname, port).to_socket_addrs()?.next() {
Some(a) => a,
None => return Err(Error::url(url, "invalid hostname: error extracting socket address")),
};
self.tp.path = path.to_owned();
Ok(self)
}
pub fn auth<S: AsRef<str>>(mut self, user: S, pass: Option<S>) -> Self {
let mut auth = user.as_ref().to_owned();
auth.push(':');
if let Some(ref pass) = pass {
auth.push_str(&pass.as_ref()[..]);
}
self.tp.basic_auth = Some(format!("Basic {}", &base64::encode(auth.as_bytes())));
self
}
pub fn cookie_auth<S: AsRef<str>>(mut self, cookie: S) -> Self {
self.tp.basic_auth = Some(format!("Basic {}", &base64::encode(cookie.as_ref().as_bytes())));
self
}
pub fn build(self) -> SimpleHttpTransport {
self.tp
}
}
impl ::Client {
pub fn simple_http(
url: &str,
user: Option<String>,
pass: Option<String>,
) -> Result<::Client, Error> {
let mut builder = Builder::new().url(&url)?;
if let Some(user) = user {
builder = builder.auth(user, pass);
}
Ok(::Client::with_transport(builder.build()))
}
}
#[cfg(test)]
mod tests {
use std::net;
use ::Client;
use super::*;
#[test]
fn test_urls() {
let addr: net::SocketAddr = ("localhost", 22).to_socket_addrs().unwrap().next().unwrap();
let urls = [
"localhost:22",
"http://localhost:22/",
"https://localhost:22/walletname/stuff?it=working",
"http://me:weak@localhost:22/wallet",
];
for u in &urls {
let tp = Builder::new().url(*u).unwrap().build();
assert_eq!(tp.addr, addr);
}
let addr: net::SocketAddr = ("localhost", 80).to_socket_addrs().unwrap().next().unwrap();
let tp = Builder::new().url("http://localhost/").unwrap().build();
assert_eq!(tp.addr, addr);
let addr: net::SocketAddr = ("localhost", 443).to_socket_addrs().unwrap().next().unwrap();
let tp = Builder::new().url("https://localhost/").unwrap().build();
assert_eq!(tp.addr, addr);
let addr: net::SocketAddr = ("localhost", super::DEFAULT_PORT).to_socket_addrs().unwrap().next().unwrap();
let tp = Builder::new().url("localhost").unwrap().build();
assert_eq!(tp.addr, addr);
let valid_urls = [
"localhost",
"127.0.0.1:8080",
"http://127.0.0.1:8080/",
"http://127.0.0.1:8080/rpc/test",
"https://127.0.0.1/rpc/test",
];
for u in &valid_urls {
Builder::new().url(*u).expect(&format!("error for: {}", u));
}
let invalid_urls = [
"127.0.0.1.0:8080",
"httpx://127.0.0.1:8080/",
"ftp://127.0.0.1:8080/rpc/test",
"http://127.0.0./rpc/test",
];
for u in &invalid_urls {
if let Ok(b) = Builder::new().url(*u) {
let tp = b.build();
panic!("expected error for url {}, got {:?}", u, tp);
}
}
}
#[test]
fn construct() {
let tp = Builder::new()
.timeout(Duration::from_millis(100))
.url("localhost:22").unwrap()
.auth("user", None)
.build();
let _ = Client::with_transport(tp);
let _ = Client::simple_http("localhost:22", None, None).unwrap();
}
}