Riven/src/req/regional_requester.rs

113 lines
4.0 KiB
Rust
Raw Normal View History

use std::future::Future;
2019-10-15 04:29:32 +00:00
use std::sync::Arc;
2019-10-14 06:38:22 +00:00
2019-10-23 02:33:15 +00:00
use log;
2019-10-19 21:39:53 +00:00
use reqwest::{ Client, StatusCode, Url };
2019-10-20 07:54:01 +00:00
use tokio_timer::delay_for;
2019-10-14 06:38:22 +00:00
2019-10-23 02:33:15 +00:00
use crate::Result;
2019-10-14 06:38:22 +00:00
use crate::riot_api_config::RiotApiConfig;
use crate::consts::Region;
use crate::util::InsertOnlyCHashMap;
use super::RateLimit;
use super::RateLimitType;
2019-10-14 06:38:22 +00:00
pub struct RegionalRequester {
2019-10-14 06:38:22 +00:00
/// Represents the app rate limit.
app_rate_limit: RateLimit,
/// Represents method rate limits.
method_rate_limits: InsertOnlyCHashMap<&'static str, RateLimit>,
2019-10-14 06:38:22 +00:00
}
impl RegionalRequester {
2019-10-14 06:38:22 +00:00
/// Request header name for the Riot API key.
const RIOT_KEY_HEADER: &'static str = "X-Riot-Token";
/// HttpStatus codes that are considered a success, but will return None.
const NONE_STATUS_CODES: [u16; 3] = [ 204, 404, 422 ];
pub fn new() -> Self {
2019-10-15 04:29:32 +00:00
Self {
2019-10-14 06:38:22 +00:00
app_rate_limit: RateLimit::new(RateLimitType::Application),
method_rate_limits: InsertOnlyCHashMap::new(),
2019-10-14 06:38:22 +00:00
}
}
pub fn get<'a, T: serde::de::DeserializeOwned>(self: Arc<Self>,
config: &'a RiotApiConfig, client: &'a Client,
2019-10-22 18:16:17 +00:00
method_id: &'static str, region: Region, path: String, query: Option<String>)
2019-10-23 02:33:15 +00:00
-> impl Future<Output = Result<Option<T>>> + 'a
{
async move {
2019-10-22 18:16:17 +00:00
let query = query.as_deref();
2019-10-14 06:38:22 +00:00
2019-10-23 02:33:15 +00:00
let mut retries: u8 = 0;
loop {
let method_rate_limit: Arc<RateLimit> = self.method_rate_limits
.get_or_insert_with(method_id, || RateLimit::new(RateLimitType::Method));
2019-10-14 06:38:22 +00:00
// Rate limiting.
while let Some(delay) = RateLimit::get_both_or_delay(&self.app_rate_limit, &*method_rate_limit) {
delay_for(delay).await;
2019-10-14 06:38:22 +00:00
}
// Send request.
let url_base = format!("https://{}.api.riotgames.com", region.platform);
let mut url = Url::parse(&*url_base)
.unwrap_or_else(|_| panic!("Failed to parse url_base: \"{}\".", url_base));
url.set_path(&*path);
url.set_query(query);
2019-10-23 02:33:15 +00:00
let response = client.get(url)
.header(Self::RIOT_KEY_HEADER, &*config.api_key)
.send()
2019-10-23 02:33:15 +00:00
.await?;
// Maybe update rate limits (based on response headers).
self.app_rate_limit.on_response(&response);
method_rate_limit.on_response(&response);
// Handle response.
let status = response.status();
2019-10-23 02:33:15 +00:00
log::trace!("Response {} (retried {} times).", status, retries);
// Special "none success" cases, return None.
if Self::is_none_status_code(&status) {
2019-10-23 02:33:15 +00:00
break Ok(None);
}
2019-10-23 02:33:15 +00:00
// Handle normal success / failure cases.
match response.error_for_status_ref() {
// Success.
Ok(_) => {
let value = response.json::<T>().await;
break value.map(|v| Some(v));
},
// Failure, may or may not be retryable.
Err(err) => {
// Not-retryable: no more retries or 4xx or ? (3xx, redirects exceeded).
// Retryable: retries remaining, and 429 or 5xx.
if retries >= config.retries ||
(StatusCode::TOO_MANY_REQUESTS != status
&& !status.is_server_error())
{
break Err(err);
}
},
};
retries += 1;
2019-10-14 06:38:22 +00:00
}
}
}
fn is_none_status_code(status: &StatusCode) -> bool {
Self::NONE_STATUS_CODES.contains(&status.as_u16())
}
}
#[cfg(test)]
mod tests {
2019-10-18 20:18:26 +00:00
// use super::*;
2019-10-14 06:38:22 +00:00
}