Skip to content

Commit

Permalink
feat(http): add client ip
Browse files Browse the repository at this point in the history
  • Loading branch information
StellarisW committed Nov 18, 2024
1 parent 94e5692 commit 5b9e113
Showing 1 changed file with 53 additions and 62 deletions.
115 changes: 53 additions & 62 deletions volo-http/src/server/layer/client_ip.rs
Original file line number Diff line number Diff line change
@@ -1,59 +1,61 @@
use std::{borrow::Cow, net::IpAddr, str::FromStr};
use std::{net::IpAddr, str::FromStr};

use http::{HeaderMap, HeaderName};
use ipnet::IpNet;
use motore::{layer::Layer, Service};
use volo::{context::Context, net::Address};

use crate::{
context::ServerContext, request::Request, response::Response, server::IntoResponse,
utils::macros::impl_deref_and_deref_mut,
};
use crate::{context::ServerContext, request::Request, utils::macros::impl_deref_and_deref_mut};

/// [`Layer`] for extracting client ip
///
/// See [`ClientIP`] for more details.
#[derive(Clone)]
pub struct ClientIPLayerImpl<H> {
pub struct ClientIPLayer {
config: ClientIPConfig,
handler: H,
handler: fn(&ClientIPConfig, &ServerContext, &HeaderMap) -> ClientIP,
}

/// [`Layer`] for extracting client ip
///
/// See [`ClientIP`] for more details.
pub type ClientIPLayer =
ClientIPLayerImpl<fn(&ClientIPConfig, &ServerContext, &HeaderMap) -> ClientIP>;

impl Default for ClientIPLayer {
fn default() -> Self {
Self::new(default_client_ip_handler)
}
}

impl<H> ClientIPLayerImpl<H> {
/// Create a new [`ClientIPLayerImpl`]
pub fn new(handler: H) -> Self {
Self {
config: ClientIPConfig::default(),
handler,
handler: default_client_ip_handler,
}
}
}

impl ClientIPLayer {
/// Create a new [`ClientIPLayer`]
pub fn new() -> Self {
Default::default()
}

/// Create a new [`ClientIPLayerImpl`] with the given [`ClientIPConfig`]
/// Create a new [`ClientIPLayer`] with the given [`ClientIPConfig`]
pub fn with_config(self, config: ClientIPConfig) -> Self {
Self {
config,
handler: self.handler,
}
}

/// Create a new [`ClientIPLayer`] with the given handler
pub fn with_handler(
self,
handler: fn(&ClientIPConfig, &ServerContext, &HeaderMap) -> ClientIP,
) -> Self {
Self {
config: self.config,
handler,
}
}
}

impl<S, H> Layer<S> for ClientIPLayerImpl<H>
impl<S> Layer<S> for ClientIPLayer
where
S: Send + Sync + 'static,
{
type Service = ClientIPService<S, H>;
type Service = ClientIPService<S>;

fn layer(self, inner: S) -> Self::Service {
ClientIPService {
Expand Down Expand Up @@ -111,15 +113,12 @@ impl ClientIPConfig {
) -> Result<Self, http::header::InvalidHeaderName>
where
I: IntoIterator,
I::Item: Into<Cow<'static, str>>,
I::Item: AsRef<str>,
{
let headers = headers.into_iter().map(Into::into).collect::<Vec<_>>();
let headers = headers.into_iter().collect::<Vec<_>>();
let mut remote_ip_headers = Vec::with_capacity(headers.len());
for header_str in headers {
let header_value = match header_str {
Cow::Owned(s) => HeaderName::from_str(&s)?,
Cow::Borrowed(s) => HeaderName::from_str(s)?,
};
let header_value = HeaderName::from_str(header_str.as_ref())?;
remote_ip_headers.push(header_value);
}

Expand Down Expand Up @@ -182,7 +181,7 @@ impl ClientIPConfig {
///
/// let router: Router = Router::new()
/// .route("/", get(handler))
/// .layer(ClientIPLayer::default());
/// .layer(ClientIPLayer::new());
/// ```
///
/// ## With custom config
Expand Down Expand Up @@ -211,37 +210,32 @@ impl ClientIPConfig {
/// }
///
/// let router: Router = Router::new().route("/", get(handler)).layer(
/// ClientIPLayer::new(client_ip_handler).with_config(
/// ClientIPConfig::new()
/// .with_remote_ip_headers(vec!["x-real-ip", "x-forwarded-for"])
/// .unwrap()
/// .with_trusted_cidrs(vec!["0.0.0.0/0".parse().unwrap(), "::/0".parse().unwrap()]),
/// ),
/// ClientIPLayer::new()
/// .with_handler(client_ip_handler)
/// .with_config(
/// ClientIPConfig::new()
/// .with_remote_ip_headers(vec!["x-real-ip", "x-forwarded-for"])
/// .unwrap()
/// .with_trusted_cidrs(vec![
/// "0.0.0.0/0".parse().unwrap(),
/// "::/0".parse().unwrap(),
/// ]),
/// ),
/// );
/// ```
pub struct ClientIP(pub Option<IpAddr>);

impl_deref_and_deref_mut!(ClientIP, Option<IpAddr>, 0);

trait ClientIPHandler<'r> {
fn call(
self,
config: &'r ClientIPConfig,
cx: &'r ServerContext,
headers: &'r HeaderMap,
) -> ClientIP;
trait ClientIPHandler {
fn call(self, config: &ClientIPConfig, cx: &ServerContext, headers: &HeaderMap) -> ClientIP;
}

impl<'r, F> ClientIPHandler<'r> for F
impl<F> ClientIPHandler for F
where
F: FnOnce(&'r ClientIPConfig, &'r ServerContext, &'r HeaderMap) -> ClientIP,
F: FnOnce(&ClientIPConfig, &ServerContext, &HeaderMap) -> ClientIP,
{
fn call(
self,
config: &'r ClientIPConfig,
cx: &'r ServerContext,
headers: &'r HeaderMap,
) -> ClientIP {
fn call(self, config: &ClientIPConfig, cx: &ServerContext, headers: &HeaderMap) -> ClientIP {
self(config, cx, headers)
}
}
Expand All @@ -254,7 +248,7 @@ fn default_client_ip_handler(
let remote_ip_headers = &config.remote_ip_headers;
let trusted_cidrs = &config.trusted_cidrs;

let remote_ip = match cx.rpc_info().caller().address() {
let remote_ip = match &cx.rpc_info().caller().address() {
Some(Address::Ip(socket_addr)) => Some(socket_addr.ip()),
Some(Address::Unix(_)) => None,
None => return ClientIP(None),
Expand Down Expand Up @@ -295,21 +289,18 @@ fn default_client_ip_handler(
}

#[derive(Clone)]
pub struct ClientIPService<S, H> {
pub struct ClientIPService<S> {
service: S,
config: ClientIPConfig,
handler: H,
handler: fn(&ClientIPConfig, &ServerContext, &HeaderMap) -> ClientIP,
}

impl<S, B, H> Service<ServerContext, Request<B>> for ClientIPService<S, H>
impl<S, B> Service<ServerContext, Request<B>> for ClientIPService<S>
where
S: Service<ServerContext, Request<B>> + Send + Sync + 'static,
S::Response: IntoResponse,
S::Error: IntoResponse,
B: Send,
H: for<'r> ClientIPHandler<'r> + Clone + Sync,
{
type Response = Response;
type Response = S::Response;
type Error = S::Error;

async fn call(
Expand All @@ -320,7 +311,7 @@ where
let client_ip = self.handler.clone().call(&self.config, cx, req.headers());

Check warning on line 311 in volo-http/src/server/layer/client_ip.rs

View workflow job for this annotation

GitHub Actions / clippy

using `clone` on type `fn(&ClientIPConfig, &ServerContext, &HeaderMap) -> ClientIP` which implements the `Copy` trait

warning: using `clone` on type `fn(&ClientIPConfig, &ServerContext, &HeaderMap) -> ClientIP` which implements the `Copy` trait --> volo-http/src/server/layer/client_ip.rs:311:25 | 311 | let client_ip = self.handler.clone().call(&self.config, cx, req.headers()); | ^^^^^^^^^^^^^^^^^^^^ help: try removing the `clone` call: `self.handler` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#clone_on_copy = note: `#[warn(clippy::clone_on_copy)]` on by default
cx.rpc_info_mut().caller_mut().tags.insert(client_ip);

Ok(self.service.call(cx, req).await.into_response())
self.service.call(cx, req).await
}
}

Expand Down Expand Up @@ -349,7 +340,7 @@ mod client_ip_tests {
}

let route: Route<&str> = Route::new(get(handler));
let service = ClientIPLayer::default()
let service = ClientIPLayer::new()
.with_config(
ClientIPConfig::default().with_trusted_cidrs(vec!["10.0.0.0/8".parse().unwrap()]),
)
Expand Down

0 comments on commit 5b9e113

Please sign in to comment.