Skip to content

Commit

Permalink
injector/optional extractor/user group
Browse files Browse the repository at this point in the history
  • Loading branch information
4t145 committed Nov 22, 2024
1 parent b16568c commit c4c4c1e
Show file tree
Hide file tree
Showing 21 changed files with 536 additions and 35 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ lazy_static = { version = "1.4" }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing = { version = "0" }

# Encode
base64 = { version = "0.22" }


# Time
chrono = { version = "0.4" }
Expand Down
5 changes: 4 additions & 1 deletion crates/kernel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ path = "src/lib.rs"
[features]
reload = []
ext-redis = ["spacegate-ext-redis"]

ipnet = ["dep:ipnet"]
[dependencies]
# http
hyper = { workspace = true }
Expand Down Expand Up @@ -60,7 +60,10 @@ regex = { workspace = true }
spacegate-ext-redis = { workspace = true, optional = true }
crossbeam-utils = "0.8"

# codec
base64 = { workspace = true }

ipnet = { workspace = true, optional = true }
[dev-dependencies]
tokio = { version = "1", features = ["net", "time", "rt", "macros"] }
axum = { workspace = true, features = ["multipart"] }
Expand Down
17 changes: 11 additions & 6 deletions crates/kernel/src/backend_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,26 @@ use crate::helper_layers::map_future::MapFuture;
use crate::utils::x_forwarded_for;
use crate::BoxError;
use crate::SgBody;
use crate::SgRequest;
use crate::SgResponse;
use crate::SgResponseExt;

pub mod echo;
pub mod http_client_service;
pub mod static_file_service;
pub mod ws_client_service;
pub trait SharedHyperService<R>: hyper::service::Service<R> + Send + Sync + 'static {}
pub trait SharedHyperService:
hyper::service::Service<SgRequest, Response = SgResponse, Error = Infallible, Future = BoxFuture<'static, Result<SgResponse, Infallible>>> + Send + Sync + 'static
{
}

impl<R, T> SharedHyperService<R> for T where T: hyper::service::Service<R> + Send + Sync + 'static {}
impl<T> SharedHyperService for T where
T: hyper::service::Service<SgRequest, Response = SgResponse, Error = Infallible, Future = BoxFuture<'static, Result<SgResponse, Infallible>>> + Send + Sync + 'static
{
}
/// a service that can be shared between threads
pub struct ArcHyperService {
pub shared: Arc<
dyn SharedHyperService<Request<SgBody>, Response = Response<SgBody>, Error = Infallible, Future = BoxFuture<'static, Result<Response<SgBody>, Infallible>>> + Send + Sync,
>,
pub shared: Arc<dyn SharedHyperService>,
}

impl std::fmt::Debug for ArcHyperService {
Expand All @@ -43,7 +48,7 @@ impl Clone for ArcHyperService {
impl ArcHyperService {
pub fn new<T>(service: T) -> Self
where
T: SharedHyperService<Request<SgBody>, Response = Response<SgBody>, Error = Infallible> + Send + Sync + 'static,
T: hyper::service::Service<SgRequest, Response = SgResponse, Error = Infallible> + Send + Sync + 'static,
T::Future: Future<Output = Result<Response<SgBody>, Infallible>> + 'static + Send,
{
let map_fut = MapFuture::new(service, |fut| Box::pin(fut) as _);
Expand Down
17 changes: 17 additions & 0 deletions crates/kernel/src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,23 @@ pub use original_ip_addr::*;
mod original_ip_addr;
pub use is_east_west_traffic::*;
mod is_east_west_traffic;
pub mod user_group;
/// Just extract and attach the extension to the request
#[derive(Debug, Clone)]
pub struct Extension<E>(E);

impl<E> Extension<E> {
pub const fn new(e: E) -> Self {
Self(e)
}

pub fn into_inner(self) -> E {
self.0
}
pub const fn inner(&self) -> &E {
&self.0
}
}
/// FromBackend is a marker type to indicate that the response is from backend.
#[derive(Debug, Clone, Copy)]
pub struct FromBackend {
Expand Down
23 changes: 16 additions & 7 deletions crates/kernel/src/extension/original_ip_addr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{net::IpAddr, str::FromStr};

use crate::Extract;
use crate::{extractor::OptionalExtract, Extract, SgRequestExt};

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
/// Extract original ip address from request
Expand All @@ -9,7 +9,7 @@ use crate::Extract;
/// ⚠ **WARNING** ⚠
///
/// If peer addr is not settled, it will panic when there's no original ip information from headers.
pub struct OriginalIpAddr(IpAddr);
pub struct OriginalIpAddr(pub IpAddr);

impl OriginalIpAddr {
pub fn into_inner(self) -> IpAddr {
Expand All @@ -24,9 +24,11 @@ impl std::ops::Deref for OriginalIpAddr {
&self.0
}
}

impl Extract for OriginalIpAddr {
fn extract(req: &hyper::Request<crate::SgBody>) -> Self {
impl OptionalExtract for OriginalIpAddr {
fn extract(req: &hyper::Request<crate::SgBody>) -> Option<Self> {
if let Some(ip) = req.extensions().get::<OriginalIpAddr>().cloned() {
return Some(ip);
}
const X_FORWARDED_FOR: &str = "x-forwarded-for";
const X_REAL_IP: &str = "x-real-ip";
fn header_to_ip(header: &hyper::header::HeaderValue) -> Option<IpAddr> {
Expand All @@ -39,7 +41,14 @@ impl Extract for OriginalIpAddr {
.get(X_REAL_IP)
.and_then(header_to_ip)
.or_else(|| req.headers().get_all(X_FORWARDED_FOR).iter().next().and_then(header_to_ip))
.unwrap_or_else(|| req.extensions().get::<crate::extension::PeerAddr>().expect("peer id should be settled").0.ip());
Self(ip)
.or_else(|| req.extensions().get::<crate::extension::PeerAddr>().map(|peer| peer.0.ip()))?;
Some(Self(ip))
}
}
impl Extract for OriginalIpAddr {
/// # Panics
/// if peer addr is not settled, it will panic when there's no original ip information from headers.
fn extract(req: &hyper::Request<crate::SgBody>) -> Self {
req.extract::<Option<OriginalIpAddr>>().expect("peer addr is not settled")
}
}
40 changes: 40 additions & 0 deletions crates/kernel/src/extension/user_group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
pub mod combinator;
pub mod implementation;

use crate::SgRequest;

pub trait UserGroup {
fn is_match(&self, req: &SgRequest) -> bool;
}

pub trait UserGroupExt: UserGroup {
fn and<B>(self, b: B) -> combinator::And<Self, B>
where
Self: Sized,
{
combinator::And { a: self, b }
}

fn or<B>(self, b: B) -> combinator::Or<Self, B>
where
Self: Sized,
{
combinator::Or { a: self, b }
}

fn not(self) -> combinator::Not<Self>
where
Self: Sized,
{
combinator::Not { a: self }
}

fn boxed(self) -> Box<dyn UserGroup>
where
Self: Sized + 'static,
{
Box::new(self)
}
}

impl<T: UserGroup> UserGroupExt for T {}
114 changes: 114 additions & 0 deletions crates/kernel/src/extension/user_group/combinator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::ops::{Deref, DerefMut};

use crate::SgRequest;

use super::UserGroup;

#[derive(Debug)]
pub struct And<A, B> {
pub a: A,
pub b: B,
}

impl<A, B> UserGroup for And<A, B>
where
A: UserGroup,
B: UserGroup,
{
fn is_match(&self, req: &SgRequest) -> bool {
self.a.is_match(req) && self.b.is_match(req)
}
}

#[derive(Debug)]
pub struct Not<A> {
pub a: A,
}

impl<A: UserGroup> UserGroup for Not<A> {
fn is_match(&self, req: &SgRequest) -> bool {
!self.a.is_match(req)
}
}

#[derive(Debug)]
pub struct Or<A, B> {
pub a: A,
pub b: B,
}

impl<A, B> UserGroup for Or<A, B>
where
A: UserGroup,
B: UserGroup,
{
fn is_match(&self, req: &SgRequest) -> bool {
self.a.is_match(req) || self.b.is_match(req)
}
}

pub struct All {
pub groups: Vec<Box<dyn UserGroup>>,
}

impl std::fmt::Debug for All {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DynamicAll").finish()
}
}

impl Deref for All {
type Target = Vec<Box<dyn UserGroup>>;

fn deref(&self) -> &Self::Target {
&self.groups
}
}

impl DerefMut for All {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.groups
}
}

impl UserGroup for All {
fn is_match(&self, req: &SgRequest) -> bool {
self.groups.iter().all(|g| g.is_match(req))
}
}

impl All {
pub fn new(groups: Vec<Box<dyn UserGroup>>) -> Self {
All { groups }
}
}

pub struct Any {
pub groups: Vec<Box<dyn UserGroup>>,
}

impl std::fmt::Debug for Any {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("DynamicAny").finish()
}
}

impl Deref for Any {
type Target = Vec<Box<dyn UserGroup>>;

fn deref(&self) -> &Self::Target {
&self.groups
}
}

impl DerefMut for Any {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.groups
}
}

impl UserGroup for Any {
fn is_match(&self, req: &SgRequest) -> bool {
self.groups.iter().any(|g| g.is_match(req))
}
}
2 changes: 2 additions & 0 deletions crates/kernel/src/extension/user_group/implementation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#[cfg(feature = "ipnet")]
pub mod ipnet;
16 changes: 16 additions & 0 deletions crates/kernel/src/extension/user_group/implementation/ipnet.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use ipnet::IpNet;

use crate::{
extension::{user_group::UserGroup, OriginalIpAddr},
SgRequestExt,
};

impl UserGroup for IpNet {
fn is_match(&self, req: &crate::SgRequest) -> bool {
if let Some(OriginalIpAddr(ip)) = req.extract() {
self.contains(&ip)
} else {
false
}
}
}
18 changes: 15 additions & 3 deletions crates/kernel/src/extractor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use hyper::Request;

use crate::SgBody;
mod extension;
pub use extension::*;
use hyper::http;
/// a marker is some information that can be attached to a request and can be extracted from a request.
pub trait Extract: Sized + Send + Sync {
Expand All @@ -21,9 +20,22 @@ impl Extract for http::method::Method {
}
}

pub trait OptionalExtract: Sized + Send + Sync {
fn extract(req: &Request<SgBody>) -> Option<Self>;
}

impl<T> Extract for Option<T>
where
T: OptionalExtract,
{
fn extract(req: &Request<SgBody>) -> Option<T> {
<T as OptionalExtract>::extract(req)
}
}

#[cfg(feature = "ext-redis")]
impl Extract for Option<spacegate_ext_redis::RedisClient> {
fn extract(req: &Request<SgBody>) -> Self {
impl OptionalExtract for spacegate_ext_redis::RedisClient {
fn extract(req: &Request<SgBody>) -> Option<Self> {
crate::SgRequestExt::get_redis_client_by_gateway_name(req)
}
}
20 changes: 3 additions & 17 deletions crates/kernel/src/extractor/extension.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
use hyper::Request;

use crate::{Extract, SgBody};

/// Just extract and attach the extension to the request
#[derive(Debug, Clone)]
pub struct Extension<E>(E);

impl<E> Extension<E> {
pub fn new(e: E) -> Self {
Self(e)
}

pub fn into_inner(self) -> E {
self.0
}
}
use crate::{extension::Extension, Extract, SgBody};

impl<E> Extract for Option<Extension<E>>
where
Expand All @@ -31,9 +17,9 @@ where
{
fn extract(req: &Request<SgBody>) -> Self {
if let Some(ext) = req.extensions().get::<Extension<E>>() {
Self(Some(ext.0.clone()))
Self::new(Some(ext.inner().clone()))
} else {
Self(None)
Self::new(None)
}
}
}
3 changes: 3 additions & 0 deletions crates/kernel/src/helper_layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,6 @@ pub mod timeout;

/// Balancer layer
pub mod balancer;

/// Inject data into request
pub mod injector;
Loading

0 comments on commit c4c4c1e

Please sign in to comment.