use actix_web::{http::StatusCode, web, App, HttpRequest, HttpResponse, HttpServer, ResponseError}; use http_signature_normalization_actix::prelude::*; use sha2::{Digest, Sha256}; use std::future::{ready, Ready}; use tracing::info; use tracing_actix_web::TracingLogger; use tracing_error::ErrorLayer; use tracing_subscriber::{layer::SubscriberExt, EnvFilter}; #[derive(Clone, Debug)] struct MyVerify; impl SignatureVerify for MyVerify { type Error = MyError; type Future = Ready>; fn signature_verify( &mut self, algorithm: Option, key_id: String, signature: String, signing_string: String, ) -> Self::Future { match algorithm { Some(Algorithm::Hs2019) => (), _ => return ready(Err(MyError::Algorithm)), }; if key_id != "my-key-id" { return ready(Err(MyError::Key)); } let decoded = match base64::decode(&signature) { Ok(decoded) => decoded, Err(_) => return ready(Err(MyError::Decode)), }; info!("Signing String\n{}", signing_string); ready(Ok(decoded == signing_string.as_bytes())) } } async fn index( (_, sig_verified): (DigestVerified, SignatureVerified), req: HttpRequest, _body: web::Bytes, ) -> &'static str { info!("Verified request for {}", sig_verified.key_id()); info!("{:?}", req); "Eyyyyup" } #[actix_rt::main] async fn main() -> Result<(), Box> { let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let subscriber = tracing_subscriber::Registry::default() .with(env_filter) .with(ErrorLayer::default()) .with(tracing_subscriber::fmt::layer()); tracing::subscriber::set_global_default(subscriber)?; let config = Config::default().require_header("accept").require_digest(); HttpServer::new(move || { App::new() .wrap(VerifyDigest::new(Sha256::new()).optional()) .wrap(VerifySignature::new(MyVerify, config.clone()).optional()) .wrap(TracingLogger::::new()) .route("/", web::post().to(index)) }) .bind("127.0.0.1:8010")? .run() .await?; Ok(()) } #[derive(Debug, thiserror::Error)] enum MyError { #[error("Failed to verify, {0}")] Verify(#[from] PrepareVerifyError), #[error("Unsupported algorithm")] Algorithm, #[error("Couldn't decode signature")] Decode, #[error("Invalid key")] Key, } impl ResponseError for MyError { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } fn error_response(&self) -> HttpResponse { HttpResponse::BadRequest().finish() } } // tracing-actix-web + tracing-error WORKAROUND use actix_web::{ dev::{ServiceRequest, ServiceResponse}, Error, }; use tracing::Span; use tracing_actix_web::root_span; pub struct RootSpanBuilder; impl tracing_actix_web::RootSpanBuilder for RootSpanBuilder { fn on_request_start(request: &ServiceRequest) -> Span { root_span!(request) } fn on_request_end(span: Span, outcome: &Result, Error>) { match &outcome { Ok(response) => { if let Some(error) = response.response().error() { handle_error(span, error) } else { span.record("http.status_code", &response.response().status().as_u16()); span.record("otel.status_code", &"OK"); } } Err(error) => handle_error(span, error), } } } fn handle_error(span: Span, error: &Error) { let response_error = error.as_response_error(); let display = format!("{}", response_error); let debug = format!("{:?}", response_error); span.record("exception.message", &tracing::field::display(display)); span.record("exception.details", &tracing::field::display(debug)); let status_code = response_error.status_code(); span.record("http.status_code", &status_code.as_u16()); if status_code.is_client_error() { span.record("otel.status_code", &"OK"); } else { span.record("otel.status_code", &"ERROR"); } }