1
0
mirror of https://github.com/actix/examples synced 2025-01-22 22:05:57 +01:00

Merge branch '1.0' into 0.7

This commit is contained in:
Nikolay Kim 2019-03-29 16:43:37 -07:00
commit 9c8389e06e
105 changed files with 1801 additions and 2585 deletions

View File

@ -40,7 +40,6 @@ script:
cd actix_todo && cargo check && cd .. cd actix_todo && cargo check && cd ..
cd basics && cargo check && cd .. cd basics && cargo check && cd ..
cd cookie-auth && cargo check && cd .. cd cookie-auth && cargo check && cd ..
cd cookie-auth-full && cargo check && cd ..
cd cookie-session && cargo check && cd .. cd cookie-session && cargo check && cd ..
cd diesel && cargo check && cd .. cd diesel && cargo check && cd ..
cd error_handling && cargo check && cd .. cd error_handling && cargo check && cd ..
@ -55,11 +54,11 @@ script:
cd protobuf && cargo check && cd .. cd protobuf && cargo check && cd ..
cd r2d2 && cargo check && cd .. cd r2d2 && cargo check && cd ..
cd redis-session && cargo check && cd .. cd redis-session && cargo check && cd ..
cd simple-auth-sarver && cargo check && cd ..
cd state && cargo check && cd .. cd state && cargo check && cd ..
cd static_index && cargo check && cd .. cd static_index && cargo check && cd ..
cd template_askama && cargo check && cd .. cd template_askama && cargo check && cd ..
cd template_tera && cargo check && cd .. cd template_tera && cargo check && cd ..
cd template_yarte && cargo check && cd ..
cd tls && cargo check && cd .. cd tls && cargo check && cd ..
cd rustls && cargo check && cd .. cd rustls && cargo check && cd ..
cd unix-socket && cargo check && cd .. cd unix-socket && cargo check && cd ..

View File

@ -7,7 +7,6 @@ members = [
"async_ex1", "async_ex1",
"basics", "basics",
"cookie-auth", "cookie-auth",
"cookie-auth-full",
"cookie-session", "cookie-session",
"diesel", "diesel",
"error_handling", "error_handling",
@ -27,6 +26,7 @@ members = [
"static_index", "static_index",
"template_askama", "template_askama",
"template_tera", "template_tera",
"template_yarte",
"tls", "tls",
"rustls", "rustls",
"unix-socket", "unix-socket",

View File

@ -2,13 +2,15 @@
name = "actix_redis" name = "actix_redis"
version = "0.1.0" version = "0.1.0"
authors = ["dowwie <dkcdkg@gmail.com>"] authors = ["dowwie <dkcdkg@gmail.com>"]
edition = "2018"
workspace = ".."
[dependencies] [dependencies]
actix = "0.7.3" actix = "0.8.0-alpha.2"
actix-web = "0.7.3" actix-web = "1.0.0-alpha.1"
actix-redis = "0.5.1" actix-redis = { git="https://github.com/actix/actix-redis.git" }
futures = "0.1.23" futures = "0.1.23"
redis-async = "0.4.0" redis-async = "0.4.0"
serde = "1.0.71" serde = "1.0.71"
serde_derive = "1.0.71" serde_derive = "1.0.71"
env_logger = "0.5.12" env_logger = "0.6"

View File

@ -1,34 +1,26 @@
extern crate actix; #[macro_use]
extern crate actix_redis; extern crate redis_async;
extern crate actix_web; #[macro_use]
extern crate env_logger; extern crate serde_derive;
extern crate futures;
#[macro_use] extern crate redis_async;
extern crate serde;
#[macro_use] extern crate serde_derive;
use std::sync::Arc;
use actix::prelude::*; use actix::prelude::*;
use actix_redis::{Command, RedisActor, Error as ARError}; use actix_redis::{Command, Error as ARError, RedisActor};
use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Json, use actix_web::{middleware, web, App, Error as AWError, HttpResponse, HttpServer};
AsyncResponder, http::Method, Error as AWError}; use futures::future::{join_all, Future};
use futures::future::{Future, join_all};
use redis_async::resp::RespValue; use redis_async::resp::RespValue;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CacheInfo { pub struct CacheInfo {
one: String, one: String,
two: String, two: String,
three: String three: String,
} }
fn cache_stuff(
fn cache_stuff((info, req): (Json<CacheInfo>, HttpRequest<AppState>)) info: web::Json<CacheInfo>,
-> impl Future<Item=HttpResponse, Error=AWError> { redis: web::Data<Addr<RedisActor>>,
) -> impl Future<Item = HttpResponse, Error = AWError> {
let info = info.into_inner(); let info = info.into_inner();
let redis = req.state().redis_addr.clone();
let one = redis.send(Command(resp_array!["SET", "mydomain:one", info.one])); let one = redis.send(Command(resp_array!["SET", "mydomain:one", info.one]));
let two = redis.send(Command(resp_array!["SET", "mydomain:two", info.two])); let two = redis.send(Command(resp_array!["SET", "mydomain:two", info.two]));
@ -56,51 +48,46 @@ fn cache_stuff((info, req): (Json<CacheInfo>, HttpRequest<AppState>))
Ok(HttpResponse::Ok().body("successfully cached values")) Ok(HttpResponse::Ok().body("successfully cached values"))
} }
) )
.responder()
} }
fn del_stuff(req: HttpRequest<AppState>) fn del_stuff(
-> impl Future<Item=HttpResponse, Error=AWError> { redis: web::Data<Addr<RedisActor>>,
let redis = req.state().redis_addr.clone(); ) -> impl Future<Item = HttpResponse, Error = AWError> {
redis
redis.send(Command(resp_array!["DEL", "mydomain:one", "mydomain:two", "mydomain:three"])) .send(Command(resp_array![
"DEL",
"mydomain:one",
"mydomain:two",
"mydomain:three"
]))
.map_err(AWError::from) .map_err(AWError::from)
.and_then(|res: Result<RespValue, ARError>| .and_then(|res: Result<RespValue, ARError>| match &res {
match &res { Ok(RespValue::Integer(x)) if x == &3 => {
Ok(RespValue::Integer(x)) if x==&3 => Ok(HttpResponse::Ok().body("successfully deleted values"))
Ok(HttpResponse::Ok().body("successfully deleted values")), }
_ =>{println!("---->{:?}", res); _ => {
Ok(HttpResponse::InternalServerError().finish())} println!("---->{:?}", res);
Ok(HttpResponse::InternalServerError().finish())
}
}) })
.responder()
} }
pub struct AppState { fn main() -> std::io::Result<()> {
pub redis_addr: Arc<Addr<RedisActor>> std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("actix_redis_ex");
server::new(|| { HttpServer::new(|| {
let redis_addr = Arc::new(RedisActor::start("127.0.0.1:6379")); let redis_addr = RedisActor::start("127.0.0.1:6379");
let app_state = AppState{redis_addr};
App::with_state(app_state) App::new()
.middleware(middleware::Logger::default()) .data(redis_addr)
.resource("/stuff", |r| { .wrap(middleware::Logger::default())
r.method(Method::POST) .service(
.with_async(cache_stuff); web::resource("/stuff")
r.method(Method::DELETE) .route(web::post().to_async(cache_stuff))
.with_async(del_stuff)}) .route(web::delete().to_async(del_stuff)),
)
}).bind("0.0.0.0:8080") })
.unwrap() .bind("0.0.0.0:8080")?
.workers(1) .run()
.start();
let _ = sys.run();
} }

View File

@ -2,10 +2,13 @@
authors = ["Dan Munckton <dangit@munckfish.net>"] authors = ["Dan Munckton <dangit@munckfish.net>"]
name = "actix-todo" name = "actix-todo"
version = "0.1.0" version = "0.1.0"
workspace = ".."
edition = "2018"
[dependencies] [dependencies]
actix = "0.7.3" actix-web = "1.0.0-alpha.1"
actix-web = "0.7.4" actix-files = "0.1.0-alpha.1"
actix-session = "0.1.0-alpha.1"
dotenv = "0.13.0" dotenv = "0.13.0"
env_logger = "0.5.10" env_logger = "0.5.10"
futures = "0.1.22" futures = "0.1.22"

View File

@ -1,41 +1,34 @@
use actix::prelude::Addr; use actix_files::NamedFile;
use actix_web::middleware::Response; use actix_session::Session;
use actix_web::{ use actix_web::middleware::errhandlers::ErrorHandlerResponse;
error, fs::NamedFile, http, AsyncResponder, Form, FutureResponse, HttpRequest, use actix_web::{dev, error, http, web, Error, HttpResponse, Responder, Result};
HttpResponse, Path, Responder, Result, use futures::future::{err, Either, Future, IntoFuture};
};
use futures::{future, Future};
use tera::{Context, Tera}; use tera::{Context, Tera};
use db::{AllTasks, CreateTask, DbExecutor, DeleteTask, ToggleTask}; use crate::db;
use session::{self, FlashMessage}; use crate::session::{self, FlashMessage};
pub struct AppState { pub fn index(
pub template: Tera, pool: web::Data<db::PgPool>,
pub db: Addr<DbExecutor>, tmpl: web::Data<Tera>,
} session: Session,
) -> impl Future<Item = HttpResponse, Error = Error> {
pub fn index(req: HttpRequest<AppState>) -> FutureResponse<HttpResponse> { web::block(move || db::get_all_tasks(&pool))
req.state()
.db
.send(AllTasks)
.from_err() .from_err()
.and_then(move |res| match res { .then(move |res| match res {
Ok(tasks) => { Ok(tasks) => {
let mut context = Context::new(); let mut context = Context::new();
context.add("tasks", &tasks); context.insert("tasks", &tasks);
//Session is set during operations on other endpoints //Session is set during operations on other endpoints
//that can redirect to index //that can redirect to index
if let Some(flash) = session::get_flash(&req)? { if let Some(flash) = session::get_flash(&session)? {
context.add("msg", &(flash.kind, flash.message)); context.insert("msg", &(flash.kind, flash.message));
session::clear_flash(&req); session::clear_flash(&session);
} }
let rendered = req.state() let rendered =
.template tmpl.render("index.html.tera", &context).map_err(|e| {
.render("index.html.tera", &context)
.map_err(|e| {
error::ErrorInternalServerError(e.description().to_owned()) error::ErrorInternalServerError(e.description().to_owned())
})?; })?;
@ -43,7 +36,6 @@ pub fn index(req: HttpRequest<AppState>) -> FutureResponse<HttpResponse> {
} }
Err(e) => Err(e), Err(e) => Err(e),
}) })
.responder()
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -52,34 +44,34 @@ pub struct CreateForm {
} }
pub fn create( pub fn create(
(req, params): (HttpRequest<AppState>, Form<CreateForm>), params: web::Form<CreateForm>,
) -> FutureResponse<HttpResponse> { pool: web::Data<db::PgPool>,
session: Session,
) -> impl Future<Item = HttpResponse, Error = Error> {
if params.description.is_empty() { if params.description.is_empty() {
future::lazy(move || { Either::A(
session::set_flash( session::set_flash(
&req, &session,
FlashMessage::error("Description cannot be empty"), FlashMessage::error("Description cannot be empty"),
)?; )
Ok(redirect_to("/")) .map(|_| redirect_to("/"))
}).responder() .into_future(),
)
} else { } else {
req.state() Either::B(
.db web::block(move || db::create_task(params.into_inner().description, &pool))
.send(CreateTask {
description: params.description.clone(),
})
.from_err() .from_err()
.and_then(move |res| match res { .then(move |res| match res {
Ok(_) => { Ok(_) => {
session::set_flash( session::set_flash(
&req, &session,
FlashMessage::success("Task successfully added"), FlashMessage::success("Task successfully added"),
)?; )?;
Ok(redirect_to("/")) Ok(redirect_to("/"))
} }
Err(e) => Err(e), Err(e) => Err(e),
}) }),
.responder() )
} }
} }
@ -94,49 +86,50 @@ pub struct UpdateForm {
} }
pub fn update( pub fn update(
(req, params, form): (HttpRequest<AppState>, Path<UpdateParams>, Form<UpdateForm>), db: web::Data<db::PgPool>,
) -> FutureResponse<HttpResponse> { params: web::Path<UpdateParams>,
form: web::Form<UpdateForm>,
session: Session,
) -> impl Future<Item = HttpResponse, Error = Error> {
match form._method.as_ref() { match form._method.as_ref() {
"put" => toggle(req, params), "put" => Either::A(Either::A(toggle(db, params))),
"delete" => delete(req, params), "delete" => Either::A(Either::B(delete(db, params, session))),
unsupported_method => { unsupported_method => {
let msg = format!("Unsupported HTTP method: {}", unsupported_method); let msg = format!("Unsupported HTTP method: {}", unsupported_method);
future::err(error::ErrorBadRequest(msg)).responder() Either::B(err(error::ErrorBadRequest(msg)))
} }
} }
} }
fn toggle( fn toggle(
req: HttpRequest<AppState>, pool: web::Data<db::PgPool>,
params: Path<UpdateParams>, params: web::Path<UpdateParams>,
) -> FutureResponse<HttpResponse> { ) -> impl Future<Item = HttpResponse, Error = Error> {
req.state() web::block(move || db::toggle_task(params.id, &pool))
.db
.send(ToggleTask { id: params.id })
.from_err() .from_err()
.and_then(move |res| match res { .then(move |res| match res {
Ok(_) => Ok(redirect_to("/")), Ok(_) => Ok(redirect_to("/")),
Err(e) => Err(e), Err(e) => Err(e),
}) })
.responder()
} }
fn delete( fn delete(
req: HttpRequest<AppState>, pool: web::Data<db::PgPool>,
params: Path<UpdateParams>, params: web::Path<UpdateParams>,
) -> FutureResponse<HttpResponse> { session: Session,
req.state() ) -> impl Future<Item = HttpResponse, Error = Error> {
.db web::block(move || db::delete_task(params.id, &pool))
.send(DeleteTask { id: params.id })
.from_err() .from_err()
.and_then(move |res| match res { .then(move |res| match res {
Ok(_) => { Ok(_) => {
session::set_flash(&req, FlashMessage::success("Task was deleted."))?; session::set_flash(
&session,
FlashMessage::success("Task was deleted."),
)?;
Ok(redirect_to("/")) Ok(redirect_to("/"))
} }
Err(e) => Err(e), Err(e) => Err(e),
}) })
.responder()
} }
fn redirect_to(location: &str) -> HttpResponse { fn redirect_to(location: &str) -> HttpResponse {
@ -145,32 +138,31 @@ fn redirect_to(location: &str) -> HttpResponse {
.finish() .finish()
} }
pub fn bad_request<S: 'static>( pub fn bad_request<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
req: &HttpRequest<S>,
resp: HttpResponse,
) -> Result<Response> {
let new_resp = NamedFile::open("static/errors/400.html")? let new_resp = NamedFile::open("static/errors/400.html")?
.set_status_code(resp.status()) .set_status_code(res.status())
.respond_to(req)?; .respond_to(res.request())?;
Ok(Response::Done(new_resp)) Ok(ErrorHandlerResponse::Response(
res.into_response(new_resp.into_body()),
))
} }
pub fn not_found<S: 'static>( pub fn not_found<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
req: &HttpRequest<S>,
resp: HttpResponse,
) -> Result<Response> {
let new_resp = NamedFile::open("static/errors/404.html")? let new_resp = NamedFile::open("static/errors/404.html")?
.set_status_code(resp.status()) .set_status_code(res.status())
.respond_to(req)?; .respond_to(res.request())?;
Ok(Response::Done(new_resp)) Ok(ErrorHandlerResponse::Response(
res.into_response(new_resp.into_body()),
))
} }
pub fn internal_server_error<S: 'static>( pub fn internal_server_error<B>(
req: &HttpRequest<S>, res: dev::ServiceResponse<B>,
resp: HttpResponse, ) -> Result<ErrorHandlerResponse<B>> {
) -> Result<Response> {
let new_resp = NamedFile::open("static/errors/500.html")? let new_resp = NamedFile::open("static/errors/500.html")?
.set_status_code(resp.status()) .set_status_code(res.status())
.respond_to(req)?; .respond_to(res.request())?;
Ok(Response::Done(new_resp)) Ok(ErrorHandlerResponse::Response(
res.into_response(new_resp.into_body()),
))
} }

View File

@ -1,13 +1,11 @@
use std::ops::Deref; use std::ops::Deref;
use actix::prelude::{Actor, Handler, Message, SyncContext};
use actix_web::{error, Error};
use diesel::pg::PgConnection; use diesel::pg::PgConnection;
use diesel::r2d2::{ConnectionManager, Pool, PoolError, PooledConnection}; use diesel::r2d2::{ConnectionManager, Pool, PoolError, PooledConnection};
use model::{NewTask, Task}; use crate::model::{NewTask, Task};
type PgPool = Pool<ConnectionManager<PgConnection>>; pub type PgPool = Pool<ConnectionManager<PgConnection>>;
type PgPooledConnection = PooledConnection<ConnectionManager<PgConnection>>; type PgPooledConnection = PooledConnection<ConnectionManager<PgConnection>>;
pub fn init_pool(database_url: &str) -> Result<PgPool, PoolError> { pub fn init_pool(database_url: &str) -> Result<PgPool, PoolError> {
@ -15,86 +13,29 @@ pub fn init_pool(database_url: &str) -> Result<PgPool, PoolError> {
Pool::builder().build(manager) Pool::builder().build(manager)
} }
pub struct DbExecutor(pub PgPool); fn get_conn(pool: &PgPool) -> Result<PgPooledConnection, &'static str> {
pool.get().map_err(|_| "can get connection")
impl DbExecutor {
pub fn get_conn(&self) -> Result<PgPooledConnection, Error> {
self.0.get().map_err(|e| error::ErrorInternalServerError(e))
}
} }
impl Actor for DbExecutor { pub fn get_all_tasks(pool: &PgPool) -> Result<Vec<Task>, &'static str> {
type Context = SyncContext<Self>; Task::all(get_conn(pool)?.deref()).map_err(|_| "Error inserting task")
} }
pub struct AllTasks; pub fn create_task(todo: String, pool: &PgPool) -> Result<(), &'static str> {
let new_task = NewTask { description: todo };
impl Message for AllTasks { Task::insert(new_task, get_conn(pool)?.deref())
type Result = Result<Vec<Task>, Error>;
}
impl Handler<AllTasks> for DbExecutor {
type Result = Result<Vec<Task>, 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 struct CreateTask {
pub description: String,
}
impl Message for CreateTask {
type Result = Result<(), Error>;
}
impl Handler<CreateTask> 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(|_| ())
.map_err(|_| error::ErrorInternalServerError("Error inserting task")) .map_err(|_| "Error inserting task")
}
} }
pub struct ToggleTask { pub fn toggle_task(id: i32, pool: &PgPool) -> Result<(), &'static str> {
pub id: i32, Task::toggle_with_id(id, get_conn(pool)?.deref())
}
impl Message for ToggleTask {
type Result = Result<(), Error>;
}
impl Handler<ToggleTask> 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(|_| ())
.map_err(|_| error::ErrorInternalServerError("Error inserting task")) .map_err(|_| "Error inserting task")
}
} }
pub struct DeleteTask { pub fn delete_task(id: i32, pool: &PgPool) -> Result<(), &'static str> {
pub id: i32, Task::delete_with_id(id, get_conn(pool)?.deref())
}
impl Message for DeleteTask {
type Result = Result<(), Error>;
}
impl Handler<DeleteTask> 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(|_| ())
.map_err(|_| error::ErrorInternalServerError("Error inserting task")) .map_err(|_| "Error inserting task")
}
} }

View File

@ -1,8 +1,3 @@
extern crate actix;
extern crate actix_web;
extern crate dotenv;
extern crate env_logger;
extern crate futures;
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
#[macro_use] #[macro_use]
@ -12,12 +7,13 @@ extern crate serde_derive;
#[macro_use] #[macro_use]
extern crate tera; extern crate tera;
use actix::prelude::SyncArbiter; use std::{env, io};
use actix_web::middleware::session::{CookieSessionBackend, SessionStorage};
use actix_web::middleware::{ErrorHandlers, Logger}; use actix_files as fs;
use actix_web::{dev::Resource, fs, http, server, App}; use actix_session::CookieSession;
use actix_web::middleware::{errhandlers::ErrorHandlers, Logger};
use actix_web::{http, web, App, HttpServer};
use dotenv::dotenv; use dotenv::dotenv;
use std::env;
use tera::Tera; use tera::Tera;
mod api; mod api;
@ -27,29 +23,22 @@ mod schema;
mod session; mod session;
static SESSION_SIGNING_KEY: &[u8] = &[0; 32]; static SESSION_SIGNING_KEY: &[u8] = &[0; 32];
const NUM_DB_THREADS: usize = 3;
fn main() { fn main() -> io::Result<()> {
dotenv().ok(); 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(); 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 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 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 || { let app = move || {
debug!("Constructing the App"); debug!("Constructing the App");
let templates: Tera = compile_templates!("templates/**/*"); let templates: Tera = compile_templates!("templates/**/*");
let session_store = SessionStorage::new( let session_store = CookieSession::signed(SESSION_SIGNING_KEY).secure(false);
CookieSessionBackend::signed(SESSION_SIGNING_KEY).secure(false),
);
let error_handlers = ErrorHandlers::new() let error_handlers = ErrorHandlers::new()
.handler( .handler(
@ -59,29 +48,20 @@ fn main() {
.handler(http::StatusCode::BAD_REQUEST, api::bad_request) .handler(http::StatusCode::BAD_REQUEST, api::bad_request)
.handler(http::StatusCode::NOT_FOUND, api::not_found); .handler(http::StatusCode::NOT_FOUND, api::not_found);
let static_files = fs::StaticFiles::new("static/") App::new()
.expect("failed constructing static files handler"); .data(templates)
.data(pool.clone())
let state = api::AppState { .wrap(Logger::default())
template: templates, .wrap(session_store)
db: addr.clone(), .wrap(error_handlers)
}; .service(web::resource("/").route(web::get().to_async(api::index)))
.service(web::resource("/todo").route(web::post().to_async(api::create)))
App::with_state(state) .service(
.middleware(Logger::default()) web::resource("/todo/{id}").route(web::post().to_async(api::update)),
.middleware(session_store) )
.middleware(error_handlers) .service(fs::Files::new("/static", "static/"))
.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)
}; };
debug!("Starting server"); debug!("Starting server");
server::new(app).bind("localhost:8088").unwrap().start(); HttpServer::new(app).bind("localhost:8088")?.run()
// Run actix system, this method actually starts all async processes
let _ = system.run();
} }

View File

@ -2,8 +2,9 @@ use diesel;
use diesel::pg::PgConnection; use diesel::pg::PgConnection;
use diesel::prelude::*; use diesel::prelude::*;
use schema::{ use crate::schema::{
tasks, tasks::dsl::{completed as task_completed, tasks as all_tasks}, tasks,
tasks::dsl::{completed as task_completed, tasks as all_tasks},
}; };
#[derive(Debug, Insertable)] #[derive(Debug, Insertable)]

View File

@ -1,19 +1,18 @@
use actix_session::Session;
use actix_web::error::Result; use actix_web::error::Result;
use actix_web::middleware::session::RequestSession;
use actix_web::HttpRequest;
const FLASH_KEY: &str = "flash"; const FLASH_KEY: &str = "flash";
pub fn set_flash<T>(request: &HttpRequest<T>, flash: FlashMessage) -> Result<()> { pub fn set_flash(session: &Session, flash: FlashMessage) -> Result<()> {
request.session().set(FLASH_KEY, flash) session.set(FLASH_KEY, flash)
} }
pub fn get_flash<T>(req: &HttpRequest<T>) -> Result<Option<FlashMessage>> { pub fn get_flash(session: &Session) -> Result<Option<FlashMessage>> {
req.session().get::<FlashMessage>(FLASH_KEY) session.get::<FlashMessage>(FLASH_KEY)
} }
pub fn clear_flash<T>(req: &HttpRequest<T>) { pub fn clear_flash(session: &Session) {
req.session().remove(FLASH_KEY); session.remove(FLASH_KEY);
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]

View File

@ -2,18 +2,21 @@
name = "async_db" name = "async_db"
version = "0.1.0" version = "0.1.0"
authors = ["Darin Gordon <dkcdkg@gmail.com>"] authors = ["Darin Gordon <dkcdkg@gmail.com>"]
edition = "2018"
workspace = ".."
[dependencies] [dependencies]
actix = "0.7" actix-rt = "0.2"
actix-web = "0.7" actix-web = "1.0.0-alpha.1"
dotenv = "0.10" dotenv = "0.10"
env_logger = "0.5" env_logger = "0.5"
failure = "0.1.1" failure = "0.1.1"
futures = "0.1" futures = "0.1"
num_cpus = "1.8.0" num_cpus = "1.10.0"
r2d2 = "0.8.2" r2d2 = "0.8.2"
r2d2_sqlite = "0.5.0" r2d2_sqlite = "0.8.0"
rusqlite = "0.16"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_derive = "1.0" serde_derive = "1.0"

View File

@ -1,17 +1,15 @@
use actix::prelude::*; use actix_web::{web, Error as AWError};
use failure::Error; use failure::Error;
use futures::Future;
use r2d2; use r2d2;
use r2d2_sqlite; use r2d2_sqlite;
use rusqlite::NO_PARAMS;
use serde_derive::{Deserialize, Serialize};
use std::{thread::sleep, time::Duration}; use std::{thread::sleep, time::Duration};
pub type Pool = r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>; pub type Pool = r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>;
pub type Connection = r2d2::PooledConnection<r2d2_sqlite::SqliteConnectionManager>; pub type Connection = r2d2::PooledConnection<r2d2_sqlite::SqliteConnectionManager>;
pub struct DbExecutor(pub Pool);
impl Actor for DbExecutor {
type Context = SyncContext<Self>;
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub enum WeatherAgg { pub enum WeatherAgg {
AnnualAgg { year: i32, total: f64 }, AnnualAgg { year: i32, total: f64 },
@ -25,23 +23,18 @@ pub enum Queries {
GetTopTenColdestMonths, GetTopTenColdestMonths,
} }
//pub struct GetTopTenHottestYears; pub fn execute(
impl Message for Queries { pool: &Pool,
type Result = Result<Vec<WeatherAgg>, Error>; query: Queries,
} ) -> impl Future<Item = Vec<WeatherAgg>, Error = AWError> {
impl Handler<Queries> for DbExecutor { let pool = pool.clone();
type Result = Result<Vec<WeatherAgg>, Error>; web::block(move || match query {
Queries::GetTopTenHottestYears => get_hottest_years(pool.get()?),
fn handle(&mut self, msg: Queries, _: &mut Self::Context) -> Self::Result { Queries::GetTopTenColdestYears => get_coldest_years(pool.get()?),
let conn: Connection = self.0.get()?; Queries::GetTopTenHottestMonths => get_hottest_months(pool.get()?),
Queries::GetTopTenColdestMonths => get_coldest_months(pool.get()?),
match msg { })
Queries::GetTopTenHottestYears => get_hottest_years(conn), .from_err()
Queries::GetTopTenColdestYears => get_coldest_years(conn),
Queries::GetTopTenHottestMonths => get_hottest_months(conn),
Queries::GetTopTenColdestMonths => get_coldest_months(conn),
}
}
} }
fn get_hottest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> { fn get_hottest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
@ -55,7 +48,7 @@ fn get_hottest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let mut prep_stmt = conn.prepare(stmt)?; let mut prep_stmt = conn.prepare(stmt)?;
let annuals = prep_stmt let annuals = prep_stmt
.query_map(&[], |row| WeatherAgg::AnnualAgg { .query_map(NO_PARAMS, |row| WeatherAgg::AnnualAgg {
year: row.get(0), year: row.get(0),
total: row.get(1), total: row.get(1),
}) })
@ -81,7 +74,7 @@ fn get_coldest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let mut prep_stmt = conn.prepare(stmt)?; let mut prep_stmt = conn.prepare(stmt)?;
let annuals = prep_stmt let annuals = prep_stmt
.query_map(&[], |row| WeatherAgg::AnnualAgg { .query_map(NO_PARAMS, |row| WeatherAgg::AnnualAgg {
year: row.get(0), year: row.get(0),
total: row.get(1), total: row.get(1),
}) })
@ -107,7 +100,7 @@ fn get_hottest_months(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let mut prep_stmt = conn.prepare(stmt)?; let mut prep_stmt = conn.prepare(stmt)?;
let annuals = prep_stmt let annuals = prep_stmt
.query_map(&[], |row| WeatherAgg::MonthAgg { .query_map(NO_PARAMS, |row| WeatherAgg::MonthAgg {
year: row.get(0), year: row.get(0),
month: row.get(1), month: row.get(1),
total: row.get(2), total: row.get(2),
@ -133,7 +126,7 @@ fn get_coldest_months(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let mut prep_stmt = conn.prepare(stmt)?; let mut prep_stmt = conn.prepare(stmt)?;
let annuals = prep_stmt let annuals = prep_stmt
.query_map(&[], |row| WeatherAgg::MonthAgg { .query_map(NO_PARAMS, |row| WeatherAgg::MonthAgg {
year: row.get(0), year: row.get(0),
month: row.get(1), month: row.get(1),
total: row.get(2), total: row.get(2),

View File

@ -9,121 +9,89 @@ This project illustrates two examples:
collecting the results and returning them as a single serialized json object collecting the results and returning them as a single serialized json object
*/ */
use std::io;
extern crate actix; use actix_web::{middleware, web, App, Error as AWError, HttpResponse, HttpServer};
extern crate actix_web;
extern crate env_logger;
extern crate failure;
extern crate futures;
extern crate num_cpus;
extern crate r2d2;
extern crate r2d2_sqlite;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
use actix::prelude::*;
use actix_web::{
http, middleware, server, App, AsyncResponder, Error as AWError, FutureResponse,
HttpResponse, State,
};
use futures::future::{join_all, ok as fut_ok, Future}; use futures::future::{join_all, ok as fut_ok, Future};
use r2d2_sqlite;
use r2d2_sqlite::SqliteConnectionManager; use r2d2_sqlite::SqliteConnectionManager;
mod db; mod db;
use db::{DbExecutor, Pool, Queries, WeatherAgg}; use db::{Pool, Queries, WeatherAgg};
/// State with DbExecutor address
struct AppState {
db: Addr<DbExecutor>,
}
/// Version 1: Calls 4 queries in sequential order, as an asynchronous handler /// Version 1: Calls 4 queries in sequential order, as an asynchronous handler
fn asyncio_weather(state: State<AppState>) -> FutureResponse<HttpResponse> { fn asyncio_weather(
db: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = AWError> {
let mut result: Vec<Vec<WeatherAgg>> = vec![]; let mut result: Vec<Vec<WeatherAgg>> = vec![];
state db::execute(&db, Queries::GetTopTenHottestYears)
.db
.send(Queries::GetTopTenHottestYears)
.from_err() .from_err()
.and_then(move |res| { .and_then(move |res| {
result.push(res.unwrap()); result.push(res);
state db::execute(&db, Queries::GetTopTenColdestYears)
.db
.send(Queries::GetTopTenColdestYears)
.from_err() .from_err()
.and_then(move |res| { .and_then(move |res| {
result.push(res.unwrap()); result.push(res);
state db::execute(&db, Queries::GetTopTenHottestMonths)
.db
.send(Queries::GetTopTenHottestMonths)
.from_err() .from_err()
.and_then(move |res| { .and_then(move |res| {
result.push(res.unwrap()); result.push(res);
state db::execute(&db, Queries::GetTopTenColdestMonths)
.db
.send(Queries::GetTopTenColdestMonths)
.from_err() .from_err()
.and_then(move |res| { .and_then(move |res| {
result.push(res.unwrap()); result.push(res);
fut_ok(result) fut_ok(result)
}) })
}) })
}) })
}) })
.and_then(|res| Ok(HttpResponse::Ok().json(res))) .and_then(|res| Ok(HttpResponse::Ok().json(res)))
.responder()
} }
/// Version 2: Calls 4 queries in parallel, as an asynchronous handler /// Version 2: Calls 4 queries in parallel, as an asynchronous handler
/// Returning Error types turn into None values in the response /// Returning Error types turn into None values in the response
fn parallel_weather(state: State<AppState>) -> FutureResponse<HttpResponse> { fn parallel_weather(
db: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = AWError> {
let fut_result = vec![ let fut_result = vec![
Box::new(state.db.send(Queries::GetTopTenHottestYears)), Box::new(db::execute(&db, Queries::GetTopTenHottestYears)),
Box::new(state.db.send(Queries::GetTopTenColdestYears)), Box::new(db::execute(&db, Queries::GetTopTenColdestYears)),
Box::new(state.db.send(Queries::GetTopTenHottestMonths)), Box::new(db::execute(&db, Queries::GetTopTenHottestMonths)),
Box::new(state.db.send(Queries::GetTopTenColdestMonths)), Box::new(db::execute(&db, Queries::GetTopTenColdestMonths)),
]; ];
join_all(fut_result) join_all(fut_result)
.map_err(AWError::from) .map_err(AWError::from)
.and_then(|result| { .map(|result| HttpResponse::Ok().json(result))
let res: Vec<Option<Vec<WeatherAgg>>> =
result.into_iter().map(|x| x.ok()).collect();
Ok(HttpResponse::Ok().json(res))
})
.responder()
} }
fn main() { fn main() -> io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("parallel_db_example"); let sys = actix_rt::System::new("parallel_db_example");
// Start N db executor actors (N = number of cores avail) // Start N db executor actors (N = number of cores avail)
let manager = SqliteConnectionManager::file("weather.db"); let manager = SqliteConnectionManager::file("weather.db");
let pool = Pool::new(manager).unwrap(); let pool = Pool::new(manager).unwrap();
let addr = SyncArbiter::start(num_cpus::get(), move || DbExecutor(pool.clone()));
// Start http server // Start http server
server::new(move || { HttpServer::new(move || {
App::with_state(AppState{db: addr.clone()}) App::new()
// enable logger .data(pool.clone())
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
.resource("/asyncio_weather", |r| .service(
r.method(http::Method::GET) web::resource("/asyncio_weather")
.with(asyncio_weather)) .route(web::get().to_async(asyncio_weather)),
.resource("/parallel_weather", |r| )
r.method(http::Method::GET) .service(
.with(parallel_weather)) web::resource("/parallel_weather")
}).bind("127.0.0.1:8080") .route(web::get().to_async(parallel_weather)),
.unwrap() )
})
.bind("127.0.0.1:8080")?
.start(); .start();
println!("Started http server: 127.0.0.1:8080"); println!("Started http server: 127.0.0.1:8080");
let _ = sys.run(); sys.run()
} }

View File

@ -2,10 +2,12 @@
name = "awc_examples" name = "awc_examples"
version = "0.1.0" version = "0.1.0"
authors = ["dowwie <dkcdkg@gmail.com>"] authors = ["dowwie <dkcdkg@gmail.com>"]
edition = "2018"
workspace = ".."
[dependencies] [dependencies]
actix = "0.7" actix-rt = "0.2"
actix-web = { version="0.7.3", features=["rust-tls"] } actix-web = { version="1.0.0-alpha.1", features=["ssl"] }
futures = "0.1" futures = "0.1"
serde = "1.0.43" serde = "1.0.43"

View File

@ -15,25 +15,18 @@
// There are 2 versions in this example, one that uses Boxed Futures and the // There are 2 versions in this example, one that uses Boxed Futures and the
// other that uses Impl Future, available since rustc v1.26. // other that uses Impl Future, available since rustc v1.26.
extern crate actix;
extern crate actix_web;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[macro_use] #[macro_use]
extern crate validator_derive; extern crate validator_derive;
extern crate env_logger; #[macro_use]
extern crate futures; extern crate serde_derive;
extern crate validator;
use actix_web::{
client, http::Method, server, App, AsyncResponder, Error, HttpMessage, HttpResponse,
Json, error::ErrorBadRequest,
};
use futures::{future::ok as fut_ok, future::result as fut_result, Future};
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Duration; use std::io;
use actix_web::client::Client;
use actix_web::web::BytesMut;
use actix_web::{web, App, Error, HttpResponse, HttpServer};
use futures::{Future, Stream};
use validator::Validate; use validator::Validate;
#[derive(Debug, Validate, Deserialize, Serialize)] #[derive(Debug, Validate, Deserialize, Serialize)]
@ -61,43 +54,47 @@ struct HttpBinResponse {
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
/// post json to httpbin, get it back in the response body, return deserialized /// post json to httpbin, get it back in the response body, return deserialized
fn step_x_v1(data: SomeData) -> Box<Future<Item = SomeData, Error = Error>> { fn step_x_v1(
data: SomeData,
client: &Client,
) -> Box<Future<Item = SomeData, Error = Error>> {
Box::new( Box::new(
fut_result(data.validate()) // <- call .validate() on data to validate the parameters client
.map_err(ErrorBadRequest) // - convert ValidationErrors to an Error .post("https://httpbin.org/post")
.and_then(|_| { .send_json(data)
client::ClientRequest::post("https://httpbin.org/post")
.json(data).unwrap()
.send()
.conn_timeout(Duration::from_secs(10))
.map_err(Error::from) // <- convert SendRequestError to an Error .map_err(Error::from) // <- convert SendRequestError to an Error
.and_then( .and_then(|resp| {
|resp| resp.body() // <- this is MessageBody type, resolves to complete body resp // <- this is MessageBody type, resolves to complete body
.from_err() // <- convert PayloadError to an Error .from_err() // <- convert PayloadError to an Error
.and_then(|body| { .fold(BytesMut::new(), |mut acc, chunk| {
let resp: HttpBinResponse = serde_json::from_slice(&body).unwrap(); acc.extend_from_slice(&chunk);
fut_ok(resp.json) Ok::<_, Error>(acc)
}) })
) .map(|body| {
let body: HttpBinResponse =
serde_json::from_slice(&body).unwrap();
body.json
}) })
}),
) )
} }
fn create_something_v1( fn create_something_v1(
some_data: Json<SomeData>, some_data: web::Json<SomeData>,
client: web::Data<Client>,
) -> Box<Future<Item = HttpResponse, Error = Error>> { ) -> Box<Future<Item = HttpResponse, Error = Error>> {
step_x_v1(some_data.into_inner()) Box::new(
.and_then(|some_data_2| { step_x_v1(some_data.into_inner(), &client).and_then(move |some_data_2| {
step_x_v1(some_data_2).and_then(|some_data_3| { step_x_v1(some_data_2, &client).and_then(move |some_data_3| {
step_x_v1(some_data_3).and_then(|d| { step_x_v1(some_data_3, &client).and_then(|d| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(serde_json::to_string(&d).unwrap()) .body(serde_json::to_string(&d).unwrap())
.into()) .into())
}) })
}) })
}) }),
.responder() )
} }
// --------------------------------------------------------------- // ---------------------------------------------------------------
@ -105,32 +102,34 @@ fn create_something_v1(
// --------------------------------------------------------------- // ---------------------------------------------------------------
/// post json to httpbin, get it back in the response body, return deserialized /// post json to httpbin, get it back in the response body, return deserialized
fn step_x_v2(data: SomeData) -> impl Future<Item = SomeData, Error = Error> { fn step_x_v2(
fut_result(data.validate()) // <- call .validate() on data to validate the parameters data: SomeData,
.map_err(ErrorBadRequest) // - convert ValidationErrors to an Error client: &Client,
.and_then(|_| { ) -> impl Future<Item = SomeData, Error = Error> {
client::ClientRequest::post("https://httpbin.org/post") client
.json(data).unwrap() .post("https://httpbin.org/post")
.send() .send_json(data)
.conn_timeout(Duration::from_secs(10))
.map_err(Error::from) // <- convert SendRequestError to an Error .map_err(Error::from) // <- convert SendRequestError to an Error
.and_then( .and_then(|resp| {
|resp| resp.body() // <- this is MessageBody type, resolves to complete body resp.from_err()
.from_err() // <- convert PayloadError to an Error .fold(BytesMut::new(), |mut acc, chunk| {
.and_then(|body| { acc.extend_from_slice(&chunk);
let resp: HttpBinResponse = serde_json::from_slice(&body).unwrap(); Ok::<_, Error>(acc)
fut_ok(resp.json) })
.map(|body| {
let body: HttpBinResponse = serde_json::from_slice(&body).unwrap();
body.json
}) })
)
}) })
} }
fn create_something_v2( fn create_something_v2(
some_data: Json<SomeData>, some_data: web::Json<SomeData>,
client: web::Data<Client>,
) -> impl Future<Item = HttpResponse, Error = Error> { ) -> impl Future<Item = HttpResponse, Error = Error> {
step_x_v2(some_data.into_inner()).and_then(|some_data_2| { step_x_v2(some_data.into_inner(), &client).and_then(move |some_data_2| {
step_x_v2(some_data_2).and_then(|some_data_3| { step_x_v2(some_data_2, &client).and_then(move |some_data_3| {
step_x_v2(some_data_3).and_then(|d| { step_x_v2(some_data_3, &client).and_then(|d| {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(serde_json::to_string(&d).unwrap()) .body(serde_json::to_string(&d).unwrap())
@ -140,23 +139,22 @@ fn create_something_v2(
}) })
} }
fn main() { fn main() -> io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("asyncio_example");
server::new(move || { HttpServer::new(|| {
App::new() App::new()
.resource("/something_v1", |r| { .data(Client::default())
r.method(Method::POST).with(create_something_v1) .service(
web::resource("/something_v1")
.route(web::post().to(create_something_v1)),
)
.service(
web::resource("/something_v2")
.route(web::post().to_async(create_something_v2)),
)
}) })
.resource("/something_v2", |r| { .bind("127.0.0.1:8088")?
r.method(Method::POST).with_async(create_something_v2) .run()
})
}).bind("127.0.0.1:8088")
.unwrap()
.start();
println!("Started http server: 127.0.0.1:8088");
let _ = sys.run();
} }

View File

@ -1,13 +1,16 @@
[package] [package]
name = "basics" name = "basics"
version = "0.1.0" version = "1.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
actix = "0.7" actix-rt = "0.2"
actix-web = "0.7" actix-web = "1.0.0-alpha.1"
actix-files = "0.1.0-alpha.1"
actix-session = "0.1.0-alpha.1"
futures = "0.1" futures = "0.1.25"
env_logger = "0.5" env_logger = "0.5"
bytes = "0.4" bytes = "0.4"

View File

@ -1,43 +1,39 @@
#![allow(unused_variables)] #[macro_use]
#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate bytes;
extern crate env_logger;
extern crate futures;
use bytes::Bytes;
use futures::sync::mpsc;
use futures::Stream;
use actix_web::http::{header, Method, StatusCode};
use actix_web::middleware::session::{self, RequestSession};
use actix_web::{
error, fs, middleware, pred, server, App, Error, HttpRequest, HttpResponse, Path,
Result,
};
use futures::future::{result, FutureResult};
use std::{env, io}; use std::{env, io};
use actix_files as fs;
use actix_session::{CookieSession, Session};
use actix_web::http::{header, Method, StatusCode};
use actix_web::{
error, guard, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
Result,
};
use bytes::Bytes;
use futures::unsync::mpsc;
use futures::{future::ok, Future, Stream};
/// favicon handler /// favicon handler
fn favicon(req: &HttpRequest) -> Result<fs::NamedFile> { #[get("/favicon")]
fn favicon() -> Result<fs::NamedFile> {
Ok(fs::NamedFile::open("static/favicon.ico")?) Ok(fs::NamedFile::open("static/favicon.ico")?)
} }
/// simple index handler /// simple index handler
fn welcome(req: &HttpRequest) -> Result<HttpResponse> { #[get("/welcome")]
fn welcome(session: Session, req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req); println!("{:?}", req);
// session // session
let mut counter = 1; let mut counter = 1;
if let Some(count) = req.session().get::<i32>("counter")? { if let Some(count) = session.get::<i32>("counter")? {
println!("SESSION value: {}", count); println!("SESSION value: {}", count);
counter = count + 1; counter = count + 1;
} }
// set counter to session // set counter to session
req.session().set("counter", counter)?; session.set("counter", counter)?;
// response // response
Ok(HttpResponse::build(StatusCode::OK) Ok(HttpResponse::build(StatusCode::OK)
@ -46,97 +42,102 @@ fn welcome(req: &HttpRequest) -> Result<HttpResponse> {
} }
/// 404 handler /// 404 handler
fn p404(req: &HttpRequest) -> Result<fs::NamedFile> { fn p404() -> Result<fs::NamedFile> {
Ok(fs::NamedFile::open("static/404.html")?.set_status_code(StatusCode::NOT_FOUND)) Ok(fs::NamedFile::open("static/404.html")?.set_status_code(StatusCode::NOT_FOUND))
} }
/// async handler /// async handler
fn index_async(req: &HttpRequest) -> FutureResult<HttpResponse, Error> { fn index_async(req: HttpRequest) -> impl Future<Item = HttpResponse, Error = Error> {
println!("{:?}", req); println!("{:?}", req);
result(Ok(HttpResponse::Ok().content_type("text/html").body( ok(HttpResponse::Ok()
format!("Hello {}!", req.match_info().get("name").unwrap()), .content_type("text/html")
))) .body(format!("Hello {}!", req.match_info().get("name").unwrap())))
} }
/// async body /// async body
fn index_async_body(path: Path<String>) -> HttpResponse { fn index_async_body(path: web::Path<String>) -> HttpResponse {
let text = format!("Hello {}!", *path); let text = format!("Hello {}!", *path);
let (tx, rx_body) = mpsc::unbounded(); let (tx, rx_body) = mpsc::unbounded();
let _ = tx.unbounded_send(Bytes::from(text.as_bytes())); let _ = tx.unbounded_send(Bytes::from(text.as_bytes()));
HttpResponse::Ok() HttpResponse::Ok()
.streaming(rx_body.map_err(|e| error::ErrorBadRequest("bad request"))) .streaming(rx_body.map_err(|_| error::ErrorBadRequest("bad request")))
} }
/// handler with path parameters like `/user/{name}/` /// handler with path parameters like `/user/{name}/`
fn with_param(req: &HttpRequest) -> HttpResponse { fn with_param(req: HttpRequest, path: web::Path<(String,)>) -> HttpResponse {
println!("{:?}", req); println!("{:?}", req);
HttpResponse::Ok() HttpResponse::Ok()
.content_type("text/plain") .content_type("text/plain")
.body(format!("Hello {}!", req.match_info().get("name").unwrap())) .body(format!("Hello {}!", path.0))
} }
fn main() { fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "actix_web=debug"); env::set_var("RUST_LOG", "actix_web=debug");
env::set_var("RUST_BACKTRACE", "1");
env_logger::init(); env_logger::init();
let sys = actix::System::new("basic-example"); let sys = actix_rt::System::new("basic-example");
let addr = server::new( HttpServer::new(|| {
|| App::new() App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
// cookie session middleware // cookie session middleware
.middleware(session::SessionStorage::new( .wrap(CookieSession::signed(&[0; 32]).secure(false))
session::CookieSessionBackend::signed(&[0; 32]).secure(false)
))
// register favicon // register favicon
.resource("/favicon", |r| r.f(favicon)) .service(favicon)
// register simple route, handle all methods // register simple route, handle all methods
.resource("/welcome", |r| r.f(welcome)) .service(welcome)
// with path parameters // with path parameters
.resource("/user/{name}", |r| r.method(Method::GET).f(with_param)) .service(web::resource("/user/{name}").route(web::get().to(with_param)))
// async handler // async handler
.resource("/async/{name}", |r| r.method(Method::GET).a(index_async)) .service(
web::resource("/async/{name}").route(web::get().to_async(index_async)),
)
// async handler // async handler
.resource("/async-body/{name}", |r| r.method(Method::GET).with(index_async_body)) .service(
.resource("/test", |r| r.f(|req| { web::resource("/async-body/{name}")
match *req.method() { .route(web::get().to(index_async_body)),
)
.service(
web::resource("/test").to(|req: HttpRequest| match *req.method() {
Method::GET => HttpResponse::Ok(), Method::GET => HttpResponse::Ok(),
Method::POST => HttpResponse::MethodNotAllowed(), Method::POST => HttpResponse::MethodNotAllowed(),
_ => HttpResponse::NotFound(), _ => HttpResponse::NotFound(),
} }),
})) )
.resource("/error", |r| r.f(|req| { .service(web::resource("/error").to(|| {
error::InternalError::new( error::InternalError::new(
io::Error::new(io::ErrorKind::Other, "test"), StatusCode::INTERNAL_SERVER_ERROR) io::Error::new(io::ErrorKind::Other, "test"),
StatusCode::INTERNAL_SERVER_ERROR,
)
})) }))
// static files // static files
.handler("/static", fs::StaticFiles::new("static").unwrap()) .service(fs::Files::new("/static", "static").show_files_listing())
// redirect // redirect
.resource("/", |r| r.method(Method::GET).f(|req| { .service(web::resource("/").route(web::get().to(|req: HttpRequest| {
println!("{:?}", req); println!("{:?}", req);
HttpResponse::Found() HttpResponse::Found()
.header(header::LOCATION, "static/welcome.html") .header(header::LOCATION, "static/welcome.html")
.finish() .finish()
})) })))
// default // default
.default_resource(|r| { .default_resource(|r| {
// 404 for GET request // 404 for GET request
r.method(Method::GET).f(p404); r.route(web::get().to(p404))
// all requests that are not `GET` // all requests that are not `GET`
r.route().filter(pred::Not(pred::Get())).f( .route(
|req| HttpResponse::MethodNotAllowed()); web::route()
})) .guard(guard::Not(guard::Get()))
.to(|| HttpResponse::MethodNotAllowed()),
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080") )
.shutdown_timeout(0) // <- Set shutdown timeout to 0 seconds (default 60s) })
})
.bind("127.0.0.1:8080")?
.start(); .start();
println!("Starting http server: 127.0.0.1:8080"); println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run(); sys.run()
} }

View File

@ -1,14 +0,0 @@
[package]
name = "cookie-auth-full"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../"
[dependencies]
actix = "0.7"
actix-web = "0.7"
cookie = { version="0.11", features=["percent-encode", "secure"] }
futures = "0.1"
time = "0.1"
env_logger = "0.5"

View File

@ -1,274 +0,0 @@
#![allow(dead_code)]
use std::rc::Rc;
use cookie::{Cookie, CookieJar, Key};
use futures::future::{err as FutErr, ok as FutOk, FutureResult};
use futures::Future;
use time::Duration;
use actix_web::http::header::{self, HeaderValue};
use actix_web::middleware::{Middleware, Response, Started};
use actix_web::{Error, HttpRequest, HttpResponse, Result};
/// Trait provides identity service for the request.
pub trait RequestIdentity {
/// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<String>;
/// Remember identity.
fn remember(&self, identity: String);
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&self);
}
impl<S> RequestIdentity for HttpRequest<S> {
fn identity(&self) -> Option<String> {
if let Some(id) = self.extensions().get::<IdentityBox>() {
return id.0.identity().map(|s| s.to_owned());
}
None
}
fn remember(&self, identity: String) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.as_mut().remember(identity);
}
}
fn forget(&self) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.forget();
}
}
}
/// An identity
pub trait Identity: 'static {
fn identity(&self) -> Option<&str>;
fn remember(&mut self, key: String);
fn forget(&mut self);
/// Write session to storage backend.
fn write(&mut self, resp: HttpResponse) -> Result<Response>;
}
/// Identity policy definition.
pub trait IdentityPolicy<S>: Sized + 'static {
type Identity: Identity;
type Future: Future<Item = Self::Identity, Error = Error>;
/// Parse the session from request and load data from a service identity.
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::Future;
}
/// Middleware that implements identity service
pub struct IdentityService<T> {
backend: T,
}
impl<T> IdentityService<T> {
/// Create new identity service with specified backend.
pub fn new(backend: T) -> Self {
IdentityService { backend }
}
}
struct IdentityBox(Box<Identity>);
#[doc(hidden)]
unsafe impl Send for IdentityBox {}
#[doc(hidden)]
unsafe impl Sync for IdentityBox {}
impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
let mut req = req.clone();
let fut = self
.backend
.from_request(&mut req)
.then(move |res| match res {
Ok(id) => {
req.extensions_mut().insert(IdentityBox(Box::new(id)));
FutOk(None)
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut)))
}
fn response(&self, req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
if let Some(mut id) = req.extensions_mut().remove::<IdentityBox>() {
id.0.write(resp)
} else {
Ok(Response::Done(resp))
}
}
}
/// Identity that uses private cookies as identity storage
pub struct CookieIdentity {
changed: bool,
identity: Option<String>,
inner: Rc<CookieIdentityInner>,
}
impl Identity for CookieIdentity {
fn identity(&self) -> Option<&str> {
self.identity.as_ref().map(|s| s.as_ref())
}
fn remember(&mut self, value: String) {
self.changed = true;
self.identity = Some(value);
}
fn forget(&mut self) {
self.changed = true;
self.identity = None;
}
fn write(&mut self, mut resp: HttpResponse) -> Result<Response> {
if self.changed {
let _ = self.inner.set_cookie(&mut resp, self.identity.take());
}
Ok(Response::Done(resp))
}
}
struct CookieIdentityInner {
key: Key,
name: String,
path: String,
domain: Option<String>,
secure: bool,
max_age: Option<Duration>,
}
impl CookieIdentityInner {
fn new(key: &[u8]) -> CookieIdentityInner {
CookieIdentityInner {
key: Key::from_master(key),
name: "actix-identity".to_owned(),
path: "/".to_owned(),
domain: None,
secure: true,
max_age: None,
}
}
fn set_cookie(&self, resp: &mut HttpResponse, id: Option<String>) -> Result<()> {
let some = id.is_some();
{
let id = id.unwrap_or_else(|| String::new());
let mut cookie = Cookie::new(self.name.clone(), id);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(true);
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
}
if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age);
}
let mut jar = CookieJar::new();
if some {
jar.private(&self.key).add(cookie);
} else {
jar.add_original(cookie.clone());
jar.private(&self.key).remove(cookie);
}
for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.to_string())?;
resp.headers_mut().append(header::SET_COOKIE, val);
}
}
Ok(())
}
fn load<S>(&self, req: &mut HttpRequest<S>) -> Option<String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies.iter() {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
let cookie_opt = jar.private(&self.key).get(&self.name);
if let Some(cookie) = cookie_opt {
return Some(cookie.value().into());
}
}
}
}
None
}
}
/// Use cookies for request identity.
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
impl CookieIdentityPolicy {
/// Construct new `CookieIdentityPolicy` instance.
///
/// Panics if key length is less than 32 bytes.
pub fn new(key: &[u8]) -> CookieIdentityPolicy {
CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key)))
}
/// Sets the `path` field in the session cookie being built.
pub fn path<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().path = value.into();
self
}
/// Sets the `name` field in the session cookie being built.
pub fn name<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().name = value.into();
self
}
/// Sets the `domain` field in the session cookie being built.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
self
}
/// Sets the `secure` field in the session cookie being built.
///
/// If the `secure` field is set, a cookie will only be transmitted when the
/// connection is secure - i.e. `https`
pub fn secure(mut self, value: bool) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().secure = value;
self
}
/// Sets the `max-age` field in the session cookie being built.
pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self
}
}
impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
type Identity = CookieIdentity;
type Future = FutureResult<CookieIdentity, Error>;
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::Future {
let identity = self.0.load(req);
FutOk(CookieIdentity {
identity,
changed: false,
inner: Rc::clone(&self.0),
})
}
}

View File

@ -1,49 +0,0 @@
extern crate actix;
extern crate actix_web;
extern crate cookie;
extern crate env_logger;
extern crate futures;
extern crate time;
use actix_web::{middleware, server, App, HttpRequest, HttpResponse};
mod auth;
use auth::{CookieIdentityPolicy, IdentityService, RequestIdentity};
fn index(req: &HttpRequest) -> String {
format!("Hello {}", req.identity().unwrap_or("Anonymous".to_owned()))
}
fn login(req: &HttpRequest) -> HttpResponse {
req.remember("user1".to_owned());
HttpResponse::Found().header("location", "/").finish()
}
fn logout(req: &HttpRequest) -> HttpResponse {
req.forget();
HttpResponse::Found().header("location", "/").finish()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("cookie-auth");
server::new(|| {
App::new()
.middleware(middleware::Logger::default())
.middleware(IdentityService::new(
CookieIdentityPolicy::new(&[0; 32])
.name("auth-example")
.secure(false),
))
.resource("/login", |r| r.f(login))
.resource("/logout", |r| r.f(logout))
.resource("/", |r| r.f(index))
}).bind("127.0.0.1:8080")
.unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
}

View File

@ -2,9 +2,9 @@
name = "cookie-auth" name = "cookie-auth"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" edition = "2018"
workspace = ".."
[dependencies] [dependencies]
actix = "0.7" actix-web = "1.0.0-alpha.1"
actix-web = "0.7" env_logger = "0.6"
env_logger = "0.5"

View File

@ -1,45 +1,37 @@
extern crate actix; use actix_web::middleware::identity::Identity;
extern crate actix_web;
extern crate env_logger;
use actix_web::{middleware, server, App, HttpRequest, HttpResponse};
use actix_web::middleware::identity::RequestIdentity;
use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService}; use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{middleware, web, App, HttpResponse, HttpServer};
fn index(req: &HttpRequest) -> String { fn index(id: Identity) -> String {
format!("Hello {}", req.identity().unwrap_or("Anonymous".to_owned())) format!("Hello {}", id.identity().unwrap_or("Anonymous".to_owned()))
} }
fn login(req: &HttpRequest) -> HttpResponse { fn login(id: Identity) -> HttpResponse {
req.remember("user1".to_owned()); id.remember("user1".to_owned());
HttpResponse::Found().header("location", "/").finish() HttpResponse::Found().header("location", "/").finish()
} }
fn logout(req: &HttpRequest) -> HttpResponse { fn logout(id: Identity) -> HttpResponse {
req.forget(); id.forget();
HttpResponse::Found().header("location", "/").finish() HttpResponse::Found().header("location", "/").finish()
} }
fn main() { fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("cookie-auth");
server::new(|| { HttpServer::new(|| {
App::new() App::new()
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
.middleware(IdentityService::new( .wrap(IdentityService::new(
CookieIdentityPolicy::new(&[0; 32]) CookieIdentityPolicy::new(&[0; 32])
.name("auth-example") .name("auth-example")
.secure(false), .secure(false),
)) ))
.resource("/login", |r| r.f(login)) .service(web::resource("/login").route(web::post().to(login)))
.resource("/logout", |r| r.f(logout)) .service(web::resource("/logout").to(logout))
.resource("/", |r| r.f(index)) .service(web::resource("/").route(web::get().to(index)))
}).bind("127.0.0.1:8080") })
.unwrap() .bind("127.0.0.1:8080")?
.start(); .run()
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
} }

View File

@ -2,12 +2,13 @@
name = "cookie-session" name = "cookie-session"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
actix = "0.7" actix-web = "1.0.0-alpha.1"
actix-web = "^0.7" actix-session = "0.1.0-alpha.1"
futures = "0.1" futures = "0.1"
time = "0.1" time = "0.1"
env_logger = "0.5" env_logger = "0.6"

View File

@ -5,50 +5,38 @@
//! //!
//! [User guide](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions) //! [User guide](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions)
extern crate actix; use actix_session::{CookieSession, Session};
extern crate actix_web; use actix_web::{middleware::Logger, web, App, HttpRequest, HttpServer, Result};
extern crate env_logger;
extern crate futures;
use actix_web::middleware::session::{self, RequestSession};
use actix_web::{middleware, server, App, HttpRequest, Result};
use std::env;
/// simple index handler with session /// simple index handler with session
fn index(req: &HttpRequest) -> Result<&'static str> { fn index(session: Session, req: HttpRequest) -> Result<&'static str> {
println!("{:?}", req); println!("{:?}", req);
// RequestSession trait is used for session access // RequestSession trait is used for session access
let mut counter = 1; let mut counter = 1;
if let Some(count) = req.session().get::<i32>("counter")? { if let Some(count) = session.get::<i32>("counter")? {
println!("SESSION value: {}", count); println!("SESSION value: {}", count);
counter = count + 1; counter = count + 1;
req.session().set("counter", counter)?; session.set("counter", counter)?;
} else { } else {
req.session().set("counter", counter)?; session.set("counter", counter)?;
} }
Ok("welcome!") Ok("welcome!")
} }
fn main() { fn main() -> std::io::Result<()> {
env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("session-example");
server::new(|| { HttpServer::new(|| {
App::new() App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .wrap(Logger::default())
// cookie session middleware // cookie session middleware
.middleware(session::SessionStorage::new( .wrap(CookieSession::signed(&[0; 32]).secure(false))
session::CookieSessionBackend::signed(&[0; 32]).secure(false) .service(web::resource("/").to(index))
)) })
.resource("/", |r| r.f(index)) .bind("127.0.0.1:8080")?
}).bind("127.0.0.1:8080") .run()
.expect("Can not bind to 127.0.0.1:8080")
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
} }

View File

@ -2,15 +2,14 @@
name = "diesel-example" name = "diesel-example"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.1"
bytes = "0.4" bytes = "0.4"
env_logger = "0.5" env_logger = "0.6"
actix = "0.7"
actix-web = "0.7"
futures = "0.1" futures = "0.1"
uuid = { version = "0.5", features = ["serde", "v4"] } uuid = { version = "0.5", features = ["serde", "v4"] }
serde = "1.0" serde = "1.0"

View File

@ -1,55 +0,0 @@
//! Db executor actor
use actix::prelude::*;
use actix_web::*;
use diesel;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
use uuid;
use models;
use schema;
/// This is db executor actor. We are going to run 3 of them in parallel.
pub struct DbExecutor(pub Pool<ConnectionManager<SqliteConnection>>);
/// This is only message that this actor can handle, but it is easy to extend
/// number of messages.
pub struct CreateUser {
pub name: String,
}
impl Message for CreateUser {
type Result = Result<models::User, Error>;
}
impl Actor for DbExecutor {
type Context = SyncContext<Self>;
}
impl Handler<CreateUser> for DbExecutor {
type Result = Result<models::User, Error>;
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
use self::schema::users::dsl::*;
let uuid = format!("{}", uuid::Uuid::new_v4());
let new_user = models::NewUser {
id: &uuid,
name: &msg.name,
};
let conn: &SqliteConnection = &self.0.get().unwrap();
diesel::insert_into(users)
.values(&new_user)
.execute(conn)
.map_err(|_| error::ErrorInternalServerError("Error inserting person"))?;
let mut items = users
.filter(id.eq(&uuid))
.load::<models::User>(conn)
.map_err(|_| error::ErrorInternalServerError("Error loading person"))?;
Ok(items.pop().unwrap())
}
}

View File

@ -4,77 +4,71 @@
//! Actix supports sync actors by default, so we going to create sync actor //! Actix supports sync actors by default, so we going to create sync actor
//! that use diesel. Technically sync actors are worker style actors, multiple //! that use diesel. Technically sync actors are worker style actors, multiple
//! of them can run in parallel and process messages from same queue. //! of them can run in parallel and process messages from same queue.
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
extern crate actix; #[macro_use]
extern crate actix_web; extern crate serde_derive;
extern crate env_logger;
extern crate futures;
extern crate r2d2;
extern crate uuid;
extern crate bytes;
// extern crate json;
use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer};
use bytes::BytesMut; use bytes::BytesMut;
use actix::prelude::*;
use actix_web::{
http, middleware, server, App, AsyncResponder, FutureResponse, HttpResponse, Path, Error, HttpRequest,
State, HttpMessage, error, Json
};
use diesel::prelude::*; use diesel::prelude::*;
use diesel::r2d2::ConnectionManager; use diesel::r2d2::{self, ConnectionManager};
use futures::{future, Future, Stream}; use futures::future::{err, Either};
use futures::{Future, Stream};
mod db;
mod models; mod models;
mod schema; mod schema;
use db::{CreateUser, DbExecutor}; type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
/// State with DbExecutor address /// Diesel query
struct AppState { fn query(
db: Addr<DbExecutor>, nm: String,
pool: web::Data<Pool>,
) -> Result<models::User, diesel::result::Error> {
use self::schema::users::dsl::*;
let uuid = format!("{}", uuid::Uuid::new_v4());
let new_user = models::NewUser {
id: &uuid,
name: nm.as_str(),
};
let conn: &SqliteConnection = &pool.get().unwrap();
diesel::insert_into(users).values(&new_user).execute(conn)?;
let mut items = users.filter(id.eq(&uuid)).load::<models::User>(conn)?;
Ok(items.pop().unwrap())
} }
/// Async request handler /// Async request handler
fn add( fn add(
(name, state): (Path<String>, State<AppState>), name: web::Path<String>,
) -> FutureResponse<HttpResponse> { pool: web::Data<Pool>,
// send async `CreateUser` message to a `DbExecutor` ) -> impl Future<Item = HttpResponse, Error = Error> {
state // run diesel blocking code
.db web::block(move || query(name.into_inner(), pool)).then(|res| match res {
.send(CreateUser {
name: name.into_inner(),
})
.from_err()
.and_then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)), Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()), Err(_) => Ok(HttpResponse::InternalServerError().into()),
}) })
.responder()
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct MyUser { struct MyUser {
name: String name: String,
} }
const MAX_SIZE: usize = 262_144; // max payload size is 256k const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// This handler manually load request payload and parse json object /// This handler manually load request payload and parse json object
fn index_add((req, state): (HttpRequest<AppState>, State<AppState>)) -> impl Future<Item = HttpResponse, Error = Error> { fn index_add(
// HttpRequest::payload() is stream of Bytes objects pl: web::Payload,
req.payload() pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = Error> {
pl
// `Future::from_err` acts like `?` in that it coerces the error type from // `Future::from_err` acts like `?` in that it coerces the error type from
// the future into the final error type // the future into the final error type
.from_err() .from_err()
// `fold` will asynchronously read each chunk of the request body and // `fold` will asynchronously read each chunk of the request body and
// call supplied closure, then it resolves to result of closure // call supplied closure, then it resolves to result of closure
.fold(BytesMut::new(), move |mut body, chunk| { .fold(BytesMut::new(), move |mut body, chunk| {
@ -92,45 +86,39 @@ fn index_add((req, state): (HttpRequest<AppState>, State<AppState>)) -> impl Fut
// Douman NOTE: // Douman NOTE:
// The return value in this closure helps, to clarify result for compiler // The return value in this closure helps, to clarify result for compiler
// as otheriwse it cannot understand it // as otheriwse it cannot understand it
.and_then(move |body| -> Box<Future<Item = HttpResponse, Error = Error>> { .and_then(move |body| {
// body is loaded, now we can deserialize serde-json // body is loaded, now we can deserialize serde-json
let r_obj = serde_json::from_slice::<MyUser>(&body); let r_obj = serde_json::from_slice::<MyUser>(&body);
// Send to the db for create // Send to the db for create
match r_obj { match r_obj {
Ok(obj) => { Ok(obj) => {
let res = state.db.send(CreateUser { name: obj.name, }) Either::A(web::block(move || query(obj.name, pool)).then(|res| {
.from_err() match res {
.and_then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)), Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()), Err(_) => Ok(HttpResponse::InternalServerError().into()),
});
Box::new(res)
} }
Err(_) => Box::new(future::err(error::ErrorBadRequest("Json Decode Failed"))) }))
}
Err(_) => Either::B(err(error::ErrorBadRequest("Json Decode Failed"))),
} }
}) })
} }
fn add2((item, state): (Json<MyUser>, State<AppState>)) -> impl Future<Item = HttpResponse, Error = Error> { fn add2(
state.db item: web::Json<MyUser>,
.send(CreateUser { pool: web::Data<Pool>,
// into_inner to move into the reference, then accessing name to ) -> impl Future<Item = HttpResponse, Error = Error> {
// move the name out. // run diesel blocking code
name: item.into_inner().name, web::block(move || query(item.into_inner().name, pool)).then(|res| match res {
})
.from_err()
.and_then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)), Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()), Err(_) => Ok(HttpResponse::InternalServerError().into()),
}) })
} }
fn main() { fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("diesel-example");
// Start 3 db executor actors // Start 3 db executor actors
let manager = ConnectionManager::<SqliteConnection>::new("test.db"); let manager = ConnectionManager::<SqliteConnection>::new("test.db");
@ -138,32 +126,40 @@ fn main() {
.build(manager) .build(manager)
.expect("Failed to create pool."); .expect("Failed to create pool.");
let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone()));
// Start http server // Start http server
server::new(move || { HttpServer::new(move || {
App::with_state(AppState{db: addr.clone()}) App::new()
.data(pool.clone())
// enable logger // enable logger
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
// This can be called with: // This can be called with:
// curl -S --header "Content-Type: application/json" --request POST --data '{"name":"xyz"}' http://127.0.0.1:8080/add // curl -S --header "Content-Type: application/json" --request POST --data '{"name":"xyz"}' http://127.0.0.1:8080/add
// Use of the extractors makes some post conditions simpler such // Use of the extractors makes some post conditions simpler such
// as size limit protections and built in json validation. // as size limit protections and built in json validation.
.resource("/add2", |r| { .service(
r.method(http::Method::POST) web::resource("/add2").route(
.with_async_config(add2, |(json_cfg, )| { web::post()
json_cfg.0.limit(4096); // <- limit size of the payload .data(
}) web::JsonConfig::default()
}) .limit(4096) // <- limit size of the payload
.error_handler(|err, _| {
// <- create custom error response
error::InternalError::from_response(
err,
HttpResponse::Conflict().finish(),
)
.into()
}),
)
.to_async(add2),
),
)
// Manual parsing would allow custom error construction, use of // Manual parsing would allow custom error construction, use of
// other parsers *beside* json (for example CBOR, protobuf, xml), and allows // other parsers *beside* json (for example CBOR, protobuf, xml), and allows
// an application to standardise on a single parser implementation. // an application to standardise on a single parser implementation.
.resource("/add", |r| r.method(http::Method::POST).with_async(index_add)) .service(web::resource("/add").route(web::post().to_async(index_add)))
.resource("/add/{name}", |r| r.method(http::Method::GET).with(add)) .service(web::resource("/add/{name}").route(web::get().to_async(add)))
}).bind("127.0.0.1:8080") })
.unwrap() .bind("127.0.0.1:8080")?
.start(); .run()
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
} }

View File

@ -2,11 +2,13 @@
name = "error_handling" name = "error_handling"
version = "0.1.0" version = "0.1.0"
authors = ["dowwie <dkcdkg@gmail.com>"] authors = ["dowwie <dkcdkg@gmail.com>"]
edition = "2018"
workspace = ".."
[dependencies] [dependencies]
actix = "0.7.3" actix-web = "1.0.0-alpha.1"
actix-web = "0.7.3"
failure = "0.1.2" derive_more = "0.14.0"
futures = "0.1.23" futures = "0.1.23"
rand = "0.5.4" rand = "0.5.4"
env_logger = "0.5.12" env_logger = "0.6"

View File

@ -1,6 +1,6 @@
/* /*
The goal of this example is to show how to propagate a custom error type, derived The goal of this example is to show how to propagate a custom error type,
from the Fail trait, to a web handler that will evaluate the type of error that to a web handler that will evaluate the type of error that
was raised and return an appropriate HTTPResponse. was raised and return an appropriate HTTPResponse.
This example uses a 50/50 chance of returning 200 Ok, otherwise one of four possible This example uses a 50/50 chance of returning 200 Ok, otherwise one of four possible
@ -12,128 +12,91 @@ http errors will be chosen, each with an equal chance of being selected:
*/ */
use actix_web::{web, App, Error, HttpResponse, HttpServer, ResponseError};
extern crate actix; use derive_more::Display; // naming it clearly for illustration purposes
extern crate actix_web; use futures::future::{err, ok, Future};
extern crate env_logger; use rand::{
#[macro_use] extern crate failure; distributions::{Distribution, Standard},
extern crate futures; thread_rng, Rng,
extern crate rand;
use actix_web::{
http::Method, server, App, AsyncResponder, Error as ActixWebError,
HttpResponse, HttpRequest
}; };
use failure::Error as FailureError; // naming it clearly for illustration purposes
use futures::{
future::{
ok as fut_ok,
err as fut_err
},
Future
};
use rand::{thread_rng, Rng, distributions::{Distribution, Standard}};
#[derive(Debug, Display)]
#[derive(Fail, Debug)]
pub enum CustomError { pub enum CustomError {
#[fail(display = "Custom Error 1")] #[display(fmt = "Custom Error 1")]
CustomOne, CustomOne,
#[fail(display = "Custom Error 2")] #[display(fmt = "Custom Error 2")]
CustomTwo, CustomTwo,
#[fail(display = "Custom Error 3")] #[display(fmt = "Custom Error 3")]
CustomThree, CustomThree,
#[fail(display = "Custom Error 4")] #[display(fmt = "Custom Error 4")]
CustomFour CustomFour,
} }
impl Distribution<CustomError> for Standard { impl Distribution<CustomError> for Standard {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> CustomError { fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> CustomError {
match rng.gen_range(0, 4) { match rng.gen_range(0, 4) {
0 => CustomError::CustomOne, 0 => CustomError::CustomOne,
1 => CustomError::CustomTwo, 1 => CustomError::CustomTwo,
2 => CustomError::CustomThree, 2 => CustomError::CustomThree,
_ => CustomError::CustomFour _ => CustomError::CustomFour,
} }
} }
} }
/* /// Actix web uses `ResponseError` for conversion of errors to a response
impl ResponseError for CustomError { impl ResponseError for CustomError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR) match self {
CustomError::CustomOne => {
println!("do some stuff related to CustomOne error");
HttpResponse::Forbidden().finish()
} }
}
*/
CustomError::CustomTwo => {
println!("do some stuff related to CustomTwo error");
HttpResponse::Unauthorized().finish()
}
CustomError::CustomThree => {
println!("do some stuff related to CustomThree error");
HttpResponse::InternalServerError().finish()
}
_ => {
println!("do some stuff related to CustomFour error");
HttpResponse::BadRequest().finish()
}
}
}
}
/// randomly returns either () or one of the 4 CustomError variants /// randomly returns either () or one of the 4 CustomError variants
//fn do_something_random() -> impl Future<Item = Result<(), FailureError>,
// Error = ActixWebError> {
fn do_something_random() -> impl Future<Item = (), Error = CustomError> { fn do_something_random() -> impl Future<Item = (), Error = CustomError> {
let mut rng = thread_rng(); let mut rng = thread_rng();
// 20% chance that () will be returned by this function // 20% chance that () will be returned by this function
if rng.gen_bool(2.0 / 10.0) { if rng.gen_bool(2.0 / 10.0) {
return fut_ok(()) ok(())
} } else {
err(rand::random::<CustomError>())
let err: CustomError = rand::random();
return fut_err(err)
}
fn do_something(_req: HttpRequest)
-> impl Future<Item = HttpResponse, Error = ActixWebError> {
do_something_random()
.then(|result| match result {
Ok(_) => Ok(HttpResponse::Ok()
.body("Nothing interesting happened. Try again.")),
Err(err) => match err {
CustomError::CustomOne => {
println!("do some stuff related to CustomOne error");
Ok(HttpResponse::Forbidden().finish())
},
CustomError::CustomTwo => {
println!("do some stuff related to CustomTwo error");
Ok(HttpResponse::Unauthorized().finish())
},
CustomError::CustomThree => {
println!("do some stuff related to CustomThree error");
Ok(HttpResponse::InternalServerError().finish())
},
_ => {
println!("do some stuff related to CustomFour error");
Ok(HttpResponse::BadRequest().finish())
} }
} }
fn do_something() -> impl Future<Item = HttpResponse, Error = Error> {
do_something_random().from_err().and_then(|_| {
HttpResponse::Ok().body("Nothing interesting happened. Try again.")
}) })
.responder()
} }
fn main() -> std::io::Result<()> {
fn main() { std::env::set_var("RUST_LOG", "actix_web=info");
::std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("error_handling_example");
server::new(move || { HttpServer::new(move || {
App::new() App::new().service(
.resource("/something", |r| web::resource("/something").route(web::get().to_async(do_something)),
r.method(Method::GET) )
.with_async(do_something)) })
}).bind("127.0.0.1:8088") .bind("127.0.0.1:8088")?
.unwrap() .run()
.start();
println!("Started http server: 127.0.0.1:8088");
let _ = sys.run();
} }

View File

@ -2,10 +2,11 @@
name = "form-example" name = "form-example"
version = "0.1.0" version = "0.1.0"
authors = ["Gorm Casper <gcasper@gmail.com>"] authors = ["Gorm Casper <gcasper@gmail.com>"]
edition = "2018"
workspace = ".."
[dependencies] [dependencies]
actix = "0.7" actix-web = "1.0.0-alpha.1"
actix-web = "0.7"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"

View File

@ -1,46 +1,32 @@
extern crate actix;
extern crate actix_web;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
use actix_web::{ use actix_web::{
http, middleware, server, App, Form, HttpRequest, HttpResponse, Result, State, middleware, web, App, HttpRequest, HttpResponse, HttpServer, Responder, Result,
}; };
struct AppState { struct AppState {
foo: String, foo: String,
} }
fn main() { fn main() -> std::io::Result<()> {
let sys = actix::System::new("form-example"); HttpServer::new(|| {
App::new()
let _addr = server::new(|| { .data(AppState {
App::with_state(AppState {
foo: "bar".to_string(), foo: "bar".to_string(),
}).middleware(middleware::Logger::default())
.resource("/", |r| {
r.method(http::Method::GET).with(index);
}) })
.resource("/post1", |r| { .wrap(middleware::Logger::default())
r.method(http::Method::POST).with(handle_post_1) .service(web::resource("/").route(web::get().to(index)))
.service(web::resource("/post1").route(web::post().to(handle_post_1)))
.service(web::resource("/post2").route(web::post().to(handle_post_2)))
.service(web::resource("/post3").route(web::post().to(handle_post_3)))
}) })
.resource("/post2", |r| { .bind("127.0.0.1:8080")?
r.method(http::Method::POST).with(handle_post_2) .run()
})
.resource("/post3", |r| {
r.method(http::Method::POST).with(handle_post_3)
})
}).bind("127.0.0.1:8080")
.expect("Can not bind to 127.0.0.1:8080")
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
} }
fn index(_req: HttpRequest<AppState>) -> Result<HttpResponse> { fn index() -> Result<HttpResponse> {
Ok(HttpResponse::build(http::StatusCode::OK) Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(include_str!("../static/form.html"))) .body(include_str!("../static/form.html")))
} }
@ -51,30 +37,28 @@ pub struct MyParams {
} }
/// Simple handle POST request /// Simple handle POST request
fn handle_post_1(params: Form<MyParams>) -> Result<HttpResponse> { fn handle_post_1(params: web::Form<MyParams>) -> Result<HttpResponse> {
Ok(HttpResponse::build(http::StatusCode::OK) Ok(HttpResponse::Ok()
.content_type("text/plain") .content_type("text/plain")
.body(format!("Your name is {}", params.name))) .body(format!("Your name is {}", params.name)))
} }
/// State and POST Params /// State and POST Params
fn handle_post_2( fn handle_post_2(
(state, params): (State<AppState>, Form<MyParams>), state: web::Data<AppState>,
) -> Result<HttpResponse> { params: web::Form<MyParams>,
Ok(HttpResponse::build(http::StatusCode::OK) ) -> HttpResponse {
.content_type("text/plain") HttpResponse::Ok().content_type("text/plain").body(format!(
.body(format!(
"Your name is {}, and in AppState I have foo: {}", "Your name is {}, and in AppState I have foo: {}",
params.name, state.foo params.name, state.foo
))) ))
} }
/// Request and POST Params /// Request and POST Params
fn handle_post_3( fn handle_post_3(req: HttpRequest, params: web::Form<MyParams>) -> impl Responder {
(req, params): (HttpRequest<AppState>, Form<MyParams>),
) -> Result<HttpResponse> {
println!("Handling POST request: {:?}", req); println!("Handling POST request: {:?}", req);
Ok(HttpResponse::build(http::StatusCode::OK)
HttpResponse::Ok()
.content_type("text/plain") .content_type("text/plain")
.body(format!("Your name is {}", params.name))) .body(format!("Your name is {}", params.name))
} }

View File

@ -2,10 +2,9 @@
name = "hello-world" name = "hello-world"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
env_logger = "0.5" actix-web = "1.0.0-alpha.1"
env_logger = "0.6"
actix = "0.7"
actix-web = "^0.7"

View File

@ -1,28 +1,21 @@
extern crate actix; use actix_web::{middleware, web, App, HttpRequest, HttpServer};
extern crate actix_web;
extern crate env_logger;
use actix_web::{middleware, server, App, HttpRequest}; fn index(req: HttpRequest) -> &'static str {
println!("REQ: {:?}", req);
fn index(_req: &HttpRequest) -> &'static str {
"Hello world!" "Hello world!"
} }
fn main() { fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("hello-world");
server::new(|| { HttpServer::new(|| {
App::new() App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
.resource("/index.html", |r| r.f(|_| "Hello world!")) .service(web::resource("/index.html").to(|| "Hello world!"))
.resource("/", |r| r.f(index)) .service(web::resource("/").to(index))
}).bind("127.0.0.1:8080") })
.unwrap() .bind("127.0.0.1:8080")?
.start(); .run()
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
} }

View File

@ -2,10 +2,13 @@
name = "http-full-proxy" name = "http-full-proxy"
version = "0.1.0" version = "0.1.0"
authors = ["Rotem Yaari"] authors = ["Rotem Yaari"]
workspace = ".."
edition = "2018"
[dependencies] [dependencies]
actix = "0.7.5" actix-rt = "0.2"
actix-web = "0.7.13" actix-web = "1.0.0-alpha.1"
clap = "2.32.0" clap = "2.32.0"
futures = "0.1.25" futures = "0.1.25"
failure = "0.1.3" failure = "0.1.3"

View File

@ -1,85 +1,53 @@
#![deny(warnings)] use actix_web::client::Client;
extern crate actix; use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
extern crate actix_web;
extern crate clap;
extern crate failure;
extern crate futures;
extern crate url;
use actix_web::{
client, http, server, App, AsyncResponder, Error, HttpMessage, HttpRequest,
HttpResponse,
};
use clap::{value_t, Arg}; use clap::{value_t, Arg};
use futures::{future, Future}; use futures::Future;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use url::Url; use url::Url;
struct AppState {
forward_url: Url,
}
impl AppState {
pub fn init(forward_url: Url) -> AppState {
AppState { forward_url }
}
}
fn forward( fn forward(
req: &HttpRequest<AppState>, req: HttpRequest,
) -> Box<Future<Item = HttpResponse, Error = Error>> { payload: web::Payload,
let mut new_url = req.state().forward_url.clone(); url: web::Data<Url>,
client: web::Data<Client>,
) -> impl Future<Item = HttpResponse, Error = Error> {
let mut new_url = url.get_ref().clone();
new_url.set_path(req.uri().path()); new_url.set_path(req.uri().path());
new_url.set_query(req.uri().query()); new_url.set_query(req.uri().query());
let mut forwarded_req = client::ClientRequest::build_from(req) let forwarded_req = client
.no_default_headers() .request_from(new_url.as_str(), &req)
.uri(new_url) .no_default_headers();
.streaming(req.payload())
.unwrap();
if let Some(addr) = req.peer_addr() { // if let Some(addr) = req.peer_addr() {
match forwarded_req.headers_mut().entry("x-forwarded-for") { // match forwarded_req.headers_mut().entry("x-forwarded-for") {
Ok(http::header::Entry::Vacant(entry)) => { // Ok(http::header::Entry::Vacant(entry)) => {
let addr = format!("{}", addr.ip()); // let addr = format!("{}", addr.ip());
entry.insert(addr.parse().unwrap()); // entry.insert(addr.parse().unwrap());
} // }
Ok(http::header::Entry::Occupied(mut entry)) => { // Ok(http::header::Entry::Occupied(mut entry)) => {
let addr = format!("{}, {}", entry.get().to_str().unwrap(), addr.ip()); // let addr = format!("{}, {}", entry.get().to_str().unwrap(), addr.ip());
entry.insert(addr.parse().unwrap()); // entry.insert(addr.parse().unwrap());
} // }
_ => unreachable!(), // _ => unreachable!(),
} // }
} // }
forwarded_req forwarded_req
.send() .send_stream(payload)
.map_err(Error::from) .map_err(Error::from)
.and_then(construct_response) .map(|res| {
.responder() let mut client_resp = HttpResponse::build(res.status());
}
fn construct_response(
resp: client::ClientResponse,
) -> Box<dyn Future<Item = HttpResponse, Error = Error>> {
let mut client_resp = HttpResponse::build(resp.status());
for (header_name, header_value) in for (header_name, header_value) in
resp.headers().iter().filter(|(h, _)| *h != "connection") res.headers().iter().filter(|(h, _)| *h != "connection")
{ {
client_resp.header(header_name.clone(), header_value.clone()); client_resp.header(header_name.clone(), header_value.clone());
} }
if resp.chunked().unwrap_or(false) { client_resp.streaming(res)
Box::new(future::ok(client_resp.streaming(resp.payload()))) })
} else {
Box::new(
resp.body()
.from_err()
.and_then(move |body| Ok(client_resp.body(body))),
)
}
} }
fn main() { fn main() -> std::io::Result<()> {
let matches = clap::App::new("HTTP Proxy") let matches = clap::App::new("HTTP Proxy")
.arg( .arg(
Arg::with_name("listen_addr") Arg::with_name("listen_addr")
@ -87,25 +55,29 @@ fn main() {
.value_name("LISTEN ADDR") .value_name("LISTEN ADDR")
.index(1) .index(1)
.required(true), .required(true),
).arg( )
.arg(
Arg::with_name("listen_port") Arg::with_name("listen_port")
.takes_value(true) .takes_value(true)
.value_name("LISTEN PORT") .value_name("LISTEN PORT")
.index(2) .index(2)
.required(true), .required(true),
).arg( )
.arg(
Arg::with_name("forward_addr") Arg::with_name("forward_addr")
.takes_value(true) .takes_value(true)
.value_name("FWD ADDR") .value_name("FWD ADDR")
.index(3) .index(3)
.required(true), .required(true),
).arg( )
.arg(
Arg::with_name("forward_port") Arg::with_name("forward_port")
.takes_value(true) .takes_value(true)
.value_name("FWD PORT") .value_name("FWD PORT")
.index(4) .index(4)
.required(true), .required(true),
).get_matches(); )
.get_matches();
let listen_addr = matches.value_of("listen_addr").unwrap(); let listen_addr = matches.value_of("listen_addr").unwrap();
let listen_port = value_t!(matches, "listen_port", u16).unwrap_or_else(|e| e.exit()); let listen_port = value_t!(matches, "listen_port", u16).unwrap_or_else(|e| e.exit());
@ -121,15 +93,16 @@ fn main() {
.unwrap() .unwrap()
.next() .next()
.unwrap() .unwrap()
)).unwrap(); ))
.unwrap();
server::new(move || { HttpServer::new(move || {
App::with_state(AppState::init(forward_url.clone())).default_resource(|r| { App::new()
r.f(forward); .data(forward_url.clone())
.wrap(middleware::Logger::default())
.default_resource(|r| r.to_async(forward))
}) })
}).workers(32) .bind((listen_addr, listen_port))?
.bind((listen_addr, listen_port))
.expect("Cannot bind listening port")
.system_exit() .system_exit()
.run(); .run()
} }

View File

@ -2,7 +2,8 @@
name = "http-proxy" name = "http-proxy"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" edition = "2018"
workspace = ".."
[[bin]] [[bin]]
name = "proxy" name = "proxy"
@ -13,8 +14,8 @@ name = "proxy-example-server"
path = "src/server.rs" path = "src/server.rs"
[dependencies] [dependencies]
actix-rt = "0.2"
actix-web = "1.0.0-alpha.1"
env_logger = "0.5" env_logger = "0.5"
futures = "0.1" futures = "0.1"
actix = "0.7"
actix-web = "^0.7"

View File

@ -1,60 +1,52 @@
extern crate actix; use actix_web::client::Client;
extern crate actix_web; use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
extern crate env_logger; use futures::Future;
extern crate futures;
use actix_web::{
client, middleware, server, App, AsyncResponder, Body, Error, HttpMessage,
HttpRequest, HttpResponse,
};
use futures::{Future, Stream};
/// Stream client request response and then send body to a server response /// Stream client request response and then send body to a server response
fn index(_req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> { fn index(client: web::Data<Client>) -> impl Future<Item = HttpResponse, Error = Error> {
client::ClientRequest::get("http://127.0.0.1:8081/") client
.finish().unwrap() .get("http://127.0.0.1:8081/")
.send() .send()
.map_err(Error::from) // <- convert SendRequestError to an Error .map_err(Error::from) // <- convert SendRequestError to an Error
.and_then( .and_then(|resp| {
|resp| resp.body() // <- this is MessageBody type, resolves to complete body resp.body() // <- this is MessageBody type, resolves to complete body
.from_err() // <- convert PayloadError to an Error .from_err() // <- convert PayloadError to an Error
.and_then(|body| { // <- we got complete body, now send as server response .and_then(|body| {
// <- we got complete body, now send as server response
Ok(HttpResponse::Ok().body(body)) Ok(HttpResponse::Ok().body(body))
})) })
.responder() })
} }
/// streaming client request to a streaming server response /// streaming client request to a streaming server response
fn streaming(_req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> { fn streaming(
client: web::Data<Client>,
) -> impl Future<Item = HttpResponse, Error = impl Into<Error>> {
// send client request // send client request
client::ClientRequest::get("https://www.rust-lang.org/en-US/") client
.finish().unwrap() .get("https://www.rust-lang.org/en-US/")
.send() // <- connect to host and send request .send() // <- connect to host and send request
.map_err(Error::from) // <- convert SendRequestError to an Error .map_err(Error::from) // <- convert SendRequestError to an Error
.and_then(|resp| { // <- we received client response .and_then(|resp| {
// <- we received client response
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
// read one chunk from client response and send this chunk to a server response // read one chunk from client response and send this chunk to a server response
// .from_err() converts PayloadError to an Error // .from_err() converts PayloadError to an Error
.body(Body::Streaming(Box::new(resp.payload().from_err())))) .streaming(resp))
}) })
.responder()
} }
fn main() { fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_server=info,actix_web=trace");
env_logger::init(); env_logger::init();
let sys = actix::System::new("http-proxy");
server::new(|| { HttpServer::new(|| {
App::new() App::new()
.middleware(middleware::Logger::default()) .data(Client::new())
.resource("/streaming", |r| r.f(streaming)) .wrap(middleware::Logger::default())
.resource("/", |r| r.f(index)) .service(web::resource("/streaming").to_async(streaming))
}).workers(1) .service(web::resource("/").to_async(index))
.bind("127.0.0.1:8080") })
.unwrap() .bind("127.0.0.1:8080")?
.start(); .run()
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
} }

View File

@ -1,34 +1,20 @@
extern crate actix; use actix_web::{middleware, web, App, HttpResponse, HttpServer, Responder};
extern crate actix_web;
extern crate env_logger;
extern crate futures;
use actix_web::*; fn index(body: web::Bytes) -> impl Responder {
use futures::Future; HttpResponse::Ok().body(body)
fn index(req: &HttpRequest) -> FutureResponse<HttpResponse> {
req.body()
.from_err()
.map(|bytes| HttpResponse::Ok().body(bytes))
.responder()
} }
fn main() { fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=error"); std::env::set_var("RUST_LOG", "actix_server=info,actix_web=trace");
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("ws-example");
server::new(|| { HttpServer::new(|| {
App::new() App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
.resource("/index.html", |r| r.f(|_| "Hello world!")) .service(web::resource("/index.html").to(|| "Hello world!"))
.resource("/", |r| r.f(index)) .service(web::resource("/").to(index))
}).workers(1) })
.bind("127.0.0.1:8081") .bind("127.0.0.1:8081")?
.unwrap() .run()
.start();
println!("Started http server: 127.0.0.1:8081");
let _ = sys.run();
} }

View File

@ -2,9 +2,12 @@
name = "json-example" name = "json-example"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.1"
bytes = "0.4" bytes = "0.4"
futures = "0.1" futures = "0.1"
env_logger = "*" env_logger = "*"
@ -13,6 +16,3 @@ serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
json = "*" json = "*"
actix = "0.7"
actix-web = "^0.7"

View File

@ -1,22 +1,13 @@
extern crate actix;
extern crate actix_web;
extern crate bytes;
extern crate env_logger;
extern crate futures;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
#[macro_use] #[macro_use]
extern crate json; extern crate json;
use actix_web::{ use actix_web::{
error, http, middleware, server, App, AsyncResponder, Error, HttpMessage, error, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
HttpRequest, HttpResponse, Json,
}; };
use bytes::BytesMut; use bytes::BytesMut;
use futures::{Future, Stream}; use futures::{Future, Stream};
use json::JsonValue; use json::JsonValue;
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct MyObj { struct MyObj {
@ -24,39 +15,31 @@ struct MyObj {
number: i32, number: i32,
} }
/// This handler uses `HttpRequest::json()` for loading json object.
fn index(req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
req.json()
.from_err() // convert all errors into `Error`
.and_then(|val: MyObj| {
println!("model: {:?}", val);
Ok(HttpResponse::Ok().json(val)) // <- send response
})
.responder()
}
/// This handler uses json extractor /// This handler uses json extractor
fn extract_item(item: Json<MyObj>) -> HttpResponse { fn index(item: web::Json<MyObj>) -> HttpResponse {
println!("model: {:?}", &item); println!("model: {:?}", &item);
HttpResponse::Ok().json(item.0) // <- send response HttpResponse::Ok().json(item.0) // <- send response
} }
/// This handler uses json extractor with limit /// This handler uses json extractor with limit
fn extract_item_limit((item, _req): (Json<MyObj>, HttpRequest)) -> HttpResponse { fn extract_item(item: web::Json<MyObj>, req: HttpRequest) -> HttpResponse {
println!("model: {:?}", &item); println!("request: {:?}", req);
HttpResponse::Ok().json(item.0) // <- send response println!("model: {:?}", item);
HttpResponse::Ok().json(item.0) // <- send json response
} }
const MAX_SIZE: usize = 262_144; // max payload size is 256k const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// This handler manually load request payload and parse json object /// This handler manually load request payload and parse json object
fn index_manual(req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> { fn index_manual(
// HttpRequest::payload() is stream of Bytes objects payload: web::Payload,
req.payload() ) -> impl Future<Item = HttpResponse, Error = Error> {
// payload is a stream of Bytes objects
payload
// `Future::from_err` acts like `?` in that it coerces the error type from // `Future::from_err` acts like `?` in that it coerces the error type from
// the future into the final error type // the future into the final error type
.from_err() .from_err()
// `fold` will asynchronously read each chunk of the request body and // `fold` will asynchronously read each chunk of the request body and
// call supplied closure, then it resolves to result of closure // call supplied closure, then it resolves to result of closure
.fold(BytesMut::new(), move |mut body, chunk| { .fold(BytesMut::new(), move |mut body, chunk| {
@ -75,59 +58,51 @@ fn index_manual(req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Er
let obj = serde_json::from_slice::<MyObj>(&body)?; let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(HttpResponse::Ok().json(obj)) // <- send response Ok(HttpResponse::Ok().json(obj)) // <- send response
}) })
.responder()
} }
/// This handler manually load request payload and parse json-rust /// This handler manually load request payload and parse json-rust
fn index_mjsonrust( fn index_mjsonrust(pl: web::Payload) -> impl Future<Item = HttpResponse, Error = Error> {
req: &HttpRequest, pl.concat2().from_err().and_then(|body| {
) -> Box<Future<Item = HttpResponse, Error = Error>> {
req.payload()
.concat2()
.from_err()
.and_then(|body| {
// body is loaded, now we can deserialize json-rust // body is loaded, now we can deserialize json-rust
let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result
let injson: JsonValue = match result { let injson: JsonValue = match result {
Ok(v) => v, Ok(v) => v,
Err(e) => object!{"err" => e.to_string() }, Err(e) => json::object! {"err" => e.to_string() },
}; };
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(injson.dump())) .body(injson.dump()))
}) })
.responder()
} }
fn main() { fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("json-example");
server::new(|| { HttpServer::new(|| {
App::new() App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
.resource("/extractor", |r| { .service(
r.method(http::Method::POST) web::resource("/extractor").route(
.with_config(extract_item, |(cfg,)| { web::post()
cfg.limit(4096); // <- limit size of the payload .data(web::JsonConfig::default().limit(4096)) // <- limit size of the payload
.to(index),
),
)
.service(
web::resource("/extractor2").route(
web::post()
.data(web::JsonConfig::default().limit(4096)) // <- limit size of the payload
.to_async(extract_item),
),
)
.service(web::resource("/manual").route(web::post().to_async(index_manual)))
.service(
web::resource("/mjsonrust").route(web::post().to_async(index_mjsonrust)),
)
.service(web::resource("/").route(web::post().to(index)))
}) })
}) .bind("127.0.0.1:8080")?
.resource("/extractor2", |r| { .run()
r.method(http::Method::POST)
.with_config(extract_item_limit, |((cfg, _),)| {
cfg.limit(4096); // <- limit size of the payload
})
})
.resource("/manual", |r| r.method(http::Method::POST).f(index_manual))
.resource("/mjsonrust", |r| r.method(http::Method::POST).f(index_mjsonrust))
.resource("/", |r| r.method(http::Method::POST).f(index))
}).bind("127.0.0.1:8080")
.unwrap()
.shutdown_timeout(1)
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
} }

View File

@ -2,17 +2,14 @@
name = "juniper-example" name = "juniper-example"
version = "0.1.0" version = "0.1.0"
authors = ["pyros2097 <pyros2097@gmail.com>"] authors = ["pyros2097 <pyros2097@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
env_logger = "0.5" actix-web = "1.0.0-alpha.1"
env_logger = "0.6"
actix = "0.7"
actix-web = "0.7"
futures = "0.1" futures = "0.1"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
juniper = "0.9.2" juniper = "0.9.2"

View File

@ -1,107 +1,59 @@
//! Actix web juniper example //! Actix web juniper example
//! //!
//! A simple example integrating juniper in actix-web //! A simple example integrating juniper in actix-web
extern crate serde; use std::io;
extern crate serde_json; use std::sync::Arc;
#[macro_use]
extern crate serde_derive;
#[macro_use] #[macro_use]
extern crate juniper; extern crate juniper;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
use actix::prelude::*; use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
use actix_web::{
http, middleware, server, App, AsyncResponder, Error, FutureResponse, HttpRequest,
HttpResponse, Json, State,
};
use futures::future::Future; use futures::future::Future;
use juniper::http::graphiql::graphiql_source; use juniper::http::graphiql::graphiql_source;
use juniper::http::GraphQLRequest; use juniper::http::GraphQLRequest;
mod schema; mod schema;
use schema::create_schema; use crate::schema::{create_schema, Schema};
use schema::Schema;
struct AppState { fn graphiql() -> HttpResponse {
executor: Addr<GraphQLExecutor>,
}
#[derive(Serialize, Deserialize)]
pub struct GraphQLData(GraphQLRequest);
impl Message for GraphQLData {
type Result = Result<String, Error>;
}
pub struct GraphQLExecutor {
schema: std::sync::Arc<Schema>,
}
impl GraphQLExecutor {
fn new(schema: std::sync::Arc<Schema>) -> GraphQLExecutor {
GraphQLExecutor { schema: schema }
}
}
impl Actor for GraphQLExecutor {
type Context = SyncContext<Self>;
}
impl Handler<GraphQLData> for GraphQLExecutor {
type Result = Result<String, Error>;
fn handle(&mut self, msg: GraphQLData, _: &mut Self::Context) -> Self::Result {
let res = msg.0.execute(&self.schema, &());
let res_text = serde_json::to_string(&res)?;
Ok(res_text)
}
}
fn graphiql(_req: &HttpRequest<AppState>) -> Result<HttpResponse, Error> {
let html = graphiql_source("http://127.0.0.1:8080/graphql"); let html = graphiql_source("http://127.0.0.1:8080/graphql");
Ok(HttpResponse::Ok() HttpResponse::Ok()
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(html)) .body(html)
} }
fn graphql( fn graphql(
(st, data): (State<AppState>, Json<GraphQLData>), st: web::Data<Arc<Schema>>,
) -> FutureResponse<HttpResponse> { data: web::Json<GraphQLRequest>,
st.executor ) -> impl Future<Item = HttpResponse, Error = Error> {
.send(data.0) web::block(move || {
.from_err() let res = data.execute(&st, &());
.and_then(|res| match res { Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?)
Ok(user) => Ok(HttpResponse::Ok() })
.content_type("application/json") .map_err(Error::from)
.body(user)), .and_then(|user| {
Err(_) => Ok(HttpResponse::InternalServerError().into()), Ok(HttpResponse::Ok()
.content_type("application/json")
.body(user))
}) })
.responder()
} }
fn main() { fn main() -> io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("juniper-example");
// Create Juniper schema
let schema = std::sync::Arc::new(create_schema()); let schema = std::sync::Arc::new(create_schema());
let addr = SyncArbiter::start(3, move || GraphQLExecutor::new(schema.clone()));
// Start http server // Start http server
server::new(move || { HttpServer::new(move || {
App::with_state(AppState{executor: addr.clone()}) App::new()
// enable logger .data(schema.clone())
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
.resource("/graphql", |r| r.method(http::Method::POST).with(graphql)) .service(web::resource("/graphql").route(web::post().to_async(graphql)))
.resource("/graphiql", |r| r.method(http::Method::GET).h(graphiql)) .service(web::resource("/graphiql").route(web::get().to(graphiql)))
}).bind("127.0.0.1:8080") })
.unwrap() .bind("127.0.0.1:8080")?
.start(); .run()
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
} }

View File

@ -2,7 +2,11 @@
name = "middleware-example" name = "middleware-example"
version = "0.1.0" version = "0.1.0"
authors = ["Gorm Casper <gcasper@gmail.com>"] authors = ["Gorm Casper <gcasper@gmail.com>"]
edition = "2018"
workspace = ".."
[dependencies] [dependencies]
actix = "0.7" actix-service = "0.3.3"
actix-web = "0.7" actix-web = "1.0.0-alpha.1"
futures = "0.1.25"
env_logger = "0.6"

View File

@ -1,29 +1,27 @@
extern crate actix; use actix_web::{web, App, HttpServer};
extern crate actix_web;
use actix_web::{server, App};
#[allow(dead_code)] #[allow(dead_code)]
mod redirect; mod redirect;
#[allow(dead_code)] #[allow(dead_code)]
mod simple; mod simple;
fn main() { fn main() -> std::io::Result<()> {
let sys = actix::System::new("middleware-example"); std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init();
let _addr = server::new(|| { HttpServer::new(|| {
App::new() App::new()
.middleware(simple::SayHi) .wrap(redirect::CheckLogin)
// .middleware(redirect::CheckLogin) .wrap(simple::SayHi)
.resource("/login", |r| { .service(web::resource("/login").to(|| {
r.f(|_| "You are on /login. Go to src/redirect.rs to change this behavior.") "You are on /login. Go to src/redirect.rs to change this behavior."
}))
.service(
web::resource("/").to(|| {
"Hello, middleware! Check the console where the server is run."
}),
)
}) })
.resource("/", |r| { .bind("127.0.0.1:8080")?
r.f(|_| "Hello, middleware! Check the console where the server is run.") .run()
})
}).bind("127.0.0.1:8080")
.unwrap()
.start();
let _ = sys.run();
} }

View File

@ -1,28 +1,64 @@
extern crate actix_web; use actix_service::{Service, Transform};
use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::middleware::{Middleware, Started}; use actix_web::{http, HttpResponse};
use actix_web::{http, HttpRequest, HttpResponse, Result}; use futures::future::{ok, Either, FutureResult};
use futures::Poll;
pub struct CheckLogin; pub struct CheckLogin;
impl<S> Middleware<S> for CheckLogin { impl<S, P, B> Transform<S> for CheckLogin
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type InitError = ();
type Transform = CheckLoginMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(CheckLoginMiddleware { service })
}
}
pub struct CheckLoginMiddleware<S> {
service: S,
}
impl<S, P, B> Service for CheckLoginMiddleware<S>
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = Either<S::Future, FutureResult<Self::Response, Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
// We only need to hook into the `start` for this middleware. // We only need to hook into the `start` for this middleware.
fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
let is_logged_in = false; // Change this to see the change in outcome in the browser let is_logged_in = false; // Change this to see the change in outcome in the browser
if is_logged_in { if is_logged_in {
return Ok(Started::Done); Either::A(self.service.call(req))
} } else {
// Don't forward to /login if we are already on /login // Don't forward to /login if we are already on /login
if req.path() == "/login" { if req.path() == "/login" {
return Ok(Started::Done); Either::A(self.service.call(req))
} } else {
Either::B(ok(req.into_response(
Ok(Started::Response(
HttpResponse::Found() HttpResponse::Found()
.header(http::header::LOCATION, "/login") .header(http::header::LOCATION, "/login")
.finish(), .finish()
)) .into_body(),
)))
}
}
} }
} }

View File

@ -1,25 +1,63 @@
extern crate actix_web; use actix_service::{Service, Transform};
use actix_web::dev::{ServiceRequest, ServiceResponse};
use futures::future::{ok, FutureResult};
use futures::{Future, Poll};
use actix_web::middleware::{Finished, Middleware, Response, Started}; // There are two step in middleware processing.
use actix_web::{HttpRequest, HttpResponse, Result}; // 1. Middleware initialization, middleware factory get called with
// next service in chain as parameter.
// Middleware can get called at three stages during the request/response handling. Below is a // 2. Middleware's call method get called with normal request.
// struct that implements all three of them.
pub struct SayHi; pub struct SayHi;
impl<S> Middleware<S> for SayHi { // Middleware factory is `Transform` trait from actix-service crate
fn start(&self, req: &HttpRequest<S>) -> Result<Started> { // `S` - type of the next service
// `P` - type of request's payload
// `B` - type of response's body
impl<S, P, B> Transform<S> for SayHi
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
B: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type InitError = ();
type Transform = SayHiMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(SayHiMiddleware { service })
}
}
pub struct SayHiMiddleware<S> {
service: S,
}
impl<S, P, B> Service for SayHiMiddleware<S>
where
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
S::Future: 'static,
S::Error: 'static,
B: 'static,
{
type Request = ServiceRequest<P>;
type Response = ServiceResponse<B>;
type Error = S::Error;
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
println!("Hi from start. You requested: {}", req.path()); println!("Hi from start. You requested: {}", req.path());
Ok(Started::Done)
}
fn response(&self, _req: &HttpRequest<S>, resp: HttpResponse) -> Result<Response> { Box::new(self.service.call(req).and_then(|res| {
println!("Hi from response"); println!("Hi from response");
Ok(Response::Done(resp)) Ok(res)
} }))
fn finish(&self, _req: &HttpRequest<S>, _resp: &HttpResponse) -> Finished {
println!("Hi from finish");
Finished::Done
} }
} }

View File

@ -2,15 +2,16 @@
name = "multipart-example" name = "multipart-example"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[[bin]] [[bin]]
name = "multipart" name = "multipart"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
env_logger = "*" #actix-web = "1.0.0-alpha.1"
futures = "0.1" actix-web = { git="https://github.com/actix/actix-web.git" }
actix = "0.7" env_logger = "0.6"
actix-web = "0.7" futures = "0.1.25"

View File

@ -1,44 +1,30 @@
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
use std::cell::Cell; use std::cell::Cell;
use std::fs; use std::fs;
use std::io::Write; use std::io::Write;
use actix_web::{ use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer};
dev, error, http, middleware, multipart, server, App, Error, FutureResponse, use futures::future::{err, Either};
HttpMessage, HttpRequest, HttpResponse,
};
use futures::future;
use futures::{Future, Stream}; use futures::{Future, Stream};
pub struct AppState { pub struct AppState {
pub counter: Cell<usize>, pub counter: Cell<usize>,
} }
pub fn save_file( pub fn save_file(field: web::MultipartField) -> impl Future<Item = i64, Error = Error> {
field: multipart::Field<dev::Payload>,
) -> Box<Future<Item = i64, Error = Error>> {
let file_path_string = "upload.png"; let file_path_string = "upload.png";
let mut file = match fs::File::create(file_path_string) { let mut file = match fs::File::create(file_path_string) {
Ok(file) => file, Ok(file) => file,
Err(e) => return Box::new(future::err(error::ErrorInternalServerError(e))), Err(e) => return Either::A(err(error::ErrorInternalServerError(e))),
}; };
Box::new( Either::B(
field field
.fold(0i64, move |acc, bytes| { .fold(0i64, move |acc, bytes| {
let rt = file file.write_all(bytes.as_ref())
.write_all(bytes.as_ref())
.map(|_| acc + bytes.len() as i64) .map(|_| acc + bytes.len() as i64)
.map_err(|e| { .map_err(|e| {
println!("file.write_all failed: {:?}", e); println!("file.write_all failed: {:?}", e);
error::MultipartError::Payload(error::PayloadError::Io(e)) error::MultipartError::Payload(error::PayloadError::Io(e))
}); })
future::result(rt)
}) })
.map_err(|e| { .map_err(|e| {
println!("save_file failed, {:?}", e); println!("save_file failed, {:?}", e);
@ -48,13 +34,11 @@ pub fn save_file(
} }
pub fn handle_multipart_item( pub fn handle_multipart_item(
item: multipart::MultipartItem<dev::Payload>, item: web::MultipartItem,
) -> Box<Stream<Item = i64, Error = Error>> { ) -> Box<Stream<Item = i64, Error = Error>> {
match item { match item {
multipart::MultipartItem::Field(field) => { web::MultipartItem::Field(field) => Box::new(save_file(field).into_stream()),
Box::new(save_file(field).into_stream()) web::MultipartItem::Nested(mp) => Box::new(
}
multipart::MultipartItem::Nested(mp) => Box::new(
mp.map_err(error::ErrorInternalServerError) mp.map_err(error::ErrorInternalServerError)
.map(handle_multipart_item) .map(handle_multipart_item)
.flatten(), .flatten(),
@ -62,11 +46,14 @@ pub fn handle_multipart_item(
} }
} }
pub fn upload(req: HttpRequest<AppState>) -> FutureResponse<HttpResponse> { pub fn upload(
req.state().counter.set(req.state().counter.get() + 1); multipart: web::Multipart,
println!("{:?}", req.state().counter.get()); counter: web::Data<Cell<usize>>,
Box::new( ) -> impl Future<Item = HttpResponse, Error = Error> {
req.multipart() counter.set(counter.get() + 1);
println!("{:?}", counter.get());
multipart
.map_err(error::ErrorInternalServerError) .map_err(error::ErrorInternalServerError)
.map(handle_multipart_item) .map(handle_multipart_item)
.flatten() .flatten()
@ -75,11 +62,10 @@ pub fn upload(req: HttpRequest<AppState>) -> FutureResponse<HttpResponse> {
.map_err(|e| { .map_err(|e| {
println!("failed: {}", e); println!("failed: {}", e);
e e
}), })
)
} }
fn index(_req: HttpRequest<AppState>) -> Result<HttpResponse, error::Error> { fn index() -> HttpResponse {
let html = r#"<html> let html = r#"<html>
<head><title>Upload Test</title></head> <head><title>Upload Test</title></head>
<body> <body>
@ -90,26 +76,23 @@ fn index(_req: HttpRequest<AppState>) -> Result<HttpResponse, error::Error> {
</body> </body>
</html>"#; </html>"#;
Ok(HttpResponse::Ok().body(html)) HttpResponse::Ok().body(html)
} }
fn main() { fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("multipart-example");
server::new(|| { HttpServer::new(|| {
App::with_state(AppState { App::new()
counter: Cell::new(0), .data(Cell::new(0usize))
}).middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
.resource("/", |r| { .service(
r.method(http::Method::GET).with(index); web::resource("/")
r.method(http::Method::POST).with(upload); .route(web::get().to(index))
.route(web::post().to_async(upload)),
)
}) })
}).bind("127.0.0.1:8080") .bind("127.0.0.1:8080")?
.unwrap() .run()
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
} }

View File

@ -45,7 +45,8 @@ fn main() {
App::new() App::new()
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/", |r| r.method(http::Method::POST).f(index)) .resource("/", |r| r.method(http::Method::POST).f(index))
}).bind("127.0.0.1:8080") })
.bind("127.0.0.1:8080")
.unwrap() .unwrap()
.shutdown_timeout(1) .shutdown_timeout(1)
.start(); .start();

View File

@ -2,20 +2,17 @@
name = "r2d2-example" name = "r2d2-example"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" edition = "2018"
workspace = ".."
[dependencies] [dependencies]
env_logger = "0.5" actix-rt = "0.2"
actix-web = "1.0.0-alpha.1"
actix = "0.7"
actix-web = "0.7"
futures = "0.1" futures = "0.1"
uuid = { version = "0.5", features = ["serde", "v4"] } env_logger = "0.6"
serde = "1.0" uuid = { version = "0.7", features = ["v4"] }
serde_json = "1.0"
serde_derive = "1.0"
r2d2 = "*" r2d2 = "0.8"
r2d2_sqlite = "*" r2d2_sqlite = "0.8"
rusqlite = "*" rusqlite = "0.16"

View File

@ -1,44 +0,0 @@
//! Db executor actor
use actix::prelude::*;
use actix_web::*;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use std::io;
use uuid;
/// This is db executor actor. We are going to run 3 of them in parallel.
pub struct DbExecutor(pub Pool<SqliteConnectionManager>);
/// This is only message that this actor can handle, but it is easy to extend
/// number of messages.
pub struct CreateUser {
pub name: String,
}
impl Message for CreateUser {
type Result = Result<String, io::Error>;
}
impl Actor for DbExecutor {
type Context = SyncContext<Self>;
}
impl Handler<CreateUser> for DbExecutor {
type Result = Result<String, io::Error>;
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
let conn = self.0.get().unwrap();
let uuid = format!("{}", uuid::Uuid::new_v4());
conn.execute(
"INSERT INTO users (id, name) VALUES ($1, $2)",
&[&uuid, &msg.name],
).unwrap();
Ok(conn
.query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| {
row.get(0)
})
.map_err(|_| io::Error::new(io::ErrorKind::Other, "db error"))?)
}
}

View File

@ -1,68 +1,56 @@
//! Actix web r2d2 example //! Actix web r2d2 example
extern crate actix; use std::io;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
extern crate r2d2;
extern crate r2d2_sqlite;
extern crate rusqlite;
extern crate serde;
extern crate serde_json;
extern crate uuid;
use actix::prelude::*; use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
use actix_web::{ use futures::Future;
http, middleware, server, App, AsyncResponder, Error, HttpRequest, HttpResponse, use r2d2::Pool;
};
use futures::future::Future;
use r2d2_sqlite::SqliteConnectionManager; use r2d2_sqlite::SqliteConnectionManager;
use uuid;
mod db; /// Async request handler. Ddb pool is stored in application state.
use db::{CreateUser, DbExecutor}; fn index(
path: web::Path<String>,
db: web::Data<Pool<SqliteConnectionManager>>,
) -> impl Future<Item = HttpResponse, Error = Error> {
// execute sync code in threadpool
web::block(move || {
let conn = db.get().unwrap();
/// State with DbExecutor address let uuid = format!("{}", uuid::Uuid::new_v4());
struct State { conn.execute(
db: Addr<DbExecutor>, "INSERT INTO users (id, name) VALUES ($1, $2)",
} &[&uuid, &path.into_inner()],
)
.unwrap();
/// Async request handler conn.query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| {
fn index(req: &HttpRequest<State>) -> Box<Future<Item = HttpResponse, Error = Error>> { row.get::<_, String>(0)
let name = &req.match_info()["name"];
req.state()
.db
.send(CreateUser {
name: name.to_owned(),
}) })
.from_err() })
.and_then(|res| match res { .then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)), Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()), Err(_) => Ok(HttpResponse::InternalServerError().into()),
}) })
.responder()
} }
fn main() { fn main() -> io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=debug"); std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init(); env_logger::init();
let sys = actix::System::new("r2d2-example"); let sys = actix_rt::System::new("r2d2-example");
// r2d2 pool // r2d2 pool
let manager = SqliteConnectionManager::file("test.db"); let manager = SqliteConnectionManager::file("test.db");
let pool = r2d2::Pool::new(manager).unwrap(); let pool = r2d2::Pool::new(manager).unwrap();
// Start db executor actors // start http server
let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone())); HttpServer::new(move || {
App::new()
// Start http server .data(pool.clone()) // <- store db pool in app state
server::new(move || { .wrap(middleware::Logger::default())
App::with_state(State{db: addr.clone()}) .route("/{name}", web::get().to_async(index))
// enable logger })
.middleware(middleware::Logger::default()) .bind("127.0.0.1:8080")?
.resource("/{name}", |r| r.method(http::Method::GET).a(index))
}).bind("127.0.0.1:8080")
.unwrap()
.start(); .start();
let _ = sys.run(); sys.run()
} }

View File

@ -2,10 +2,12 @@
name = "redis-session" name = "redis-session"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
env_logger = "0.5" actix = "0.8.0-alpha.2"
actix = "0.7" actix-web = "1.0.0-alpha.1"
actix-web = "0.7.3" actix-session = "0.1.0-alpha.1"
actix-redis = { version = "0.5.1", features = ["web"] } actix-redis = { git = "https://github.com/actix/actix-redis.git", features = ["web"] }
env_logger = "0.6"

View File

@ -1,48 +1,38 @@
//! Example of redis based session //! Example of redis based session
//! //!
//! [User guide](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions) //! [User guide](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions)
extern crate actix; use actix_redis::RedisSession;
extern crate actix_redis; use actix_session::Session;
extern crate actix_web; use actix_web::{middleware, web, App, HttpRequest, HttpResponse, HttpServer, Result};
extern crate env_logger;
use actix_redis::RedisSessionBackend;
use actix_web::middleware::session::{RequestSession, SessionStorage};
use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Result};
/// simple handler /// simple handler
fn index(req: &HttpRequest) -> Result<HttpResponse> { fn index(req: HttpRequest, session: Session) -> Result<HttpResponse> {
println!("{:?}", req); println!("{:?}", req);
// session // session
if let Some(count) = req.session().get::<i32>("counter")? { if let Some(count) = session.get::<i32>("counter")? {
println!("SESSION value: {}", count); println!("SESSION value: {}", count);
req.session().set("counter", count + 1)?; session.set("counter", count + 1)?;
} else { } else {
req.session().set("counter", 1)?; session.set("counter", 1)?;
} }
Ok("Welcome!".into()) Ok("Welcome!".into())
} }
fn main() { fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info"); std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("basic-example");
server::new(|| { HttpServer::new(|| {
App::new() App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
// redis session middleware // redis session middleware
.middleware(SessionStorage::new( .wrap(RedisSession::new("127.0.0.1:6379", &[0; 32]))
RedisSessionBackend::new("127.0.0.1:6379", &[0; 32])
))
// register simple route, handle all methods // register simple route, handle all methods
.resource("/", |r| r.f(index)) .service(web::resource("/").to(index))
}).bind("127.0.0.1:8080") })
.unwrap() .bind("127.0.0.1:8080")?
.start(); .run()
let _ = sys.run();
} }

View File

@ -1,5 +1,2 @@
max_width = 89 max_width = 89
reorder_imports = true reorder_imports = true
#wrap_comments = true
fn_args_density = "Compressed"
#use_small_heuristics = false

View File

@ -49,13 +49,16 @@ fn main() {
// register simple handler, handle all methods // register simple handler, handle all methods
.resource("/index.html", |r| r.f(index)) .resource("/index.html", |r| r.f(index))
// with path parameters // with path parameters
.resource("/", |r| r.method(http::Method::GET).f(|_| { .resource("/", |r| {
r.method(http::Method::GET).f(|_| {
HttpResponse::Found() HttpResponse::Found()
.header("LOCATION", "/index.html") .header("LOCATION", "/index.html")
.finish() .finish()
})) })
})
.handler("/static", StaticFiles::new("static").unwrap()) .handler("/static", StaticFiles::new("static").unwrap())
}).bind_with("127.0.0.1:8443", move || acceptor.clone()) })
.bind_with("127.0.0.1:8443", move || acceptor.clone())
.unwrap() .unwrap()
.start(); .start();

View File

@ -2,18 +2,22 @@
name = "simple-auth-server" name = "simple-auth-server"
version = "0.1.0" version = "0.1.0"
authors = ["mygnu <tech@hgill.io>"] authors = ["mygnu <tech@hgill.io>"]
edition = "2018"
workspace = ".."
[dependencies] [dependencies]
actix = "0.7.7" actix = { version = "0.8.0-alpha.2", features = ["http"] }
actix-web = "0.7.14" actix-web = "1.0.0-alpha.1"
actix-files = "0.1.0-alpha.1"
bcrypt = "0.2.1" bcrypt = "0.2.1"
chrono = { version = "0.4.6", features = ["serde"] } chrono = { version = "0.4.6", features = ["serde"] }
diesel = { version = "1.3.3", features = ["postgres", "uuid", "r2d2", "chrono"] } diesel = { version = "1.3.3", features = ["postgres", "uuid", "r2d2", "chrono"] }
dotenv = "0.13.0" dotenv = "0.13.0"
derive_more = "0.14"
env_logger = "0.6.0" env_logger = "0.6.0"
failure = "0.1.3"
jsonwebtoken = "5.0" jsonwebtoken = "5.0"
futures = "0.1" futures = "0.1.25"
r2d2 = "0.8.3" r2d2 = "0.8.3"
serde_derive="1.0.80" serde_derive="1.0.80"
serde_json="1.0" serde_json="1.0"

View File

@ -1,54 +0,0 @@
use actix::prelude::*;
use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{fs, http::Method, middleware::Logger, App};
use auth_routes::{get_me, login, logout};
use chrono::Duration;
use invitation_routes::register_email;
use models::DbExecutor;
use register_routes::register_user;
pub struct AppState {
pub db: Addr<DbExecutor>,
}
/// creates and returns the app after mounting all routes/resources
pub fn create_app(db: Addr<DbExecutor>) -> App<AppState> {
// secret is a random minimum 32 bytes long base 64 string
let secret: String = std::env::var("SECRET_KEY").unwrap_or_else(|_| "0123".repeat(8));
let domain: String = std::env::var("DOMAIN").unwrap_or_else(|_| "localhost".to_string());
App::with_state(AppState { db })
.middleware(Logger::default())
.middleware(IdentityService::new(
CookieIdentityPolicy::new(secret.as_bytes())
.name("auth")
.path("/")
.domain(domain.as_str())
.max_age(Duration::days(1))
.secure(false), // this can only be true if you have https
))
// everything under '/api/' route
.scope("/api", |api| {
// routes for authentication
api.resource("/auth", |r| {
r.method(Method::POST).with(login);
r.method(Method::DELETE).with(logout);
r.method(Method::GET).with(get_me);
})
// routes to invitation
.resource("/invitation", |r| {
r.method(Method::POST).with(register_email);
})
// routes to register as a user after the
.resource("/register/{invitation_id}", |r| {
r.method(Method::POST).with(register_user);
})
})
// serve static files
.handler(
"/",
fs::StaticFiles::new("./static/")
.unwrap()
.index_file("index.html"),
)
}

View File

@ -1,10 +1,12 @@
use actix::{Handler, Message}; use actix::{Handler, Message};
use diesel::prelude::*; use actix_web::{dev::ServiceFromRequest, Error};
use errors::ServiceError; use actix_web::{middleware::identity::Identity, FromRequest, HttpRequest};
use models::{DbExecutor, User, SlimUser};
use bcrypt::verify; use bcrypt::verify;
use actix_web::{FromRequest, HttpRequest, middleware::identity::RequestIdentity}; use diesel::prelude::*;
use utils::decode_token;
use crate::errors::ServiceError;
use crate::models::{DbExecutor, SlimUser, User};
use crate::utils::decode_token;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct AuthData { pub struct AuthData {
@ -19,22 +21,24 @@ impl Message for AuthData {
impl Handler<AuthData> for DbExecutor { impl Handler<AuthData> for DbExecutor {
type Result = Result<SlimUser, ServiceError>; type Result = Result<SlimUser, ServiceError>;
fn handle(&mut self, msg: AuthData, _: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: AuthData, _: &mut Self::Context) -> Self::Result {
use schema::users::dsl::{users, email}; use crate::schema::users::dsl::{email, users};
let conn: &PgConnection = &self.0.get().unwrap(); let conn: &PgConnection = &self.0.get().unwrap();
let mut items = users let mut items = users.filter(email.eq(&msg.email)).load::<User>(conn)?;
.filter(email.eq(&msg.email))
.load::<User>(conn)?;
if let Some(user) = items.pop() { if let Some(user) = items.pop() {
match verify(&msg.password, &user.password) { match verify(&msg.password, &user.password) {
Ok(matching) => if matching { Ok(matching) => {
if matching {
return Ok(user.into()); return Ok(user.into());
}, }
}
Err(_) => (), Err(_) => (),
} }
} }
Err(ServiceError::BadRequest("Username and Password don't match".into())) Err(ServiceError::BadRequest(
"Username and Password don't match".into(),
))
} }
} }
@ -42,14 +46,15 @@ impl Handler<AuthData> for DbExecutor {
// simple aliasing makes the intentions clear and its more readable // simple aliasing makes the intentions clear and its more readable
pub type LoggedUser = SlimUser; pub type LoggedUser = SlimUser;
impl<S> FromRequest<S> for LoggedUser { impl<P> FromRequest<P> for LoggedUser {
type Config = (); type Error = Error;
type Result = Result<LoggedUser, ServiceError>; type Future = Result<LoggedUser, Error>;
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
if let Some(identity) = req.identity() { fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
if let Some(identity) = Identity::from_request(req)?.identity() {
let user: SlimUser = decode_token(&identity)?; let user: SlimUser = decode_token(&identity)?;
return Ok(user as LoggedUser); return Ok(user as LoggedUser);
} }
Err(ServiceError::Unauthorized) Err(ServiceError::Unauthorized.into())
} }
} }

View File

@ -1,30 +1,32 @@
use actix_web::{AsyncResponder, FutureResponse, HttpResponse, HttpRequest, ResponseError, Json}; use actix::Addr;
use actix_web::middleware::identity::RequestIdentity; use actix_web::middleware::identity::Identity;
use futures::future::Future; use actix_web::{web, Error, HttpRequest, HttpResponse, Responder, ResponseError};
use utils::create_token; use futures::Future;
use app::AppState; use crate::auth_handler::{AuthData, LoggedUser};
use auth_handler::{AuthData, LoggedUser}; use crate::models::DbExecutor;
use crate::utils::create_token;
pub fn login((auth_data, req): (Json<AuthData>, HttpRequest<AppState>)) pub fn login(
-> FutureResponse<HttpResponse> { auth_data: web::Json<AuthData>,
req.state() id: Identity,
.db db: web::Data<Addr<DbExecutor>>,
.send(auth_data.into_inner()) ) -> impl Future<Item = HttpResponse, Error = Error> {
db.send(auth_data.into_inner())
.from_err() .from_err()
.and_then(move |res| match res { .and_then(move |res| match res {
Ok(user) => { Ok(user) => {
let token = create_token(&user)?; let token = create_token(&user)?;
req.remember(token); id.remember(token);
Ok(HttpResponse::Ok().into()) Ok(HttpResponse::Ok().into())
} }
Err(err) => Ok(err.error_response()), Err(err) => Ok(err.error_response()),
}).responder() })
} }
pub fn logout(req: HttpRequest<AppState>) -> HttpResponse { pub fn logout(id: Identity) -> impl Responder {
req.forget(); id.forget();
HttpResponse::Ok().into() HttpResponse::Ok()
} }
pub fn get_me(logged_user: LoggedUser) -> HttpResponse { pub fn get_me(logged_user: LoggedUser) -> HttpResponse {

View File

@ -1,4 +1,4 @@
use models::Invitation; use crate::models::Invitation;
use sparkpost::transmission::{ use sparkpost::transmission::{
EmailAddress, Message, Options, Recipient, Transmission, TransmissionResponse, EmailAddress, Message, Options, Recipient, Transmission, TransmissionResponse,
}; };
@ -9,8 +9,8 @@ fn get_api_key() -> String {
pub fn send_invitation(invitation: &Invitation) { pub fn send_invitation(invitation: &Invitation) {
let tm = Transmission::new_eu(get_api_key()); let tm = Transmission::new_eu(get_api_key());
let sending_email = let sending_email = std::env::var("SENDING_EMAIL_ADDRESS")
std::env::var("SENDING_EMAIL_ADDRESS").expect("SENDING_EMAIL_ADDRESS must be set"); .expect("SENDING_EMAIL_ADDRESS must be set");
// new email message with sender name and email // new email message with sender name and email
let mut email = Message::new(EmailAddress::new(sending_email, "Let's Organise")); let mut email = Message::new(EmailAddress::new(sending_email, "Let's Organise"));
@ -39,7 +39,6 @@ pub fn send_invitation(invitation: &Invitation) {
.to_string() .to_string()
); );
// complete the email message with details // complete the email message with details
email email
.add_recipient(recipient) .add_recipient(recipient)
@ -51,16 +50,14 @@ pub fn send_invitation(invitation: &Invitation) {
// Note that we only print out the error response from email api // Note that we only print out the error response from email api
match result { match result {
Ok(res) => { Ok(res) => match res {
match res {
TransmissionResponse::ApiResponse(api_res) => { TransmissionResponse::ApiResponse(api_res) => {
println!("API Response: \n {:#?}", api_res); println!("API Response: \n {:#?}", api_res);
} }
TransmissionResponse::ApiError(errors) => { TransmissionResponse::ApiError(errors) => {
println!("Response Errors: \n {:#?}", &errors); println!("Response Errors: \n {:#?}", &errors);
} }
} },
}
Err(error) => { Err(error) => {
println!("error \n {:#?}", error); println!("error \n {:#?}", error);
} }

View File

@ -1,18 +1,18 @@
use actix_web::{error::ResponseError, HttpResponse}; use actix_web::{error::ResponseError, HttpResponse};
use std::convert::From; use derive_more::Display;
use diesel::result::{DatabaseErrorKind, Error}; use diesel::result::{DatabaseErrorKind, Error};
use std::convert::From;
use uuid::ParseError; use uuid::ParseError;
#[derive(Debug, Display)]
#[derive(Fail, Debug)]
pub enum ServiceError { pub enum ServiceError {
#[fail(display = "Internal Server Error")] #[display(fmt = "Internal Server Error")]
InternalServerError, InternalServerError,
#[fail(display = "BadRequest: {}", _0)] #[display(fmt = "BadRequest: {}", _0)]
BadRequest(String), BadRequest(String),
#[fail(display = "Unauthorized")] #[display(fmt = "Unauthorized")]
Unauthorized, Unauthorized,
} }
@ -20,9 +20,14 @@ pub enum ServiceError {
impl ResponseError for ServiceError { impl ResponseError for ServiceError {
fn error_response(&self) -> HttpResponse { fn error_response(&self) -> HttpResponse {
match *self { match *self {
ServiceError::InternalServerError => HttpResponse::InternalServerError().json("Internal Server Error, Please try later"), ServiceError::InternalServerError => HttpResponse::InternalServerError()
ServiceError::BadRequest(ref message) => HttpResponse::BadRequest().json(message), .json("Internal Server Error, Please try later"),
ServiceError::Unauthorized => HttpResponse::Unauthorized().json("Unauthorized") ServiceError::BadRequest(ref message) => {
HttpResponse::BadRequest().json(message)
}
ServiceError::Unauthorized => {
HttpResponse::Unauthorized().json("Unauthorized")
}
} }
} }
} }
@ -42,12 +47,13 @@ impl From<Error> for ServiceError {
match error { match error {
Error::DatabaseError(kind, info) => { Error::DatabaseError(kind, info) => {
if let DatabaseErrorKind::UniqueViolation = kind { if let DatabaseErrorKind::UniqueViolation = kind {
let message = info.details().unwrap_or_else(|| info.message()).to_string(); let message =
info.details().unwrap_or_else(|| info.message()).to_string();
return ServiceError::BadRequest(message); return ServiceError::BadRequest(message);
} }
ServiceError::InternalServerError ServiceError::InternalServerError
} }
_ => ServiceError::InternalServerError _ => ServiceError::InternalServerError,
} }
} }
} }

View File

@ -1,10 +1,11 @@
use actix::{Handler, Message}; use actix::{Handler, Message};
use chrono::{Duration, Local}; use chrono::{Duration, Local};
use diesel::{self, prelude::*}; use diesel::{self, prelude::*};
use errors::ServiceError;
use models::{DbExecutor, Invitation};
use uuid::Uuid; use uuid::Uuid;
use crate::errors::ServiceError;
use crate::models::{DbExecutor, Invitation};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct CreateInvitation { pub struct CreateInvitation {
pub email: String, pub email: String,
@ -18,7 +19,7 @@ impl Handler<CreateInvitation> for DbExecutor {
type Result = Result<Invitation, ServiceError>; type Result = Result<Invitation, ServiceError>;
fn handle(&mut self, msg: CreateInvitation, _: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: CreateInvitation, _: &mut Self::Context) -> Self::Result {
use schema::invitations::dsl::*; use crate::schema::invitations::dsl::*;
let conn: &PgConnection = &self.0.get().unwrap(); let conn: &PgConnection = &self.0.get().unwrap();
// creating a new Invitation object with expired at time that is 24 hours from now // creating a new Invitation object with expired at time that is 24 hours from now
@ -35,5 +36,3 @@ impl Handler<CreateInvitation> for DbExecutor {
Ok(inserted_invitation) Ok(inserted_invitation)
} }
} }

View File

@ -1,16 +1,16 @@
use actix_web::{AsyncResponder, FutureResponse, HttpResponse, Json, ResponseError, State}; use actix::Addr;
use actix_web::{web, Error, HttpResponse, ResponseError};
use futures::future::Future; use futures::future::Future;
use app::AppState; use crate::email_service::send_invitation;
use email_service::send_invitation; use crate::invitation_handler::CreateInvitation;
use invitation_handler::CreateInvitation; use crate::models::DbExecutor;
pub fn register_email( pub fn register_email(
(signup_invitation, state): (Json<CreateInvitation>, State<AppState>), signup_invitation: web::Json<CreateInvitation>,
) -> FutureResponse<HttpResponse> { db: web::Data<Addr<DbExecutor>>,
state ) -> impl Future<Item = HttpResponse, Error = Error> {
.db db.send(signup_invitation.into_inner())
.send(signup_invitation.into_inner())
.from_err() .from_err()
.and_then(|db_response| match db_response { .and_then(|db_response| match db_response {
Ok(invitation) => { Ok(invitation) => {
@ -18,5 +18,5 @@ pub fn register_email(
Ok(HttpResponse::Ok().into()) Ok(HttpResponse::Ok().into())
} }
Err(err) => Ok(err.error_response()), Err(err) => Ok(err.error_response()),
}).responder() })
} }

View File

@ -1,53 +1,44 @@
// to avoid the warning from diesel macros #![allow(unused_imports)]
#![allow(proc_macro_derive_resolution_fallback)]
extern crate bcrypt;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate serde;
extern crate chrono;
extern crate dotenv;
extern crate futures;
extern crate r2d2;
extern crate uuid;
extern crate jsonwebtoken as jwt;
extern crate sparkpost;
#[macro_use] #[macro_use]
extern crate diesel; extern crate diesel;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
#[macro_use]
extern crate failure;
mod app;
mod models;
mod schema;
mod errors;
mod auth_handler;
mod auth_routes;
mod invitation_handler;
mod invitation_routes;
mod register_handler;
mod register_routes;
mod utils;
mod email_service;
use models::DbExecutor;
use actix::prelude::*; use actix::prelude::*;
use actix_web::server; use actix_files as fs;
use actix_web::middleware::{
identity::{CookieIdentityPolicy, IdentityService},
Logger,
};
use actix_web::{web, App, HttpServer};
use chrono::Duration;
use diesel::{r2d2::ConnectionManager, PgConnection}; use diesel::{r2d2::ConnectionManager, PgConnection};
use dotenv::dotenv; use dotenv::dotenv;
use std::env;
mod auth_handler;
mod auth_routes;
mod email_service;
mod errors;
mod invitation_handler;
mod invitation_routes;
mod models;
mod register_handler;
mod register_routes;
mod schema;
mod utils;
fn main() { use crate::models::DbExecutor;
fn main() -> std::io::Result<()> {
dotenv().ok(); dotenv().ok();
std::env::set_var("RUST_LOG", "simple-auth-server=debug,actix_web=info"); std::env::set_var(
std::env::set_var("RUST_BACKTRACE", "1"); "RUST_LOG",
"simple-auth-server=debug,actix_web=info,actix_server=info",
);
env_logger::init(); env_logger::init();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let sys = actix::System::new("Actix_Tutorial"); let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
// create db connection pool // create db connection pool
let manager = ConnectionManager::<PgConnection>::new(database_url); let manager = ConnectionManager::<PgConnection>::new(database_url);
@ -55,12 +46,52 @@ fn main() {
.build(manager) .build(manager)
.expect("Failed to create pool."); .expect("Failed to create pool.");
let address: Addr<DbExecutor> = SyncArbiter::start(4, move || DbExecutor(pool.clone())); let address: Addr<DbExecutor> =
SyncArbiter::start(4, move || DbExecutor(pool.clone()));
server::new(move || app::create_app(address.clone())) HttpServer::new(move || {
.bind("127.0.0.1:3000") // secret is a random minimum 32 bytes long base 64 string
.expect("Can not bind to '127.0.0.1:3000'") let secret: String =
.start(); std::env::var("SECRET_KEY").unwrap_or_else(|_| "0123".repeat(8));
let domain: String =
std::env::var("DOMAIN").unwrap_or_else(|_| "localhost".to_string());
sys.run(); App::new()
.data(address.clone())
.wrap(Logger::default())
.wrap(IdentityService::new(
CookieIdentityPolicy::new(secret.as_bytes())
.name("auth")
.path("/")
.domain(domain.as_str())
.max_age(Duration::days(1))
.secure(false), // this can only be true if you have https
))
// everything under '/api/' route
.service(
web::scope("/api")
// routes for authentication
.service(
web::resource("/auth")
.route(web::post().to_async(auth_routes::login))
.route(web::delete().to(auth_routes::logout))
.route(web::get().to_async(auth_routes::get_me)),
)
// routes to invitation
.service(
web::resource("/invitation").route(
web::post().to_async(invitation_routes::register_email),
),
)
// routes to register as a user after the
.service(
web::resource("/register/{invitation_id}")
.route(web::post().to_async(register_routes::register_user)),
),
)
// serve static files
.service(fs::Files::new("/", "./static/").index_file("index.html"))
})
.bind("127.0.0.1:3000")?
.run()
} }

View File

@ -1,11 +1,11 @@
use actix::{Actor, SyncContext}; use actix::{Actor, SyncContext};
use chrono::{Local, NaiveDateTime};
use diesel::pg::PgConnection; use diesel::pg::PgConnection;
use diesel::r2d2::{ConnectionManager, Pool}; use diesel::r2d2::{ConnectionManager, Pool};
use chrono::{NaiveDateTime, Local};
use uuid::Uuid;
use std::convert::From; use std::convert::From;
use uuid::Uuid;
use schema::{users, invitations}; use crate::schema::{invitations, users};
/// This is db executor actor. can be run in parallel /// This is db executor actor. can be run in parallel
pub struct DbExecutor(pub Pool<ConnectionManager<PgConnection>>); pub struct DbExecutor(pub Pool<ConnectionManager<PgConnection>>);
@ -51,8 +51,6 @@ pub struct SlimUser {
impl From<User> for SlimUser { impl From<User> for SlimUser {
fn from(user: User) -> Self { fn from(user: User) -> Self {
SlimUser { SlimUser { email: user.email }
email: user.email
}
} }
} }

View File

@ -1,10 +1,11 @@
use actix::{Handler, Message}; use actix::{Handler, Message};
use chrono::Local; use chrono::Local;
use diesel::prelude::*; use diesel::prelude::*;
use errors::ServiceError;
use models::{DbExecutor, Invitation, User, SlimUser};
use uuid::Uuid; use uuid::Uuid;
use utils::hash_password;
use crate::errors::ServiceError;
use crate::models::{DbExecutor, Invitation, SlimUser, User};
use crate::utils::hash_password;
// UserData is used to extract data from a post request by the client // UserData is used to extract data from a post request by the client
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -23,19 +24,19 @@ impl Message for RegisterUser {
type Result = Result<SlimUser, ServiceError>; type Result = Result<SlimUser, ServiceError>;
} }
impl Handler<RegisterUser> for DbExecutor { impl Handler<RegisterUser> for DbExecutor {
type Result = Result<SlimUser, ServiceError>; type Result = Result<SlimUser, ServiceError>;
fn handle(&mut self, msg: RegisterUser, _: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: RegisterUser, _: &mut Self::Context) -> Self::Result {
use schema::invitations::dsl::{invitations, id}; use crate::schema::invitations::dsl::{id, invitations};
use schema::users::dsl::users; use crate::schema::users::dsl::users;
let conn: &PgConnection = &self.0.get().unwrap(); let conn: &PgConnection = &self.0.get().unwrap();
// try parsing the string provided by the user as url parameter // try parsing the string provided by the user as url parameter
// return early with error that will be converted to ServiceError // return early with error that will be converted to ServiceError
let invitation_id = Uuid::parse_str(&msg.invitation_id)?; let invitation_id = Uuid::parse_str(&msg.invitation_id)?;
invitations.filter(id.eq(invitation_id)) invitations
.filter(id.eq(invitation_id))
.load::<Invitation>(conn) .load::<Invitation>(conn)
.map_err(|_db_error| ServiceError::BadRequest("Invalid Invitation".into())) .map_err(|_db_error| ServiceError::BadRequest("Invalid Invitation".into()))
.and_then(|mut result| { .and_then(|mut result| {
@ -45,9 +46,8 @@ impl Handler<RegisterUser> for DbExecutor {
// try hashing the password, else return the error that will be converted to ServiceError // try hashing the password, else return the error that will be converted to ServiceError
let password: String = hash_password(&msg.password)?; let password: String = hash_password(&msg.password)?;
let user = User::with_details(invitation.email, password); let user = User::with_details(invitation.email, password);
let inserted_user: User = diesel::insert_into(users) let inserted_user: User =
.values(&user) diesel::insert_into(users).values(&user).get_result(conn)?;
.get_result(conn)?;
return Ok(inserted_user.into()); return Ok(inserted_user.into());
} }
@ -56,5 +56,3 @@ impl Handler<RegisterUser> for DbExecutor {
}) })
} }
} }

View File

@ -1,22 +1,25 @@
use actix_web::{AsyncResponder, FutureResponse, HttpResponse, ResponseError, State, Json, Path}; use actix::Addr;
use futures::future::Future; use actix_web::{web, Error, HttpResponse, ResponseError};
use futures::Future;
use app::AppState; use crate::models::DbExecutor;
use register_handler::{RegisterUser, UserData}; use crate::register_handler::{RegisterUser, UserData};
pub fn register_user(
pub fn register_user((invitation_id, user_data, state): (Path<String>, Json<UserData>, State<AppState>)) invitation_id: web::Path<String>,
-> FutureResponse<HttpResponse> { user_data: web::Json<UserData>,
db: web::Data<Addr<DbExecutor>>,
) -> impl Future<Item = HttpResponse, Error = Error> {
let msg = RegisterUser { let msg = RegisterUser {
// into_inner() returns the inner string value from Path // into_inner() returns the inner string value from Path
invitation_id: invitation_id.into_inner(), invitation_id: invitation_id.into_inner(),
password: user_data.password.clone(), password: user_data.password.clone(),
}; };
state.db.send(msg) db.send(msg)
.from_err() .from_err()
.and_then(|db_response| match db_response { .and_then(|db_response| match db_response {
Ok(slim_user) => Ok(HttpResponse::Ok().json(slim_user)), Ok(slim_user) => Ok(HttpResponse::Ok().json(slim_user)),
Err(service_error) => Ok(service_error.error_response()), Err(service_error) => Ok(service_error.error_response()),
}).responder() })
} }

View File

@ -14,7 +14,4 @@ table! {
} }
} }
allow_tables_to_appear_in_same_query!( allow_tables_to_appear_in_same_query!(invitations, users,);
invitations,
users,
);

View File

@ -1,14 +1,13 @@
use bcrypt::{hash, DEFAULT_COST}; use bcrypt::{hash, DEFAULT_COST};
use chrono::{Duration, Local}; use chrono::{Duration, Local};
use errors::ServiceError; use jsonwebtoken::{decode, encode, Header, Validation};
use jwt::{decode, encode, Header, Validation};
use models::SlimUser; use crate::errors::ServiceError;
use std::convert::From; use crate::models::SlimUser;
use std::env;
pub fn hash_password(plain: &str) -> Result<String, ServiceError> { pub fn hash_password(plain: &str) -> Result<String, ServiceError> {
// get the hashing cost from the env variable or use default // get the hashing cost from the env variable or use default
let hashing_cost: u32 = match env::var("HASH_ROUNDS") { let hashing_cost: u32 = match std::env::var("HASH_ROUNDS") {
Ok(cost) => cost.parse().unwrap_or(DEFAULT_COST), Ok(cost) => cost.parse().unwrap_or(DEFAULT_COST),
_ => DEFAULT_COST, _ => DEFAULT_COST,
}; };
@ -64,5 +63,5 @@ pub fn decode_token(token: &str) -> Result<SlimUser, ServiceError> {
} }
fn get_secret() -> String { fn get_secret() -> String {
env::var("JWT_SECRET").unwrap_or_else(|_| "my secret".into()) std::env::var("JWT_SECRET").unwrap_or_else(|_| "my secret".into())
} }

View File

@ -2,11 +2,10 @@
name = "state" name = "state"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
futures = "0.1" actix-web = "1.0.0-alpha.1"
env_logger = "0.5" futures = "0.1.25"
env_logger = "0.6"
actix = "0.7"
actix-web = "0.7"

View File

@ -1,55 +1,44 @@
#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] #![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
//! There are two level of statefulness in actix-web. Application has state //! Application may have multiple data objects that are shared across
//! that is shared across all handlers within same Application. //! all handlers within same Application. Data could be added
//! And individual handler can have state. //! with `App::data()` method, multiple different data objects could be added.
//! //!
//! > **Note**: http server accepts an application factory rather than an //! > **Note**: http server accepts an application factory rather than an
//! application > instance. Http server constructs an application instance for //! application > instance. Http server constructs an application instance for
//! each thread, > thus application state //! each thread, > thus application data
//! > must be constructed multiple times. If you want to share state between //! > must be constructed multiple times. If you want to share data between
//! different > threads, a shared object should be used, e.g. `Arc`. //! different > threads, a shared object should be used, e.g. `Arc`.
//! //!
//! Check [user guide](https://actix.rs/book/actix-web/sec-2-application.html) for more info. //! Check [user guide](https://actix.rs/book/actix-web/sec-2-application.html) for more info.
extern crate actix; use std::io;
extern crate actix_web; use std::sync::{Arc, Mutex};
extern crate env_logger;
use std::sync::Arc; use actix_web::{middleware, web, App, HttpRequest, HttpResponse, HttpServer};
use std::sync::Mutex;
use actix_web::{middleware, server, App, HttpRequest, HttpResponse};
/// Application state
struct AppState {
counter: Arc<Mutex<usize>>,
}
/// simple handle /// simple handle
fn index(req: &HttpRequest<AppState>) -> HttpResponse { fn index(state: web::Data<Arc<Mutex<usize>>>, req: HttpRequest) -> HttpResponse {
println!("{:?}", req); println!("{:?}", req);
*(req.state().counter.lock().unwrap()) += 1; *(state.lock().unwrap()) += 1;
HttpResponse::Ok().body(format!("Num of requests: {}", req.state().counter.lock().unwrap())) HttpResponse::Ok().body(format!("Num of requests: {}", state.lock().unwrap()))
} }
fn main() { fn main() -> io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("ws-example");
let counter = Arc::new(Mutex::new(0)); let counter = Arc::new(Mutex::new(0));
//move is necessary to give closure below ownership of counter
server::new(move || {
App::with_state(AppState{counter: counter.clone()}) // <- create app with shared state
// enable logger
.middleware(middleware::Logger::default())
// register simple handler, handle all methods
.resource("/", |r| r.f(index))
}).bind("127.0.0.1:8080")
.unwrap()
.start();
println!("Started http server: 127.0.0.1:8080"); //move is necessary to give closure below ownership of counter
let _ = sys.run(); HttpServer::new(move || {
App::new()
.data(counter.clone()) // <- create app with shared state
// enable logger
.wrap(middleware::Logger::default())
// register simple handler, handle all methods
.service(web::resource("/").to(index))
})
.bind("127.0.0.1:8080")?
.run()
} }

View File

@ -2,11 +2,12 @@
name = "static_index" name = "static_index"
version = "0.1.0" version = "0.1.0"
authors = ["Jose Marinez <digeratus@gmail.com>"] authors = ["Jose Marinez <digeratus@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
futures = "0.1" futures = "0.1"
env_logger = "0.5" env_logger = "0.5"
actix = "0.7" actix-web = "1.0.0-alpha.1"
actix-web = "0.7" actix-files = "0.1.0-alpha.1"

View File

@ -1,28 +1,19 @@
extern crate actix; use actix_files as fs;
extern crate actix_web; use actix_web::{middleware, App, HttpServer};
extern crate env_logger;
use actix_web::{fs, middleware, server, App}; fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
::std::env::set_var("RUST_BACKTRACE", "1");
env_logger::init(); env_logger::init();
let sys = actix::System::new("static_index"); HttpServer::new(|| {
server::new(|| {
App::new() App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
.handler( .service(
"/", // static files
fs::StaticFiles::new("./static/").unwrap().index_file("index.html") fs::Files::new("/", "./static/").index_file("index.html"),
) )
}).bind("127.0.0.1:8080") })
.expect("Can not start server on given IP/Port") .bind("127.0.0.1:8080")?
.start(); .run()
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
} }

View File

@ -2,14 +2,14 @@
name = "template-askama" name = "template-askama"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
env_logger = "0.5" #askama = { version = "0.8", features = ["with-actix-web"] }
askama = { version = "0.8", features = ["with-actix-web"] } actix-web = "1.0.0-alpha.1"
env_logger = "0.6"
actix = "0.7" askama = "0.8"
actix-web = "0.7"
[build-dependencies] [build-dependencies]
askama = "0.8" askama = "0.8"

View File

@ -1,13 +1,7 @@
extern crate actix;
extern crate actix_web;
#[macro_use]
extern crate askama;
use std::collections::HashMap; use std::collections::HashMap;
use actix_web::{http, server, App, HttpResponse, Query, Result}; use actix_web::{web, App, HttpResponse, HttpServer, Result};
use askama::Template; use askama::Template;
use askama::actix_web::TemplateIntoResponse;
#[derive(Template)] #[derive(Template)]
#[template(path = "user.html")] #[template(path = "user.html")]
@ -20,27 +14,25 @@ struct UserTemplate<'a> {
#[template(path = "index.html")] #[template(path = "index.html")]
struct Index; struct Index;
fn index(query: Query<HashMap<String, String>>) -> Result<HttpResponse> { fn index(query: web::Query<HashMap<String, String>>) -> Result<HttpResponse> {
if let Some(name) = query.get("name") { let s = if let Some(name) = query.get("name") {
UserTemplate { UserTemplate {
name: name, name: name,
text: "Welcome!", text: "Welcome!",
}.into_response()
} else {
Index.into_response()
} }
} .render()
fn main() {
let sys = actix::System::new("template-askama");
// start http server
server::new(move || {
App::new().resource("/", |r| r.method(http::Method::GET).with(index))
}).bind("127.0.0.1:8080")
.unwrap() .unwrap()
.start(); } else {
Index.render().unwrap()
println!("Started http server: 127.0.0.1:8080"); };
let _ = sys.run(); Ok(HttpResponse::Ok().content_type("text/html").body(s))
}
fn main() -> std::io::Result<()> {
// start http server
HttpServer::new(move || {
App::new().service(web::resource("/").route(web::get()).to(index))
})
.bind("127.0.0.1:8080")?
.run()
} }

View File

@ -2,10 +2,10 @@
name = "template-tera" name = "template-tera"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[dependencies] [dependencies]
env_logger = "0.5" env_logger = "0.6"
tera = "*" tera = "0.11"
actix = "0.7" actix-web = "1.0.0-alpha.1"
actix-web = "0.7"

View File

@ -1,57 +1,42 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
#[macro_use] #[macro_use]
extern crate tera; extern crate tera;
use std::collections::HashMap; use std::collections::HashMap;
use actix_web::{ use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer};
error, http, middleware, server, App, Error, HttpResponse, Query, State,
};
struct AppState {
template: tera::Tera, // <- store tera template in application state
}
// store tera template in application state
fn index( fn index(
(state, query): (State<AppState>, Query<HashMap<String, String>>), tmpl: web::Data<tera::Tera>,
query: web::Query<HashMap<String, String>>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let s = if let Some(name) = query.get("name") { let s = if let Some(name) = query.get("name") {
// <- submitted form // submitted form
let mut ctx = tera::Context::new(); let mut ctx = tera::Context::new();
ctx.add("name", &name.to_owned()); ctx.insert("name", &name.to_owned());
ctx.add("text", &"Welcome!".to_owned()); ctx.insert("text", &"Welcome!".to_owned());
state tmpl.render("user.html", &ctx)
.template
.render("user.html", &ctx)
.map_err(|_| error::ErrorInternalServerError("Template error"))? .map_err(|_| error::ErrorInternalServerError("Template error"))?
} else { } else {
state tmpl.render("index.html", &tera::Context::new())
.template
.render("index.html", &tera::Context::new())
.map_err(|_| error::ErrorInternalServerError("Template error"))? .map_err(|_| error::ErrorInternalServerError("Template error"))?
}; };
Ok(HttpResponse::Ok().content_type("text/html").body(s)) Ok(HttpResponse::Ok().content_type("text/html").body(s))
} }
fn main() { fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("tera-example");
server::new(|| { HttpServer::new(|| {
let tera = let tera =
compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*")); compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"));
App::with_state(AppState{template: tera}) App::new()
// enable logger .data(tera)
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default()) // enable logger
.resource("/", |r| r.method(http::Method::GET).with(index)) .service(web::resource("/").route(web::get().to(index)))
}).bind("127.0.0.1:8080") })
.unwrap() .bind("127.0.0.1:8080")?
.start(); .run()
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
} }

15
template_yarte/Cargo.toml Normal file
View File

@ -0,0 +1,15 @@
[package]
name = "example"
version = "0.0.1"
authors = ["Rust-iendo Barcelona <riendocontributions@gmail.com>"]
publish = false
edition = "2018"
workspace = ".."
[dependencies]
actix-web = "1.0.0-alpha.1"
env_logger = "0.6"
yarte = "0.1"
[build-dependencies]
yarte = "0.1"

11
template_yarte/README.md Normal file
View File

@ -0,0 +1,11 @@
# yarte
Example of composition with partials and `with-actix-web` feature
See the generated code on stdout when run at debug
```bash
cargo run
```
> open `localhost:8080`
More at [mdbook](https://yarte.netlify.com/) and [repository](https://gitlab.com/r-iendo/yarte)

5
template_yarte/build.rs Normal file
View File

@ -0,0 +1,5 @@
use yarte::recompile;
fn main() {
recompile::when_changed();
}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,43 @@
#[macro_use]
extern crate actix_web;
use std::collections::HashMap;
use actix_web::{
error::ErrorInternalServerError, middleware, web::Query, App, HttpResponse,
HttpServer, Result,
};
use yarte::Template;
#[derive(Template)]
#[template(path = "index.hbs")]
struct IndexTemplate {
query: Query<HashMap<String, String>>,
}
#[get("/")]
pub fn index(query: Query<HashMap<String, String>>) -> Result<HttpResponse> {
IndexTemplate { query }
.call()
.map(|s| {
HttpResponse::Ok()
.content_type(IndexTemplate::mime())
.body(s)
})
.map_err(|_| ErrorInternalServerError("Template parsing error"))
}
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
// start http server
HttpServer::new(|| {
App::new()
// enable logger
.wrap(middleware::Logger::default())
.service(index)
})
.bind("127.0.0.1:8080")?
.run()
}

View File

@ -0,0 +1 @@
<{{ tag }}>Welcome!</{{ tag }}>

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Actix web</title>
</head>
<body>
{{~#if let Some(name) = query.get("name") ~}}
<h1>Hi, {{ name }}!</h1>
{{~> alias/welcome tag='p' ~}}
{{~ else ~}}
{{~> alias/welcome tag="h1" ~}}
<p>
<h3>What is your name?</h3>
<form>
<input type="text" name="name"/><br/>
<p><input type="submit"></p>
</form>
</p>
{{~/if~}}
</body>
</html>

View File

@ -0,0 +1,7 @@
# root dir of templates
[main]
dir = "templates"
# Alias for partials. In call, change the start of partial path with one of this, if exist.
[partials]
alias = "./deep/more/deep"

View File

@ -1,15 +1,16 @@
[package] [package]
name = "tls-example" name = "tls-example"
version = "0.1.0" version = "0.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" edition = "2018"
workspace = ".."
[[bin]] [[bin]]
name = "tls-server" name = "tls-server"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
env_logger = "0.5" actix-rt = "0.2"
actix-web = { version="1.0.0-alpha.1", features=["ssl"] }
env_logger = "0.6"
openssl = { version="0.10" } openssl = { version="0.10" }
actix = "0.7"
actix-web = { version = "0.7", features=["ssl"] }

View File

@ -1,31 +1,16 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV MIICljCCAX4CCQDztMNlxk6oeTANBgkqhkiG9w0BAQsFADANMQswCQYDVQQIDAJj
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww YTAeFw0xOTAzMDcwNzEyNThaFw0yMDAzMDYwNzEyNThaMA0xCzAJBgNVBAgMAmNh
CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0GMP3YzDVFWgNhRiHnfe
NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD d192131Zi23p8WiutneD9I5WO42c79fOXsxLWn+2HSqPvCPHIBLoMX8o9lgCxt2P
QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY /JUCAWbrE2EuvhkMrWk6/q7xB211XZYfnkqdt7mA0jMUC5o32AX3ew456TAq5P8Y
MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A dq9H/qXdRtAvKD0QdkFfq8ePCiqOhcqacZ/NWva7R4HdgTnbL1DRQjGBXszI07P9
MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1 1yw8GOym46uxNHRujQp3lYEhc1V3JTF9kETpSBHyEAkQ8WHxGf8UBHDhh7hcc+KI
sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U JHMlVYy5wDv4ZJeYsY1rD6/n4tyd3r0yzBM57UGf6qrVZEYmLB7Jad+8Df5vIoGh
NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy WwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQB1DEu9NiShCfQuA17MG5O0Jr2/PS1z
voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr /+HW7oW15WXpqDKOEJalid31/Bzwvwq0bE12xKE4ZLdbqJHmJTdSUoGfOfBZKka6
odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND R2thOjqH7hFvxjfgS7kBy5BrRZewM9xKIJ6zU6+6mxR64x9vmkOmppV0fx5clZjH
xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA c7qn5kSNWTMsFbjPnb5BeJJwZdqpMLs99jgoMvGtCUmkyVYODGhh65g6tR9kIPvM
CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI zu/Cw122/y7tFfkuknMSYwGEYF3XcZpXt54a6Lu5hk6PuOTsK+7lC+HX7CSF1dpv
yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U u1szL5fDgiCBFCnyKeOqF61mxTCUht3U++37VDFvhzN1t6HIVTYm2JJ7
UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO
vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un
CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN
BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk
3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI
JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD
JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL
d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu
ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC
CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur
y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7
YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh
g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt
tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y
1QU=
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,51 +1,28 @@
-----BEGIN RSA PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQYw/djMNUVaA2
n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M FGIed953X3bXfVmLbenxaK62d4P0jlY7jZzv185ezEtaf7YdKo+8I8cgEugxfyj2
IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5 WALG3Y/8lQIBZusTYS6+GQytaTr+rvEHbXVdlh+eSp23uYDSMxQLmjfYBfd7Djnp
4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ MCrk/xh2r0f+pd1G0C8oPRB2QV+rx48KKo6Fyppxn81a9rtHgd2BOdsvUNFCMYFe
WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk zMjTs/3XLDwY7Kbjq7E0dG6NCneVgSFzVXclMX2QROlIEfIQCRDxYfEZ/xQEcOGH
oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli uFxz4ogkcyVVjLnAO/hkl5ixjWsPr+fi3J3evTLMEzntQZ/qqtVkRiYsHslp37wN
JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6 /m8igaFbAgMBAAECggEAJI278rkGany6pcHdlEqik34DcrliQ7r8FoSuYQOF+hgd
/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD uESXCttoL+jWLwHICEW3AOGlxFKMuGH95Xh6xDeJUl0xBN3wzm11rZLnTmPvHU3C
YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP qfLha5Ex6qpcECZSGo0rLv3WXeZuCv/r2KPCYnj86ZTFpD2kGw/Ztc1AXf4Jsi/1
wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA 478Mf23QmAvCAPimGCyjLQx2c9/vg/6K7WnDevY4tDuDKLeSJxKZBSHUn3cM1Bwj
69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA 2QzaHfSFA5XljOF5PLeR3cY5ncrrVLWChT9XuGt9YMdLAcSQxgE6kWV1RSCq+lbj
AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/ e6OOe879IrrqwBvMQfKQqnm1kl8OrfPMT5CNWKvEgQKBgQD8q5E4x9taDS9RmhRO
9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm 07ptsr/I795tX8CaJd/jc4xGuCGBqpNw/hVebyNNYQvpiYzDNBSEhtd59957VyET
YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR hcrGyxD0ByKm8F/lPgFw5y6wi3RUnucCV/jxkMHmxVzYMbFUEGCQ0pIU9/GFS7RZ
6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM 9VjqRDeE86U3yHO+WCFoHtd8aQKBgQDTIhi0uq0oY87bUGnWbrrkR0UVRNPDG1BT
ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI cuXACYlv/DV/XpxPC8iPK1UwG4XaOVxodtIRjdBqvb8fUM6HSY6qll64N/4/1jre
7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab Ho+d4clE4tK6a9WU96CKxwHn2BrWUZJPtoldaCZJFJ7SfiHuLlqW7TtYFrOfPIjN
L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+ ADiqK+bHIwKBgQCpfIiAVwebo0Z/bWR77+iZFxMwvT4tjdJLVGaXUvXgpjjLmtkm
vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ LTm2S8SZbiSodfz3H+M3dp/pj8wsXiiwyMlZifOITZT/+DPLOUmMK3cVM6ZH8QMy
b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz fkJd/+UhYHhECSlTI10zKByXdi4LZNnIkhwfoLzBMRI9lfeV0dYu2qlfKQKBgEVI
0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL kRbtk1kHt5/ceX62g3nZsV/TYDJMSkW4FJC6EHHBL8UGRQDjewMQUzogLgJ4hEx7
OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI gV/lS5lbftZF7CAVEU4FXjvRlAtav6KYIMTMjQGf9UrbjBEAWZxwxb1Q+y2NQxgJ
6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC bHZMcRPWQnAMmBHTAEM6whicCoGcmb+77Nxa37ZFAoGBALBuUNeD3fKvQR8v6GoA
71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g spv+RYL9TB4wz2Oe9EYSp9z5EiWlTmuvFz3zk8pHDSpntxYH5O5HJ/3OzwhHz9ym
9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu +DNE9AP9LW9hAzMuu7Gob1h8ShGwJVYwrQN3q/83ooUL7WSAuVOLpzJ7BFFlcCjp
bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb MhFvd9iOt/R0N30/3AbQXkOp
IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga -----END PRIVATE KEY-----
/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc
KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2
iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP
tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD
jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY
l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj
gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh
Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q
1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW
t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI
fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9
5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt
+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc
3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf
cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T
qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU
DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K
5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc
fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc
Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ
4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6
I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c=
-----END RSA PRIVATE KEY-----

View File

@ -1,26 +1,21 @@
#![allow(unused_variables)] use std::io;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate openssl;
use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse}; use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
/// simple handle /// simple handle
fn index(req: &HttpRequest) -> Result<HttpResponse, Error> { fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
println!("{:?}", req); println!("{:?}", req);
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("text/plain") .content_type("text/plain")
.body("Welcome!")) .body("Welcome!"))
} }
fn main() { fn main() -> io::Result<()> {
if ::std::env::var("RUST_LOG").is_err() { std::env::set_var("RUST_LOG", "actix_web=debug");
::std::env::set_var("RUST_LOG", "actix_web=info");
}
env_logger::init(); env_logger::init();
let sys = actix::System::new("ws-example");
let sys = actix_rt::System::new("tls-example");
// load ssl keys // load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
@ -29,22 +24,22 @@ fn main() {
.unwrap(); .unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap(); builder.set_certificate_chain_file("cert.pem").unwrap();
server::new(|| { HttpServer::new(|| {
App::new() App::new()
// enable logger // enable logger
.middleware(middleware::Logger::default()) .wrap(middleware::Logger::default())
// register simple handler, handle all methods // register simple handler, handle all methods
.resource("/index.html", |r| r.f(index)) .service(web::resource("/index.html").to(index))
// with path parameters // with path parameters
.resource("/", |r| r.method(http::Method::GET).f(|req| { .service(web::resource("/").route(web::get().to(|| {
HttpResponse::Found() HttpResponse::Found()
.header("LOCATION", "/index.html") .header("LOCATION", "/index.html")
.finish() .finish()
})) })))
}).bind_ssl("127.0.0.1:8443", builder) })
.unwrap() .bind_ssl("127.0.0.1:8443", builder)?
.start(); .start();
println!("Started http server: 127.0.0.1:8443"); println!("Started http server: 127.0.0.1:8443");
let _ = sys.run(); sys.run()
} }

View File

@ -23,7 +23,8 @@ fn main() {
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/index.html", |r| r.f(|_| "Hello world!")) .resource("/index.html", |r| r.f(|_| "Hello world!"))
.resource("/", |r| r.f(index)) .resource("/", |r| r.f(index))
}).start_incoming(listener.incoming(), false); })
.start_incoming(listener.incoming(), false);
println!("Started http server: /tmp/actix-uds.socket"); println!("Started http server: /tmp/actix-uds.socket");
let _ = sys.run(); let _ = sys.run();

View File

@ -3,16 +3,13 @@ name = "actix-web-cors"
version = "0.1.0" version = "0.1.0"
authors = ["krircc <krircc@aliyun.com>"] authors = ["krircc <krircc@aliyun.com>"]
workspace = "../../" workspace = "../../"
edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0-alpha.1"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
http = "0.1"
actix = "0.7"
actix-web = "0.7"
dotenv = "0.10" dotenv = "0.10"
env_logger = "0.5" env_logger = "0.6"
futures = "0.1" futures = "0.1"

View File

@ -1,48 +1,29 @@
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
extern crate serde;
extern crate serde_json;
use actix_web::{ use actix_web::{
http::{header, Method}, http::header, middleware::cors::Cors, middleware::Logger, web, App, HttpServer,
middleware,
middleware::cors::Cors,
server, App,
}; };
use std::env;
mod user; mod user;
use user::info;
fn main() { fn main() -> std::io::Result<()> {
env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix::System::new("Actix-web-CORS"); HttpServer::new(move || {
server::new(move || {
App::new() App::new()
.middleware(middleware::Logger::default()) .wrap(
.configure(|app| { Cors::new()
Cors::for_app(app)
.allowed_origin("http://localhost:1234") .allowed_origin("http://localhost:1234")
.allowed_methods(vec!["GET", "POST"]) .allowed_methods(vec!["GET", "POST"])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT]) .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_header(header::CONTENT_TYPE) .allowed_header(header::CONTENT_TYPE)
.max_age(3600) .max_age(3600),
.resource("/user/info", |r| { )
r.method(Method::POST).with(info); .wrap(Logger::default())
.service(web::resource("/user/info").route(web::post().to(user::info)))
}) })
.register() .bind("127.0.0.1:8000")?
}) .run()
}).bind("127.0.0.1:8000")
.unwrap()
.shutdown_timeout(2)
.start();
let _ = sys.run();
} }

View File

@ -1,4 +1,4 @@
use actix_web::{Json, Result}; use actix_web::web;
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
pub struct Info { pub struct Info {
@ -8,12 +8,12 @@ pub struct Info {
confirm_password: String, confirm_password: String,
} }
pub fn info(info: Json<Info>) -> Result<Json<Info>> { pub fn info(info: web::Json<Info>) -> web::Json<Info> {
println!("=========={:?}=========", info); println!("=========={:?}=========", info);
Ok(Json(Info { web::Json(Info {
username: info.username.clone(), username: info.username.clone(),
email: info.email.clone(), email: info.email.clone(),
password: info.password.clone(), password: info.password.clone(),
confirm_password: info.confirm_password.clone(), confirm_password: info.confirm_password.clone(),
})) })
} }

View File

@ -52,7 +52,8 @@ impl WsChatSession {
} }
fut::ok(()) fut::ok(())
}).spawn(ctx); })
.spawn(ctx);
} }
fn list_rooms(&mut self, ctx: &mut ws::WebsocketContext<Self>) { fn list_rooms(&mut self, ctx: &mut ws::WebsocketContext<Self>) {
@ -66,7 +67,8 @@ impl WsChatSession {
} }
} }
fut::ok(()) fut::ok(())
}).spawn(ctx); })
.spawn(ctx);
} }
fn send_msg(&self, msg: &str) { fn send_msg(&self, msg: &str) {
@ -158,7 +160,8 @@ fn main() {
.unwrap() .unwrap()
.index_file("index.html"), .index_file("index.html"),
) )
}).bind("127.0.0.1:8080") })
.bind("127.0.0.1:8080")
.unwrap() .unwrap()
.start(); .start();

View File

@ -2,23 +2,24 @@
name = "websocket-example" name = "websocket-example"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[[bin]] [[bin]]
name = "websocket-chat-server" name = "websocket-chat-server"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
rand = "*" actix = "0.8.0-alpha.2"
actix-web = "1.0.0-alpha.1"
actix-web-actors = "1.0.0-alpha.1"
actix-files = "0.1.0-alpha.1"
rand = "0.6"
bytes = "0.4" bytes = "0.4"
byteorder = "1.1" byteorder = "1.1"
futures = "0.1" futures = "0.1.25"
tokio-io = "0.1" tokio-io = "0.1"
tokio-core = "0.1" env_logger = "0.6"
env_logger = "*"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
actix = "0.7"
actix-web = "0.7"

View File

@ -1,22 +1,9 @@
#![allow(unused_variables)] use std::time::{Duration, Instant};
extern crate byteorder;
extern crate bytes;
extern crate env_logger;
extern crate futures;
extern crate rand;
extern crate serde;
extern crate serde_json;
extern crate tokio_core;
extern crate tokio_io;
extern crate actix;
extern crate actix_web;
use std::time::{Instant, Duration};
use actix::*; use actix::*;
use actix_web::server::HttpServer; use actix_files as fs;
use actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse}; use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_web_actors::ws;
mod server; mod server;
@ -25,22 +12,22 @@ const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
/// How long before lack of client response causes a timeout /// How long before lack of client response causes a timeout
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
/// This is our websocket route state, this state is shared with all route
/// instances via `HttpContext::state()`
struct WsChatSessionState {
addr: Addr<server::ChatServer>,
}
/// Entry point for our route /// Entry point for our route
fn chat_route(req: &HttpRequest<WsChatSessionState>) -> Result<HttpResponse, Error> { fn chat_route(
req: HttpRequest,
stream: web::Payload,
srv: web::Data<Addr<server::ChatServer>>,
) -> Result<HttpResponse, Error> {
ws::start( ws::start(
req,
WsChatSession { WsChatSession {
id: 0, id: 0,
hb: Instant::now(), hb: Instant::now(),
room: "Main".to_owned(), room: "Main".to_owned(),
name: None, name: None,
addr: srv.get_ref().clone(),
}, },
&req,
stream,
) )
} }
@ -54,10 +41,12 @@ struct WsChatSession {
room: String, room: String,
/// peer name /// peer name
name: Option<String>, name: Option<String>,
/// Chat server
addr: Addr<server::ChatServer>,
} }
impl Actor for WsChatSession { impl Actor for WsChatSession {
type Context = ws::WebsocketContext<Self, WsChatSessionState>; type Context = ws::WebsocketContext<Self>;
/// Method is called on actor start. /// Method is called on actor start.
/// We register ws session with ChatServer /// We register ws session with ChatServer
@ -71,8 +60,7 @@ impl Actor for WsChatSession {
// HttpContext::state() is instance of WsChatSessionState, state is shared // HttpContext::state() is instance of WsChatSessionState, state is shared
// across all routes within application // across all routes within application
let addr = ctx.address(); let addr = ctx.address();
ctx.state() self.addr
.addr
.send(server::Connect { .send(server::Connect {
addr: addr.recipient(), addr: addr.recipient(),
}) })
@ -88,9 +76,9 @@ impl Actor for WsChatSession {
.wait(ctx); .wait(ctx);
} }
fn stopping(&mut self, ctx: &mut Self::Context) -> Running { fn stopping(&mut self, _: &mut Self::Context) -> Running {
// notify chat server // notify chat server
ctx.state().addr.do_send(server::Disconnect { id: self.id }); self.addr.do_send(server::Disconnect { id: self.id });
Running::Stop Running::Stop
} }
} }
@ -126,8 +114,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
// Send ListRooms message to chat server and wait for // Send ListRooms message to chat server and wait for
// response // response
println!("List rooms"); println!("List rooms");
ctx.state() self.addr
.addr
.send(server::ListRooms) .send(server::ListRooms)
.into_actor(self) .into_actor(self)
.then(|res, _, ctx| { .then(|res, _, ctx| {
@ -149,7 +136,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
"/join" => { "/join" => {
if v.len() == 2 { if v.len() == 2 {
self.room = v[1].to_owned(); self.room = v[1].to_owned();
ctx.state().addr.do_send(server::Join { self.addr.do_send(server::Join {
id: self.id, id: self.id,
name: self.room.clone(), name: self.room.clone(),
}); });
@ -175,17 +162,17 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
m.to_owned() m.to_owned()
}; };
// send message to chat server // send message to chat server
ctx.state().addr.do_send(server::ClientMessage { self.addr.do_send(server::ClientMessage {
id: self.id, id: self.id,
msg: msg, msg: msg,
room: self.room.clone(), room: self.room.clone(),
}) })
} }
} }
ws::Message::Binary(bin) => println!("Unexpected binary"), ws::Message::Binary(_) => println!("Unexpected binary"),
ws::Message::Close(_) => { ws::Message::Close(_) => {
ctx.stop(); ctx.stop();
}, }
} }
} }
} }
@ -194,7 +181,7 @@ impl WsChatSession {
/// helper method that sends ping to client every second. /// helper method that sends ping to client every second.
/// ///
/// also this method checks heartbeats from client /// also this method checks heartbeats from client
fn hb(&self, ctx: &mut ws::WebsocketContext<Self, WsChatSessionState>) { fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
// check client heartbeats // check client heartbeats
if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
@ -202,9 +189,7 @@ impl WsChatSession {
println!("Websocket Client heartbeat failed, disconnecting!"); println!("Websocket Client heartbeat failed, disconnecting!");
// notify chat server // notify chat server
ctx.state() act.addr.do_send(server::Disconnect { id: act.id });
.addr
.do_send(server::Disconnect { id: act.id });
// stop actor // stop actor
ctx.stop(); ctx.stop();
@ -218,35 +203,30 @@ impl WsChatSession {
} }
} }
fn main() { fn main() -> std::io::Result<()> {
let _ = env_logger::init(); env_logger::init();
let sys = actix::System::new("websocket-example"); let sys = System::new("ws-example");
// Start chat server actor in separate thread // Start chat server actor
let server = Arbiter::start(|_| server::ChatServer::default()); let server = server::ChatServer::default().start();
// Create Http server with websocket support // Create Http server with websocket support
HttpServer::new(move || { HttpServer::new(move || {
// Websocket sessions state App::new()
let state = WsChatSessionState { .data(server.clone())
addr: server.clone(),
};
App::with_state(state)
// redirect to websocket.html // redirect to websocket.html
.resource("/", |r| r.method(http::Method::GET).f(|_| { .service(web::resource("/").route(web::get().to(|| {
HttpResponse::Found() HttpResponse::Found()
.header("LOCATION", "/static/websocket.html") .header("LOCATION", "/static/websocket.html")
.finish() .finish()
})) })))
// websocket // websocket
.resource("/ws/", |r| r.route().f(chat_route)) .service(web::resource("/ws/").to(chat_route))
// static resources // static resources
.handler("/static/", fs::StaticFiles::new("static/").unwrap()) .service(fs::Files::new("/static/", "static/"))
}).bind("127.0.0.1:8080") })
.unwrap() .bind("127.0.0.1:8080")?
.start(); .start();
println!("Started http server: 127.0.0.1:8080"); sys.run()
let _ = sys.run();
} }

View File

@ -2,7 +2,8 @@
name = "websocket-tcp-example" name = "websocket-tcp-example"
version = "0.1.0" version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../" workspace = ".."
edition = "2018"
[[bin]] [[bin]]
name = "websocket-tcp-server" name = "websocket-tcp-server"
@ -13,18 +14,19 @@ name = "websocket-tcp-client"
path = "src/client.rs" path = "src/client.rs"
[dependencies] [dependencies]
rand = "*" actix = "0.8.0-alpha.2"
actix-web = "1.0.0-alpha.1"
actix-web-actors = "1.0.0-alpha.1"
actix-files = "0.1.0-alpha.1"
rand = "0.6"
bytes = "0.4" bytes = "0.4"
byteorder = "1.1" byteorder = "1.1"
futures = "0.1" futures = "0.1"
tokio-io = "0.1" tokio-io = "0.1"
tokio-tcp = "0.1" tokio-tcp = "0.1"
tokio-codec = "0.1" tokio-codec = "0.1"
env_logger = "*" env_logger = "0.6"
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"
serde_derive = "1.0" serde_derive = "1.0"
actix = "0.7"
actix-web = "0.7"

View File

@ -65,7 +65,9 @@ impl Encoder for ChatCodec {
type Error = io::Error; type Error = io::Error;
fn encode( fn encode(
&mut self, msg: ChatResponse, dst: &mut BytesMut, &mut self,
msg: ChatResponse,
dst: &mut BytesMut,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
let msg = json::to_string(&msg).unwrap(); let msg = json::to_string(&msg).unwrap();
let msg_ref: &[u8] = msg.as_ref(); let msg_ref: &[u8] = msg.as_ref();
@ -108,7 +110,9 @@ impl Encoder for ClientChatCodec {
type Error = io::Error; type Error = io::Error;
fn encode( fn encode(
&mut self, msg: ChatRequest, dst: &mut BytesMut, &mut self,
msg: ChatRequest,
dst: &mut BytesMut,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
let msg = json::to_string(&msg).unwrap(); let msg = json::to_string(&msg).unwrap();
let msg_ref: &[u8] = msg.as_ref(); let msg_ref: &[u8] = msg.as_ref();

View File

@ -1,25 +1,14 @@
#![allow(unused_variables)]
extern crate byteorder;
extern crate bytes;
extern crate env_logger;
extern crate futures;
extern crate rand;
extern crate serde;
extern crate serde_json;
extern crate tokio_codec;
extern crate tokio_io;
extern crate tokio_tcp;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
#[macro_use] #[macro_use]
extern crate actix; extern crate actix;
extern crate actix_web;
use std::time::{Duration, Instant};
use actix::*; use actix::*;
use actix_web::server::HttpServer; use actix_files as fs;
use actix_web::{fs, http, ws, App, Error, HttpRequest, HttpResponse}; use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};
use std::time::{Instant, Duration}; use actix_web_actors::ws;
mod codec; mod codec;
mod server; mod server;
@ -30,22 +19,22 @@ const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
/// How long before lack of client response causes a timeout /// How long before lack of client response causes a timeout
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10); const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
/// This is our websocket route state, this state is shared with all route
/// instances via `HttpContext::state()`
struct WsChatSessionState {
addr: Addr<server::ChatServer>,
}
/// Entry point for our route /// Entry point for our route
fn chat_route(req: &HttpRequest<WsChatSessionState>) -> Result<HttpResponse, Error> { fn chat_route(
req: HttpRequest,
stream: web::Payload,
srv: web::Data<Addr<server::ChatServer>>,
) -> Result<HttpResponse, Error> {
ws::start( ws::start(
req,
WsChatSession { WsChatSession {
id: 0, id: 0,
hb: Instant::now(), hb: Instant::now(),
room: "Main".to_owned(), room: "Main".to_owned(),
name: None, name: None,
addr: srv.get_ref().clone(),
}, },
&req,
stream,
) )
} }
@ -59,26 +48,26 @@ struct WsChatSession {
room: String, room: String,
/// peer name /// peer name
name: Option<String>, name: Option<String>,
/// Chat server
addr: Addr<server::ChatServer>,
} }
impl Actor for WsChatSession { impl Actor for WsChatSession {
type Context = ws::WebsocketContext<Self, WsChatSessionState>; type Context = ws::WebsocketContext<Self>;
/// Method is called on actor start. /// Method is called on actor start.
/// We register ws session with ChatServer /// We register ws session with ChatServer
fn started(&mut self, ctx: &mut Self::Context) { fn started(&mut self, ctx: &mut Self::Context) {
// we'll start heartbeat process on session start.
self.hb(ctx);
// register self in chat server. `AsyncContext::wait` register // register self in chat server. `AsyncContext::wait` register
// future within context, but context waits until this future resolves // future within context, but context waits until this future resolves
// before processing any other events. // before processing any other events.
// HttpContext::state() is instance of WsChatSessionState, state is shared // HttpContext::state() is instance of WsChatSessionState, state is shared
// across all routes within application // across all routes within application
// we'll start heartbeat process on session start.
self.hb(ctx);
let addr = ctx.address(); let addr = ctx.address();
ctx.state() self.addr
.addr
.send(server::Connect { .send(server::Connect {
addr: addr.recipient(), addr: addr.recipient(),
}) })
@ -94,9 +83,9 @@ impl Actor for WsChatSession {
.wait(ctx); .wait(ctx);
} }
fn stopping(&mut self, ctx: &mut Self::Context) -> Running { fn stopping(&mut self, _: &mut Self::Context) -> Running {
// notify chat server // notify chat server
ctx.state().addr.do_send(server::Disconnect { id: self.id }); self.addr.do_send(server::Disconnect { id: self.id });
Running::Stop Running::Stop
} }
} }
@ -132,8 +121,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
// Send ListRooms message to chat server and wait for // Send ListRooms message to chat server and wait for
// response // response
println!("List rooms"); println!("List rooms");
ctx.state() self.addr
.addr
.send(server::ListRooms) .send(server::ListRooms)
.into_actor(self) .into_actor(self)
.then(|res, _, ctx| { .then(|res, _, ctx| {
@ -155,7 +143,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
"/join" => { "/join" => {
if v.len() == 2 { if v.len() == 2 {
self.room = v[1].to_owned(); self.room = v[1].to_owned();
ctx.state().addr.do_send(server::Join { self.addr.do_send(server::Join {
id: self.id, id: self.id,
name: self.room.clone(), name: self.room.clone(),
}); });
@ -181,17 +169,17 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for WsChatSession {
m.to_owned() m.to_owned()
}; };
// send message to chat server // send message to chat server
ctx.state().addr.do_send(server::Message { self.addr.do_send(server::Message {
id: self.id, id: self.id,
msg: msg, msg: msg,
room: self.room.clone(), room: self.room.clone(),
}) })
} }
} }
ws::Message::Binary(bin) => println!("Unexpected binary"), ws::Message::Binary(_) => println!("Unexpected binary"),
ws::Message::Close(_) => { ws::Message::Close(_) => {
ctx.stop(); ctx.stop();
}, }
} }
} }
} }
@ -200,7 +188,7 @@ impl WsChatSession {
/// helper method that sends ping to client every second. /// helper method that sends ping to client every second.
/// ///
/// also this method checks heartbeats from client /// also this method checks heartbeats from client
fn hb(&self, ctx: &mut ws::WebsocketContext<Self, WsChatSessionState>) { fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| { ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
// check client heartbeats // check client heartbeats
if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT { if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
@ -208,9 +196,7 @@ impl WsChatSession {
println!("Websocket Client heartbeat failed, disconnecting!"); println!("Websocket Client heartbeat failed, disconnecting!");
// notify chat server // notify chat server
ctx.state() act.addr.do_send(server::Disconnect { id: act.id });
.addr
.do_send(server::Disconnect { id: act.id });
// stop actor // stop actor
ctx.stop(); ctx.stop();
@ -224,42 +210,38 @@ impl WsChatSession {
} }
} }
fn main() { fn main() -> std::io::Result<()> {
let _ = env_logger::init(); let _ = env_logger::init();
let sys = actix::System::new("websocket-example"); let sys = actix::System::new("websocket-example");
// Start chat server actor in separate thread // Start chat server actor
let server = Arbiter::start(|_| server::ChatServer::default()); let server = server::ChatServer::default().start();
// Start tcp server in separate thread // Start tcp server in separate thread
let srv = server.clone(); let srv = server.clone();
Arbiter::new("tcp-server").do_send::<msgs::Execute>(msgs::Execute::new(move || { Arbiter::new().exec(move || {
session::TcpServer::new("127.0.0.1:12345", srv); session::TcpServer::new("127.0.0.1:12345", srv);
Ok(()) Ok::<_, ()>(())
})); });
// Create Http server with websocket support // Create Http server with websocket support
HttpServer::new(move || { HttpServer::new(move || {
// Websocket sessions state App::new()
let state = WsChatSessionState { .data(server.clone())
addr: server.clone(),
};
App::with_state(state)
// redirect to websocket.html // redirect to websocket.html
.resource("/", |r| r.method(http::Method::GET).f(|_| { .service(web::resource("/").route(web::get().to(|| {
HttpResponse::Found() HttpResponse::Found()
.header("LOCATION", "/static/websocket.html") .header("LOCATION", "/static/websocket.html")
.finish() .finish()
})) })))
// websocket // websocket
.resource("/ws/", |r| r.route().f(chat_route)) .service(web::resource("/ws/").to(chat_route))
// static resources // static resources
.handler("/static/", fs::StaticFiles::new("static/").unwrap()) .service(fs::Files::new("/static/", "static/"))
}).bind("127.0.0.1:8080") })
.unwrap() .bind("127.0.0.1:8080")?
.start(); .start();
println!("Started http server: 127.0.0.1:8080"); println!("Started http server: 127.0.0.1:8080");
let _ = sys.run(); sys.run()
} }

Some files were not shown because too many files have changed in this diff Show More