diff --git a/Cargo.lock b/Cargo.lock index cd419c10..1d6ae8c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3179,6 +3179,7 @@ dependencies = [ "futures", "http", "hyper", + "once_cell", "prometheus", "svc-agent", "svc-authn", diff --git a/Cargo.toml b/Cargo.toml index 2b5b3e06..ded65060 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ svc-agent = { version = "0.20", features = ["sqlx", "queue-counter"] } svc-authn = { version = "0.8", features = ["jose", "sqlx"] } svc-authz = { version = "0.12" } svc-error = { version = "0.5", features = ["sqlx", "svc-agent", "svc-authn", "svc-authz", "sentry-extension"] } -svc-utils = { version = "0.7", features = ["authn-extractor", "cors-middleware", "log-middleware"] } +svc-utils = { version = "0.7", features = ["authn-extractor", "cors-middleware", "log-middleware", "metrics-middleware"] } svc-nats-client = { version = "0.2" } svc-conference-events = { version = "0.2" } tokio = { version = "1.28", features = ["full"] } diff --git a/src/app/endpoint/agent.rs b/src/app/endpoint/agent.rs index d40a2849..8a2093ce 100644 --- a/src/app/endpoint/agent.rs +++ b/src/app/endpoint/agent.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyhow::Context as AnyhowContext; use async_trait::async_trait; use axum::extract::{ - Json, {Path, Query, State}, + self, Json, {Path, Query}, }; use serde_derive::{Deserialize, Serialize}; use serde_json::json; @@ -39,7 +39,7 @@ pub struct ListRequest { } pub async fn list( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Query(payload): Query, @@ -150,7 +150,7 @@ pub struct TenantBanNotification { } pub async fn update( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Json(payload): Json, diff --git a/src/app/endpoint/ban.rs b/src/app/endpoint/ban.rs index 6085aa4e..673f4bbd 100644 --- a/src/app/endpoint/ban.rs +++ b/src/app/endpoint/ban.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use anyhow::Context as AnyhowContext; use async_trait::async_trait; -use axum::extract::{Path, State}; +use axum::extract::{self, Path}; use serde_derive::Deserialize; use svc_agent::mqtt::ResponseStatus; use svc_authn::Authenticable; @@ -21,7 +21,7 @@ pub struct ListRequest { } pub async fn list( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, ) -> RequestResult { diff --git a/src/app/endpoint/change/create.rs b/src/app/endpoint/change/create.rs index b47809b5..39fa2a41 100644 --- a/src/app/endpoint/change/create.rs +++ b/src/app/endpoint/change/create.rs @@ -1,7 +1,7 @@ use anyhow::Context as AnyhowContext; use async_trait::async_trait; use axum::{ - extract::{Path, State}, + extract::{self, Path}, Json, }; use svc_agent::mqtt::ResponseStatus; @@ -18,7 +18,7 @@ use crate::db; pub struct CreateHandler; pub async fn create( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(edition_id): Path, Json(changeset): Json, diff --git a/src/app/endpoint/change/delete.rs b/src/app/endpoint/change/delete.rs index f2a30fd8..297fc425 100644 --- a/src/app/endpoint/change/delete.rs +++ b/src/app/endpoint/change/delete.rs @@ -1,6 +1,6 @@ use anyhow::Context as AnyhowContext; use async_trait::async_trait; -use axum::extract::{Path, State}; +use axum::extract::{self, Path}; use serde_derive::Deserialize; use svc_agent::mqtt::ResponseStatus; use svc_authn::Authenticable; @@ -22,7 +22,7 @@ pub struct DeleteRequest { } pub async fn delete( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(id): Path, ) -> RequestResult { diff --git a/src/app/endpoint/change/list.rs b/src/app/endpoint/change/list.rs index 49e24a0d..c63eb91f 100644 --- a/src/app/endpoint/change/list.rs +++ b/src/app/endpoint/change/list.rs @@ -1,6 +1,6 @@ use anyhow::Context as AnyhowContext; use async_trait::async_trait; -use axum::extract::{Path, Query, State}; +use axum::extract::{self, Path, Query}; use chrono::{DateTime, Utc}; use serde_derive::Deserialize; use svc_agent::mqtt::ResponseStatus; @@ -29,7 +29,7 @@ pub struct ListRequest { } pub async fn list( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(id): Path, Query(payload): Query, diff --git a/src/app/endpoint/edition/commit.rs b/src/app/endpoint/edition/commit.rs index 154ec1f8..1006f8aa 100644 --- a/src/app/endpoint/edition/commit.rs +++ b/src/app/endpoint/edition/commit.rs @@ -1,6 +1,6 @@ use anyhow::Context as AnyhowContext; use async_trait::async_trait; -use axum::extract::{Json, Path, State}; +use axum::extract::{self, Json, Path}; use chrono::Utc; use serde_derive::{Deserialize, Serialize}; use serde_json::{json, Value as JsonValue}; @@ -35,7 +35,7 @@ pub struct CommitRequest { } pub async fn commit( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(id): Path, Json(payload): Json, diff --git a/src/app/endpoint/edition/create.rs b/src/app/endpoint/edition/create.rs index f46ccdae..97810e70 100644 --- a/src/app/endpoint/edition/create.rs +++ b/src/app/endpoint/edition/create.rs @@ -1,6 +1,6 @@ use anyhow::Context as AnyhowContext; use async_trait::async_trait; -use axum::extract::{Path, State}; +use axum::extract::{self, Path}; use serde_derive::Deserialize; use svc_agent::{mqtt::ResponseStatus, Addressable}; use svc_authn::Authenticable; @@ -20,7 +20,7 @@ pub struct CreateRequest { } pub async fn create( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, ) -> RequestResult { diff --git a/src/app/endpoint/edition/delete.rs b/src/app/endpoint/edition/delete.rs index b03c86e4..491ac204 100644 --- a/src/app/endpoint/edition/delete.rs +++ b/src/app/endpoint/edition/delete.rs @@ -1,6 +1,6 @@ use anyhow::Context as AnyhowContext; use async_trait::async_trait; -use axum::extract::{Path, State}; +use axum::extract::{self, Path}; use serde_derive::Deserialize; use svc_agent::mqtt::ResponseStatus; use svc_authn::Authenticable; @@ -20,7 +20,7 @@ pub struct DeleteRequest { } pub async fn delete( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(id): Path, ) -> RequestResult { diff --git a/src/app/endpoint/edition/list.rs b/src/app/endpoint/edition/list.rs index 1a100621..96e1d965 100644 --- a/src/app/endpoint/edition/list.rs +++ b/src/app/endpoint/edition/list.rs @@ -1,6 +1,6 @@ use anyhow::Context as AnyhowContext; use async_trait::async_trait; -use axum::extract::{Path, Query, State}; +use axum::extract::{self, Path, Query}; use chrono::{DateTime, Utc}; use serde_derive::Deserialize; use svc_agent::mqtt::ResponseStatus; @@ -29,7 +29,7 @@ pub struct ListRequest { } pub async fn list( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Query(payload): Query, diff --git a/src/app/endpoint/event.rs b/src/app/endpoint/event.rs index 18dc79b5..d8fc3bbf 100644 --- a/src/app/endpoint/event.rs +++ b/src/app/endpoint/event.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use anyhow::Context as AnyhowContext; use async_trait::async_trait; use axum::{ - extract::{Path, Query, State}, + extract::{self, Path, Query}, Json, }; use chrono::Utc; @@ -55,7 +55,7 @@ impl CreateRequest { } pub async fn create( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Json(payload): Json, @@ -332,7 +332,7 @@ pub struct ListRequest { } pub async fn list( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Query(payload): Query, diff --git a/src/app/endpoint/room.rs b/src/app/endpoint/room.rs index a18c823f..f11fd8d9 100644 --- a/src/app/endpoint/room.rs +++ b/src/app/endpoint/room.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use anyhow::Context as AnyhowContext; use async_trait::async_trait; use axum::{ - extract::{Path, State}, + extract::{self, Path}, Json, }; use chrono::{DateTime, Utc}; @@ -47,7 +47,7 @@ pub struct CreateRequest { } pub async fn create( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Json(request): Json, ) -> RequestResult { @@ -167,7 +167,7 @@ pub struct ReadRequest { } pub async fn read( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, ) -> RequestResult { @@ -242,7 +242,7 @@ pub struct UpdateRequest { } pub async fn update( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Json(payload): Json, @@ -397,7 +397,7 @@ pub struct RoomEnterEvent { pub struct EnterHandler; pub async fn enter( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Json(payload): Json, @@ -555,7 +555,7 @@ pub struct LockedTypesRequest { } pub async fn locked_types( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Json(payload): Json, @@ -669,7 +669,7 @@ pub struct WhiteboardAccessRequest { } pub async fn whiteboard_access( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Json(payload): Json, @@ -792,7 +792,7 @@ pub struct AdjustRequest { } pub async fn adjust( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, Json(payload): Json, diff --git a/src/app/endpoint/room/dump_events.rs b/src/app/endpoint/room/dump_events.rs index f0f8840b..3c20ba47 100644 --- a/src/app/endpoint/room/dump_events.rs +++ b/src/app/endpoint/room/dump_events.rs @@ -44,7 +44,7 @@ impl EventsDumpResult { } pub async fn dump_events( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, ) -> RequestResult { diff --git a/src/app/endpoint/state.rs b/src/app/endpoint/state.rs index f6984581..90529f5d 100644 --- a/src/app/endpoint/state.rs +++ b/src/app/endpoint/state.rs @@ -2,7 +2,7 @@ use std::ops::Bound; use anyhow::Context as AnyhowContext; use async_trait::async_trait; -use axum::extract::{Path, RawQuery, State}; +use axum::extract::{self, Path, RawQuery}; use serde_derive::Deserialize; use serde_json::{map::Map as JsonMap, Value as JsonValue}; use svc_agent::mqtt::ResponseStatus; @@ -36,7 +36,7 @@ pub struct ReadRequest { } pub async fn read( - State(ctx): State>, + ctx: extract::Extension>, AgentIdExtractor(agent_id): AgentIdExtractor, Path(room_id): Path, RawQuery(query): RawQuery, diff --git a/src/app/http.rs b/src/app/http.rs index 847ba1ab..3a895329 100644 --- a/src/app/http.rs +++ b/src/app/http.rs @@ -17,6 +17,7 @@ use http::{ }; use hyper::Body; use svc_agent::mqtt::Agent; +use svc_utils::middleware::MeteredRoute; use tower::{layer::layer_fn, Service, ServiceBuilder}; use tower_http::cors::{Any, CorsLayer}; use tracing::error; @@ -26,11 +27,7 @@ use crate::app::{ service_utils, }; -use super::{ - context::{AppContext, GlobalContext}, - endpoint, - error::{Error as AppError, ErrorKind}, -}; +use super::{context::AppContext, endpoint, error::Error as AppError}; pub fn build_router( context: Arc, @@ -54,79 +51,77 @@ pub fn build_router( let middleware = ServiceBuilder::new() .layer(Extension(agent)) .layer(Extension(Arc::new(authn))) - .layer(Extension(context.clone())) + .layer(Extension(context)) .layer(layer_fn(|inner| NotificationsMiddleware { inner })) - .layer(layer_fn(|inner| MetricsMiddleware { inner })) .layer(cors); let router = Router::new() - .route("/rooms", post(endpoint::room::create)) - .route( + .metered_route("/rooms", post(endpoint::room::create)) + .metered_route( "/rooms/:id", get(endpoint::room::read) .patch(endpoint::room::update) .options(endpoint::read_options), ) - .route("/rooms/:id/adjust", post(endpoint::room::adjust)) - .route( + .metered_route("/rooms/:id/adjust", post(endpoint::room::adjust)) + .metered_route( "/rooms/:id/enter", post(endpoint::room::enter).options(endpoint::read_options), ) - .route( + .metered_route( "/rooms/:id/locked_types", post(endpoint::room::locked_types).options(endpoint::read_options), ) - .route( + .metered_route( "/rooms/:id/whiteboard_access", post(endpoint::room::whiteboard_access).options(endpoint::read_options), ) - .route("/rooms/:id/dump_events", post(endpoint::room::dump_events)) - .route( + .metered_route("/rooms/:id/dump_events", post(endpoint::room::dump_events)) + .metered_route( "/rooms/:id/events", get(endpoint::event::list) .post(endpoint::event::create) .options(endpoint::read_options), ) - .route( + .metered_route( "/rooms/:id/state", get(endpoint::state::read).options(endpoint::read_options), ) - .route( + .metered_route( "/rooms/:id/agents", get(endpoint::agent::list) .patch(endpoint::agent::update) .options(endpoint::read_options), ) - .route( + .metered_route( "/rooms/:id/editions", get(endpoint::edition::list) .post(endpoint::edition::create) .options(endpoint::read_options), ) - .route( + .metered_route( "/rooms/:id/bans", get(endpoint::ban::list).options(endpoint::read_options), ) - .route( + .metered_route( "/editions/:id", delete(endpoint::edition::delete).options(endpoint::read_options), ) - .route( + .metered_route( "/editions/:id/commit", post(endpoint::edition::commit).options(endpoint::read_options), ) - .route( + .metered_route( "/editions/:id/changes", get(endpoint::change::list) .post(endpoint::change::create) .options(endpoint::read_options), ) - .route( + .metered_route( "/changes/:id", delete(endpoint::change::delete).options(endpoint::read_options), ) - .layer(middleware) - .with_state(context); + .layer(middleware); let routes = Router::new().nest("/api/v1", router); @@ -209,44 +204,3 @@ where }) } } - -#[derive(Clone)] -struct MetricsMiddleware { - inner: S, -} - -impl Service> for MetricsMiddleware -where - S: Service, Response = Response> + Clone + Send + 'static, - S::Future: Send + 'static, - ReqBody: Send + 'static, - ResBody: Send + 'static, -{ - type Response = S::Response; - type Error = S::Error; - type Future = BoxFuture<'static, Result>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.inner.poll_ready(cx) - } - - fn call(&mut self, req: Request) -> Self::Future { - // best practice is to clone the inner service like this - // see https://github.com/tower-rs/tower/issues/547 for details - let clone = self.inner.clone(); - let mut inner = std::mem::replace(&mut self.inner, clone); - - Box::pin(async move { - let ctx = req.extensions().get::>().cloned().unwrap(); - let res: Response = inner.call(req).await?; - - if res.status().is_success() { - ctx.metrics().observe_app_ok(); - } else if let Some(error_kind) = res.extensions().get::() { - ctx.metrics().observe_app_error(error_kind); - } - - Ok(res) - }) - } -}