This repository has been archived by the owner on Oct 17, 2021. It is now read-only.
forked from tgstation/rust-g
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Port Skull132's HTTP system (tgstation#23)
Adds in a HTTP system for use in a PR over at TG to auto-role discord members when they link their accounts. Could probably replace some world/Export calls in the future.
- Loading branch information
1 parent
43c7181
commit bbc9891
Showing
8 changed files
with
1,277 additions
and
17 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
use std::collections::hash_map::{ HashMap }; | ||
use std::collections::BTreeMap; | ||
|
||
use error::Result; | ||
use jobs; | ||
|
||
// ---------------------------------------------------------------------------- | ||
// Interface | ||
|
||
#[derive(Serialize)] | ||
struct Response<'a> { | ||
status_code: u16, | ||
headers: HashMap<&'a str, &'a str>, | ||
body: &'a str, | ||
} | ||
|
||
// If the response can be deserialized -> success. | ||
// If the response can't be deserialized -> failure. | ||
byond_fn! { http_request_blocking(method, url, body, headers) { | ||
let req = match construct_request(method, url, body, headers) { | ||
Ok(r) => r, | ||
Err(e) => return Some(e.to_string()) | ||
}; | ||
|
||
match submit_request(req) { | ||
Ok(r) => Some(r), | ||
Err(e) => Some(e.to_string()) | ||
} | ||
} } | ||
|
||
// Returns new job-id. | ||
byond_fn! { http_request_async(method, url, body, headers) { | ||
let req = match construct_request(method, url, body, headers) { | ||
Ok(r) => r, | ||
Err(e) => return Some(e.to_string()) | ||
}; | ||
|
||
Some(jobs::start(move || { | ||
match submit_request(req) { | ||
Ok(r) => r, | ||
Err(e) => e.to_string() | ||
} | ||
})) | ||
} } | ||
|
||
// If the response can be deserialized -> success. | ||
// If the response can't be deserialized -> failure or WIP. | ||
byond_fn! { http_check_request(id) { | ||
Some(jobs::check(id)) | ||
} } | ||
|
||
// ---------------------------------------------------------------------------- | ||
// Shared HTTP client state | ||
|
||
const VERSION: &str = env!("CARGO_PKG_VERSION"); | ||
const PKG_NAME: &str = env!("CARGO_PKG_NAME"); | ||
|
||
fn setup_http_client() -> reqwest::Client { | ||
use reqwest::{ Client, header::{ HeaderMap, USER_AGENT } }; | ||
|
||
let mut headers = HeaderMap::new(); | ||
headers.insert(USER_AGENT, format!("{}/{}", PKG_NAME, VERSION).parse().unwrap()); | ||
|
||
Client::builder() | ||
.default_headers(headers) | ||
.build() | ||
.unwrap() | ||
} | ||
|
||
lazy_static! { | ||
static ref HTTP_CLIENT: reqwest::Client = setup_http_client(); | ||
} | ||
|
||
// ---------------------------------------------------------------------------- | ||
// Request construction and execution | ||
|
||
fn construct_request(method: &str, url: &str, body: &str, headers: &str) -> Result<reqwest::RequestBuilder> { | ||
let mut req = match method { | ||
"post" => HTTP_CLIENT.post(url), | ||
"put" => HTTP_CLIENT.put(url), | ||
"patch" => HTTP_CLIENT.patch(url), | ||
"delete" => HTTP_CLIENT.delete(url), | ||
"head" => HTTP_CLIENT.head(url), | ||
_ => HTTP_CLIENT.get(url), | ||
}; | ||
|
||
if !body.is_empty() { | ||
req = req.body(body.to_owned()); | ||
} | ||
|
||
if !headers.is_empty() { | ||
let headers: BTreeMap<&str, &str> = serde_json::from_str(headers)?; | ||
for (key, value) in headers { | ||
req = req.header(key, value); | ||
} | ||
} | ||
|
||
Ok(req) | ||
} | ||
|
||
fn submit_request(req: reqwest::RequestBuilder) -> Result<String> { | ||
let mut response = req.send()?; | ||
|
||
let body = response.text()?; | ||
|
||
let mut resp = Response { | ||
status_code: response.status().as_u16(), | ||
headers: HashMap::new(), | ||
body: &body, | ||
}; | ||
|
||
for (key, value) in response.headers().iter() { | ||
if let Ok(value) = value.to_str() { | ||
resp.headers.insert(key.as_str(), value); | ||
} | ||
} | ||
|
||
Ok(serde_json::to_string(&resp)?) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
//! Job system | ||
use std::sync::mpsc; | ||
use std::thread; | ||
use std::collections::hash_map::{HashMap, Entry}; | ||
use std::cell::RefCell; | ||
|
||
struct Job { | ||
rx: mpsc::Receiver<Output>, | ||
handle: thread::JoinHandle<()>, | ||
} | ||
|
||
type Output = String; | ||
type JobId = String; | ||
|
||
const NO_RESULTS_YET: &str = "NO RESULTS YET"; | ||
const NO_SUCH_JOB: &str = "NO SUCH JOB"; | ||
const JOB_PANICKED: &str = "JOB PANICKED"; | ||
|
||
#[derive(Default)] | ||
struct Jobs { | ||
map: HashMap<JobId, Job>, | ||
next_job: usize, | ||
} | ||
|
||
impl Jobs { | ||
fn start<F: FnOnce() -> Output + Send + 'static>(&mut self, f: F) -> JobId { | ||
let (tx, rx) = mpsc::channel(); | ||
let handle = thread::spawn(move || { | ||
let _ = tx.send(f()); | ||
}); | ||
let id = self.next_job.to_string(); | ||
self.next_job += 1; | ||
self.map.insert(id.clone(), Job { rx, handle }); | ||
id | ||
} | ||
|
||
fn check(&mut self, id: &str) -> Output { | ||
let entry = match self.map.entry(id.to_owned()) { | ||
Entry::Occupied(occupied) => occupied, | ||
Entry::Vacant(_) => return NO_SUCH_JOB.to_owned(), | ||
}; | ||
let result = match entry.get().rx.try_recv() { | ||
Ok(result) => result, | ||
Err(mpsc::TryRecvError::Disconnected) => JOB_PANICKED.to_owned(), | ||
Err(mpsc::TryRecvError::Empty) => return NO_RESULTS_YET.to_owned(), | ||
}; | ||
let _ = entry.remove().handle.join(); | ||
result | ||
} | ||
} | ||
|
||
thread_local! { | ||
static JOBS: RefCell<Jobs> = Default::default(); | ||
} | ||
|
||
pub fn start<F: FnOnce() -> Output + Send + 'static>(f: F) -> JobId { | ||
JOBS.with(|jobs| jobs.borrow_mut().start(f)) | ||
} | ||
|
||
pub fn check(id: &str) -> String { | ||
JOBS.with(|jobs| jobs.borrow_mut().check(id)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters