[fractal] backend: Initial support for session proxy



commit f9fb4455c728b2fab1f4590791655df4116b5196
Author: Xiang Fan <sfanxiang gmail com>
Date:   Sun Feb 24 15:03:28 2019 +0800

    backend: Initial support for session proxy
    
    This patch adds support for using the current session's HTTP and HTTPS
    proxy from gio's ProxyResolver. For each type of proxy, it only uses the
    first url because reqwest only supports that. We reuse the client
    connection pool until it detects any proxy change.

 fractal-matrix-api/src/client.rs | 111 +++++++++++++++++++++++++++++++++++++++
 fractal-matrix-api/src/error.rs  |   2 +
 fractal-matrix-api/src/lib.rs    |   1 +
 fractal-matrix-api/src/util.rs   |  25 +++++----
 4 files changed, 126 insertions(+), 13 deletions(-)
---
diff --git a/fractal-matrix-api/src/client.rs b/fractal-matrix-api/src/client.rs
new file mode 100644
index 00000000..8c4ee9f1
--- /dev/null
+++ b/fractal-matrix-api/src/client.rs
@@ -0,0 +1,111 @@
+use crate::error::Error;
+use crate::globals;
+
+use gio;
+use gio::prelude::*;
+use reqwest;
+
+use std::sync::Mutex;
+use std::time::Duration;
+
+// Special URI used by gio to indicate no proxy
+const PROXY_DIRECT_URI: &str = "direct://";
+
+#[derive(Debug, Eq, PartialEq)]
+struct ProxySettings {
+    http_proxy: Vec<String>,
+    https_proxy: Vec<String>,
+}
+
+impl ProxySettings {
+    fn new(http_proxy: Vec<String>, https_proxy: Vec<String>) -> ProxySettings {
+        ProxySettings {
+            http_proxy,
+            https_proxy,
+        }
+    }
+
+    fn direct() -> ProxySettings {
+        Self::new(
+            vec![PROXY_DIRECT_URI.to_string()],
+            vec![PROXY_DIRECT_URI.to_string()],
+        )
+    }
+
+    fn apply_to_client_builder(
+        &self,
+        mut builder: reqwest::ClientBuilder,
+    ) -> Result<reqwest::ClientBuilder, reqwest::Error> {
+        // Reqwest only supports one proxy for each type
+
+        if !self.http_proxy.is_empty() && self.http_proxy[0] != PROXY_DIRECT_URI {
+            let proxy = reqwest::Proxy::http(&self.http_proxy[0])?;
+            builder = builder.proxy(proxy);
+        }
+        if !self.https_proxy.is_empty() && self.https_proxy[0] != PROXY_DIRECT_URI {
+            let proxy = reqwest::Proxy::https(&self.https_proxy[0])?;
+            builder = builder.proxy(proxy);
+        }
+
+        Ok(builder)
+    }
+}
+
+// gio::ProxyResolver can't be sent or shared
+thread_local! {
+    static proxy_resolver: gio::ProxyResolver =
+        gio::ProxyResolver::get_default().expect("Couldn't get proxy resolver");
+}
+
+#[derive(Debug)]
+struct ClientInner {
+    client: reqwest::Client,
+    proxy_settings: ProxySettings,
+}
+
+#[derive(Debug)]
+pub struct Client {
+    inner: Mutex<ClientInner>,
+}
+
+impl Client {
+    pub fn new() -> Client {
+        Client {
+            inner: Mutex::new(ClientInner {
+                client: Self::build(reqwest::Client::builder()),
+                proxy_settings: ProxySettings::direct(),
+            }),
+        }
+    }
+
+    pub fn get_client(&self) -> Result<reqwest::Client, Error> {
+        // Lock first so we don't overwrite proxy settings with outdated information
+        let mut inner = self.inner.lock().unwrap();
+
+        let http_proxy = proxy_resolver.with(|resolver| resolver.lookup("http://";, None))?;
+        let https_proxy = proxy_resolver.with(|resolver| resolver.lookup("https://";, None))?;
+
+        let new_proxy_settings = ProxySettings::new(http_proxy, https_proxy);
+
+        if inner.proxy_settings == new_proxy_settings {
+            Ok(inner.client.clone())
+        } else {
+            let mut builder = reqwest::Client::builder();
+            builder = new_proxy_settings.apply_to_client_builder(builder)?;
+            let client = Self::build(builder);
+
+            inner.client = client;
+            inner.proxy_settings = new_proxy_settings;
+
+            Ok(inner.client.clone())
+        }
+    }
+
+    fn build(builder: reqwest::ClientBuilder) -> reqwest::Client {
+        builder
+            .gzip(true)
+            .timeout(Duration::from_secs(globals::TIMEOUT))
+            .build()
+            .expect("Couldn't create a http client")
+    }
+}
diff --git a/fractal-matrix-api/src/error.rs b/fractal-matrix-api/src/error.rs
index 3e733c1d..f2d18629 100644
--- a/fractal-matrix-api/src/error.rs
+++ b/fractal-matrix-api/src/error.rs
@@ -1,3 +1,4 @@
+use gio;
 use std::io;
 use std::time::SystemTimeError;
 
@@ -21,6 +22,7 @@ impl From<reqwest::Error> for Error {
 
 derror!(url::ParseError, Error::BackendError);
 derror!(io::Error, Error::BackendError);
+derror!(gio::Error, Error::BackendError);
 derror!(regex::Error, Error::BackendError);
 derror!(SystemTimeError, Error::BackendError);
 
diff --git a/fractal-matrix-api/src/lib.rs b/fractal-matrix-api/src/lib.rs
index 6fd58229..a302385b 100644
--- a/fractal-matrix-api/src/lib.rs
+++ b/fractal-matrix-api/src/lib.rs
@@ -5,6 +5,7 @@ pub mod globals;
 
 pub mod backend;
 pub mod cache;
+mod client;
 mod model;
 pub mod types;
 
diff --git a/fractal-matrix-api/src/util.rs b/fractal-matrix-api/src/util.rs
index 1d9f9bef..d1f15593 100644
--- a/fractal-matrix-api/src/util.rs
+++ b/fractal-matrix-api/src/util.rs
@@ -18,8 +18,7 @@ use std::io::prelude::*;
 use std::sync::{Arc, Condvar, Mutex};
 use std::thread;
 
-use std::time::Duration;
-
+use crate::client::Client;
 use crate::error::Error;
 use crate::types::Message;
 use crate::types::RoomEventFilter;
@@ -29,11 +28,7 @@ use reqwest::header::CONTENT_TYPE;
 use crate::globals;
 
 lazy_static! {
-    static ref HTTP_CLIENT: reqwest::Client = reqwest::Client::builder()
-        .gzip(true)
-        .timeout(Duration::from_secs(globals::TIMEOUT))
-        .build()
-        .expect("Couldn't create a http client");
+    static ref HTTP_CLIENT: Client = Client::new();
 }
 
 pub fn semaphore<F>(thread_count: Arc<(Mutex<u8>, Condvar)>, func: F)
@@ -232,7 +227,7 @@ pub fn get_room_media_list(
 }
 
 pub fn get_media(url: &str) -> Result<Vec<u8>, Error> {
-    let conn = HTTP_CLIENT.get(url);
+    let conn = HTTP_CLIENT.get_client()?.get(url);
     let mut res = conn.send()?;
 
     let mut buffer = Vec::new();
@@ -244,7 +239,11 @@ pub fn get_media(url: &str) -> Result<Vec<u8>, Error> {
 pub fn put_media(url: &str, file: Vec<u8>) -> Result<JsonValue, Error> {
     let (mime, _) = gio::content_type_guess(None, file.as_slice());
 
-    let conn = HTTP_CLIENT.post(url).body(file).header(CONTENT_TYPE, mime);
+    let conn = HTTP_CLIENT
+        .get_client()?
+        .post(url)
+        .body(file)
+        .header(CONTENT_TYPE, mime);
 
     let mut res = conn.send()?;
 
@@ -350,10 +349,10 @@ pub fn download_file(url: &str, fname: String, dest: Option<&str>) -> Result<Str
 
 pub fn json_q(method: &str, url: &Url, attrs: &JsonValue) -> Result<JsonValue, Error> {
     let mut conn = match method {
-        "post" => HTTP_CLIENT.post(url.as_str()),
-        "put" => HTTP_CLIENT.put(url.as_str()),
-        "delete" => HTTP_CLIENT.delete(url.as_str()),
-        _ => HTTP_CLIENT.get(url.as_str()),
+        "post" => HTTP_CLIENT.get_client()?.post(url.as_str()),
+        "put" => HTTP_CLIENT.get_client()?.put(url.as_str()),
+        "delete" => HTTP_CLIENT.get_client()?.delete(url.as_str()),
+        _ => HTTP_CLIENT.get_client()?.get(url.as_str()),
     };
 
     if !attrs.is_null() {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]