1
0
mirror of https://github.com/actix/examples synced 2025-02-02 09:39:03 +01:00

Merge branch 'master' into udp-echo

# Conflicts:
#	.travis.yml
#	Cargo.toml
This commit is contained in:
Anton Patrushev 2019-09-17 21:42:44 +02:00
commit bbcd4f4c93
168 changed files with 3467 additions and 10835 deletions

5
.gitignore vendored
View File

@ -13,3 +13,8 @@ Cargo.lock
# intellij files
.idea/**
.history/
# For multipart example
upload.png

View File

@ -3,8 +3,13 @@ sudo: false
dist: trusty
cache:
cargo: true
apt: true
directories:
- $HOME/.cargo
- $HOME/.rustup
before_cache:
- rm -rf $HOME/.cargo/registry
matrix:
include:
@ -28,44 +33,13 @@ before_install:
before_script:
- |
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false );
rustup component add clippy --toolchain=nightly
fi
- export PATH=$PATH:~/.cargo/bin
script:
- cargo check --all
- |
cd async_db && cargo check && cd ..
cd async_ex1 && cargo check && cd ..
cd actix_redis && cargo check && cd ..
cd actix_todo && cargo check && cd ..
cd basics && cargo check && cd ..
cd cookie-auth && cargo check && cd ..
cd cookie-auth-full && cargo check && cd ..
cd cookie-session && cargo check && cd ..
cd diesel && cargo check && cd ..
cd error_handling && cargo check && cd ..
cd form && cargo check && cd ..
cd hello-world && cargo check && cd ..
cd http-proxy && cargo check && cd ..
cd http-full-proxy && cargo check && cd ..
cd json && cargo check && cd ..
cd juniper && cargo check && cd ..
cd middleware && cargo check && cd ..
cd multipart && cargo check && cd ..
cd protobuf && cargo check && cd ..
cd r2d2 && cargo check && cd ..
cd redis-session && cargo check && cd ..
cd simple-auth-sarver && cargo check && cd ..
cd state && cargo check && cd ..
cd static_index && cargo check && cd ..
cd template_askama && cargo check && cd ..
cd template_tera && cargo check && cd ..
cd tls && cargo check && cd ..
cd rustls && cargo check && cd ..
cd udp-echo && cargo check && cd ..
cd unix-socket && cargo check && cd ..
cd web-cors/backend && cargo check && cd ../..
cd websocket && cargo check && cd ..
cd websocket-chat && cargo check && cd ..
cd websocket-chat-broker && cargo check && cd ..
cd websocket-tcp-chat && cargo check && cd ..
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
cargo clippy
fi

View File

@ -5,30 +5,33 @@ members = [
"actix_todo",
"async_db",
"async_ex1",
"async_ex2",
"basics",
"cookie-auth",
"cookie-auth-full",
"cookie-session",
"diesel",
"error_handling",
"form",
"hello-world",
"http-proxy",
"http-full-proxy",
"json",
"json_error",
"jsonrpc",
"juniper",
"middleware",
"multipart",
"protobuf",
"r2d2",
"redis-session",
"rustls",
"simple-auth-server",
"state",
"static_index",
"template_askama",
"template_tera",
"template_yarte",
"template_handlebars",
"tls",
"rustls",
"udp-echo",
"unix-socket",
"web-cors/backend",

View File

@ -3,9 +3,7 @@
A curated list of examples related to actix.
## from community
* [Rust-webapp-starter
](https://github.com/rustlang-cn/Rust-webapp-starter) : Rust single page webapp written in actix-web with vuejs.
* [Rust中文社区](http://47.104.146.58/) : A CN online community forum written in Actix-web with vuejs [source](https://github.com/rustlang-cn/ruster).
* [OUISRC](http://ouisrc.xyz/) : Welcome to OUISRC, let us Gain more in exploration and interaction.
* [Roseline](https://github.com/DoumanAsh/roseline.rs) : A personal web site and discord & IRC bot to access simple SQLite database. Demonstrates usage of various actix and actix-web concepts.
* [Actix Auth Server](https://hgill.io/posts/auth-microservice-rust-actix-web-diesel-complete-tutorial-part-1/) : Auth web micro-service with rust using actix-web - complete tutorial. See code in [examples/simple-auth-server](https://github.com/actix/examples/tree/master/simple-auth-server)
## Contribute

View File

@ -2,13 +2,15 @@
name = "actix_redis"
version = "0.1.0"
authors = ["dowwie <dkcdkg@gmail.com>"]
edition = "2018"
workspace = ".."
[dependencies]
actix = "0.7.3"
actix-web = "0.7.3"
actix-redis = "0.5.1"
actix = "0.8.2"
actix-web = "1.0.3"
actix-redis = "0.6"
futures = "0.1.23"
redis-async = "0.4.0"
serde = "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;
extern crate actix_redis;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
#[macro_use] extern crate redis_async;
extern crate serde;
#[macro_use] extern crate serde_derive;
#[macro_use]
extern crate redis_async;
#[macro_use]
extern crate serde_derive;
use std::sync::Arc;
use actix::prelude::*;
use actix_redis::{Command, RedisActor, Error as ARError};
use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Json,
AsyncResponder, http::Method, Error as AWError};
use futures::future::{Future, join_all};
use actix_redis::{Command, Error as ARError, RedisActor};
use actix_web::{middleware, web, App, Error as AWError, HttpResponse, HttpServer};
use futures::future::{join_all, Future};
use redis_async::resp::RespValue;
#[derive(Deserialize)]
pub struct CacheInfo {
one: String,
two: String,
three: String
three: String,
}
fn cache_stuff((info, req): (Json<CacheInfo>, HttpRequest<AppState>))
-> impl Future<Item=HttpResponse, Error=AWError> {
fn cache_stuff(
info: web::Json<CacheInfo>,
redis: web::Data<Addr<RedisActor>>,
) -> impl Future<Item = HttpResponse, Error = AWError> {
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 two = redis.send(Command(resp_array!["SET", "mydomain:two", info.two]));
@ -44,63 +36,58 @@ fn cache_stuff((info, req): (Json<CacheInfo>, HttpRequest<AppState>))
let info_set = join_all(vec![one, two, three].into_iter());
info_set
.map_err(AWError::from)
.and_then(|res: Vec<Result<RespValue, ARError>>|
// successful operations return "OK", so confirm that all returned as so
if !res.iter().all(|res| match res {
Ok(RespValue::SimpleString(x)) if x=="OK" => true,
_ => false
}) {
Ok(HttpResponse::InternalServerError().finish())
} else {
Ok(HttpResponse::Ok().body("successfully cached values"))
}
)
.responder()
.map_err(AWError::from)
.and_then(|res: Vec<Result<RespValue, ARError>>|
// successful operations return "OK", so confirm that all returned as so
if !res.iter().all(|res| match res {
Ok(RespValue::SimpleString(x)) if x=="OK" => true,
_ => false
}) {
Ok(HttpResponse::InternalServerError().finish())
} else {
Ok(HttpResponse::Ok().body("successfully cached values"))
}
)
}
fn del_stuff(req: HttpRequest<AppState>)
-> impl Future<Item=HttpResponse, Error=AWError> {
let redis = req.state().redis_addr.clone();
redis.send(Command(resp_array!["DEL", "mydomain:one", "mydomain:two", "mydomain:three"]))
.map_err(AWError::from)
.and_then(|res: Result<RespValue, ARError>|
match &res {
Ok(RespValue::Integer(x)) if x==&3 =>
Ok(HttpResponse::Ok().body("successfully deleted values")),
_ =>{println!("---->{:?}", res);
Ok(HttpResponse::InternalServerError().finish())}
})
.responder()
fn del_stuff(
redis: web::Data<Addr<RedisActor>>,
) -> impl Future<Item = HttpResponse, Error = AWError> {
redis
.send(Command(resp_array![
"DEL",
"mydomain:one",
"mydomain:two",
"mydomain:three"
]))
.map_err(AWError::from)
.and_then(|res: Result<RespValue, ARError>| match &res {
Ok(RespValue::Integer(x)) if x == &3 => {
Ok(HttpResponse::Ok().body("successfully deleted values"))
}
_ => {
println!("---->{:?}", res);
Ok(HttpResponse::InternalServerError().finish())
}
})
}
pub struct AppState {
pub redis_addr: Arc<Addr<RedisActor>>
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
env_logger::init();
let sys = actix::System::new("actix_redis_ex");
server::new(|| {
let redis_addr = Arc::new(RedisActor::start("127.0.0.1:6379"));
let app_state = AppState{redis_addr};
HttpServer::new(|| {
let redis_addr = RedisActor::start("127.0.0.1:6379");
App::with_state(app_state)
.middleware(middleware::Logger::default())
.resource("/stuff", |r| {
r.method(Method::POST)
.with_async(cache_stuff);
r.method(Method::DELETE)
.with_async(del_stuff)})
}).bind("0.0.0.0:8080")
.unwrap()
.workers(1)
.start();
let _ = sys.run();
App::new()
.data(redis_addr)
.wrap(middleware::Logger::default())
.service(
web::resource("/stuff")
.route(web::post().to_async(cache_stuff))
.route(web::delete().to_async(del_stuff)),
)
})
.bind("0.0.0.0:8080")?
.run()
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
#!/usr/bin/env bash
sqlite3 weather.db < db.sql
sqlite3 -csv weather.db ".import nyc_centralpark_weather.csv nyc_weather"
cd $(dirname "$0")
sqlite3 ../weather.db < db.sql
sqlite3 -csv ../weather.db ".import nyc_centralpark_weather.csv nyc_weather"

View File

@ -1,17 +1,15 @@
use actix::prelude::*;
use actix_web::{web, Error as AWError};
use failure::Error;
use futures::Future;
use r2d2;
use r2d2_sqlite;
use rusqlite::NO_PARAMS;
use serde_derive::{Deserialize, Serialize};
use std::{thread::sleep, time::Duration};
pub type Pool = r2d2::Pool<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)]
pub enum WeatherAgg {
AnnualAgg { year: i32, total: f64 },
@ -25,29 +23,24 @@ pub enum Queries {
GetTopTenColdestMonths,
}
//pub struct GetTopTenHottestYears;
impl Message for Queries {
type Result = Result<Vec<WeatherAgg>, Error>;
}
impl Handler<Queries> for DbExecutor {
type Result = Result<Vec<WeatherAgg>, Error>;
fn handle(&mut self, msg: Queries, _: &mut Self::Context) -> Self::Result {
let conn: Connection = self.0.get()?;
match msg {
Queries::GetTopTenHottestYears => get_hottest_years(conn),
Queries::GetTopTenColdestYears => get_coldest_years(conn),
Queries::GetTopTenHottestMonths => get_hottest_months(conn),
Queries::GetTopTenColdestMonths => get_coldest_months(conn),
}
}
pub fn execute(
pool: &Pool,
query: Queries,
) -> impl Future<Item = Vec<WeatherAgg>, Error = AWError> {
let pool = pool.clone();
web::block(move || match query {
Queries::GetTopTenHottestYears => get_hottest_years(pool.get()?),
Queries::GetTopTenColdestYears => get_coldest_years(pool.get()?),
Queries::GetTopTenHottestMonths => get_hottest_months(pool.get()?),
Queries::GetTopTenColdestMonths => get_coldest_months(pool.get()?),
})
.from_err()
}
fn get_hottest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let stmt = "
SELECT cast(strftime('%Y', date) as int) as theyear,
sum(tmax) as total
sum(tmax) as total
FROM nyc_weather
WHERE tmax <> 'TMAX'
GROUP BY theyear
@ -55,7 +48,7 @@ fn get_hottest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let mut prep_stmt = conn.prepare(stmt)?;
let annuals = prep_stmt
.query_map(&[], |row| WeatherAgg::AnnualAgg {
.query_map(NO_PARAMS, |row| WeatherAgg::AnnualAgg {
year: row.get(0),
total: row.get(1),
})
@ -65,7 +58,7 @@ fn get_hottest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
.collect::<Vec<WeatherAgg>>())
})?;
sleep(Duration::from_secs(2));
sleep(Duration::from_secs(2)); //see comments at top of main.rs
Ok(annuals)
}
@ -73,7 +66,7 @@ fn get_hottest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
fn get_coldest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let stmt = "
SELECT cast(strftime('%Y', date) as int) as theyear,
sum(tmax) as total
sum(tmax) as total
FROM nyc_weather
WHERE tmax <> 'TMAX'
GROUP BY theyear
@ -81,7 +74,7 @@ fn get_coldest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let mut prep_stmt = conn.prepare(stmt)?;
let annuals = prep_stmt
.query_map(&[], |row| WeatherAgg::AnnualAgg {
.query_map(NO_PARAMS, |row| WeatherAgg::AnnualAgg {
year: row.get(0),
total: row.get(1),
})
@ -91,15 +84,15 @@ fn get_coldest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
.collect::<Vec<WeatherAgg>>())
})?;
sleep(Duration::from_secs(2));
sleep(Duration::from_secs(2)); //see comments at top of main.rs
Ok(annuals)
}
fn get_hottest_months(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let stmt = "SELECT cast(strftime('%Y', date) as int) as theyear,
cast(strftime('%m', date) as int) as themonth,
sum(tmax) as total
cast(strftime('%m', date) as int) as themonth,
sum(tmax) as total
FROM nyc_weather
WHERE tmax <> 'TMAX'
GROUP BY theyear, themonth
@ -107,7 +100,7 @@ fn get_hottest_months(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let mut prep_stmt = conn.prepare(stmt)?;
let annuals = prep_stmt
.query_map(&[], |row| WeatherAgg::MonthAgg {
.query_map(NO_PARAMS, |row| WeatherAgg::MonthAgg {
year: row.get(0),
month: row.get(1),
total: row.get(2),
@ -118,14 +111,14 @@ fn get_hottest_months(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
.collect::<Vec<WeatherAgg>>())
})?;
sleep(Duration::from_secs(2));
sleep(Duration::from_secs(2)); //see comments at top of main.rs
Ok(annuals)
}
fn get_coldest_months(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let stmt = "SELECT cast(strftime('%Y', date) as int) as theyear,
cast(strftime('%m', date) as int) as themonth,
sum(tmax) as total
cast(strftime('%m', date) as int) as themonth,
sum(tmax) as total
FROM nyc_weather
WHERE tmax <> 'TMAX'
GROUP BY theyear, themonth
@ -133,7 +126,7 @@ fn get_coldest_months(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
let mut prep_stmt = conn.prepare(stmt)?;
let annuals = prep_stmt
.query_map(&[], |row| WeatherAgg::MonthAgg {
.query_map(NO_PARAMS, |row| WeatherAgg::MonthAgg {
year: row.get(0),
month: row.get(1),
total: row.get(2),
@ -144,6 +137,6 @@ fn get_coldest_months(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {
.collect::<Vec<WeatherAgg>>())
})?;
sleep(Duration::from_secs(2));
sleep(Duration::from_secs(2)); //see comments at top of main.rs
Ok(annuals)
}

View File

@ -8,122 +8,92 @@ This project illustrates two examples:
2. An asynchronous handler that executes 4 queries in *parallel*,
collecting the results and returning them as a single serialized json object
*/
Note: The use of sleep(Duration::from_secs(2)); in db.rs is to make performance
improvement with parallelism more obvious.
*/
use std::io;
extern crate actix;
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 actix_web::{middleware, web, App, Error as AWError, HttpResponse, HttpServer};
use futures::future::{join_all, ok as fut_ok, Future};
use r2d2_sqlite;
use r2d2_sqlite::SqliteConnectionManager;
mod db;
use db::{DbExecutor, Pool, Queries, WeatherAgg};
/// State with DbExecutor address
struct AppState {
db: Addr<DbExecutor>,
}
use db::{Pool, Queries, WeatherAgg};
/// 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![];
state
.db
.send(Queries::GetTopTenHottestYears)
db::execute(&db, Queries::GetTopTenHottestYears)
.from_err()
.and_then(move |res| {
result.push(res.unwrap());
state
.db
.send(Queries::GetTopTenColdestYears)
result.push(res);
db::execute(&db, Queries::GetTopTenColdestYears)
.from_err()
.and_then(move |res| {
result.push(res.unwrap());
state
.db
.send(Queries::GetTopTenHottestMonths)
result.push(res);
db::execute(&db, Queries::GetTopTenHottestMonths)
.from_err()
.and_then(move |res| {
result.push(res.unwrap());
state
.db
.send(Queries::GetTopTenColdestMonths)
result.push(res);
db::execute(&db, Queries::GetTopTenColdestMonths)
.from_err()
.and_then(move |res| {
result.push(res.unwrap());
result.push(res);
fut_ok(result)
})
})
})
})
.and_then(|res| Ok(HttpResponse::Ok().json(res)))
.responder()
}
/// Version 2: Calls 4 queries in parallel, as an asynchronous handler
/// 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![
Box::new(state.db.send(Queries::GetTopTenHottestYears)),
Box::new(state.db.send(Queries::GetTopTenColdestYears)),
Box::new(state.db.send(Queries::GetTopTenHottestMonths)),
Box::new(state.db.send(Queries::GetTopTenColdestMonths)),
Box::new(db::execute(&db, Queries::GetTopTenHottestYears)),
Box::new(db::execute(&db, Queries::GetTopTenColdestYears)),
Box::new(db::execute(&db, Queries::GetTopTenHottestMonths)),
Box::new(db::execute(&db, Queries::GetTopTenColdestMonths)),
];
join_all(fut_result)
.map_err(AWError::from)
.and_then(|result| {
let res: Vec<Option<Vec<WeatherAgg>>> =
result.into_iter().map(|x| x.ok()).collect();
Ok(HttpResponse::Ok().json(res))
})
.responder()
.map(|result| HttpResponse::Ok().json(result))
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
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)
let manager = SqliteConnectionManager::file("weather.db");
let pool = Pool::new(manager).unwrap();
let addr = SyncArbiter::start(num_cpus::get(), move || DbExecutor(pool.clone()));
// Start http server
server::new(move || {
App::with_state(AppState{db: addr.clone()})
// enable logger
.middleware(middleware::Logger::default())
.resource("/asyncio_weather", |r|
r.method(http::Method::GET)
.with(asyncio_weather))
.resource("/parallel_weather", |r|
r.method(http::Method::GET)
.with(parallel_weather))
}).bind("127.0.0.1:8080")
.unwrap()
.start();
HttpServer::new(move || {
App::new()
.data(pool.clone())
.wrap(middleware::Logger::default())
.service(
web::resource("/asyncio_weather")
.route(web::get().to_async(asyncio_weather)),
)
.service(
web::resource("/parallel_weather")
.route(web::get().to_async(parallel_weather)),
)
})
.bind("127.0.0.1:8080")?
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
sys.run()
}

View File

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

View File

@ -16,5 +16,4 @@ Actix-web features illustrated here include:
Example query from the command line using httpie:
```http post 127.0.0.1:8088/something id=1 name=JohnDoe```
```echo '{"id":"1", "name": "JohnDoe"}' | http 127.0.0.1:8080/something```

View File

@ -10,30 +10,23 @@
// 2. validating user-submitted parameters using the 'validator' crate
// 2. actix-web client features:
// - POSTing json body
// 3. chaining futures into a single response used by an asynch endpoint
//
// There are 2 versions in this example, one that uses Boxed Futures and the
// other that uses Impl Future, available since rustc v1.26.
// 3. chaining futures into a single response used by an async endpoint
extern crate actix;
extern crate actix_web;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[macro_use]
extern crate validator_derive;
extern crate env_logger;
extern crate futures;
extern crate validator;
#[macro_use]
extern crate serde_derive;
use std::collections::HashMap;
use std::io;
use actix_web::{
client, http::Method, server, App, AsyncResponder, Error, HttpMessage, HttpResponse,
Json,
client::Client,
error::ErrorBadRequest,
web::{self, BytesMut},
App, Error, HttpResponse, HttpServer,
};
use futures::{future::ok as fut_ok, Future};
use std::collections::HashMap;
use std::time::Duration;
use futures::{Future, Stream};
use validator::Validate;
#[derive(Debug, Validate, Deserialize, Serialize)]
@ -56,99 +49,57 @@ struct HttpBinResponse {
url: String,
}
// -----------------------------------------------------------------------
// v1 uses Boxed Futures, which were the only option prior to rustc v1.26
// -----------------------------------------------------------------------
/// 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>> {
Box::new(
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
.and_then(
|resp| resp.body() // <- this is MessageBody type, resolves to complete body
.from_err() // <- convert PayloadError to an Error
.and_then(|body| {
let resp: HttpBinResponse = serde_json::from_slice(&body).unwrap();
fut_ok(resp.json)
})
),
)
}
fn create_something_v1(
some_data: Json<SomeData>,
) -> Box<Future<Item = HttpResponse, Error = Error>> {
step_x_v1(some_data.into_inner())
.and_then(|some_data_2| {
step_x_v1(some_data_2).and_then(|some_data_3| {
step_x_v1(some_data_3).and_then(|d| {
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&d).unwrap())
.into())
/// validate data, post json to httpbin, get it back in the response body, return deserialized
fn step_x(
data: SomeData,
client: &Client,
) -> impl Future<Item = SomeData, Error = Error> {
let validation = futures::future::result(data.validate()).map_err(ErrorBadRequest);
let post_response = client
.post("https://httpbin.org/post")
.send_json(&data)
.map_err(Error::from) // <- convert SendRequestError to an Error
.and_then(|resp| {
resp.from_err()
.fold(BytesMut::new(), |mut acc, chunk| {
acc.extend_from_slice(&chunk);
Ok::<_, Error>(acc)
})
})
})
.responder()
}
// ---------------------------------------------------------------
// v2 uses impl Future, available as of rustc v1.26
// ---------------------------------------------------------------
/// 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> {
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
.and_then(
|resp| resp.body() // <- this is MessageBody type, resolves to complete body
.from_err() // <- convert PayloadError to an Error
.and_then(|body| {
let resp: HttpBinResponse = serde_json::from_slice(&body).unwrap();
fut_ok(resp.json)
.map(|body| {
let body: HttpBinResponse = serde_json::from_slice(&body).unwrap();
body.json
})
)
});
validation.and_then(|_| post_response)
}
fn create_something_v2(
some_data: Json<SomeData>,
fn create_something(
some_data: web::Json<SomeData>,
client: web::Data<Client>,
) -> impl Future<Item = HttpResponse, Error = Error> {
step_x_v2(some_data.into_inner()).and_then(|some_data_2| {
step_x_v2(some_data_2).and_then(|some_data_3| {
step_x_v2(some_data_3).and_then(|d| {
step_x(some_data.into_inner(), &client).and_then(move |some_data_2| {
step_x(some_data_2, &client).and_then(move |some_data_3| {
step_x(some_data_3, &client).and_then(|d| {
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(serde_json::to_string(&d).unwrap())
.into())
.body(serde_json::to_string(&d).unwrap()))
})
})
})
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("asyncio_example");
let endpoint = "127.0.0.1:8080";
server::new(move || {
App::new()
.resource("/something_v1", |r| {
r.method(Method::POST).with(create_something_v1)
})
.resource("/something_v2", |r| {
r.method(Method::POST).with_async(create_something_v2)
})
}).bind("127.0.0.1:8088")
.unwrap()
.start();
println!("Started http server: 127.0.0.1:8088");
let _ = sys.run();
println!("Starting server at: {:?}", endpoint);
HttpServer::new(|| {
App::new().data(Client::default()).service(
web::resource("/something").route(web::post().to_async(create_something)),
)
})
.bind(endpoint)?
.run()
}

21
async_ex2/Cargo.toml Normal file
View File

@ -0,0 +1,21 @@
[package]
name = "async_ex2"
version = "0.1.0"
authors = ["dowwie <dkcdkg@gmail.com>"]
edition = "2018"
workspace = ".."
[dependencies]
actix-rt = "0.2.2"
actix-web = { version="1.0.0", features=["ssl"] }
actix-multipart = "0.1.1"
actix-service = "0.4.1"
bytes = "0.4.12"
env_logger = "0.6.1"
futures = "0.1"
serde = { version = "^1.0", features = ["derive"] }
serde_derive = "1.0.90"
serde_json = "1.0.39"
time = "0.1.42"
validator = "0.8.0"
validator_derive = "0.8.0"

3
async_ex2/README.md Normal file
View File

@ -0,0 +1,3 @@
This example illustrates how to use nested resource registration through application-level configuration.
The endpoints do nothing.

View File

@ -0,0 +1,36 @@
use actix_web::web;
use crate::handlers::{parts, products};
pub fn config_app(cfg: &mut web::ServiceConfig) {
// domain includes: /products/{product_id}/parts/{part_id}
cfg.service(
web::scope("/products")
.service(
web::resource("")
.route(web::get().to_async(products::get_products))
.route(web::post().to_async(products::add_product)),
)
.service(
web::scope("/{product_id}")
.service(
web::resource("")
.route(web::get().to_async(products::get_product_detail))
.route(web::delete().to_async(products::remove_product)),
)
.service(
web::scope("/parts")
.service(
web::resource("")
.route(web::get().to_async(parts::get_parts))
.route(web::post().to_async(parts::add_part)),
)
.service(
web::resource("/{part_id}")
.route(web::get().to_async(parts::get_part_detail))
.route(web::delete().to_async(parts::remove_part)),
),
),
),
);
}

16
async_ex2/src/bin/main.rs Normal file
View File

@ -0,0 +1,16 @@
use actix_web::{middleware, App, HttpServer};
use async_ex2::appconfig::config_app;
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init();
HttpServer::new(|| {
App::new()
.configure(config_app)
.wrap(middleware::Logger::default())
})
.bind("127.0.0.1:8080")?
.run()
}

15
async_ex2/src/common.rs Normal file
View File

@ -0,0 +1,15 @@
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
pub struct Product {
id: Option<i64>,
product_type: Option<String>,
name: Option<String>,
}
#[derive(Deserialize, Serialize)]
pub struct Part {
id: Option<i64>,
part_type: Option<String>,
name: Option<String>,
}

View File

@ -0,0 +1,2 @@
pub mod parts;
pub mod products;

View File

@ -0,0 +1,28 @@
use actix_web::{web, Error, HttpResponse};
use futures::{future::ok as fut_ok, Future};
use crate::common::{Part, Product};
pub fn get_parts(
query: web::Query<Option<Part>>,
) -> impl Future<Item = HttpResponse, Error = Error> {
fut_ok(HttpResponse::Ok().finish())
}
pub fn add_part(
new_part: web::Json<Product>,
) -> impl Future<Item = HttpResponse, Error = Error> {
fut_ok(HttpResponse::Ok().finish())
}
pub fn get_part_detail(
id: web::Path<String>,
) -> impl Future<Item = HttpResponse, Error = Error> {
fut_ok(HttpResponse::Ok().finish())
}
pub fn remove_part(
id: web::Path<String>,
) -> impl Future<Item = HttpResponse, Error = Error> {
fut_ok(HttpResponse::Ok().finish())
}

View File

@ -0,0 +1,56 @@
use actix_web::{web, Error, HttpResponse};
use futures::{future::ok as fut_ok, Future};
use crate::common::{Part, Product};
pub fn get_products(
query: web::Query<Option<Part>>,
) -> impl Future<Item = HttpResponse, Error = Error> {
fut_ok(HttpResponse::Ok().finish())
}
pub fn add_product(
new_product: web::Json<Product>,
) -> impl Future<Item = HttpResponse, Error = Error> {
fut_ok(HttpResponse::Ok().finish())
}
pub fn get_product_detail(
id: web::Path<String>,
) -> impl Future<Item = HttpResponse, Error = Error> {
fut_ok(HttpResponse::Ok().finish())
}
pub fn remove_product(
id: web::Path<String>,
) -> impl Future<Item = HttpResponse, Error = Error> {
fut_ok(HttpResponse::Ok().finish())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::appconfig::config_app;
use actix_service::Service;
use actix_web::{
http::{header, StatusCode},
test, web, App, HttpRequest, HttpResponse,
};
#[test]
fn test_add_product() {
let mut app = test::init_service(App::new().configure(config_app));
let payload = r#"{"id":12345,"product_type":"fancy","name":"test"}"#.as_bytes();
let req = test::TestRequest::post()
.uri("/products")
.header(header::CONTENT_TYPE, "application/json")
.set_payload(payload)
.to_request();
let resp = test::block_on(app.call(req)).unwrap();
assert_eq!(resp.status(), StatusCode::OK);
}
}

3
async_ex2/src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod appconfig;
pub mod common;
pub mod handlers;

View File

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

View File

@ -1,43 +1,39 @@
#![allow(unused_variables)]
#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
extern crate actix;
#[macro_use]
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 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
fn favicon(req: &HttpRequest) -> Result<fs::NamedFile> {
#[get("/favicon")]
fn favicon() -> Result<fs::NamedFile> {
Ok(fs::NamedFile::open("static/favicon.ico")?)
}
/// simple index handler
fn welcome(req: &HttpRequest) -> Result<HttpResponse> {
#[get("/welcome")]
fn welcome(session: Session, req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
// session
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);
counter = count + 1;
}
// set counter to session
req.session().set("counter", counter)?;
session.set("counter", counter)?;
// response
Ok(HttpResponse::build(StatusCode::OK)
@ -46,97 +42,103 @@ fn welcome(req: &HttpRequest) -> Result<HttpResponse> {
}
/// 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))
}
/// async handler
fn index_async(req: &HttpRequest) -> FutureResult<HttpResponse, Error> {
fn index_async(req: HttpRequest) -> impl Future<Item = HttpResponse, Error = Error> {
println!("{:?}", req);
result(Ok(HttpResponse::Ok().content_type("text/html").body(
format!("Hello {}!", req.match_info().get("name").unwrap()),
)))
ok(HttpResponse::Ok()
.content_type("text/html")
.body(format!("Hello {}!", req.match_info().get("name").unwrap())))
}
/// 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 (tx, rx_body) = mpsc::unbounded();
let _ = tx.unbounded_send(Bytes::from(text.as_bytes()));
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}/`
fn with_param(req: &HttpRequest) -> HttpResponse {
fn with_param(req: HttpRequest, path: web::Path<(String,)>) -> HttpResponse {
println!("{:?}", req);
HttpResponse::Ok()
.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_BACKTRACE", "1");
env_logger::init();
let sys = actix::System::new("basic-example");
let sys = actix_rt::System::new("basic-example");
let addr = server::new(
|| App::new()
// enable logger
.middleware(middleware::Logger::default())
HttpServer::new(|| {
App::new()
// cookie session middleware
.middleware(session::SessionStorage::new(
session::CookieSessionBackend::signed(&[0; 32]).secure(false)
))
.wrap(CookieSession::signed(&[0; 32]).secure(false))
// enable logger - always register actix-web Logger middleware last
.wrap(middleware::Logger::default())
// register favicon
.resource("/favicon", |r| r.f(favicon))
.service(favicon)
// register simple route, handle all methods
.resource("/welcome", |r| r.f(welcome))
.service(welcome)
// 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
.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
.resource("/async-body/{name}", |r| r.method(Method::GET).with(index_async_body))
.resource("/test", |r| r.f(|req| {
match *req.method() {
.service(
web::resource("/async-body/{name}")
.route(web::get().to(index_async_body)),
)
.service(
web::resource("/test").to(|req: HttpRequest| match *req.method() {
Method::GET => HttpResponse::Ok(),
Method::POST => HttpResponse::MethodNotAllowed(),
_ => HttpResponse::NotFound(),
}
}))
.resource("/error", |r| r.f(|req| {
}),
)
.service(web::resource("/error").to(|| {
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
.handler("/static", fs::StaticFiles::new("static").unwrap())
.service(fs::Files::new("/static", "static").show_files_listing())
// redirect
.resource("/", |r| r.method(Method::GET).f(|req| {
.service(web::resource("/").route(web::get().to(|req: HttpRequest| {
println!("{:?}", req);
HttpResponse::Found()
.header(header::LOCATION, "static/welcome.html")
.finish()
}))
})))
// default
.default_resource(|r| {
.default_service(
// 404 for GET request
r.method(Method::GET).f(p404);
// all requests that are not `GET`
r.route().filter(pred::Not(pred::Get())).f(
|req| 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)
.start();
web::resource("")
.route(web::get().to(p404))
// all requests that are not `GET`
.route(
web::route()
.guard(guard::Not(guard::Get()))
.to(HttpResponse::MethodNotAllowed),
),
)
})
.bind("127.0.0.1:8080")?
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
sys.run()
}

View File

@ -1,7 +1,14 @@
<!DOCTYPE html><html><head><title>actix - basics</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
<body>
<a href="static/index.html">back to home</a>
<h1>404</h1>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>actix - basics</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon" />
</head>
<body>
<a href="/static/welcome.html">back to home</a>
<h1>404</h1>
</body>
</html>

View File

@ -1,6 +1,13 @@
<!DOCTYPE html><html><head><title>actix - basics</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon" /></head>
<body>
<h1>Welcome <img width="30px" height="30px" src="/static/actixLogo.png" /></h1>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>actix - basics</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon" />
</head>
<body>
<h1>Welcome <img width="30px" height="30px" src="/static/actixLogo.png" /></h1>
</body>
</html>

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,10 @@
name = "cookie-auth"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../"
edition = "2018"
workspace = ".."
[dependencies]
actix = "0.7"
actix-web = "0.7"
env_logger = "0.5"
actix-web = "1.0.0"
actix-identity = "0.1.0"
env_logger = "0.6"

View File

@ -1,45 +1,41 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix_identity::Identity;
use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{middleware, web, App, HttpResponse, HttpServer};
use actix_web::{middleware, server, App, HttpRequest, HttpResponse};
use actix_web::middleware::identity::RequestIdentity;
use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
fn index(req: &HttpRequest) -> String {
format!("Hello {}", req.identity().unwrap_or("Anonymous".to_owned()))
fn index(id: Identity) -> String {
format!(
"Hello {}",
id.identity().unwrap_or_else(|| "Anonymous".to_owned())
)
}
fn login(req: &HttpRequest) -> HttpResponse {
req.remember("user1".to_owned());
fn login(id: Identity) -> HttpResponse {
id.remember("user1".to_owned());
HttpResponse::Found().header("location", "/").finish()
}
fn logout(req: &HttpRequest) -> HttpResponse {
req.forget();
fn logout(id: Identity) -> HttpResponse {
id.forget();
HttpResponse::Found().header("location", "/").finish()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("cookie-auth");
server::new(|| {
HttpServer::new(|| {
App::new()
.middleware(middleware::Logger::default())
.middleware(IdentityService::new(
.wrap(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();
// enable logger - always register actix-web Logger middleware last
.wrap(middleware::Logger::default())
.service(web::resource("/login").route(web::post().to(login)))
.service(web::resource("/logout").to(logout))
.service(web::resource("/").route(web::get().to(index)))
})
.bind("127.0.0.1:8080")?
.run()
}

View File

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

7
cookie-session/README.md Normal file
View File

@ -0,0 +1,7 @@
## Cookie session example
```sh
cd cookie-session
cargo run
# Starting http server: 127.0.0.1:8080
```

View File

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

View File

@ -1 +1 @@
DATABASE_URL=file:test.db
DATABASE_URL=test.db

View File

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

View File

@ -7,9 +7,10 @@ Diesel's `Getting Started` guide using SQLite for Actix web
### init database sqlite
```bash
# if opensuse: sudo zypper install sqlite3-devel
cargo install diesel_cli --no-default-features --features sqlite
cd examples/diesel
echo "DATABASE_URL=file:test.db" > .env
echo "DATABASE_URL=test.db" > .env
diesel migration run
```
@ -18,6 +19,7 @@ diesel migration run
```bash
# if ubuntu : sudo apt-get install libsqlite3-dev
# if fedora : sudo dnf install libsqlite3x-devel
# if opensuse: sudo zypper install libsqlite3-0
cd examples/diesel
cargo run (or ``cargo watch -x run``)
# Started http server: 127.0.0.1:8080
@ -32,6 +34,7 @@ cargo run (or ``cargo watch -x run``)
```bash
# if ubuntu : sudo apt-get install sqlite3
# if fedora : sudo dnf install sqlite3x
# if opensuse: sudo zypper install sqlite3
sqlite3 test.db
sqlite> .tables
sqlite> select * from users;

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,72 @@
//! Actix supports sync actors by default, so we going to create sync actor
//! that use diesel. Technically sync actors are worker style actors, multiple
//! 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]
extern crate diesel;
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
extern crate r2d2;
extern crate uuid;
extern crate bytes;
// extern crate json;
#[macro_use]
extern crate serde_derive;
use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer};
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::r2d2::ConnectionManager;
use futures::{future, Future, Stream};
use diesel::r2d2::{self, ConnectionManager};
use dotenv;
use futures::future::{err, Either};
use futures::{Future, Stream};
mod db;
mod models;
mod schema;
use db::{CreateUser, DbExecutor};
type Pool = r2d2::Pool<ConnectionManager<SqliteConnection>>;
/// State with DbExecutor address
struct AppState {
db: Addr<DbExecutor>,
/// Diesel query
fn query(
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
fn add(
(name, state): (Path<String>, State<AppState>),
) -> FutureResponse<HttpResponse> {
// send async `CreateUser` message to a `DbExecutor`
state
.db
.send(CreateUser {
name: name.into_inner(),
})
.from_err()
.and_then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
.responder()
name: web::Path<String>,
pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = Error> {
// run diesel blocking code
web::block(move || query(name.into_inner(), pool)).then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
}
#[derive(Debug, Serialize, Deserialize)]
struct MyUser {
name: String
name: String,
}
const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// 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> {
// HttpRequest::payload() is stream of Bytes objects
req.payload()
fn index_add(
pl: web::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
// the future into the final error type
.from_err()
// `fold` will asynchronously read each chunk of the request body and
// call supplied closure, then it resolves to result of closure
.fold(BytesMut::new(), move |mut body, chunk| {
@ -92,78 +87,80 @@ fn index_add((req, state): (HttpRequest<AppState>, State<AppState>)) -> impl Fut
// Douman NOTE:
// The return value in this closure helps, to clarify result for compiler
// 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
let r_obj = serde_json::from_slice::<MyUser>(&body);
// Send to the db for create
match r_obj {
Ok(obj) => {
let res = state.db.send(CreateUser { name: obj.name, })
.from_err()
.and_then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
});
Box::new(res)
Either::A(web::block(move || query(obj.name, pool)).then(|res| {
match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
}
}))
}
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> {
state.db
.send(CreateUser {
// into_inner to move into the reference, then accessing name to
// move the name out.
name: item.into_inner().name,
})
.from_err()
.and_then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
fn add2(
item: web::Json<MyUser>,
pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = Error> {
// run diesel blocking code
web::block(move || query(item.into_inner().name, pool)).then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("diesel-example");
// Start 3 db executor actors
let manager = ConnectionManager::<SqliteConnection>::new("test.db");
dotenv::dotenv().ok();
let connspec = std::env::var("DATABASE_URL").expect("DATABASE_URL");
let manager = ConnectionManager::<SqliteConnection>::new(connspec);
let pool = r2d2::Pool::builder()
.build(manager)
.expect("Failed to create pool.");
let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone()));
// Start http server
server::new(move || {
App::with_state(AppState{db: addr.clone()})
HttpServer::new(move || {
App::new()
.data(pool.clone())
// enable logger
.middleware(middleware::Logger::default())
.wrap(middleware::Logger::default())
// This can be called with:
// 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
// as size limit protections and built in json validation.
.resource("/add2", |r| {
r.method(http::Method::POST)
.with_async_config(add2, |(json_cfg, )| {
json_cfg.0.limit(4096); // <- limit size of the payload
})
})
.service(
web::resource("/add2")
.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()
}),
)
.route(web::post().to_async(add2)),
)
// Manual parsing would allow custom error construction, use of
// other parsers *beside* json (for example CBOR, protobuf, xml), and allows
// an application to standardise on a single parser implementation.
.resource("/add", |r| r.method(http::Method::POST).with_async(index_add))
.resource("/add/{name}", |r| r.method(http::Method::GET).with(add))
}).bind("127.0.0.1:8080")
.unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
.service(web::resource("/add").route(web::post().to_async(index_add)))
.service(web::resource("/add/{name}").route(web::get().to_async(add)))
})
.bind("127.0.0.1:8080")?
.run()
}

View File

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

View File

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

View File

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

View File

@ -1,46 +1,32 @@
extern crate actix;
extern crate actix_web;
#[macro_use]
extern crate serde_derive;
use actix_web::{
http, middleware, server, App, Form, HttpRequest, HttpResponse, Result, State,
middleware, web, App, HttpRequest, HttpResponse, HttpServer, Responder, Result,
};
struct AppState {
foo: String,
}
fn main() {
let sys = actix::System::new("form-example");
let _addr = server::new(|| {
App::with_state(AppState {
foo: "bar".to_string(),
}).middleware(middleware::Logger::default())
.resource("/", |r| {
r.method(http::Method::GET).with(index);
fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.data(AppState {
foo: "bar".to_string(),
})
.resource("/post1", |r| {
r.method(http::Method::POST).with(handle_post_1)
})
.resource("/post2", |r| {
r.method(http::Method::POST).with(handle_post_2)
})
.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();
.wrap(middleware::Logger::default())
.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)))
})
.bind("127.0.0.1:8080")?
.run()
}
fn index(_req: HttpRequest<AppState>) -> Result<HttpResponse> {
Ok(HttpResponse::build(http::StatusCode::OK)
fn index() -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(include_str!("../static/form.html")))
}
@ -51,30 +37,28 @@ pub struct MyParams {
}
/// Simple handle POST request
fn handle_post_1(params: Form<MyParams>) -> Result<HttpResponse> {
Ok(HttpResponse::build(http::StatusCode::OK)
fn handle_post_1(params: web::Form<MyParams>) -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/plain")
.body(format!("Your name is {}", params.name)))
}
/// State and POST Params
fn handle_post_2(
(state, params): (State<AppState>, Form<MyParams>),
) -> Result<HttpResponse> {
Ok(HttpResponse::build(http::StatusCode::OK)
.content_type("text/plain")
.body(format!(
"Your name is {}, and in AppState I have foo: {}",
params.name, state.foo
)))
state: web::Data<AppState>,
params: web::Form<MyParams>,
) -> HttpResponse {
HttpResponse::Ok().content_type("text/plain").body(format!(
"Your name is {}, and in AppState I have foo: {}",
params.name, state.foo
))
}
/// Request and POST Params
fn handle_post_3(
(req, params): (HttpRequest<AppState>, Form<MyParams>),
) -> Result<HttpResponse> {
fn handle_post_3(req: HttpRequest, params: web::Form<MyParams>) -> impl Responder {
println!("Handling POST request: {:?}", req);
Ok(HttpResponse::build(http::StatusCode::OK)
HttpResponse::Ok()
.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"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../"
workspace = ".."
edition = "2018"
[dependencies]
env_logger = "0.5"
actix = "0.7"
actix-web = "^0.7"
actix-web = "1.0.0"
env_logger = "0.6"

View File

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

View File

@ -1,12 +0,0 @@
[package]
name = "http-full-proxy"
version = "0.1.0"
authors = ["Rotem Yaari"]
[dependencies]
actix = "0.7.5"
actix-web = "0.7.13"
clap = "2.32.0"
futures = "0.1.25"
failure = "0.1.3"
url = "1.7.1"

View File

@ -1,10 +0,0 @@
## HTTP Full proxy example
This proxy forwards all types of requests, including ones with body, to another HTTP server,
returning the response to the client.
To start:
``` shell
cargo run <listen addr> <listen port> <forward addr> <forward port>
```

View File

@ -1,135 +0,0 @@
#![deny(warnings)]
extern crate actix;
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 futures::{future, Future};
use std::net::ToSocketAddrs;
use url::Url;
struct AppState {
forward_url: Url,
}
impl AppState {
pub fn init(forward_url: Url) -> AppState {
AppState { forward_url }
}
}
fn forward(
req: &HttpRequest<AppState>,
) -> Box<Future<Item = HttpResponse, Error = Error>> {
let mut new_url = req.state().forward_url.clone();
new_url.set_path(req.uri().path());
new_url.set_query(req.uri().query());
let mut forwarded_req = client::ClientRequest::build_from(req)
.no_default_headers()
.uri(new_url)
.streaming(req.payload())
.unwrap();
if let Some(addr) = req.peer_addr() {
match forwarded_req.headers_mut().entry("x-forwarded-for") {
Ok(http::header::Entry::Vacant(entry)) => {
let addr = format!("{}", addr.ip());
entry.insert(addr.parse().unwrap());
}
Ok(http::header::Entry::Occupied(mut entry)) => {
let addr = format!("{}, {}", entry.get().to_str().unwrap(), addr.ip());
entry.insert(addr.parse().unwrap());
}
_ => unreachable!(),
}
}
forwarded_req
.send()
.map_err(Error::from)
.and_then(construct_response)
.responder()
}
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
resp.headers().iter().filter(|(h, _)| *h != "connection")
{
client_resp.header(header_name.clone(), header_value.clone());
}
if resp.chunked().unwrap_or(false) {
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() {
let matches = clap::App::new("HTTP Proxy")
.arg(
Arg::with_name("listen_addr")
.takes_value(true)
.value_name("LISTEN ADDR")
.index(1)
.required(true),
).arg(
Arg::with_name("listen_port")
.takes_value(true)
.value_name("LISTEN PORT")
.index(2)
.required(true),
).arg(
Arg::with_name("forward_addr")
.takes_value(true)
.value_name("FWD ADDR")
.index(3)
.required(true),
).arg(
Arg::with_name("forward_port")
.takes_value(true)
.value_name("FWD PORT")
.index(4)
.required(true),
).get_matches();
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 forwarded_addr = matches.value_of("forward_addr").unwrap();
let forwarded_port =
value_t!(matches, "forward_port", u16).unwrap_or_else(|e| e.exit());
let forward_url = Url::parse(&format!(
"http://{}",
(forwarded_addr, forwarded_port)
.to_socket_addrs()
.unwrap()
.next()
.unwrap()
)).unwrap();
server::new(move || {
App::with_state(AppState::init(forward_url.clone())).default_resource(|r| {
r.f(forward);
})
}).workers(32)
.bind((listen_addr, listen_port))
.expect("Cannot bind listening port")
.system_exit()
.run();
}

View File

@ -1,20 +1,14 @@
[package]
name = "http-proxy"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../"
[[bin]]
name = "proxy"
path = "src/main.rs"
[[bin]]
name = "proxy-example-server"
path = "src/server.rs"
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rotem Yaari <vmalloc@gmail.com>"]
workspace = ".."
edition = "2018"
[dependencies]
env_logger = "0.5"
futures = "0.1"
actix = "0.7"
actix-web = "^0.7"
actix-rt = "0.2"
actix-web = { version = "1.0.0", features=["ssl"] }
clap = "2.32.0"
futures = "0.1.25"
failure = "0.1.3"
url = "1.7.1"

View File

@ -1,13 +1,10 @@
## Http proxy example
## HTTP Full proxy example
To start proxy server:
This is a relatively simple HTTP proxy, forwarding HTTP requests to another HTTP server, including
request body, headers, and streaming uploads.
```sh
cargo run --bin proxy
```
To start local backend server:
```sh
cargo run --bin proxy-example-server
To start:
``` shell
cargo run <listen addr> <listen port> <forward addr> <forward port>
```

View File

@ -1,60 +1,102 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
use actix_web::client::Client;
use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use clap::{value_t, Arg};
use futures::Future;
use std::net::ToSocketAddrs;
use url::Url;
use actix_web::{
client, middleware, server, App, AsyncResponder, Body, Error, HttpMessage,
HttpRequest, HttpResponse,
};
use futures::{Future, Stream};
fn forward(
req: HttpRequest,
payload: web::Payload,
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_query(req.uri().query());
/// Stream client request response and then send body to a server response
fn index(_req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
client::ClientRequest::get("http://127.0.0.1:8081/")
.finish().unwrap()
.send()
.map_err(Error::from) // <- convert SendRequestError to an Error
.and_then(
|resp| resp.body() // <- this is MessageBody type, resolves to complete body
.from_err() // <- convert PayloadError to an Error
.and_then(|body| { // <- we got complete body, now send as server response
Ok(HttpResponse::Ok().body(body))
}))
.responder()
}
let forwarded_req = client
.request_from(new_url.as_str(), req.head())
.no_decompress();
let forwarded_req = if let Some(addr) = req.head().peer_addr {
forwarded_req.header("x-forwarded-for", format!("{}", addr.ip()))
} else {
forwarded_req
};
/// streaming client request to a streaming server response
fn streaming(_req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
// send client request
client::ClientRequest::get("https://www.rust-lang.org/en-US/")
.finish().unwrap()
.send() // <- connect to host and send request
.map_err(Error::from) // <- convert SendRequestError to an Error
.and_then(|resp| { // <- we received client response
Ok(HttpResponse::Ok()
// read one chunk from client response and send this chunk to a server response
// .from_err() converts PayloadError to an Error
.body(Body::Streaming(Box::new(resp.payload().from_err()))))
forwarded_req
.send_stream(payload)
.map_err(Error::from)
.map(|res| {
let mut client_resp = HttpResponse::build(res.status());
for (header_name, header_value) in res
.headers()
.iter()
.filter(|(h, _)| *h != "connection" && *h != "content-length")
{
client_resp.header(header_name.clone(), header_value.clone());
}
client_resp.streaming(res)
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("http-proxy");
fn main() -> std::io::Result<()> {
let matches = clap::App::new("HTTP Proxy")
.arg(
Arg::with_name("listen_addr")
.takes_value(true)
.value_name("LISTEN ADDR")
.index(1)
.required(true),
)
.arg(
Arg::with_name("listen_port")
.takes_value(true)
.value_name("LISTEN PORT")
.index(2)
.required(true),
)
.arg(
Arg::with_name("forward_addr")
.takes_value(true)
.value_name("FWD ADDR")
.index(3)
.required(true),
)
.arg(
Arg::with_name("forward_port")
.takes_value(true)
.value_name("FWD PORT")
.index(4)
.required(true),
)
.get_matches();
server::new(|| {
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 forwarded_addr = matches.value_of("forward_addr").unwrap();
let forwarded_port =
value_t!(matches, "forward_port", u16).unwrap_or_else(|e| e.exit());
let forward_url = Url::parse(&format!(
"http://{}",
(forwarded_addr, forwarded_port)
.to_socket_addrs()
.unwrap()
.next()
.unwrap()
))
.unwrap();
HttpServer::new(move || {
App::new()
.middleware(middleware::Logger::default())
.resource("/streaming", |r| r.f(streaming))
.resource("/", |r| r.f(index))
}).workers(1)
.bind("127.0.0.1:8080")
.unwrap()
.start();
println!("Started http server: 127.0.0.1:8080");
let _ = sys.run();
.data(Client::new())
.data(forward_url.clone())
.wrap(middleware::Logger::default())
.default_service(web::route().to_async(forward))
})
.bind((listen_addr, listen_port))?
.system_exit()
.run()
}

View File

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

View File

@ -2,9 +2,12 @@
name = "json-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../"
workspace = ".."
edition = "2018"
[dependencies]
actix-web = "1.0.0"
bytes = "0.4"
futures = "0.1"
env_logger = "*"
@ -13,6 +16,3 @@ serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
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]
extern crate json;
use actix_web::{
error, http, middleware, server, App, AsyncResponder, Error, HttpMessage,
HttpRequest, HttpResponse, Json,
error, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
};
use bytes::BytesMut;
use futures::{Future, Stream};
use json::JsonValue;
use serde_derive::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct MyObj {
@ -24,39 +15,31 @@ struct MyObj {
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
fn extract_item(item: Json<MyObj>) -> HttpResponse {
fn index(item: web::Json<MyObj>) -> HttpResponse {
println!("model: {:?}", &item);
HttpResponse::Ok().json(item.0) // <- send response
}
/// This handler uses json extractor with limit
fn extract_item_limit((item, _req): (Json<MyObj>, HttpRequest)) -> HttpResponse {
println!("model: {:?}", &item);
HttpResponse::Ok().json(item.0) // <- send response
fn extract_item(item: web::Json<MyObj>, req: HttpRequest) -> HttpResponse {
println!("request: {:?}", req);
println!("model: {:?}", item);
HttpResponse::Ok().json(item.0) // <- send json response
}
const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// This handler manually load request payload and parse json object
fn index_manual(req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
// HttpRequest::payload() is stream of Bytes objects
req.payload()
fn index_manual(
payload: web::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
// the future into the final error type
.from_err()
// `fold` will asynchronously read each chunk of the request body and
// call supplied closure, then it resolves to result of closure
.fold(BytesMut::new(), move |mut body, chunk| {
@ -75,59 +58,44 @@ fn index_manual(req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Er
let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(HttpResponse::Ok().json(obj)) // <- send response
})
.responder()
}
/// This handler manually load request payload and parse json-rust
fn index_mjsonrust(
req: &HttpRequest,
) -> Box<Future<Item = HttpResponse, Error = Error>> {
req.payload()
.concat2()
.from_err()
.and_then(|body| {
// body is loaded, now we can deserialize json-rust
let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result
let injson: JsonValue = match result {
Ok(v) => v,
Err(e) => object!{"err" => e.to_string() },
};
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(injson.dump()))
})
.responder()
fn index_mjsonrust(pl: web::Payload) -> impl Future<Item = HttpResponse, Error = Error> {
pl.concat2().from_err().and_then(|body| {
// body is loaded, now we can deserialize json-rust
let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result
let injson: JsonValue = match result {
Ok(v) => v,
Err(e) => json::object! {"err" => e.to_string() },
};
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(injson.dump()))
})
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
let sys = actix::System::new("json-example");
server::new(|| {
HttpServer::new(|| {
App::new()
// enable logger
.middleware(middleware::Logger::default())
.resource("/extractor", |r| {
r.method(http::Method::POST)
.with_config(extract_item, |(cfg,)| {
cfg.limit(4096); // <- limit size of the payload
})
})
.resource("/extractor2", |r| {
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();
.wrap(middleware::Logger::default())
.data(web::JsonConfig::default().limit(4096)) // <- limit size of the payload (global configuration)
.service(web::resource("/extractor").route(web::post().to(index)))
.service(
web::resource("/extractor2")
.data(web::JsonConfig::default().limit(1024)) // <- limit size of the payload (resource level)
.route(web::post().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")?
.run()
}

13
json_error/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "json_error"
version = "0.1.0"
authors = ["Kai Yao <kai.b.yao@gmail.com>"]
edition = "2018"
[dependencies]
actix = "0.8"
actix-web = "1.0"
failure = "0.1"
futures = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

53
json_error/src/main.rs Normal file
View File

@ -0,0 +1,53 @@
// This example is meant to show how to automatically generate a json error response when something goes wrong.
use actix::System;
use actix_web::http::StatusCode;
use actix_web::web::{get, resource, HttpRequest, HttpResponse};
use actix_web::{App, HttpServer, ResponseError};
use futures::future::err;
use futures::Future;
use serde::Serialize;
use serde_json::{json, to_string_pretty};
use std::fmt::{Display, Formatter, Result as FmtResult};
use std::io;
#[derive(Debug, Serialize)]
struct Error {
msg: String,
status: u16,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
write!(f, "{}", to_string_pretty(self).unwrap())
}
}
impl ResponseError for Error {
// builds the actual response to send back when an error occurs
fn render_response(&self) -> HttpResponse {
let err_json = json!({ "error": self.msg });
HttpResponse::build(StatusCode::from_u16(self.status).unwrap()).json(err_json)
}
}
fn index(_: HttpRequest) -> impl Future<Item = HttpResponse, Error = Error> {
err(Error {
msg: "an example error message".to_string(),
status: 400,
})
}
fn main() -> io::Result<()> {
let sys = System::new("json_error_example");
let ip_address = "127.0.0.1:8000";
HttpServer::new(|| App::new().service(resource("/").route(get().to_async(index))))
.bind(ip_address)
.expect("Can not bind to port 8000")
.start();
println!("Running server on {}", ip_address);
sys.run()
}

17
jsonrpc/Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "jsonrpc-example"
version = "0.1.0"
authors = ["mohanson <mohanson@outlook.com>"]
edition = "2018"
workspace = ".."
[dependencies]
actix = "0.8.2"
actix-web = "1.0.0"
env_logger = "0.6"
futures = "0.1.23"
futures-timer = "0.1"
log = "0.4"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"

34
jsonrpc/README.md Normal file
View File

@ -0,0 +1,34 @@
A simple demo for building a `JSONRPC over HTTP` server in [actix-web](https://github.com/actix/actix-web).
# Server
```sh
$ cargo run
# Starting server on 127.0.0.1:8080
```
# Client
**curl**
```sh
$ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "ping", "params": [], "id": 1}' http://127.0.0.1:8080
# {"jsonrpc":"2.0","result":"pong","error":null,"id":1}
```
**python**
```sh
$ python tests\test_client.py
# {'jsonrpc': '2.0', 'result': 'pong', 'error': None, 'id': 1}
```
# Methods
- `ping`: Pong immeditely
- `wait`: Wait `n` seconds, and then pong
- `get`: Get global count
- `inc`: Increment global count
See `tests\test_client.py` to get more information.

137
jsonrpc/src/convention.rs Normal file
View File

@ -0,0 +1,137 @@
//! JSON-RPC 2.0 Specification
//! See: https://www.jsonrpc.org/specification
use std::error;
use std::fmt;
use serde_derive::{Deserialize, Serialize};
use serde_json::Value;
pub static JSONRPC_VERSION: &str = "2.0";
/// When a rpc call encounters an error, the Response Object MUST contain the
/// error member with a value that is a Object with the following members:
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorData {
/// A Number that indicates the error type that occurred. This MUST be an integer.
pub code: i32,
/// A String providing a short description of the error. The message SHOULD be
/// limited to a concise single sentence.
pub message: String,
/// A Primitive or Structured value that contains additional information
/// about the error. This may be omitted. The value of this member is
/// defined by the Server (e.g. detailed error information, nested errors
/// etc.).
pub data: Value,
}
impl ErrorData {
pub fn new(code: i32, message: &str) -> Self {
Self {
code,
message: String::from(message),
data: Value::Null,
}
}
pub fn std(code: i32) -> Self {
match code {
// Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.
-32700 => ErrorData::new(-32700, "Parse error"),
// The JSON sent is not a valid Request object.
-32600 => ErrorData::new(-32600, "Invalid Request"),
// The method does not exist / is not available.
-32601 => ErrorData::new(-32601, "Method not found"),
// Invalid method parameter(s).
-32602 => ErrorData::new(-32602, "Invalid params"),
// Internal JSON-RPC error.
-32603 => ErrorData::new(-32603, "Internal error"),
// The error codes from and including -32768 to -32000 are reserved for pre-defined errors. Any code within
// this range, but not defined explicitly below is reserved for future use.
_ => panic!("Undefined pre-defined error codes"),
}
}
/// Prints out the value as JSON string.
pub fn dump(&self) -> String {
serde_json::to_string(self).expect("Should never failed")
}
}
impl error::Error for ErrorData {}
impl fmt::Display for ErrorData {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {}, {})", self.code, self.message, self.data)
}
}
/// A rpc call is represented by sending a Request object to a Server.
#[derive(Debug, Serialize, Deserialize)]
pub struct Request {
/// A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
pub jsonrpc: String,
/// A String containing the name of the method to be invoked. Method names that begin with the word rpc followed by
/// a period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and MUST NOT be
/// used for anything else.
pub method: String,
/// A Structured value that holds the parameter values to be used during the invocation of the method. This member
/// MAY be omitted.
pub params: Vec<Value>,
/// An identifier established by the Client that MUST contain a String, Number, or NULL value if included. If it is
/// not included it is assumed to be a notification. The value SHOULD normally not be Null [1] and Numbers SHOULD
/// NOT contain fractional parts.
pub id: Value,
}
impl Request {
/// Prints out the value as JSON string.
pub fn dump(&self) -> String {
serde_json::to_string(self).expect("Should never failed")
}
}
/// When a rpc call is made, the Server MUST reply with a Response, except for in the case of Notifications. The
/// Response is expressed as a single JSON Object, with the following members:
#[derive(Debug, Serialize, Deserialize)]
pub struct Response {
/// A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".
pub jsonrpc: String,
/// This member is REQUIRED on success.
/// This member MUST NOT exist if there was an error invoking the method.
/// The value of this member is determined by the method invoked on the Server.
pub result: Value,
// This member is REQUIRED on error.
// This member MUST NOT exist if there was no error triggered during invocation.
// The value for this member MUST be an Object as defined in section 5.1.
pub error: Option<ErrorData>,
/// This member is REQUIRED.
/// It MUST be the same as the value of the id member in the Request Object.
/// If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request),
/// it MUST be Null.
pub id: Value,
}
impl Response {
/// Prints out the value as JSON string.
pub fn dump(&self) -> String {
serde_json::to_string(self).expect("Should never failed")
}
}
impl Default for Response {
fn default() -> Self {
Self {
jsonrpc: JSONRPC_VERSION.into(),
result: Value::Null,
error: None,
id: Value::Null,
}
}
}

164
jsonrpc/src/main.rs Normal file
View File

@ -0,0 +1,164 @@
use std::error;
use std::sync::Arc;
use std::sync::RwLock;
use std::time::Duration;
use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use futures::{future, Future, Stream};
use futures_timer::Delay;
use serde_json;
use serde_json::Value;
#[allow(dead_code)]
mod convention;
/// The main handler for JSONRPC server.
fn rpc_handler(
req: HttpRequest,
payload: web::Payload,
) -> impl Future<Item = HttpResponse, Error = Error> {
payload.concat2().from_err().and_then(move |body| {
let reqjson: convention::Request = match serde_json::from_slice(body.as_ref()) {
Ok(ok) => ok,
Err(_) => {
let r = convention::Response {
jsonrpc: String::from(convention::JSONRPC_VERSION),
result: Value::Null,
error: Some(convention::ErrorData::std(-32700)),
id: Value::Null,
};
return Ok(HttpResponse::Ok()
.content_type("application/json")
.body(r.dump()));
}
};
let app_state = req.app_data().unwrap();
let mut result = convention::Response::default();
result.id = reqjson.id.clone();
match rpc_select(&app_state, reqjson.method.as_str(), reqjson.params) {
Ok(ok) => result.result = ok,
Err(e) => result.error = Some(e),
}
Ok(HttpResponse::Ok()
.content_type("application/json")
.body(result.dump()))
})
}
fn rpc_select(
app_state: &AppState,
method: &str,
params: Vec<Value>,
) -> Result<Value, convention::ErrorData> {
match method {
"ping" => {
let r = app_state.network.read().unwrap().ping();
Ok(Value::from(r))
}
"wait" => {
if params.len() != 1 || !params[0].is_u64() {
return Err(convention::ErrorData::std(-32602));
}
match app_state
.network
.read()
.unwrap()
.wait(params[0].as_u64().unwrap())
.wait()
{
Ok(ok) => Ok(Value::from(ok)),
Err(e) => Err(convention::ErrorData::new(500, &format!("{:?}", e)[..])),
}
}
"get" => {
let r = app_state.network.read().unwrap().get();
Ok(Value::from(r))
}
"inc" => {
app_state.network.write().unwrap().inc();
Ok(Value::Null)
}
_ => Err(convention::ErrorData::std(-32601)),
}
}
pub trait ImplNetwork {
fn ping(&self) -> String;
fn wait(
&self,
d: u64,
) -> Box<dyn Future<Item = String, Error = Box<dyn error::Error>>>;
fn get(&self) -> u32;
fn inc(&mut self);
}
pub struct ObjNetwork {
c: u32,
}
impl ObjNetwork {
fn new() -> Self {
Self { c: 0 }
}
}
impl ImplNetwork for ObjNetwork {
fn ping(&self) -> String {
String::from("pong")
}
fn wait(
&self,
d: u64,
) -> Box<dyn Future<Item = String, Error = Box<dyn error::Error>>> {
if let Err(e) = Delay::new(Duration::from_secs(d)).wait() {
let e: Box<dyn error::Error> = Box::new(e);
return Box::new(future::err(e));
};
Box::new(future::ok(String::from("pong")))
}
fn get(&self) -> u32 {
self.c
}
fn inc(&mut self) {
self.c += 1;
}
}
#[derive(Clone)]
pub struct AppState {
network: Arc<RwLock<dyn ImplNetwork>>,
}
impl AppState {
pub fn new(network: Arc<RwLock<dyn ImplNetwork>>) -> Self {
Self { network }
}
}
fn main() {
std::env::set_var("RUST_LOG", "info");
env_logger::init();
let network = Arc::new(RwLock::new(ObjNetwork::new()));
let sys = actix::System::new("actix_jrpc");
HttpServer::new(move || {
let app_state = AppState::new(network.clone());
App::new()
.data(app_state)
.wrap(middleware::Logger::default())
.service(web::resource("/").route(web::post().to_async(rpc_handler)))
})
.bind("127.0.0.1:8080")
.unwrap()
.workers(1)
.start();
let _ = sys.run();
}

View File

@ -0,0 +1,38 @@
import requests
print('ping: pong immediately')
r = requests.post('http://127.0.0.1:8080/', json={
'jsonrpc': '2.0',
'method': 'ping',
'params': [],
'id': 1
})
print(r.json())
print('ping: pong after 4 secs')
r = requests.post('http://127.0.0.1:8080/', json={
'jsonrpc': '2.0',
'method': 'wait',
'params': [4],
'id': 1
})
print(r.json())
for i in range(10):
print(f'inc {i:>02}')
r = requests.post('http://127.0.0.1:8080/', json={
'jsonrpc': '2.0',
'method': 'inc',
'params': [],
'id': 1
})
print(f'get')
r = requests.post('http://127.0.0.1:8080/', json={
'jsonrpc': '2.0',
'method': 'get',
'params': [],
'id': 1
})
print(r.json())

View File

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

View File

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

View File

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

View File

@ -1,7 +1,11 @@
## Middleware example
# middleware examples
This example showcases a bunch of different uses of middlewares. See also the [Middleware guide](https://actix.rs/docs/middleware/)..
## Usage
```bash
cd form
cd middleware
cargo run
# Started http server: 127.0.0.1:8080
```
@ -9,3 +13,17 @@ cargo run
Look in `src/main.rs` and comment the different middlewares in/out to see how
they function.
## Middlewares
### redirect::CheckLogin
A middleware implementing a request guard which sketches a rough approximation of what a login could look like.
### read_body::Logging
A middleware demonstrating how to read out the incoming request body.
### simple::SayHi
A minimal middleware demonstrating the sequence of operations in an actix middleware.
There is a second version of the same middleware using `wrap_fn` which shows how easily a middleware can be implemented in actix.

View File

@ -1,29 +1,40 @@
extern crate actix;
extern crate actix_web;
use actix_web::{server, App};
use actix_web::{web, App, HttpServer};
use actix_service::Service;
use futures::future::Future;
#[allow(dead_code)]
mod redirect;
#[allow(dead_code)]
mod read_body;
#[allow(dead_code)]
mod simple;
fn main() {
let sys = actix::System::new("middleware-example");
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init();
let _addr = server::new(|| {
HttpServer::new(|| {
App::new()
.middleware(simple::SayHi)
// .middleware(redirect::CheckLogin)
.resource("/login", |r| {
r.f(|_| "You are on /login. Go to src/redirect.rs to change this behavior.")
})
.resource("/", |r| {
r.f(|_| "Hello, middleware! Check the console where the server is run.")
})
}).bind("127.0.0.1:8080")
.unwrap()
.start();
.wrap(redirect::CheckLogin)
.wrap(read_body::Logging)
.wrap(simple::SayHi)
.wrap_fn(|req, srv| {
println!("Hi from start. You requested: {}", req.path());
let _ = sys.run();
srv.call(req).map(|res| {
println!("Hi from response");
res
})
})
.service(web::resource("/login").to(|| {
"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."
}),
)
})
.bind("127.0.0.1:8080")?
.run()
}

View File

@ -0,0 +1,70 @@
use actix_service::{Service, Transform};
use actix_web::error::PayloadError;
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error, HttpMessage};
use bytes::BytesMut;
use futures::future::{ok, FutureResult};
use futures::stream::Stream;
use futures::{Future, Poll};
use std::cell::RefCell;
use std::rc::Rc;
pub struct Logging;
impl<S: 'static, B> Transform<S> for Logging
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = LoggingMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>;
fn new_transform(&self, service: S) -> Self::Future {
ok(LoggingMiddleware {
service: Rc::new(RefCell::new(service)),
})
}
}
pub struct LoggingMiddleware<S> {
// This is special: We need this to avoid lifetime issues.
service: Rc<RefCell<S>>,
}
impl<S, B> Service for LoggingMiddleware<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>
+ 'static,
S::Future: 'static,
B: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
type Error = Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
self.service.poll_ready()
}
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let mut svc = self.service.clone();
Box::new(
req.take_payload()
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, PayloadError>(body)
})
.map_err(|e| e.into())
.and_then(move |bytes| {
println!("request body: {:?}", bytes);
svc.call(req).and_then(|res| Ok(res))
}),
)
}
}

View File

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

View File

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

View File

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

View File

@ -12,13 +12,14 @@ cargo run (or ``cargo watch -x run``)
# Started http server: 127.0.0.1:8080
```
### client
### browser
- ``pip install aiohttp``
- ``python client.py``
- go to ``http://localhost:8080``
- upload file
- you should see the action reflected in server console
### client (optional)
- ``./client.sh``
- you must see in server console multipart fields
if ubuntu :
- ``pip3 install aiohttp``
- ``python3 client.py``

View File

@ -1,36 +0,0 @@
#!/usr/bin/env python3
# This script could be used for actix-web multipart example test
# just start server and run client.py
import asyncio
import aiohttp
async def req1():
with aiohttp.MultipartWriter() as writer:
writer.append('test')
writer.append_json({'passed': True})
resp = await aiohttp.ClientSession().request(
"post", 'http://localhost:8080/multipart',
data=writer, headers=writer.headers)
print(resp)
assert 200 == resp.status
async def req2():
with aiohttp.MultipartWriter() as writer:
writer.append('test')
writer.append_json({'passed': True})
writer.append(open('src/main.rs'))
resp = await aiohttp.ClientSession().request(
"post", 'http://localhost:8080/multipart',
data=writer, headers=writer.headers)
print(resp)
assert 200 == resp.status
loop = asyncio.get_event_loop()
loop.run_until_complete(req1())
loop.run_until_complete(req2())

11
multipart/client.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
function SubmitFile () {
curl -X POST \
-H "Content-Type: multipart/related" \
--form "data=@example.png;type=image/png" http://localhost:8080
}
SubmitFile & SubmitFile & SubmitFile &
SubmitFile & SubmitFile & SubmitFile &
SubmitFile & SubmitFile & SubmitFile

BIN
multipart/example.png Normal file

Binary file not shown.

View File

@ -1,45 +1,43 @@
#![allow(unused_variables)]
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
use std::cell::Cell;
use std::fs;
use std::io::Write;
use actix_web::{
dev, error, http, middleware, multipart, server, App, Error, FutureResponse,
HttpMessage, HttpRequest, HttpResponse,
};
use futures::future;
use actix_multipart::{Field, Multipart, MultipartError};
use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer};
use futures::future::{err, Either};
use futures::{Future, Stream};
pub struct AppState {
pub counter: Cell<usize>,
}
pub fn save_file(
field: multipart::Field<dev::Payload>,
) -> Box<Future<Item = i64, Error = Error>> {
pub fn save_file(field: Field) -> impl Future<Item = i64, Error = Error> {
let file_path_string = "upload.png";
let mut file = match fs::File::create(file_path_string) {
let file = match fs::File::create(file_path_string) {
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
.fold(0i64, move |acc, bytes| {
let rt = file
.write_all(bytes.as_ref())
.map(|_| acc + bytes.len() as i64)
.map_err(|e| {
.fold((file, 0i64), move |(mut file, mut acc), bytes| {
// fs operations are blocking, we have to execute writes
// on threadpool
web::block(move || {
file.write_all(bytes.as_ref()).map_err(|e| {
println!("file.write_all failed: {:?}", e);
error::MultipartError::Payload(error::PayloadError::Io(e))
});
future::result(rt)
MultipartError::Payload(error::PayloadError::Io(e))
})?;
acc += bytes.len() as i64;
Ok((file, acc))
})
.map_err(|e: error::BlockingError<MultipartError>| {
match e {
error::BlockingError::Error(e) => e,
error::BlockingError::Canceled => MultipartError::Incomplete,
}
})
})
.map(|(_, acc)| acc)
.map_err(|e| {
println!("save_file failed, {:?}", e);
error::ErrorInternalServerError(e)
@ -47,39 +45,26 @@ pub fn save_file(
)
}
pub fn handle_multipart_item(
item: multipart::MultipartItem<dev::Payload>,
) -> Box<Stream<Item = i64, Error = Error>> {
match item {
multipart::MultipartItem::Field(field) => {
Box::new(save_file(field).into_stream())
}
multipart::MultipartItem::Nested(mp) => Box::new(
mp.map_err(error::ErrorInternalServerError)
.map(handle_multipart_item)
.flatten(),
),
}
pub fn upload(
multipart: Multipart,
counter: web::Data<Cell<usize>>,
) -> impl Future<Item = HttpResponse, Error = Error> {
counter.set(counter.get() + 1);
println!("{:?}", counter.get());
multipart
.map_err(error::ErrorInternalServerError)
.map(|field| save_file(field).into_stream())
.flatten()
.collect()
.map(|sizes| HttpResponse::Ok().json(sizes))
.map_err(|e| {
println!("failed: {}", e);
e
})
}
pub fn upload(req: HttpRequest<AppState>) -> FutureResponse<HttpResponse> {
req.state().counter.set(req.state().counter.get() + 1);
println!("{:?}", req.state().counter.get());
Box::new(
req.multipart()
.map_err(error::ErrorInternalServerError)
.map(handle_multipart_item)
.flatten()
.collect()
.map(|sizes| HttpResponse::Ok().json(sizes))
.map_err(|e| {
println!("failed: {}", e);
e
}),
)
}
fn index(_req: HttpRequest<AppState>) -> Result<HttpResponse, error::Error> {
fn index() -> HttpResponse {
let html = r#"<html>
<head><title>Upload Test</title></head>
<body>
@ -90,26 +75,23 @@ fn index(_req: HttpRequest<AppState>) -> Result<HttpResponse, error::Error> {
</body>
</html>"#;
Ok(HttpResponse::Ok().body(html))
HttpResponse::Ok().body(html)
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init();
let sys = actix::System::new("multipart-example");
server::new(|| {
App::with_state(AppState {
counter: Cell::new(0),
}).middleware(middleware::Logger::default())
.resource("/", |r| {
r.method(http::Method::GET).with(index);
r.method(http::Method::POST).with(upload);
})
}).bind("127.0.0.1:8080")
.unwrap()
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
HttpServer::new(|| {
App::new()
.data(Cell::new(0usize))
.wrap(middleware::Logger::default())
.service(
web::resource("/")
.route(web::get().to(index))
.route(web::post().to_async(upload)),
)
})
.bind("127.0.0.1:8080")?
.run()
}

View File

@ -2,16 +2,17 @@
name = "protobuf-example"
version = "0.1.0"
authors = ["kingxsp <jin_hb_zh@126.com>"]
workspace = "../"
workspace = ".."
edition = "2018"
[dependencies]
bytes = "0.4"
futures = "0.1"
failure = "0.1"
env_logger = "*"
env_logger = "0.6"
derive_more = "0.14"
prost = "0.4.0"
prost-derive = "0.4.0"
prost = "0.2.0"
prost-derive = "0.2.0"
actix = "0.7"
actix-web = "0.7"
actix = "0.8.2"
actix-web = "1.0.0"
actix-protobuf = "0.4.0"

View File

@ -46,7 +46,7 @@ async def fetch(session):
obj = test_pb2.MyObj()
obj.number = 9
obj.name = 'USB'
async with session.post('http://localhost:8080/', data=obj.SerializeToString(),
async with session.post('http://localhost:8081/', data=obj.SerializeToString(),
headers={"content-type": "application/protobuf"}) as resp:
print(resp.status)
data = await resp.read()

View File

@ -1,23 +1,16 @@
extern crate actix;
extern crate actix_protobuf;
extern crate actix_web;
extern crate bytes;
extern crate futures;
#[macro_use]
extern crate failure;
extern crate env_logger;
extern crate prost;
#[macro_use]
extern crate prost_derive;
use actix_web::{
http, middleware, server, App, AsyncResponder, Error, HttpRequest, HttpResponse,
};
use futures::Future;
use actix_protobuf::*;
use actix_web::*;
mod protobuf;
use protobuf::ProtoBufResponseBuilder;
#[derive(Clone, Debug, PartialEq, Message)]
#[derive(Clone, PartialEq, Message)]
pub struct MyObj {
#[prost(int32, tag = "1")]
pub number: i32,
@ -25,31 +18,26 @@ pub struct MyObj {
pub name: String,
}
/// This handler uses `ProtoBufMessage` for loading protobuf object.
fn index(req: &HttpRequest) -> Box<Future<Item = HttpResponse, Error = Error>> {
protobuf::ProtoBufMessage::new(req)
.from_err() // convert all errors into `Error`
.and_then(|val: MyObj| {
println!("model: {:?}", val);
Ok(HttpResponse::Ok().protobuf(val)?) // <- send response
})
.responder()
fn index(msg: ProtoBuf<MyObj>) -> Result<HttpResponse> {
println!("model: {:?}", msg);
HttpResponse::Ok().protobuf(msg.0) // <- send response
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
::std::env::set_var("RUST_LOG", "actix_web=info,actix_server=info");
env_logger::init();
let sys = actix::System::new("protobuf-example");
server::new(|| {
HttpServer::new(|| {
App::new()
.middleware(middleware::Logger::default())
.resource("/", |r| r.method(http::Method::POST).f(index))
}).bind("127.0.0.1:8080")
.unwrap()
.shutdown_timeout(1)
.start();
.wrap(middleware::Logger::default())
.service(web::resource("/").route(web::post().to(index)))
})
.bind("127.0.0.1:8081")
.unwrap()
.shutdown_timeout(1)
.start();
println!("Started http server: 127.0.0.1:8080");
println!("Started http server: 127.0.0.1:8081");
let _ = sys.run();
}

View File

@ -1,118 +0,0 @@
use bytes::BytesMut;
use futures::{Future, Poll, Stream};
use bytes::IntoBuf;
use prost::DecodeError as ProtoBufDecodeError;
use prost::EncodeError as ProtoBufEncodeError;
use prost::Message;
use actix_web::dev::HttpResponseBuilder;
use actix_web::error::{Error, PayloadError, ResponseError};
use actix_web::http::header::CONTENT_TYPE;
use actix_web::{HttpMessage, HttpRequest, HttpResponse, Responder};
#[derive(Fail, Debug)]
pub enum ProtoBufPayloadError {
/// Payload size is bigger than 256k
#[fail(display = "Payload size is bigger than 256k")]
Overflow,
/// Content type error
#[fail(display = "Content type error")]
ContentType,
/// Serialize error
#[fail(display = "ProtoBud serialize error: {}", _0)]
Serialize(#[cause] ProtoBufEncodeError),
/// Deserialize error
#[fail(display = "ProtoBud deserialize error: {}", _0)]
Deserialize(#[cause] ProtoBufDecodeError),
/// Payload error
#[fail(display = "Error that occur during reading payload: {}", _0)]
Payload(#[cause] PayloadError),
}
impl ResponseError for ProtoBufPayloadError {
fn error_response(&self) -> HttpResponse {
match *self {
ProtoBufPayloadError::Overflow => HttpResponse::PayloadTooLarge().into(),
_ => HttpResponse::BadRequest().into(),
}
}
}
impl From<PayloadError> for ProtoBufPayloadError {
fn from(err: PayloadError) -> ProtoBufPayloadError {
ProtoBufPayloadError::Payload(err)
}
}
impl From<ProtoBufDecodeError> for ProtoBufPayloadError {
fn from(err: ProtoBufDecodeError) -> ProtoBufPayloadError {
ProtoBufPayloadError::Deserialize(err)
}
}
#[derive(Debug)]
pub struct ProtoBuf<T: Message>(pub T);
impl<T: Message> Responder for ProtoBuf<T> {
type Item = HttpResponse;
type Error = Error;
fn respond_to<S>(self, _: &HttpRequest<S>) -> Result<HttpResponse, Error> {
let mut buf = Vec::new();
self.0
.encode(&mut buf)
.map_err(|e| Error::from(ProtoBufPayloadError::Serialize(e)))
.and_then(|()| {
Ok(HttpResponse::Ok()
.content_type("application/protobuf")
.body(buf)
.into())
})
}
}
pub struct ProtoBufMessage<U: Message + Default> {
fut: Box<Future<Item = U, Error = ProtoBufPayloadError>>,
}
impl<U: Message + Default + 'static> ProtoBufMessage<U> {
/// Create `ProtoBufMessage` for request.
pub fn new(req: &HttpRequest) -> Self {
let fut = req
.payload()
.map_err(|e| ProtoBufPayloadError::Payload(e))
.fold(BytesMut::new(), move |mut body, chunk| {
body.extend_from_slice(&chunk);
Ok::<_, ProtoBufPayloadError>(body)
})
.and_then(|body| Ok(<U>::decode(&mut body.into_buf())?));
ProtoBufMessage { fut: Box::new(fut) }
}
}
impl<U: Message + Default + 'static> Future for ProtoBufMessage<U> where {
type Item = U;
type Error = ProtoBufPayloadError;
fn poll(&mut self) -> Poll<U, ProtoBufPayloadError> {
self.fut.poll()
}
}
pub trait ProtoBufResponseBuilder {
fn protobuf<T: Message>(&mut self, value: T) -> Result<HttpResponse, Error>;
}
impl ProtoBufResponseBuilder for HttpResponseBuilder {
fn protobuf<T: Message>(&mut self, value: T) -> Result<HttpResponse, Error> {
self.header(CONTENT_TYPE, "application/protobuf");
let mut body = Vec::new();
value
.encode(&mut body)
.map_err(|e| ProtoBufPayloadError::Serialize(e))?;
Ok(self.body(body))
}
}

View File

@ -2,20 +2,17 @@
name = "r2d2-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../"
edition = "2018"
workspace = ".."
[dependencies]
env_logger = "0.5"
actix = "0.7"
actix-web = "0.7"
actix-rt = "0.2"
actix-web = "1.0.0"
futures = "0.1"
uuid = { version = "0.5", features = ["serde", "v4"] }
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
env_logger = "0.6"
uuid = { version = "0.7", features = ["v4"] }
r2d2 = "*"
r2d2_sqlite = "*"
rusqlite = "*"
r2d2 = "0.8"
r2d2_sqlite = "0.8"
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
extern crate actix;
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 std::io;
use actix::prelude::*;
use actix_web::{
http, middleware, server, App, AsyncResponder, Error, HttpRequest, HttpResponse,
};
use futures::future::Future;
use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
use futures::Future;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use uuid;
mod db;
use db::{CreateUser, DbExecutor};
/// Async request handler. Ddb pool is stored in application state.
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
struct State {
db: Addr<DbExecutor>,
let uuid = format!("{}", uuid::Uuid::new_v4());
conn.execute(
"INSERT INTO users (id, name) VALUES ($1, $2)",
&[&uuid, &path.into_inner()],
)
.unwrap();
conn.query_row("SELECT name FROM users WHERE id=$1", &[&uuid], |row| {
row.get::<_, String>(0)
})
})
.then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
}
/// Async request handler
fn index(req: &HttpRequest<State>) -> Box<Future<Item = HttpResponse, Error = Error>> {
let name = &req.match_info()["name"];
req.state()
.db
.send(CreateUser {
name: name.to_owned(),
})
.from_err()
.and_then(|res| match res {
Ok(user) => Ok(HttpResponse::Ok().json(user)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=debug");
fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init();
let sys = actix::System::new("r2d2-example");
let sys = actix_rt::System::new("r2d2-example");
// r2d2 pool
let manager = SqliteConnectionManager::file("test.db");
let pool = r2d2::Pool::new(manager).unwrap();
// Start db executor actors
let addr = SyncArbiter::start(3, move || DbExecutor(pool.clone()));
// start http server
HttpServer::new(move || {
App::new()
.data(pool.clone()) // <- store db pool in app state
.wrap(middleware::Logger::default())
.route("/{name}", web::get().to_async(index))
})
.bind("127.0.0.1:8080")?
.start();
// Start http server
server::new(move || {
App::with_state(State{db: addr.clone()})
// enable logger
.middleware(middleware::Logger::default())
.resource("/{name}", |r| r.method(http::Method::GET).a(index))
}).bind("127.0.0.1:8080")
.unwrap()
.start();
let _ = sys.run();
sys.run()
}

View File

@ -1,11 +1,18 @@
[package]
name = "redis-session"
name = "redis_session"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../"
workspace = ".."
edition = "2018"
[dependencies]
env_logger = "0.5"
actix = "0.7"
actix-web = "0.7.3"
actix-redis = { version = "0.5.1", features = ["web"] }
actix-web = "1.0.3"
actix-session = "0.2.0"
actix-redis = { version = "0.6.0", features = ["web"] }
env_logger = "0.6"
serde = { version = "^1.0", features = ["derive"] }
actix-service = "0.4.1"
actix-http-test = "0.2.2"
actix-http = "0.2.5"
serde_json = "1.0.40"
time = "0.1.42"

0
redis-session/README.md Normal file
View File

View File

@ -1,48 +1,329 @@
//! Example of redis based session
//! Example of login and logout using redis-based sessions
//!
//! [User guide](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions)
extern crate actix;
extern crate actix_redis;
extern crate actix_web;
extern crate env_logger;
//! Every request gets a session, corresponding to a cache entry and cookie.
//! At login, the session key changes and session state in cache re-assigns.
//! At logout, session state in cache is removed and cookie is invalidated.
//!
use actix_redis::RedisSession;
use actix_session::Session;
use actix_web::{
middleware, web,
web::{get, post, resource},
App, HttpResponse, HttpServer, Result,
};
use serde::{Deserialize, Serialize};
use actix_redis::RedisSessionBackend;
use actix_web::middleware::session::{RequestSession, SessionStorage};
use actix_web::{middleware, server, App, HttpRequest, HttpResponse, Result};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
pub struct IndexResponse {
user_id: Option<String>,
counter: i32,
}
/// simple handler
fn index(req: &HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
fn index(session: Session) -> Result<HttpResponse> {
let user_id: Option<String> = session.get::<String>("user_id").unwrap();
let counter: i32 = session
.get::<i32>("counter")
.unwrap_or(Some(0))
.unwrap_or(0);
// session
if let Some(count) = req.session().get::<i32>("counter")? {
println!("SESSION value: {}", count);
req.session().set("counter", count + 1)?;
Ok(HttpResponse::Ok().json(IndexResponse { user_id, counter }))
}
fn do_something(session: Session) -> Result<HttpResponse> {
let user_id: Option<String> = session.get::<String>("user_id").unwrap();
let counter: i32 = session
.get::<i32>("counter")
.unwrap_or(Some(0))
.map_or(1, |inner| inner + 1);
session.set("counter", counter)?;
Ok(HttpResponse::Ok().json(IndexResponse { user_id, counter }))
}
#[derive(Deserialize)]
struct Identity {
user_id: String,
}
fn login(user_id: web::Json<Identity>, session: Session) -> Result<HttpResponse> {
let id = user_id.into_inner().user_id;
session.set("user_id", &id)?;
session.renew();
let counter: i32 = session
.get::<i32>("counter")
.unwrap_or(Some(0))
.unwrap_or(0);
Ok(HttpResponse::Ok().json(IndexResponse {
user_id: Some(id),
counter,
}))
}
fn logout(session: Session) -> Result<HttpResponse> {
let id: Option<String> = session.get("user_id")?;
if let Some(x) = id {
session.purge();
Ok(format!("Logged out: {}", x).into())
} else {
req.session().set("counter", 1)?;
Ok("Could not log out anonymous user".into())
}
Ok("Welcome!".into())
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info,actix_redis=info");
env_logger::init();
let sys = actix::System::new("basic-example");
server::new(|| {
HttpServer::new(|| {
App::new()
// enable logger
.middleware(middleware::Logger::default())
// redis session middleware
.middleware(SessionStorage::new(
RedisSessionBackend::new("127.0.0.1:6379", &[0; 32])
))
// register simple route, handle all methods
.resource("/", |r| r.f(index))
}).bind("127.0.0.1:8080")
.unwrap()
.start();
let _ = sys.run();
.wrap(RedisSession::new("127.0.0.1:6379", &[0; 32]))
// enable logger - always register actix-web Logger middleware last
.wrap(middleware::Logger::default())
.service(resource("/").route(get().to(index)))
.service(resource("/do_something").route(post().to(do_something)))
.service(resource("/login").route(post().to(login)))
.service(resource("/logout").route(post().to(logout)))
})
.bind("127.0.0.1:8080")?
.run()
}
#[cfg(test)]
mod test {
use super::*;
use actix_http::{httpmessage::HttpMessage, HttpService};
use actix_http_test::{block_on, TestServer};
use actix_web::{
middleware,
web::{get, post, resource},
App,
};
use serde_json::json;
use time;
#[test]
fn test_workflow() {
// Step 1: GET index
// - set-cookie actix-session will be in response (session cookie #1)
// - response should be: {"counter": 0, "user_id": None}
// Step 2: GET index, including session cookie #1 in request
// - set-cookie will *not* be in response
// - response should be: {"counter": 0, "user_id": None}
// Step 3: POST to do_something, including session cookie #1 in request
// - adds new session state in redis: {"counter": 1}
// - response should be: {"counter": 1, "user_id": None}
// Step 4: POST again to do_something, including session cookie #1 in request
// - updates session state in redis: {"counter": 2}
// - response should be: {"counter": 2, "user_id": None}
// Step 5: POST to login, including session cookie #1 in request
// - set-cookie actix-session will be in response (session cookie #2)
// - updates session state in redis: {"counter": 2, "user_id": "ferris"}
// Step 6: GET index, including session cookie #2 in request
// - response should be: {"counter": 2, "user_id": "ferris"}
// Step 7: POST again to do_something, including session cookie #2 in request
// - updates session state in redis: {"counter": 3, "user_id": "ferris"}
// - response should be: {"counter": 2, "user_id": None}
// Step 8: GET index, including session cookie #1 in request
// - set-cookie actix-session will be in response (session cookie #3)
// - response should be: {"counter": 0, "user_id": None}
// Step 9: POST to logout, including session cookie #2
// - set-cookie actix-session will be in response with session cookie #2
// invalidation logic
// Step 10: GET index, including session cookie #2 in request
// - set-cookie actix-session will be in response (session cookie #3)
// - response should be: {"counter": 0, "user_id": None}
let mut srv = TestServer::new(|| {
HttpService::new(
App::new()
.wrap(
RedisSession::new("127.0.0.1:6379", &[0; 32])
.cookie_name("test-session"),
)
.wrap(middleware::Logger::default())
.service(resource("/").route(get().to(index)))
.service(resource("/do_something").route(post().to(do_something)))
.service(resource("/login").route(post().to(login)))
.service(resource("/logout").route(post().to(logout))),
)
});
// Step 1: GET index
// - set-cookie actix-session will be in response (session cookie #1)
// - response should be: {"counter": 0, "user_id": None}
let req_1a = srv.get("/").send();
let mut resp_1 = srv.block_on(req_1a).unwrap();
let cookie_1 = resp_1
.cookies()
.unwrap()
.clone()
.into_iter()
.find(|c| c.name() == "test-session")
.unwrap();
let result_1 = block_on(resp_1.json::<IndexResponse>()).unwrap();
assert_eq!(
result_1,
IndexResponse {
user_id: None,
counter: 0
}
);
// Step 2: GET index, including session cookie #1 in request
// - set-cookie will *not* be in response
// - response should be: {"counter": 0, "user_id": None}
let req_2 = srv.get("/").cookie(cookie_1.clone()).send();
let resp_2 = srv.block_on(req_2).unwrap();
let cookie_2 = resp_2
.cookies()
.unwrap()
.clone()
.into_iter()
.find(|c| c.name() == "test-session");
assert_eq!(cookie_2, None);
// Step 3: POST to do_something, including session cookie #1 in request
// - adds new session state in redis: {"counter": 1}
// - response should be: {"counter": 1, "user_id": None}
let req_3 = srv.post("/do_something").cookie(cookie_1.clone()).send();
let mut resp_3 = srv.block_on(req_3).unwrap();
let result_3 = block_on(resp_3.json::<IndexResponse>()).unwrap();
assert_eq!(
result_3,
IndexResponse {
user_id: None,
counter: 1
}
);
// Step 4: POST again to do_something, including session cookie #1 in request
// - updates session state in redis: {"counter": 2}
// - response should be: {"counter": 2, "user_id": None}
let req_4 = srv.post("/do_something").cookie(cookie_1.clone()).send();
let mut resp_4 = srv.block_on(req_4).unwrap();
let result_4 = block_on(resp_4.json::<IndexResponse>()).unwrap();
assert_eq!(
result_4,
IndexResponse {
user_id: None,
counter: 2
}
);
// Step 5: POST to login, including session cookie #1 in request
// - set-cookie actix-session will be in response (session cookie #2)
// - updates session state in redis: {"counter": 2, "user_id": "ferris"}
let req_5 = srv
.post("/login")
.cookie(cookie_1.clone())
.send_json(&json!({"user_id": "ferris"}));
let mut resp_5 = srv.block_on(req_5).unwrap();
let cookie_2 = resp_5
.cookies()
.unwrap()
.clone()
.into_iter()
.find(|c| c.name() == "test-session")
.unwrap();
assert_eq!(
true,
cookie_1.value().to_string() != cookie_2.value().to_string()
);
let result_5 = block_on(resp_5.json::<IndexResponse>()).unwrap();
assert_eq!(
result_5,
IndexResponse {
user_id: Some("ferris".into()),
counter: 2
}
);
// Step 6: GET index, including session cookie #2 in request
// - response should be: {"counter": 2, "user_id": "ferris"}
let req_6 = srv.get("/").cookie(cookie_2.clone()).send();
let mut resp_6 = srv.block_on(req_6).unwrap();
let result_6 = block_on(resp_6.json::<IndexResponse>()).unwrap();
assert_eq!(
result_6,
IndexResponse {
user_id: Some("ferris".into()),
counter: 2
}
);
// Step 7: POST again to do_something, including session cookie #2 in request
// - updates session state in redis: {"counter": 3, "user_id": "ferris"}
// - response should be: {"counter": 2, "user_id": None}
let req_7 = srv.post("/do_something").cookie(cookie_2.clone()).send();
let mut resp_7 = srv.block_on(req_7).unwrap();
let result_7 = block_on(resp_7.json::<IndexResponse>()).unwrap();
assert_eq!(
result_7,
IndexResponse {
user_id: Some("ferris".into()),
counter: 3
}
);
// Step 8: GET index, including session cookie #1 in request
// - set-cookie actix-session will be in response (session cookie #3)
// - response should be: {"counter": 0, "user_id": None}
let req_8 = srv.get("/").cookie(cookie_1.clone()).send();
let mut resp_8 = srv.block_on(req_8).unwrap();
let cookie_3 = resp_8
.cookies()
.unwrap()
.clone()
.into_iter()
.find(|c| c.name() == "test-session")
.unwrap();
let result_8 = block_on(resp_8.json::<IndexResponse>()).unwrap();
assert_eq!(
result_8,
IndexResponse {
user_id: None,
counter: 0
}
);
assert!(cookie_3.value().to_string() != cookie_2.value().to_string());
// Step 9: POST to logout, including session cookie #2
// - set-cookie actix-session will be in response with session cookie #2
// invalidation logic
let req_9 = srv.post("/logout").cookie(cookie_2.clone()).send();
let resp_9 = srv.block_on(req_9).unwrap();
let cookie_4 = resp_9
.cookies()
.unwrap()
.clone()
.into_iter()
.find(|c| c.name() == "test-session")
.unwrap();
assert!(&time::now().tm_year != &cookie_4.expires().map(|t| t.tm_year).unwrap());
// Step 10: GET index, including session cookie #2 in request
// - set-cookie actix-session will be in response (session cookie #3)
// - response should be: {"counter": 0, "user_id": None}
let req_10 = srv.get("/").cookie(cookie_2.clone()).send();
let mut resp_10 = srv.block_on(req_10).unwrap();
let result_10 = block_on(resp_10.json::<IndexResponse>()).unwrap();
assert_eq!(
result_10,
IndexResponse {
user_id: None,
counter: 0
}
);
let cookie_5 = resp_10
.cookies()
.unwrap()
.clone()
.into_iter()
.find(|c| c.name() == "test-session")
.unwrap();
assert!(cookie_5.value().to_string() != cookie_2.value().to_string());
}
}

View File

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

View File

@ -2,7 +2,8 @@
name = "rustls-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../"
workspace = ".."
edition = "2018"
[[bin]]
name = "rustls-server"
@ -10,6 +11,6 @@ path = "src/main.rs"
[dependencies]
env_logger = "0.5"
rustls = "0.14"
actix = "0.7"
actix-web = { version = "0.7", features=["rust-tls"] }
rustls = "0.15"
actix-web = { version = "1.0.0", features=["rust-tls"] }
actix-files = "0.1.0"

View File

@ -1,32 +1,24 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate rustls;
use std::fs::File;
use std::io::BufReader;
use actix_web::{http, middleware, server, App, Error, HttpRequest, HttpResponse};
use actix_files::Files;
use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{NoClientAuth, ServerConfig};
use server::ServerFlags;
use actix_web::fs::StaticFiles;
/// simple handle
fn index(req: &HttpRequest) -> Result<HttpResponse, Error> {
fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
println!("{:?}", req);
Ok(HttpResponse::Ok()
.content_type("text/plain")
.body("Welcome!"))
}
fn main() {
if ::std::env::var("RUST_LOG").is_err() {
::std::env::set_var("RUST_LOG", "actix_web=info");
fn main() -> std::io::Result<()> {
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "actix_web=info");
}
env_logger::init();
let sys = actix::System::new("ws-example");
// load ssl keys
let mut config = ServerConfig::new(NoClientAuth::new());
@ -36,29 +28,20 @@ fn main() {
let mut keys = rsa_private_keys(key_file).unwrap();
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
// actix acceptor
let acceptor = server::RustlsAcceptor::with_flags(
config,
ServerFlags::HTTP1 | ServerFlags::HTTP2,
);
server::new(|| {
HttpServer::new(|| {
App::new()
// enable logger
.middleware(middleware::Logger::default())
.wrap(middleware::Logger::default())
// register simple handler, handle all methods
.resource("/index.html", |r| r.f(index))
.service(web::resource("/index.html").to(index))
// with path parameters
.resource("/", |r| r.method(http::Method::GET).f(|_| {
.service(web::resource("/").route(web::get().to(|| {
HttpResponse::Found()
.header("LOCATION", "/index.html")
.finish()
}))
.handler("/static", StaticFiles::new("static").unwrap())
}).bind_with("127.0.0.1:8443", move || acceptor.clone())
.unwrap()
.start();
println!("Started http server: 127.0.0.1:8443");
let _ = sys.run();
}
})))
.service(Files::new("/static", "static"))
})
.bind_rustls("127.0.0.1:8443", config)?
.run()
}

View File

@ -2,21 +2,24 @@
name = "simple-auth-server"
version = "0.1.0"
authors = ["mygnu <tech@hgill.io>"]
edition = "2018"
workspace = ".."
[dependencies]
actix = "0.7.7"
actix-web = "0.7.14"
bcrypt = "0.2.1"
actix-identity = "0.1.0"
actix-web = "1.0.3"
argonautica = "0.2.0"
chrono = { version = "0.4.6", features = ["serde"] }
diesel = { version = "1.3.3", features = ["postgres", "uuid", "r2d2", "chrono"] }
dotenv = "0.13.0"
env_logger = "0.6.0"
failure = "0.1.3"
jsonwebtoken = "5.0"
derive_more = "0.15.0"
diesel = { version = "1.4.2", features = ["postgres","uuidv07", "r2d2", "chrono"] }
dotenv = "0.14.1"
env_logger = "0.6"
futures = "0.1"
r2d2 = "0.8.3"
serde_derive="1.0.80"
serde_json="1.0"
serde="1.0"
r2d2 = "0.8"
lazy_static = "1.3.0"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
sparkpost = "0.5.2"
uuid = { version = "0.6.5", features = ["serde", "v4"] }
uuid = { version = "0.7", features = ["serde", "v4"] }

View File

@ -1,21 +1,22 @@
## Auth Web Microservice with rust using Actix-Web 1.0
##### Flow of the event would look like this:
- Registers with email address ➡ Receive an 📨 with a link to verify
- Follow the link ➡ register with same email and a password
- Login with email and password ➡ Get verified and receive jwt token
- Login with email and password ➡ Get verified and receive auth cookie
##### Crates we are going to use
##### Crates Used
- [actix](https://crates.io/crates/actix) // Actix is a Rust actors framework.
- [actix-web](https://crates.io/crates/actix-web) // Actix web is a simple, pragmatic and extremely fast web framework for Rust.
- [bcrypt](https://crates.io/crates/bcrypt) // Easily hash and verify passwords using bcrypt.
- [argonautica](https://docs.rs/argonautica) // crate for hashing passwords using the cryptographically-secure Argon2 hashing algorithm.
- [chrono](https://crates.io/crates/chrono) // Date and time library for Rust.
- [diesel](https://crates.io/crates/diesel) // A safe, extensible ORM and Query Builder for PostgreSQL, SQLite, and MySQL.
- [dotenv](https://crates.io/crates/dotenv) // A dotenv implementation for Rust.
- [derive_more](https://crates.io/crates/derive_more) // Convenience macros to derive tarits easily
- [env_logger](https://crates.io/crates/env_logger) // A logging implementation for log which is configured via an environment variable.
- [failure](https://crates.io/crates/failure) // Experimental error handling abstraction.
- [jsonwebtoken](https://crates.io/crates/jsonwebtoken) // Create and parse JWT in a strongly typed way.
- [futures](https://crates.io/crates/futures) // An implementation of futures and streams featuring zero allocations, composability, and iterator-like interfaces.
- [lazy_static](https://docs.rs/lazy_static) // A macro for declaring lazily evaluated statics.
- [r2d2](https://crates.io/crates/r2d2) // A generic connection pool.
- [serde](https://crates.io/crates/serde) // A generic serialization/deserialization framework.
- [serde_json](https://crates.io/crates/serde_json) // A JSON serialization file format.
@ -24,9 +25,6 @@
- [uuid](https://crates.io/crates/uuid) // A library to generate and parse UUIDs.
Read the full tutorial series on [hgill.io](https://hgill.io)
- [Auth Web Microservice with rust using Actix-Web - Complete Tutorial Part 1](https://hgill.io/posts/auth-microservice-rust-actix-web-diesel-complete-tutorial-part-1/)
- [Auth Web Microservice with rust using Actix-Web - Complete Tutorial Part 2](https://hgill.io/posts/auth-microservice-rust-actix-web-diesel-complete-tutorial-part-2/)
- [Auth Web Microservice with rust using Actix-Web - Complete Tutorial Part 3](https://hgill.io/posts/auth-microservice-rust-actix-web-diesel-complete-tutorial-part-3/)
Read the full tutorial series on [gill.net.in](https://gill.net.in)
- [Auth Web Microservice with rust using Actix-Web 1.0 - Complete Tutorial](https://gill.net.in/posts/auth-microservice-rust-actix-web1.0-diesel-complete-tutorial/)

View File

@ -1,6 +1,6 @@
-- Your SQL goes here
CREATE TABLE users (
email VARCHAR(100) NOT NULL UNIQUE PRIMARY KEY,
password VARCHAR(64) NOT NULL, --bcrypt hash
hash VARCHAR(122) NOT NULL, --argon hash
created_at TIMESTAMP NOT NULL
);

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,15 @@
use actix::{Handler, Message};
use actix_identity::Identity;
use actix_web::{
dev::Payload, error::BlockingError, web, Error, FromRequest, HttpRequest,
HttpResponse,
};
use diesel::prelude::*;
use errors::ServiceError;
use models::{DbExecutor, User, SlimUser};
use bcrypt::verify;
use actix_web::{FromRequest, HttpRequest, middleware::identity::RequestIdentity};
use utils::decode_token;
use diesel::PgConnection;
use futures::Future;
use crate::errors::ServiceError;
use crate::models::{Pool, SlimUser, User};
use crate::utils::verify;
#[derive(Debug, Deserialize)]
pub struct AuthData {
@ -12,44 +17,66 @@ pub struct AuthData {
pub password: String,
}
impl Message for AuthData {
type Result = Result<SlimUser, ServiceError>;
}
impl Handler<AuthData> for DbExecutor {
type Result = Result<SlimUser, ServiceError>;
fn handle(&mut self, msg: AuthData, _: &mut Self::Context) -> Self::Result {
use schema::users::dsl::{users, email};
let conn: &PgConnection = &self.0.get().unwrap();
let mut items = users
.filter(email.eq(&msg.email))
.load::<User>(conn)?;
if let Some(user) = items.pop() {
match verify(&msg.password, &user.password) {
Ok(matching) => if matching {
return Ok(user.into());
},
Err(_) => (),
}
}
Err(ServiceError::BadRequest("Username and Password don't match".into()))
}
}
// we need the same data
// simple aliasing makes the intentions clear and its more readable
pub type LoggedUser = SlimUser;
impl<S> FromRequest<S> for LoggedUser {
impl FromRequest for LoggedUser {
type Config = ();
type Result = Result<LoggedUser, ServiceError>;
fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
if let Some(identity) = req.identity() {
let user: SlimUser = decode_token(&identity)?;
return Ok(user as LoggedUser);
type Error = Error;
type Future = Result<LoggedUser, Error>;
fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
if let Some(identity) = Identity::from_request(req, pl)?.identity() {
let user: LoggedUser = serde_json::from_str(&identity)?;
return Ok(user);
}
Err(ServiceError::Unauthorized)
Err(ServiceError::Unauthorized.into())
}
}
pub fn logout(id: Identity) -> HttpResponse {
id.forget();
HttpResponse::Ok().finish()
}
pub fn login(
auth_data: web::Json<AuthData>,
id: Identity,
pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = ServiceError> {
web::block(move || query(auth_data.into_inner(), pool)).then(
move |res: Result<SlimUser, BlockingError<ServiceError>>| match res {
Ok(user) => {
let user_string = serde_json::to_string(&user).unwrap();
id.remember(user_string);
Ok(HttpResponse::Ok().finish())
}
Err(err) => match err {
BlockingError::Error(service_error) => Err(service_error),
BlockingError::Canceled => Err(ServiceError::InternalServerError),
},
},
)
}
pub fn get_me(logged_user: LoggedUser) -> HttpResponse {
HttpResponse::Ok().json(logged_user)
}
/// Diesel query
fn query(auth_data: AuthData, pool: web::Data<Pool>) -> Result<SlimUser, ServiceError> {
use crate::schema::users::dsl::{email, users};
let conn: &PgConnection = &pool.get().unwrap();
let mut items = users
.filter(email.eq(&auth_data.email))
.load::<User>(conn)?;
if let Some(user) = items.pop() {
if let Ok(matching) = verify(&user.hash, &auth_data.password) {
if matching {
return Ok(user.into());
}
}
}
Err(ServiceError::Unauthorized)
}

View File

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

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