Should I create a Service, Layer, Handler, or Extractor for a slack verification "middleware" #625
-
👋 Hello all, this is my first time using Axum and have been loving it so far to build a slack bot. To quickly summarize my situation, I created this tiny demo repo with a block comment for the relevant section: https://github.com/noxasaxon/slack-axum-demo/blob/main/src/main.rs#L33-L45 TLDR:
Implementing a layer seems pretty straightforward but writing Service behind it (which I think is required too for blocking/allowing the request to proceed to handlers?) is where im tripping up. any guidance or help here would be extremely welcome, and I would be willing to write new documentation or a guide if this turns out to be something that can be done simply that I've overlooked and just isn't written yet. 🙇 |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
Sounds to me like you want a use axum::{
body::{Body, BoxBody, Bytes},
http::{HeaderValue, Request, Response, StatusCode},
response::IntoResponse,
routing::get,
Router,
};
use std::{convert::Infallible, future::Future, net::SocketAddr, pin::Pin};
use tower::{Service, ServiceBuilder};
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(|| async { "hi!" }))
.layer(ServiceBuilder::new().layer_fn(|inner| RequestVerifier { inner }));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
#[derive(Clone)]
struct RequestVerifier<S> {
inner: S,
}
impl<S> Service<Request<Body>> for RequestVerifier<S>
where
S: Service<Request<Body>, Error = Infallible> + Clone + Send + 'static,
S::Response: IntoResponse,
S::Future: Send + 'static,
{
type Response = Response<BoxBody>;
type Error = Infallible;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
let mut inner = self.inner.clone();
Box::pin(async move {
let (parts, body) = req.into_parts();
// get whatever headers you might need
let some_header = if let Some(header) = parts.headers.get("x-some-header") {
header
} else {
return Ok(StatusCode::UNAUTHORIZED.into_response());
};
// buffer the request body into a `Bytes` (https://docs.rs/bytes/latest/bytes/struct.Bytes.html)
// which is kinda like a `&[u8]` but reference counted so cheap to clone
//
// this wont work well for streaming requests but that probably doesn' matter to you
let bytes = if let Ok(bytes) = hyper::body::to_bytes(body).await {
bytes
} else {
return Ok(StatusCode::BAD_REQUEST.into_response());
};
// check if the request is valid
if request_is_valid(some_header, bytes.clone()) {
// its valid, so rebuild the request and pass it on to the inner service
let req = Request::from_parts(parts, Body::from(bytes));
inner.call(req).await.map(|res| res.into_response())
} else {
// its invalid, return some response
Ok(StatusCode::UNAUTHORIZED.into_response())
}
})
}
}
// insert validation code here
fn request_is_valid(some_header: &HeaderValue, bytes: Bytes) -> bool {
true
} |
Beta Was this translation helpful? Give feedback.
Sounds to me like you want a
Service
(a middleware) that grabs some headers, buffers the request body, and checks that they match something. You can do that along the lines of: