1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
//! Middleware for rendering telescope errors into full pages on the way out.

use crate::error::{TelescopeError, TELESCOPE_ERROR_MIME};
use actix_web::body::{Body, ResponseBody};
use actix_web::dev::{HttpResponseBuilder, Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::error::Error as ActixError;
use actix_web::http::header::CONTENT_TYPE;
use actix_web::HttpRequest;
use actix_web::{HttpResponse, ResponseError};
use futures::future::{ok, Ready};
use futures::task::{Context, Poll};
use futures::TryStreamExt;
use std::future::Future;
use std::pin::Pin;

/// The factory to create handlers for telescope errors.
pub struct TelescopeErrorHandler;

/// Middleware to transform telescope errors in results from a service,
/// into the appropriate web pages.
pub struct TelescopeErrorHandlerMiddleware<S> {
    /// The next service in the chain.
    service: S,
}

impl<S> Transform<S> for TelescopeErrorHandler
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse, Error = ActixError>,
    S::Future: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse;
    type Error = ActixError;
    type Transform = TelescopeErrorHandlerMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(TelescopeErrorHandlerMiddleware { service })
    }
}

impl<S> Service for TelescopeErrorHandlerMiddleware<S>
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse, Error = ActixError>,
    S::Future: 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse;
    type Error = ActixError;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(ctx)
    }

    fn call(&mut self, req: Self::Request) -> Self::Future {
        // Call wrapped service.
        let service_response_future = self.service.call(req);

        // Create the pinned, boxed, async future here.
        Box::pin(async move {
            // Wait for the service response to resolve. Propagate any
            // errors that have not already been converted to an HTTP
            // response. (All telescope errors should have been serialized
            // into an HTTP response at this point).
            let mut service_response: ServiceResponse = service_response_future.await?;

            // See if the success response is a serialized telescope error.
            let has_telescope_mime: bool = service_response
                .headers()
                .get(CONTENT_TYPE)
                .map_or(false, |val| val == TELESCOPE_ERROR_MIME);

            // If not just return it as is.
            if !has_telescope_mime {
                return Ok(service_response);
            }

            // If it is, we will collect the body and deserialize the error
            // from it.
            // First, get the body without destroying or loosing ownership of the service response.
            // This will remove the body from the response, leaving the response with no body.
            let body: ResponseBody<Body> = service_response.response_mut().take_body();
            // Then convert it to a string using Stream future utility functions.
            let body_str: String = body
                // Convert every segment of the body into a string.
                .map_ok(|bytes| String::from_utf8_lossy(bytes.as_ref()).to_string())
                // Collect all of the segments of the stream into one string.
                .try_collect::<String>()
                // Waif for the stream to collect and propagate any errors.
                .await?;

            // Deserialize the telescope error from the response.
            let err: TelescopeError = serde_json::from_str(body_str.as_str())
                // Convert and propagate any serialization errors.
                .map_err(ActixError::from)?;

            // Get a reference to the original request.
            let req: &HttpRequest = service_response.request();
            // Render the error page to a string
            let rendered: String = err.render_error_page(req).await?;
            // Convert the rendered page into a response with the right headers and status code.
            let intermediate_response: HttpResponse = HttpResponseBuilder::new(err.status_code())
                .header(CONTENT_TYPE, "text/html;charset=UTF-8")
                .body(rendered);
            // Construct and return the appropriate service response.
            let final_response: ServiceResponse =
                service_response.into_response(intermediate_response);
            return Ok(final_response);
        })
    }
}