Skip to content

Commit

Permalink
WIP: impl get_proxy_route_by_uri_inbound func and `test_get_proxy_r…
Browse files Browse the repository at this point in the history
…oute_by_uri_inbound` test, update modify_request_uri logic
  • Loading branch information
laruh committed Jun 30, 2024
1 parent 765f584 commit efce9fa
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 37 deletions.
28 changes: 28 additions & 0 deletions assets/.config_test
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,34 @@
"rp_30_min": 1000,
"rp_60_min": 2000
}
},
{
"inbound_route": "/nft-test/special",
"outbound_route": "https://nft.special",
"proxy_type": "moralis",
"authorized": false,
"allowed_rpc_methods": [],
"rate_limiter": {
"rp_1_min": 60,
"rp_5_min": 200,
"rp_15_min": 700,
"rp_30_min": 1000,
"rp_60_min": 2000
}
},
{
"inbound_route": "/",
"outbound_route": "https://adex.io",
"proxy_type": "moralis",
"authorized": false,
"allowed_rpc_methods": [],
"rate_limiter": {
"rp_1_min": 60,
"rp_5_min": 200,
"rp_15_min": 700,
"rp_30_min": 1000,
"rp_60_min": 2000
}
}
],
"rate_limiter": {
Expand Down
111 changes: 111 additions & 0 deletions src/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use hyper::Uri;
use once_cell::sync::OnceCell;
use proxy::ProxyType;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -90,6 +91,60 @@ impl AppConfig {

route_index.map(|index| &self.proxy_routes[index])
}

#[allow(dead_code)]
pub(crate) fn get_proxy_route_by_uri_inbound(
&self,
uri: &mut Uri,
) -> GenericResult<Option<&ProxyRoute>> {
let path_segments: Vec<&str> = uri.path().split('/').filter(|s| !s.is_empty()).collect();

let mut best_match: Option<(&ProxyRoute, usize)> = None;

for r in &self.proxy_routes {
let route_segments: Vec<&str> = r
.inbound_route
.split('/')
.filter(|s| !s.is_empty())
.collect();
let matched_segments = route_segments
.iter()
.zip(&path_segments)
.take_while(|(route_seg, path_seg)| route_seg == path_seg)
.count();
if matched_segments == route_segments.len()
&& (best_match.is_none() || matched_segments > best_match.unwrap().1)
{
best_match = Some((r, matched_segments));
}
}

if let Some((route, matched_segments)) = best_match {
let remaining_path: String = path_segments.iter().skip(matched_segments).fold(
String::new(),
|mut acc, segment| {
acc.push('/');
acc.push_str(segment);
acc
},
);

// Construct the new path and query
let new_path_and_query = match uri.query() {
Some(query) => format!("{}?{}", remaining_path, query),
None => remaining_path,
};

let mut parts = uri.clone().into_parts();
parts.path_and_query = Some(new_path_and_query.parse()?);
let new_uri = Uri::from_parts(parts)?;
*uri = new_uri;

return Ok(Some(route));
}

Ok(None)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -131,6 +186,34 @@ pub(crate) fn get_app_config_test_instance() -> AppConfig {
rp_60_min: 2000,
}),
},
ProxyRoute {
inbound_route: String::from("/nft-test/special"),
outbound_route: String::from("https://nft.special"),
proxy_type: ProxyType::Moralis,
authorized: false,
allowed_rpc_methods: Vec::default(),
rate_limiter: Some(RateLimiter {
rp_1_min: 60,
rp_5_min: 200,
rp_15_min: 700,
rp_30_min: 1000,
rp_60_min: 2000,
}),
},
ProxyRoute {
inbound_route: String::from("/"),
outbound_route: String::from("https://adex.io"),
proxy_type: ProxyType::Moralis,
authorized: false,
allowed_rpc_methods: Vec::default(),
rate_limiter: Some(RateLimiter {
rp_1_min: 60,
rp_5_min: 200,
rp_15_min: 700,
rp_30_min: 1000,
rp_60_min: 2000,
}),
},
]),
rate_limiter: RateLimiter {
rp_1_min: 555,
Expand Down Expand Up @@ -180,6 +263,34 @@ fn test_app_config_serialzation_and_deserialization() {
"rp_30_min": 1000,
"rp_60_min": 2000
}
},
{
"inbound_route": "/nft-test/special",
"outbound_route": "https://nft.special",
"proxy_type":"moralis",
"authorized": false,
"allowed_rpc_methods": [],
"rate_limiter": {
"rp_1_min": 60,
"rp_5_min": 200,
"rp_15_min": 700,
"rp_30_min": 1000,
"rp_60_min": 2000
}
},
{
"inbound_route": "/",
"outbound_route": "https://adex.io",
"proxy_type":"moralis",
"authorized": false,
"allowed_rpc_methods": [],
"rate_limiter": {
"rp_1_min": 60,
"rp_5_min": 200,
"rp_15_min": 700,
"rp_30_min": 1000,
"rp_60_min": 2000
}
}
],
"rate_limiter": {
Expand Down
41 changes: 41 additions & 0 deletions src/net/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ pub(crate) async fn http_handler(
return handle_preflight();
}

// TODO instead of let inbound_route = match req.method() use get_proxy_route_by_uri_inbound fnc for GET method
// for other methods use get_proxy_route_by_inbound

let inbound_route = match req.method() {
// should take the second element as path string starts with a delimiter
&Method::GET => req.uri().path().split('/').nth(1).unwrap_or("").to_string(),
Expand Down Expand Up @@ -209,6 +212,44 @@ fn test_get_proxy_route_by_inbound() {
assert_eq!(proxy_route.outbound_route, "https://nft.proxy");
}

#[test]
fn test_get_proxy_route_by_uri_inbound() {
use hyper::Uri;
use std::str::FromStr;

let cfg = ctx::get_app_config_test_instance();

// test "/nft-test" inbound case
let mut url = Uri::from_str("https://komodo.proxy:5535/nft-test/nft/api/v2.2/0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326/nft/transfers?chain=eth&format=decimal&order=DESC").unwrap();
let proxy_route = cfg
.get_proxy_route_by_uri_inbound(&mut url)
.unwrap()
.unwrap();
assert_eq!(proxy_route.outbound_route, "https://nft.proxy");
let expected = Uri::from_str("https://komodo.proxy:5535/nft/api/v2.2/0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326/nft/transfers?chain=eth&format=decimal&order=DESC").unwrap();
assert_eq!(expected, url);

// test "/nft-test/special" inbound case
let mut url = Uri::from_str("https://komodo.proxy:3333/nft-test/special/api/v2.2/0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326/nft/transfers?chain=eth&format=decimal&order=DESC").unwrap();
let proxy_route = cfg
.get_proxy_route_by_uri_inbound(&mut url)
.unwrap()
.unwrap();
assert_eq!(proxy_route.outbound_route, "https://nft.special");
let expected = Uri::from_str("https://komodo.proxy:3333/api/v2.2/0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326/nft/transfers?chain=eth&format=decimal&order=DESC").unwrap();
assert_eq!(expected, url);

// test "/" inbound case
let mut url = Uri::from_str("https://komodo.proxy:0333/api/v2.2/0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326/nft/transfers?chain=eth&format=decimal&order=DESC").unwrap();
let proxy_route = cfg
.get_proxy_route_by_uri_inbound(&mut url)
.unwrap()
.unwrap();
assert_eq!(proxy_route.outbound_route, "https://adex.io");
let expected = Uri::from_str("https://komodo.proxy:0333/api/v2.2/0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326/nft/transfers?chain=eth&format=decimal&order=DESC").unwrap();
assert_eq!(expected, url);
}

#[test]
fn test_respond_by_status() {
let all_supported_status_codes = [
Expand Down
44 changes: 7 additions & 37 deletions src/proxy/moralis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ use crate::rate_limiter::RateLimitOperations;
use crate::sign::{SignOps, SignedMessage};
use crate::{log_format, GenericResult};
use hyper::header::{HeaderName, HeaderValue};
use hyper::http::uri::PathAndQuery;
use hyper::{header, Body, Request, Response, StatusCode, Uri};
use hyper_tls::HttpsConnector;
use std::net::SocketAddr;
use std::str::FromStr;

pub(crate) async fn proxy_moralis(
cfg: &AppConfig,
Expand Down Expand Up @@ -87,43 +85,14 @@ pub(crate) async fn proxy_moralis(
Ok(res)
}

/// Modifies the URI of an HTTP request by replacing its base URI with the outbound URI specified in `ProxyRoute`,
/// while incorporating the path and query parameters from the original request URI. Additionally, this function
/// removes the first path segment from the original URI.
/// Modifies the request URI to use the proxy base URI from `outbound_route`.
fn modify_request_uri(req: &mut Request<Body>, proxy_route: &ProxyRoute) -> GenericResult<()> {
let proxy_base_uri = proxy_route.outbound_route.parse::<Uri>()?;

let req_uri = req.uri().clone();

// Remove the first path segment
let mut path_segments: Vec<&str> = req_uri
.path()
.split('/')
.filter(|s| !s.is_empty())
.collect();
if !path_segments.is_empty() {
path_segments.remove(0);
}
let new_path = format!("/{}", path_segments.join("/"));

// Construct the new path and query
let path_and_query_str = match req_uri.query() {
Some(query) => format!("{}?{}", new_path, query),
None => new_path,
};

let path_and_query = PathAndQuery::from_str(&path_and_query_str)?;

// Update the proxy URI with the new path and query
let mut proxy_outbound_parts = proxy_base_uri.into_parts();
proxy_outbound_parts.path_and_query = Some(path_and_query);

// Reconstruct the full URI with the updated parts
let new_uri = Uri::from_parts(proxy_outbound_parts)?;

// Update the request URI
let original_uri = req.uri();
let mut base_uri_parts = proxy_base_uri.into_parts();
base_uri_parts.path_and_query = original_uri.path_and_query().cloned();
let new_uri = Uri::from_parts(base_uri_parts)?;
*req.uri_mut() = new_uri;

Ok(())
}

Expand Down Expand Up @@ -283,9 +252,10 @@ async fn test_parse_moralis_payload() {
#[tokio::test]
async fn test_modify_request_uri() {
use super::ProxyType;
use std::str::FromStr;

let mut req = Request::builder()
.uri("https://komodoproxy.com/nft-proxy/api/v2.2/0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326/nft/transfers?chain=eth&format=decimal&order=DESC")
.uri("https://komodoproxy.com/api/v2.2/0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326/nft/transfers?chain=eth&format=decimal&order=DESC")
.body(Body::empty())
.unwrap();

Expand Down
1 change: 1 addition & 0 deletions src/security/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl JwtClaims {
}

static AUTH_DECODING_KEY: OnceCell<DecodingKey> = OnceCell::new();

#[allow(dead_code)]
pub(crate) fn get_decoding_key(cfg: &AppConfig) -> &'static DecodingKey {
let buffer_closure = || -> Vec<u8> { read_file_buffer(&cfg.pubkey_path) };
Expand Down

0 comments on commit efce9fa

Please sign in to comment.