Adding example API proxy project

This commit is contained in:
Mingwei Samuel 2020-06-05 01:51:25 -07:00
parent 3d56d7c722
commit 2516fe269b
4 changed files with 182 additions and 0 deletions

1
example_proxy/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target

12
example_proxy/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "riven_example_proxy"
version = "0.0.0"
authors = [ "Mingwei Samuel <mingwei.samuel@gmail.com>" ]
edition = "2018"
[dependencies]
riven = { path = '..' }
hyper = "0.13"
lazy_static = "1.4"
tokio = { version = "0.2", features = [ "full" ] }

20
example_proxy/README.md Normal file
View file

@ -0,0 +1,20 @@
# Riven Example Proxy
This is a simple example implementation of a Riot API proxy server using `hyper`. This adds the API key and forwards
requests to the Riot API, then returns and forwards responses back to the requester. It handles error cases but only
provides minimal failure information. HTTP requests will wait to complete when Riven is waiting on rate limits.
Set `RGAPI_KEY` env var then run:
```bash
export RGAPI_KEY=RGAPI-XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
cargo run
```
Test in your browser or using `curl`. The first path segment specifies the region:
```json
$ curl http://localhost:3000/na1/lol/summoner/v4/summoners/by-name/LugnutsK
{"id":"...","accountId":"...","puuid":"...","name":"LugnutsK","profileIconId":4540,"revisionDate":1589704662000,"summonerLevel":111}
$ curl http://localhost:3000/na1/valorant/v4/players/by-name/LugnutsK # not yet :)
{"error":"Riot API endpoint method not found."}
```

149
example_proxy/src/main.rs Normal file
View file

@ -0,0 +1,149 @@
// #![deny(warnings)]
use std::convert::Infallible;
use hyper::service::{ make_service_fn, service_fn };
use hyper::{ Body, Method, Request, Response, Server, StatusCode };
use hyper::header::HeaderValue;
use lazy_static::lazy_static;
use riven::{ RiotApi, RiotApiConfig };
use riven::consts::Region;
lazy_static! {
/// Create lazy static RiotApi instance.
/// Easier than passing it around.
pub static ref RIOT_API: RiotApi = {
let api_key = std::env::var("RGAPI_KEY").ok()
.or_else(|| std::fs::read_to_string("../apikey.txt").ok())
.expect("Failed to find RGAPI_KEY env var or apikey.txt.");
RiotApi::with_config(RiotApiConfig::with_key(api_key.trim())
.preconfig_burst())
};
}
/// Helper to create JSON error responses.
fn create_json_response(body: &'static str, status: StatusCode) -> Response<Body> {
let mut resp = Response::new(Body::from(body));
*resp.status_mut() = status;
resp.headers_mut().insert(hyper::header::CONTENT_TYPE, HeaderValue::from_static("application/json"));
return resp;
}
/// Main request handler service.
async fn handle_request(req: Request<Body>) -> Result<Response<Body>, Infallible> {
if Method::GET != req.method() {
return Ok(create_json_response(r#"{"error":"HTTP method must be GET."}"#, StatusCode::METHOD_NOT_ALLOWED));
}
// Handle path.
let path_data_opt = parse_path(req.uri().path());
let ( region, method_id, req_path ) = match path_data_opt {
None => return Ok(create_json_response(
r#"{"error":"Riot API endpoint method not found."}"#, StatusCode::NOT_FOUND)),
Some(path_data) => path_data,
};
println!("Request to region {} path {}:\n\t{} {}", region, method_id, region, req_path);
// Send request to Riot API.
let query = req.uri().query().map(|s| s.to_owned());
let resp_result = RIOT_API.get_raw_response(method_id, region.into(), req_path.to_owned(), query).await;
let resp_info = match resp_result {
Err(_err) => return Ok(create_json_response(
r#"{"error":"Riot API request failed."}"#, StatusCode::INTERNAL_SERVER_ERROR)),
Ok(resp_info) => resp_info,
};
// Write output.
let api_response = resp_info.response;
let mut out_response = Response::default();
*out_response.headers_mut() = api_response.headers().clone();
// If None, write "null" to body to be extra nice.
if resp_info.status_none {
*out_response.body_mut() = Body::from("null");
}
// Otherwise copy body.
else {
*out_response.status_mut() = api_response.status();
// Using streams would be faster.
let bytes_result = api_response.bytes().await;
let bytes = match bytes_result {
Err(_err) => return Ok(create_json_response(
r#"{"error":"Failed to get body from Riot API response."}"#, StatusCode::INTERNAL_SERVER_ERROR)),
Ok(bytes) => bytes,
};
*out_response.body_mut() = Body::from((&bytes[..]).to_vec());
}
return Ok(out_response);
}
/// Gets the region, method_id, and Riot API path based on the given path.
fn parse_path<'a>(req_path: &'a str) -> Option<( Region, &'static str, &'a str )> {
// Split URI into region and rest of path.
let req_path = req_path.trim_start_matches('/');
let ( region, req_path ) = req_path.split_at(req_path.find('/')?);
let region: Region = region.to_uppercase().parse().ok()?;
// Find method_id for given path.
let method_id = find_matching_method_id(req_path)?;
return Some(( region, method_id, req_path ))
}
/// Finds the method_id given the request path.
fn find_matching_method_id(req_path: &str) -> Option<&'static str> {
for ( ref_path, method_id ) in &*riven::meta::ENDPOINT_PATH_METHODID {
if paths_match(ref_path, req_path) {
return Some(method_id);
}
}
None
}
/// Checks if the request path (req_path) matches the reference path (ref_path).
fn paths_match(ref_path: &str, req_path: &str) -> bool {
let mut ref_iter = ref_path.split('/');
let mut req_iter = req_path.split('/');
loop {
let ref_seg_opt = ref_iter.next();
let req_seg_opt = req_iter.next();
if ref_seg_opt.is_none() != req_seg_opt.is_none() {
return false;
}
if let Some(ref_seg) = ref_seg_opt {
if let Some(req_seg) = req_seg_opt {
if ref_seg.starts_with('{') || ref_seg == req_seg {
continue;
}
return false;
}}
return true;
}
}
#[tokio::main]
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// For every connection, we must make a `Service` to handle all
// incoming HTTP requests on said connection.
let make_svc = make_service_fn(|_conn| {
// This is the `Service` that will handle the connection.
// `service_fn` is a helper to convert a function that
// returns a Response into a `Service`.
async { Ok::<_, Infallible>(service_fn(handle_request)) }
});
let addr = ([ 127, 0, 0, 1 ], 3000).into();
let server = Server::bind(&addr).serve(make_svc);
println!("Listening on http://{}", addr);
server.await?;
Ok(())
}