diff --git a/.gitignore b/.gitignore index 444d287..168a3fe 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ log/** .env *.txt *.log -/lib/npm/**/*.js \ No newline at end of file +/lib/npm/**/*.js diff --git a/Cargo.lock b/Cargo.lock index 5dc3a44..225d6e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,6 +730,18 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1050,6 +1062,7 @@ dependencies = [ "hmac 0.11.0", "hocon", "hyper", + "hyper-timeout", "hyper-tls", "image", "lazy_static", @@ -1866,6 +1879,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "tokio-io-timeout" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c49f106be240de154571dd31fbe48acb10ba6c6dd6f6517ad603abffa42de9" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 72c8306..45c7cb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ built = { version = "0.5", features = ["git2"] } [dependencies] hyper = { version = "0.14", features = ["full"] } +hyper-timeout = "0.4" tokio = { version = "1", features = ["full"] } log = "0.4.14" log4rs = "1.0.0" diff --git a/proxy.conf b/proxy.conf index b455abd..ef6f3c3 100644 --- a/proxy.conf +++ b/proxy.conf @@ -27,6 +27,9 @@ # Maximum image size the proxy attempt to fetch in bytes. # Omit this entry if want to support any size imageas "max_document_size": 26214400 + + # Time out in seconds for request,response and connecting to a url + "timeout": 100 # Api access keys. Random ones provided below for testing, replace with your # list. diff --git a/src/config.rs b/src/config.rs index 1ce08ec..6b4ffd9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -51,6 +51,7 @@ pub struct Configuration { pub workers: u16, pub bind_address: Ipv4Addr, pub port: u16, + pub timeout: u64, pub metrics_enabled: bool, pub dashboard_enabled: bool, pub max_document_size: Option, diff --git a/src/error.rs b/src/error.rs index 64d0f33..753aee3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,6 +27,7 @@ pub enum Errors { UnsupportedUriScheme, InvalidUri, InvalidOrBlockedHost, + TimedOut, } impl Errors { @@ -45,6 +46,7 @@ impl Errors { Errors::InvalidOrBlockedHost => { (110, "Invalid or blocked destination host".to_string()) } + Errors::TimedOut => (111, "Connection/Request/Response from the destination timed out".to_string()), }; RpcError { diff --git a/src/http/mod.rs b/src/http/mod.rs index ed9d7e9..9b91363 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -1,9 +1,14 @@ use std::borrow::Borrow; +use std::time::Duration; + +use std::error::Error as StdError; +use std::io::ErrorKind; use hyper::client::HttpConnector; use hyper::http::uri::Scheme; use hyper::Client; use hyper::{body::to_bytes, Uri}; +use hyper_timeout::TimeoutConnector; use hyper_tls::HttpsConnector; use log::error; use log::info; @@ -15,12 +20,15 @@ use crate::document::Document; use crate::metrics; use crate::rpc::error::Errors; +use hyper::client::connect::dns::GaiResolver; +use hyper::Body; + use self::filters::UriFilter; pub mod filters; pub struct HttpClient { - client: Client>, + client: Client>>, Body>, _max_document_size: Option, ipfs_config: Host, uri_filters: Vec>, @@ -31,9 +39,16 @@ impl HttpClient { ipfs_config: Host, max_document_size: Option, uri_filters: Vec>, + timeout: u64, ) -> HttpClient { let https = HttpsConnector::new(); - let client = Client::builder().build(https); + let mut connector = TimeoutConnector::new(https); + + connector.set_connect_timeout(Some(Duration::from_secs(timeout))); + connector.set_read_timeout(Some(Duration::from_secs(timeout))); + connector.set_write_timeout(Some(Duration::from_secs(timeout))); + + let client = Client::builder().build::<_, hyper::Body>(connector); assert!( !uri_filters.is_empty(), "No URI filters provided. This is insecure, check code. Exiting..." @@ -145,6 +160,7 @@ impl HttpClient { error!("Document not found on remote, id={}", req_id); Err(Errors::NotFound) } + e => { metrics::DOCUMENT.with_label_values(&["fetch_error"]).inc(); error!( @@ -156,6 +172,14 @@ impl HttpClient { }, Err(e) => { metrics::DOCUMENT.with_label_values(&["fetch_error"]).inc(); + if let Some(err_ref) = e.source() { + if let Some(err) = err_ref.downcast_ref::() { + if let ErrorKind::TimedOut = err.kind() { + error!("Unable to fetch document, connection/response/request to the server timed out, id={}", req_id); + return Err(Errors::TimedOut); + } + } + } error!( "Unable to fetch document, id={}, reason={}, url={}", req_id, e, url diff --git a/src/proxy.rs b/src/proxy.rs index 0c13735..56071c0 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -56,9 +56,13 @@ impl Context { //TODO: Add more filters here let uri_filters: Vec> = vec![Box::new(PrivateNetworkFilter::new(Box::new(dns_resolver)))]; - let http_client = - HttpClient::new(config.ipfs.clone(), config.max_document_size, uri_filters); - Ok(Context { + let http_client = HttpClient::new( + config.ipfs.clone(), + config.max_document_size, + uri_filters, + config.timeout + ); + Ok(Context{ config: config.clone(), database, moderation_provider, diff --git a/src/rpc/error.rs b/src/rpc/error.rs index 30f50de..b48ef92 100644 --- a/src/rpc/error.rs +++ b/src/rpc/error.rs @@ -26,6 +26,7 @@ pub enum Errors { UnsupportedUriScheme, InvalidUri, InvalidOrBlockedHost, + TimedOut, } impl Errors { @@ -44,6 +45,7 @@ impl Errors { Errors::InvalidOrBlockedHost => { (110, "Invalid or blocked destination host".to_string()) } + Errors::TimedOut => (111, "Connection/Request/Response from the destination timed out".to_string()) }; RpcError {