From dde31288bdfb00cf325ae1f669d5fc098cee98ea Mon Sep 17 00:00:00 2001 From: equal-l2 Date: Sat, 18 Jul 2020 05:26:31 +0900 Subject: Update to actix 2 and futures 0.3 --- src/auth.rs | 105 ++++++++++++++++++++++++++++++---------- src/errors.rs | 2 +- src/file_upload.rs | 139 +++++++++++++++++++++++++--------------------------- src/listing.rs | 140 +++++++++++++++++++++++++++++------------------------ src/main.rs | 103 +++++++++++++++++++-------------------- src/pipe.rs | 22 ++++----- 6 files changed, 283 insertions(+), 228 deletions(-) (limited to 'src') diff --git a/src/auth.rs b/src/auth.rs index 6081a9d..31826d8 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,6 +1,7 @@ +use actix_web::dev::{Body, Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::{header, StatusCode}; -use actix_web::middleware::{Middleware, Response}; use actix_web::{HttpRequest, HttpResponse, Result}; +use futures::future; use sha2::{Digest, Sha256, Sha512}; use crate::errors::{self, ContextualError}; @@ -87,16 +88,61 @@ pub fn get_hash(text: &str) -> Vec { hasher.finalize().to_vec() } -impl Middleware for Auth { - fn response( - &self, - req: &HttpRequest, - resp: HttpResponse, - ) -> Result { - let required_auth = &req.state().auth; +pub struct AuthMiddleware { + service: S, +} + +impl Transform for Auth +where + S: Service< + Request = ServiceRequest, + Response = ServiceResponse, + Error = actix_web::Error, + >, + S::Future: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type Future = future::Ready>; + type Transform = AuthMiddleware; + type InitError = (); + fn new_transform(&self, service: S) -> Self::Future { + future::ok(AuthMiddleware { service }) + } +} + +impl Service for AuthMiddleware +where + S: Service< + Request = ServiceRequest, + Response = ServiceResponse, + Error = actix_web::Error, + >, + S::Future: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type Future = + std::pin::Pin>>>; + + fn poll_ready( + &mut self, + cx: &mut std::task::Context, + ) -> std::task::Poll> { + self.service.poll_ready(cx) + } + + fn call(&mut self, req: Self::Request) -> Self::Future { + let (req, pl) = req.into_parts(); + let required_auth = &req.app_data::().unwrap().auth; if required_auth.is_empty() { - return Ok(Response::Done(resp)); + let resp = self + .service + .call(ServiceRequest::from_parts(req, pl).unwrap_or_else(|_| unreachable!())); + return Box::pin(async { resp.await }); } if let Some(auth_headers) = req.headers().get(header::AUTHORIZATION) { @@ -104,30 +150,38 @@ impl Middleware for Auth { Ok(auth_req) => auth_req, Err(err) => { let auth_err = ContextualError::HTTPAuthenticationError(Box::new(err)); - return Ok(Response::Done(HttpResponse::BadRequest().body( - build_unauthorized_response(&req, auth_err, true, StatusCode::BAD_REQUEST), + let body = + build_unauthorized_response(&req, auth_err, true, StatusCode::BAD_REQUEST); + return Box::pin(future::ok(ServiceResponse::new( + req, + HttpResponse::BadRequest().body(body), ))); } }; if match_auth(auth_req, required_auth) { - return Ok(Response::Done(resp)); + let resp = self + .service + .call(ServiceRequest::from_parts(req, pl).unwrap_or_else(|_| unreachable!())); + return Box::pin(async { resp.await }); } } - Ok(Response::Done( + let body = build_unauthorized_response( + &req, + ContextualError::InvalidHTTPCredentials, + true, + StatusCode::UNAUTHORIZED, + ); + Box::pin(future::ok(ServiceResponse::new( + req, HttpResponse::Unauthorized() .header( header::WWW_AUTHENTICATE, header::HeaderValue::from_static("Basic realm=\"miniserve\""), ) - .body(build_unauthorized_response( - &req, - ContextualError::InvalidHTTPCredentials, - true, - StatusCode::UNAUTHORIZED, - )), - )) + .body(body), + ))) } } @@ -135,18 +189,19 @@ impl Middleware for Auth { /// The reason why log_error_chain is optional is to handle cases where the auth pop-up appears and when the user clicks Cancel. /// In those case, we do not log the error to the terminal since it does not really matter. fn build_unauthorized_response( - req: &HttpRequest, + req: &HttpRequest, error: ContextualError, log_error_chain: bool, error_code: StatusCode, ) -> String { + let state = req.app_data::().unwrap(); let error = ContextualError::HTTPAuthenticationError(Box::new(error)); if log_error_chain { errors::log_error_chain(error.to_string()); } - let return_path = match &req.state().random_route { - Some(random_route) => format!("/{}", random_route), + let return_path = match state.random_route { + Some(ref random_route) => format!("/{}", random_route), None => "/".to_string(), }; @@ -156,8 +211,8 @@ fn build_unauthorized_response( &return_path, None, None, - req.state().default_color_scheme, - req.state().default_color_scheme, + state.default_color_scheme, + state.default_color_scheme, false, false, ) diff --git a/src/errors.rs b/src/errors.rs index 2878e37..feb91e2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -12,7 +12,7 @@ pub enum ContextualError { /// MultipartError, which might occur during file upload, when processing the multipart request fails #[fail(display = "Failed to process multipart request\ncaused by: {}", _0)] - MultipartError(actix_web::error::MultipartError), + MultipartError(actix_multipart::MultipartError), /// This error might occur when decoding the HTTP authentication header. #[fail( diff --git a/src/file_upload.rs b/src/file_upload.rs index f9bf002..136bd45 100644 --- a/src/file_upload.rs +++ b/src/file_upload.rs @@ -1,13 +1,12 @@ use actix_web::{ - dev, http::{header, StatusCode}, - multipart, FutureResponse, HttpMessage, HttpRequest, HttpResponse, + HttpRequest, HttpResponse, }; -use futures::{future, future::FutureResult, Future, Stream}; +use futures::{future, Future, FutureExt, Stream, TryStreamExt}; use std::{ - fs, io::Write, path::{Component, PathBuf}, + pin::Pin, }; use crate::errors::{self, ContextualError}; @@ -17,12 +16,12 @@ use crate::themes::ColorScheme; /// Create future to save file. fn save_file( - field: multipart::Field, + field: actix_multipart::Field, file_path: PathBuf, overwrite_files: bool, -) -> Box> { +) -> Pin>>> { if !overwrite_files && file_path.exists() { - return Box::new(future::err(ContextualError::CustomError( + return Box::pin(future::err(ContextualError::CustomError( "File already exists, and the overwrite_files option has not been set".to_string(), ))); } @@ -30,85 +29,75 @@ fn save_file( let mut file = match std::fs::File::create(&file_path) { Ok(file) => file, Err(e) => { - return Box::new(future::err(ContextualError::IOError( + return Box::pin(future::err(ContextualError::IOError( format!("Failed to create {}", file_path.display()), e, ))); } }; - Box::new( + Box::pin( field .map_err(ContextualError::MultipartError) - .fold(0i64, move |acc, bytes| { + .try_fold(0i64, move |acc, bytes| { let rt = file .write_all(bytes.as_ref()) .map(|_| acc + bytes.len() as i64) .map_err(|e| { ContextualError::IOError("Failed to write to file".to_string(), e) }); - future::result(rt) + future::ready(rt) }), ) } /// Create new future to handle file as multipart data. fn handle_multipart( - item: multipart::MultipartItem, + field: actix_multipart::Field, mut file_path: PathBuf, overwrite_files: bool, -) -> Box> { - match item { - multipart::MultipartItem::Field(field) => { - let filename = field - .headers() - .get(header::CONTENT_DISPOSITION) +) -> Pin>>> { + let filename = field + .headers() + .get(header::CONTENT_DISPOSITION) + .ok_or(ContextualError::ParseError) + .and_then(|cd| { + header::ContentDisposition::from_raw(cd).map_err(|_| ContextualError::ParseError) + }) + .and_then(|content_disposition| { + content_disposition + .get_filename() .ok_or(ContextualError::ParseError) - .and_then(|cd| { - header::ContentDisposition::from_raw(cd) - .map_err(|_| ContextualError::ParseError) - }) - .and_then(|content_disposition| { - content_disposition - .get_filename() - .ok_or(ContextualError::ParseError) - .map(String::from) - }); - let err = |e: ContextualError| Box::new(future::err(e).into_stream()); - match filename { - Ok(f) => { - match fs::metadata(&file_path) { - Ok(metadata) => { - if !metadata.is_dir() { - return err(ContextualError::InvalidPathError(format!( - "cannot upload file to {}, since it's not a directory", - &file_path.display() - ))); - } else if metadata.permissions().readonly() { - return err(ContextualError::InsufficientPermissionsError( - file_path.display().to_string(), - )); - } - } - Err(_) => { - return err(ContextualError::InsufficientPermissionsError( - file_path.display().to_string(), - )); - } + .map(String::from) + }); + let err = |e: ContextualError| Box::pin(future::err(e).into_stream()); + match filename { + Ok(f) => { + match std::fs::metadata(&file_path) { + Ok(metadata) => { + if !metadata.is_dir() { + return err(ContextualError::InvalidPathError(format!( + "cannot upload file to {}, since it's not a directory", + &file_path.display() + ))); + } else if metadata.permissions().readonly() { + return err(ContextualError::InsufficientPermissionsError( + file_path.display().to_string(), + )); } - file_path = file_path.join(f); - Box::new(save_file(field, file_path, overwrite_files).into_stream()) } - Err(e) => err(e( - "HTTP header".to_string(), - "Failed to retrieve the name of the file to upload".to_string(), - )), + Err(_) => { + return err(ContextualError::InsufficientPermissionsError( + file_path.display().to_string(), + )); + } } + file_path = file_path.join(f); + Box::pin(save_file(field, file_path, overwrite_files).into_stream()) } - multipart::MultipartItem::Nested(mp) => Box::new( - mp.map_err(ContextualError::MultipartError) - .map(move |item| handle_multipart(item, file_path.clone(), overwrite_files)) - .flatten(), - ), + Err(e) => err(e( + "HTTP header".to_string(), + "Failed to retrieve the name of the file to upload".to_string(), + )), } } @@ -118,17 +107,19 @@ fn handle_multipart( /// invalid. /// This method returns future. pub fn upload_file( - req: &HttpRequest, + req: HttpRequest, + payload: actix_web::web::Payload, default_color_scheme: ColorScheme, uses_random_route: bool, -) -> FutureResponse { +) -> Pin>>> { + let conf = req.app_data::().unwrap(); let return_path = if let Some(header) = req.headers().get(header::REFERER) { header.to_str().unwrap_or("/").to_owned() } else { "/".to_string() }; - let query_params = listing::extract_query_parameters(req); + let query_params = listing::extract_query_parameters(&req); let color_scheme = query_params.theme.unwrap_or(default_color_scheme); let upload_path = match query_params.path.clone() { Some(path) => match path.strip_prefix(Component::RootDir) { @@ -139,7 +130,7 @@ pub fn upload_file( let err = ContextualError::InvalidHTTPRequestError( "Missing query parameter 'path'".to_string(), ); - return Box::new(create_error_response( + return Box::pin(create_error_response( &err.to_string(), StatusCode::BAD_REQUEST, &return_path, @@ -152,14 +143,14 @@ pub fn upload_file( } }; - let app_root_dir = match req.state().path.canonicalize() { + let app_root_dir = match conf.path.canonicalize() { Ok(dir) => dir, Err(e) => { let err = ContextualError::IOError( "Failed to resolve path served by miniserve".to_string(), e, ); - return Box::new(create_error_response( + return Box::pin(create_error_response( &err.to_string(), StatusCode::INTERNAL_SERVER_ERROR, &return_path, @@ -179,7 +170,7 @@ pub fn upload_file( let err = ContextualError::InvalidHTTPRequestError( "Invalid value for 'path' parameter".to_string(), ); - return Box::new(create_error_response( + return Box::pin(create_error_response( &err.to_string(), StatusCode::BAD_REQUEST, &return_path, @@ -191,13 +182,13 @@ pub fn upload_file( )); } }; - let overwrite_files = req.state().overwrite_files; - Box::new( - req.multipart() + let overwrite_files = conf.overwrite_files; + Box::pin( + actix_multipart::Multipart::new(req.headers(), payload) .map_err(ContextualError::MultipartError) - .map(move |item| handle_multipart(item, target_dir.clone(), overwrite_files)) - .flatten() - .collect() + .map_ok(move |item| handle_multipart(item, target_dir.clone(), overwrite_files)) + .try_flatten() + .try_collect::>() .then(move |e| match e { Ok(_) => future::ok( HttpResponse::SeeOther() @@ -229,7 +220,7 @@ fn create_error_response( color_scheme: ColorScheme, default_color_scheme: ColorScheme, uses_random_route: bool, -) -> FutureResult { +) -> future::Ready> { errors::log_error_chain(description.to_string()); future::ok( HttpResponse::BadRequest() diff --git a/src/listing.rs b/src/listing.rs index 063fbf3..a4eda88 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -1,7 +1,9 @@ +use actix_web::body::Body; +use actix_web::dev::ServiceResponse; use actix_web::http::StatusCode; -use actix_web::{fs, Body, FromRequest, HttpRequest, HttpResponse, Query, Result}; +use actix_web::web::Query; +use actix_web::{HttpRequest, HttpResponse, Result}; use bytesize::ByteSize; -use futures::Stream; use htmlescape::encode_minimal as escape_html_entity; use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; use qrcodegen::{QrCode, QrCodeEcc}; @@ -122,17 +124,17 @@ impl Entry { } } -pub fn file_handler(req: &HttpRequest) -> Result { - let path = &req.state().path; - Ok(fs::NamedFile::open(path)?) +pub async fn file_handler(req: HttpRequest) -> Result { + let path = &req.app_data::().unwrap().path; + actix_files::NamedFile::open(path).map_err(Into::into) } /// List a directory and renders a HTML file accordingly /// Adapted from https://docs.rs/actix-web/0.7.13/src/actix_web/fs.rs.html#564 #[allow(clippy::too_many_arguments)] -pub fn directory_listing( - dir: &fs::Directory, - req: &HttpRequest, +pub fn directory_listing( + dir: &actix_files::Directory, + req: &HttpRequest, skip_symlinks: bool, file_upload: bool, random_route: Option, @@ -141,7 +143,8 @@ pub fn directory_listing( upload_route: String, tar_enabled: bool, zip_enabled: bool, -) -> Result { +) -> Result { + use actix_web::dev::BodyEncoding; let serve_path = req.path(); // In case the current path is a directory, we want to make sure that the current URL ends @@ -151,9 +154,12 @@ pub fn directory_listing( "" => String::new(), _ => format!("?{}", req.query_string()), }; - return Ok(HttpResponse::MovedPermanenty() - .header("Location", format!("{}/{}", serve_path, query)) - .body("301")); + return Ok(ServiceResponse::new( + req.clone(), + HttpResponse::MovedPermanently() + .header("Location", format!("{}/{}", serve_path, query)) + .body("301"), + )); } let base = Path::new(serve_path); @@ -177,7 +183,7 @@ pub fn directory_listing( HttpResponse::UriTooLong().body(Body::Empty) } }; - return Ok(res); + return Ok(ServiceResponse::new(req.clone(), res)); } let mut entries: Vec = Vec::new(); @@ -269,22 +275,25 @@ pub fn directory_listing( if let Some(compression_method) = query_params.download { if !compression_method.is_enabled(tar_enabled, zip_enabled) { - return Ok(HttpResponse::Forbidden() - .content_type("text/html; charset=utf-8") - .body( - renderer::render_error( - "Archive creation is disabled.", - StatusCode::FORBIDDEN, - "/", - None, - None, - color_scheme, - default_color_scheme, - false, - false, - ) - .into_string(), - )); + return Ok(ServiceResponse::new( + req.clone(), + HttpResponse::Forbidden() + .content_type("text/html; charset=utf-8") + .body( + renderer::render_error( + "Archive creation is disabled.", + StatusCode::FORBIDDEN, + "/", + None, + None, + color_scheme, + default_color_scheme, + false, + false, + ) + .into_string(), + ), + )); } log::info!( "Creating an archive ({extension}) of {path}...", @@ -301,7 +310,7 @@ pub fn directory_listing( // We will create the archive in a separate thread, and stream the content using a pipe. // The pipe is made of a futures channel, and an adapter to implement the `Write` trait. // Include 10 messages of buffer for erratic connection speeds. - let (tx, rx) = futures::sync::mpsc::channel(10); + let (tx, rx) = futures::channel::mpsc::channel::>(10); let pipe = crate::pipe::Pipe::new(tx); // Start the actual archive creation in a separate thread. @@ -320,47 +329,52 @@ pub fn directory_listing( // // That being said, `rx` will never fail because the `Stream` implementation for `Receiver` // never returns an error - it simply cannot fail. - let rx = rx.map_err(|_| unreachable!("pipes never fail")); + //let rx = rx.map_err(|_| unreachable!("pipes never fail")); // At this point, `rx` implements everything actix want for a streamed response, // so we can just give a `Box::new(rx)` as streaming body. - Ok(HttpResponse::Ok() - .content_type(compression_method.content_type()) - .content_encoding(compression_method.content_encoding()) - .header("Content-Transfer-Encoding", "binary") - .header( - "Content-Disposition", - format!("attachment; filename={:?}", filename), - ) - .chunked() - .body(Body::Streaming(Box::new(rx)))) - } else { - Ok(HttpResponse::Ok() - .content_type("text/html; charset=utf-8") - .body( - renderer::page( - serve_path, - entries, - is_root, - query_params.sort, - query_params.order, - default_color_scheme, - color_scheme, - show_qrcode, - file_upload, - &upload_route, - ¤t_dir.display().to_string(), - tar_enabled, - zip_enabled, + Ok(ServiceResponse::new( + req.clone(), + HttpResponse::Ok() + .content_type(compression_method.content_type()) + .encoding(compression_method.content_encoding()) + .header("Content-Transfer-Encoding", "binary") + .header( + "Content-Disposition", + format!("attachment; filename={:?}", filename), ) - .into_string(), - )) + .body(actix_web::body::BodyStream::new(rx)), + )) + } else { + Ok(ServiceResponse::new( + req.clone(), + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body( + renderer::page( + serve_path, + entries, + is_root, + query_params.sort, + query_params.order, + default_color_scheme, + color_scheme, + show_qrcode, + file_upload, + &upload_route, + ¤t_dir.display().to_string(), + tar_enabled, + zip_enabled, + ) + .into_string(), + ), + )) } } -pub fn extract_query_parameters(req: &HttpRequest) -> QueryParameters { - match Query::::extract(req) { +pub fn extract_query_parameters(req: &HttpRequest) -> QueryParameters { + match Query::::from_query(req.query_string()) { Ok(query) => QueryParameters { sort: query.sort, order: query.order, diff --git a/src/main.rs b/src/main.rs index a9e7944..1477652 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,8 @@ #![feature(proc_macro_hygiene)] -use actix_web::http::{Method, StatusCode}; -use actix_web::{fs, middleware, server, App, HttpRequest, HttpResponse}; +use actix_web::http::StatusCode; +use actix_web::web; +use actix_web::{middleware, App, HttpRequest, HttpResponse}; use std::io::{self, Write}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::thread; @@ -80,12 +81,12 @@ fn main() { } } -fn run() -> Result<(), ContextualError> { +#[actix_rt::main(miniserve)] +async fn run() -> Result<(), ContextualError> { if cfg!(windows) && !Paint::enable_windows_ascii() { Paint::disable(); } - let sys = actix::System::new("miniserve"); let miniserve_config = args::parse_args(); let log_level = if miniserve_config.verbose { @@ -229,16 +230,18 @@ fn run() -> Result<(), ContextualError> { } }; - server::new(move || { - App::with_state(inside_config.clone()) - .middleware(auth::Auth) - .middleware(middleware::Logger::default()) - .configure(configure_app) + let srv = actix_web::HttpServer::new(move || { + App::new() + .app_data(inside_config.clone()) + .wrap(auth::Auth) + .wrap(middleware::Logger::default()) + .configure(|c| configure_app(c, &inside_config)) + .default_service(web::get().to(error_404)) }) .bind(socket_addresses.as_slice()) .map_err(|e| ContextualError::IOError("Failed to bind server".to_string(), e))? .shutdown_timeout(0) - .start(); + .run(); println!( "Serving path {path} at {addresses}", @@ -248,41 +251,41 @@ fn run() -> Result<(), ContextualError> { println!("\nQuit by pressing CTRL-C"); - let _ = sys.run(); - - Ok(()) + srv.await + .map_err(|e| ContextualError::IOError("".to_owned(), e)) } /// Configures the Actix application -fn configure_app(app: App) -> App { +fn configure_app(app: &mut web::ServiceConfig, conf: &MiniserveConfig) { + let random_route = conf.random_route.clone().unwrap_or_default(); + let uses_random_route = conf.random_route.clone().is_some(); + let full_route = format!("/{}", random_route); + let upload_route; let s = { - let path = &app.state().path; - let no_symlinks = app.state().no_symlinks; - let random_route = app.state().random_route.clone(); - let default_color_scheme = app.state().default_color_scheme; - let show_qrcode = app.state().show_qrcode; - let file_upload = app.state().file_upload; - let tar_enabled = app.state().tar_enabled; - let zip_enabled = app.state().zip_enabled; - upload_route = if let Some(random_route) = app.state().random_route.clone() { + let path = &conf.path; + let no_symlinks = conf.no_symlinks; + let random_route = conf.random_route.clone(); + let default_color_scheme = conf.default_color_scheme; + let show_qrcode = conf.show_qrcode; + let file_upload = conf.file_upload; + let tar_enabled = conf.tar_enabled; + let zip_enabled = conf.zip_enabled; + upload_route = if let Some(random_route) = conf.random_route.clone() { format!("/{}/upload", random_route) } else { "/upload".to_string() }; if path.is_file() { None - } else if let Some(index_file) = &app.state().index { + } else if let Some(index_file) = &conf.index { Some( - fs::StaticFiles::new(path) - .expect("Failed to setup static file handler") - .index_file(index_file.to_string_lossy()), + actix_files::Files::new(&full_route, path).index_file(index_file.to_string_lossy()), ) } else { let u_r = upload_route.clone(); Some( - fs::StaticFiles::new(path) - .expect("Failed to setup static file handler") + actix_files::Files::new(&full_route, path) .show_files_listing() .files_listing_renderer(move |dir, req| { listing::directory_listing( @@ -298,49 +301,43 @@ fn configure_app(app: App) -> App { zip_enabled, ) }) - .default_handler(error_404), + .default_handler(web::to(error_404)), ) } }; - let random_route = app.state().random_route.clone().unwrap_or_default(); - let uses_random_route = app.state().random_route.clone().is_some(); - let full_route = format!("/{}", random_route); - if let Some(s) = s { - if app.state().file_upload { - let default_color_scheme = app.state().default_color_scheme; + if conf.file_upload { + let default_color_scheme = conf.default_color_scheme; // Allow file upload - app.resource(&upload_route, move |r| { - r.method(Method::POST).f(move |file| { - file_upload::upload_file(file, default_color_scheme, uses_random_route) - }) - }) + app.service( + web::resource(&upload_route).route(web::post().to(move |req, payload| { + file_upload::upload_file(req, payload, default_color_scheme, uses_random_route) + })), + ) // Handle directories - .handler(&full_route, s) - .default_resource(|r| r.method(Method::GET).f(error_404)) + .service(s); } else { // Handle directories - app.handler(&full_route, s) - .default_resource(|r| r.method(Method::GET).f(error_404)) + app.service(s); } } else { // Handle single files - app.resource(&full_route, |r| r.f(listing::file_handler)) - .default_resource(|r| r.method(Method::GET).f(error_404)) + app.service(web::resource(&full_route).route(web::to(listing::file_handler))); } } -fn error_404(req: &HttpRequest) -> Result { +async fn error_404(req: HttpRequest) -> HttpResponse { let err_404 = ContextualError::RouteNotFoundError(req.path().to_string()); - let default_color_scheme = req.state().default_color_scheme; - let uses_random_route = req.state().random_route.is_some(); - let query_params = listing::extract_query_parameters(req); + let conf = req.app_data::().unwrap(); + let default_color_scheme = conf.default_color_scheme; + let uses_random_route = conf.random_route.is_some(); + let query_params = listing::extract_query_parameters(&req); let color_scheme = query_params.theme.unwrap_or(default_color_scheme); errors::log_error_chain(err_404.to_string()); - Ok(actix_web::HttpResponse::NotFound().body( + actix_web::HttpResponse::NotFound().body( renderer::render_error( &err_404.to_string(), StatusCode::NOT_FOUND, @@ -353,5 +350,5 @@ fn error_404(req: &HttpRequest) -> Result`. -use bytes::{Bytes, BytesMut}; -use futures::sink::{Sink, Wait}; -use futures::sync::mpsc::Sender; +use actix_web::web::{Bytes, BytesMut}; +use futures::channel::mpsc::Sender; +use futures::executor::block_on; +use futures::sink::SinkExt; use std::io::{Error, ErrorKind, Result, Write}; /// Adapter to implement the `std::io::Write` trait on a `Sender` from a futures channel. @@ -10,15 +11,15 @@ use std::io::{Error, ErrorKind, Result, Write}; pub struct Pipe { // Wrapping the sender in `Wait` makes it blocking, so we can implement blocking-style // io::Write over the async-style Sender. - dest: Wait>, + dest: Sender>, bytes: BytesMut, } impl Pipe { /// Wrap the given sender in a `Pipe`. - pub fn new(destination: Sender) -> Self { + pub fn new(destination: Sender>) -> Self { Pipe { - dest: destination.wait(), + dest: destination, bytes: BytesMut::new(), } } @@ -28,7 +29,7 @@ impl Drop for Pipe { fn drop(&mut self) { // This is the correct thing to do, but is not super important since the `Sink` // implementation of `Sender` just returns `Ok` without doing anything else. - let _ = self.dest.close(); + let _ = block_on(self.dest.close()); } } @@ -38,8 +39,7 @@ impl Write for Pipe { self.bytes.extend_from_slice(buf); // Then, take the buffer and send it in the channel. - self.dest - .send(self.bytes.take().into()) + block_on(self.dest.send(Ok(self.bytes.split().into()))) .map_err(|e| Error::new(ErrorKind::UnexpectedEof, e))?; // Return how much we sent - all of it. @@ -47,8 +47,6 @@ impl Write for Pipe { } fn flush(&mut self) -> Result<()> { - self.dest - .flush() - .map_err(|e| Error::new(ErrorKind::UnexpectedEof, e)) + block_on(self.dest.flush()).map_err(|e| Error::new(ErrorKind::UnexpectedEof, e)) } } -- cgit v1.2.3