From 17744bbfe4dd3f9df42fcbd6fdbc38ed8e99b757 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 10 Mar 2019 18:23:44 -0700 Subject: [PATCH] upgrade actix_todo --- actix_todo/Cargo.toml | 7 +- actix_todo/src/api.rs | 184 ++++++++++++++++++-------------------- actix_todo/src/db.rs | 97 ++++---------------- actix_todo/src/main.rs | 56 ++++-------- actix_todo/src/model.rs | 2 +- actix_todo/src/session.rs | 15 ++-- 6 files changed, 137 insertions(+), 224 deletions(-) diff --git a/actix_todo/Cargo.toml b/actix_todo/Cargo.toml index eb17097e..09b9a010 100644 --- a/actix_todo/Cargo.toml +++ b/actix_todo/Cargo.toml @@ -2,10 +2,13 @@ authors = ["Dan Munckton "] name = "actix-todo" version = "0.1.0" +workspace = ".." +edition = "2018" [dependencies] -actix = "0.7.3" -actix-web = "0.7.4" +actix-web = { git="https://github.com/actix/actix-web.git", branch = "1.0" } +actix-files = { git="https://github.com/actix/actix-web.git", branch = "1.0" } +actix-session = { git="https://github.com/actix/actix-web.git", branch = "1.0" } dotenv = "0.13.0" env_logger = "0.5.10" futures = "0.1.22" diff --git a/actix_todo/src/api.rs b/actix_todo/src/api.rs index 73c58e30..069e5db8 100644 --- a/actix_todo/src/api.rs +++ b/actix_todo/src/api.rs @@ -1,42 +1,34 @@ -use actix::prelude::Addr; -use actix_web::middleware::Response; -use actix_web::{ - error, fs::NamedFile, http, AsyncResponder, Form, FutureResponse, HttpRequest, - HttpResponse, Path, Responder, Result, -}; -use futures::{future, Future}; +use actix_files::NamedFile; +use actix_session::Session; +use actix_web::middleware::ErrorHandlerResponse; +use actix_web::{dev, error, http, web, Error, HttpResponse, Responder, Result}; +use futures::future::{err, Either, Future, IntoFuture}; use tera::{Context, Tera}; -use db::{AllTasks, CreateTask, DbExecutor, DeleteTask, ToggleTask}; -use session::{self, FlashMessage}; +use crate::db; +use crate::session::{self, FlashMessage}; -pub struct AppState { - pub template: Tera, - pub db: Addr, -} - -pub fn index(req: HttpRequest) -> FutureResponse { - req.state() - .db - .send(AllTasks) +pub fn index( + pool: web::State, + tmpl: web::State, + session: Session, +) -> impl Future { + web::block(move || db::get_all_tasks(&pool)) .from_err() - .and_then(move |res| match res { + .then(move |res| match res { Ok(tasks) => { let mut context = Context::new(); - context.add("tasks", &tasks); + context.insert("tasks", &tasks); //Session is set during operations on other endpoints //that can redirect to index - if let Some(flash) = session::get_flash(&req)? { - context.add("msg", &(flash.kind, flash.message)); - session::clear_flash(&req); + if let Some(flash) = session::get_flash(&session)? { + context.insert("msg", &(flash.kind, flash.message)); + session::clear_flash(&session); } - let rendered = req - .state() - .template - .render("index.html.tera", &context) - .map_err(|e| { + let rendered = + tmpl.render("index.html.tera", &context).map_err(|e| { error::ErrorInternalServerError(e.description().to_owned()) })?; @@ -44,7 +36,6 @@ pub fn index(req: HttpRequest) -> FutureResponse { } Err(e) => Err(e), }) - .responder() } #[derive(Deserialize)] @@ -53,35 +44,34 @@ pub struct CreateForm { } pub fn create( - (req, params): (HttpRequest, Form), -) -> FutureResponse { + params: web::Form, + pool: web::State, + session: Session, +) -> impl Future { if params.description.is_empty() { - future::lazy(move || { + Either::A( session::set_flash( - &req, + &session, FlashMessage::error("Description cannot be empty"), - )?; - Ok(redirect_to("/")) - }) - .responder() + ) + .map(|_| redirect_to("/")) + .into_future(), + ) } else { - req.state() - .db - .send(CreateTask { - description: params.description.clone(), - }) - .from_err() - .and_then(move |res| match res { - Ok(_) => { - session::set_flash( - &req, - FlashMessage::success("Task successfully added"), - )?; - Ok(redirect_to("/")) - } - Err(e) => Err(e), - }) - .responder() + Either::B( + web::block(move || db::create_task(params.into_inner().description, &pool)) + .from_err() + .then(move |res| match res { + Ok(_) => { + session::set_flash( + &session, + FlashMessage::success("Task successfully added"), + )?; + Ok(redirect_to("/")) + } + Err(e) => Err(e), + }), + ) } } @@ -96,49 +86,50 @@ pub struct UpdateForm { } pub fn update( - (req, params, form): (HttpRequest, Path, Form), -) -> FutureResponse { + db: web::State, + params: web::Path, + form: web::Form, + session: Session, +) -> impl Future { match form._method.as_ref() { - "put" => toggle(req, params), - "delete" => delete(req, params), + "put" => Either::A(Either::A(toggle(db, params))), + "delete" => Either::A(Either::B(delete(db, params, session))), unsupported_method => { let msg = format!("Unsupported HTTP method: {}", unsupported_method); - future::err(error::ErrorBadRequest(msg)).responder() + Either::B(err(error::ErrorBadRequest(msg))) } } } fn toggle( - req: HttpRequest, - params: Path, -) -> FutureResponse { - req.state() - .db - .send(ToggleTask { id: params.id }) + pool: web::State, + params: web::Path, +) -> impl Future { + web::block(move || db::toggle_task(params.id, &pool)) .from_err() - .and_then(move |res| match res { + .then(move |res| match res { Ok(_) => Ok(redirect_to("/")), Err(e) => Err(e), }) - .responder() } fn delete( - req: HttpRequest, - params: Path, -) -> FutureResponse { - req.state() - .db - .send(DeleteTask { id: params.id }) + pool: web::State, + params: web::Path, + session: Session, +) -> impl Future { + web::block(move || db::delete_task(params.id, &pool)) .from_err() - .and_then(move |res| match res { + .then(move |res| match res { Ok(_) => { - session::set_flash(&req, FlashMessage::success("Task was deleted."))?; + session::set_flash( + &session, + FlashMessage::success("Task was deleted."), + )?; Ok(redirect_to("/")) } Err(e) => Err(e), }) - .responder() } fn redirect_to(location: &str) -> HttpResponse { @@ -147,32 +138,31 @@ fn redirect_to(location: &str) -> HttpResponse { .finish() } -pub fn bad_request( - req: &HttpRequest, - resp: HttpResponse, -) -> Result { +pub fn bad_request(res: dev::ServiceResponse) -> Result> { let new_resp = NamedFile::open("static/errors/400.html")? - .set_status_code(resp.status()) - .respond_to(req)?; - Ok(Response::Done(new_resp)) + .set_status_code(res.status()) + .respond_to(res.request())?; + Ok(ErrorHandlerResponse::Response( + res.into_response(new_resp.into_body()), + )) } -pub fn not_found( - req: &HttpRequest, - resp: HttpResponse, -) -> Result { +pub fn not_found(res: dev::ServiceResponse) -> Result> { let new_resp = NamedFile::open("static/errors/404.html")? - .set_status_code(resp.status()) - .respond_to(req)?; - Ok(Response::Done(new_resp)) + .set_status_code(res.status()) + .respond_to(res.request())?; + Ok(ErrorHandlerResponse::Response( + res.into_response(new_resp.into_body()), + )) } -pub fn internal_server_error( - req: &HttpRequest, - resp: HttpResponse, -) -> Result { +pub fn internal_server_error( + res: dev::ServiceResponse, +) -> Result> { let new_resp = NamedFile::open("static/errors/500.html")? - .set_status_code(resp.status()) - .respond_to(req)?; - Ok(Response::Done(new_resp)) + .set_status_code(res.status()) + .respond_to(res.request())?; + Ok(ErrorHandlerResponse::Response( + res.into_response(new_resp.into_body()), + )) } diff --git a/actix_todo/src/db.rs b/actix_todo/src/db.rs index 534ae648..579b0b5d 100644 --- a/actix_todo/src/db.rs +++ b/actix_todo/src/db.rs @@ -1,13 +1,11 @@ use std::ops::Deref; -use actix::prelude::{Actor, Handler, Message, SyncContext}; -use actix_web::{error, Error}; use diesel::pg::PgConnection; use diesel::r2d2::{ConnectionManager, Pool, PoolError, PooledConnection}; -use model::{NewTask, Task}; +use crate::model::{NewTask, Task}; -type PgPool = Pool>; +pub type PgPool = Pool>; type PgPooledConnection = PooledConnection>; pub fn init_pool(database_url: &str) -> Result { @@ -15,86 +13,29 @@ pub fn init_pool(database_url: &str) -> Result { Pool::builder().build(manager) } -pub struct DbExecutor(pub PgPool); - -impl DbExecutor { - pub fn get_conn(&self) -> Result { - self.0.get().map_err(|e| error::ErrorInternalServerError(e)) - } +fn get_conn(pool: &PgPool) -> Result { + pool.get().map_err(|_| "can get connection") } -impl Actor for DbExecutor { - type Context = SyncContext; +pub fn get_all_tasks(pool: &PgPool) -> Result, &'static str> { + Task::all(get_conn(pool)?.deref()).map_err(|_| "Error inserting task") } -pub struct AllTasks; - -impl Message for AllTasks { - type Result = Result, Error>; +pub fn create_task(todo: String, pool: &PgPool) -> Result<(), &'static str> { + let new_task = NewTask { description: todo }; + Task::insert(new_task, get_conn(pool)?.deref()) + .map(|_| ()) + .map_err(|_| "Error inserting task") } -impl Handler for DbExecutor { - type Result = Result, Error>; - - fn handle(&mut self, _: AllTasks, _: &mut Self::Context) -> Self::Result { - Task::all(self.get_conn()?.deref()) - .map_err(|_| error::ErrorInternalServerError("Error inserting task")) - } +pub fn toggle_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { + Task::toggle_with_id(id, get_conn(pool)?.deref()) + .map(|_| ()) + .map_err(|_| "Error inserting task") } -pub struct CreateTask { - pub description: String, -} - -impl Message for CreateTask { - type Result = Result<(), Error>; -} - -impl Handler for DbExecutor { - type Result = Result<(), Error>; - - fn handle(&mut self, todo: CreateTask, _: &mut Self::Context) -> Self::Result { - let new_task = NewTask { - description: todo.description, - }; - Task::insert(new_task, self.get_conn()?.deref()) - .map(|_| ()) - .map_err(|_| error::ErrorInternalServerError("Error inserting task")) - } -} - -pub struct ToggleTask { - pub id: i32, -} - -impl Message for ToggleTask { - type Result = Result<(), Error>; -} - -impl Handler for DbExecutor { - type Result = Result<(), Error>; - - fn handle(&mut self, task: ToggleTask, _: &mut Self::Context) -> Self::Result { - Task::toggle_with_id(task.id, self.get_conn()?.deref()) - .map(|_| ()) - .map_err(|_| error::ErrorInternalServerError("Error inserting task")) - } -} - -pub struct DeleteTask { - pub id: i32, -} - -impl Message for DeleteTask { - type Result = Result<(), Error>; -} - -impl Handler for DbExecutor { - type Result = Result<(), Error>; - - fn handle(&mut self, task: DeleteTask, _: &mut Self::Context) -> Self::Result { - Task::delete_with_id(task.id, self.get_conn()?.deref()) - .map(|_| ()) - .map_err(|_| error::ErrorInternalServerError("Error inserting task")) - } +pub fn delete_task(id: i32, pool: &PgPool) -> Result<(), &'static str> { + Task::delete_with_id(id, get_conn(pool)?.deref()) + .map(|_| ()) + .map_err(|_| "Error inserting task") } diff --git a/actix_todo/src/main.rs b/actix_todo/src/main.rs index f2de26af..9c9664aa 100644 --- a/actix_todo/src/main.rs +++ b/actix_todo/src/main.rs @@ -1,8 +1,3 @@ -extern crate actix; -extern crate actix_web; -extern crate dotenv; -extern crate env_logger; -extern crate futures; #[macro_use] extern crate diesel; #[macro_use] @@ -12,12 +7,13 @@ extern crate serde_derive; #[macro_use] extern crate tera; -use actix::prelude::SyncArbiter; -use actix_web::middleware::session::{CookieSessionBackend, SessionStorage}; +use std::{env, io}; + +use actix_files as fs; +use actix_session::CookieSession; use actix_web::middleware::{ErrorHandlers, Logger}; -use actix_web::{dev::Resource, fs, http, server, App}; +use actix_web::{http, web, App, HttpServer}; use dotenv::dotenv; -use std::env; use tera::Tera; mod api; @@ -27,29 +23,22 @@ mod schema; mod session; static SESSION_SIGNING_KEY: &[u8] = &[0; 32]; -const NUM_DB_THREADS: usize = 3; -fn main() { +fn main() -> io::Result<()> { dotenv().ok(); - std::env::set_var("RUST_LOG", "actix_todo=debug,actix_web=info"); + env::set_var("RUST_LOG", "actix_todo=debug,actix_web=info"); env_logger::init(); - // Start the Actix system - let system = actix::System::new("todo-app"); - let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let pool = db::init_pool(&database_url).expect("Failed to create pool"); - let addr = SyncArbiter::start(NUM_DB_THREADS, move || db::DbExecutor(pool.clone())); let app = move || { debug!("Constructing the App"); let templates: Tera = compile_templates!("templates/**/*"); - let session_store = SessionStorage::new( - CookieSessionBackend::signed(SESSION_SIGNING_KEY).secure(false), - ); + let session_store = CookieSession::signed(SESSION_SIGNING_KEY).secure(false); let error_handlers = ErrorHandlers::new() .handler( @@ -59,29 +48,20 @@ fn main() { .handler(http::StatusCode::BAD_REQUEST, api::bad_request) .handler(http::StatusCode::NOT_FOUND, api::not_found); - let static_files = fs::StaticFiles::new("static/") - .expect("failed constructing static files handler"); - - let state = api::AppState { - template: templates, - db: addr.clone(), - }; - - App::with_state(state) + App::new() + .state(templates) + .state(pool.clone()) .middleware(Logger::default()) .middleware(session_store) .middleware(error_handlers) - .route("/", http::Method::GET, api::index) - .route("/todo", http::Method::POST, api::create) - .resource("/todo/{id}", |r: &mut Resource<_>| { - r.post().with(api::update) - }) - .handler("/static", static_files) + .service(web::resource("/").route(web::get().to_async(api::index))) + .service(web::resource("/todo").route(web::post().to_async(api::create))) + .service( + web::resource("/todo/{id}").route(web::post().to_async(api::update)), + ) + .service(fs::Files::new("/static", "static/")) }; debug!("Starting server"); - server::new(app).bind("localhost:8088").unwrap().start(); - - // Run actix system, this method actually starts all async processes - let _ = system.run(); + HttpServer::new(app).bind("localhost:8088")?.run() } diff --git a/actix_todo/src/model.rs b/actix_todo/src/model.rs index 60eb551c..4b80f4a8 100644 --- a/actix_todo/src/model.rs +++ b/actix_todo/src/model.rs @@ -2,7 +2,7 @@ use diesel; use diesel::pg::PgConnection; use diesel::prelude::*; -use schema::{ +use crate::schema::{ tasks, tasks::dsl::{completed as task_completed, tasks as all_tasks}, }; diff --git a/actix_todo/src/session.rs b/actix_todo/src/session.rs index c4c6aadb..d1cfc7a4 100644 --- a/actix_todo/src/session.rs +++ b/actix_todo/src/session.rs @@ -1,19 +1,18 @@ +use actix_session::Session; use actix_web::error::Result; -use actix_web::middleware::session::RequestSession; -use actix_web::HttpRequest; const FLASH_KEY: &str = "flash"; -pub fn set_flash(request: &HttpRequest, flash: FlashMessage) -> Result<()> { - request.session().set(FLASH_KEY, flash) +pub fn set_flash(session: &Session, flash: FlashMessage) -> Result<()> { + session.set(FLASH_KEY, flash) } -pub fn get_flash(req: &HttpRequest) -> Result> { - req.session().get::(FLASH_KEY) +pub fn get_flash(session: &Session) -> Result> { + session.get::(FLASH_KEY) } -pub fn clear_flash(req: &HttpRequest) { - req.session().remove(FLASH_KEY); +pub fn clear_flash(session: &Session) { + session.remove(FLASH_KEY); } #[derive(Deserialize, Serialize)]