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:
commit
bbcd4f4c93
5
.gitignore
vendored
5
.gitignore
vendored
@ -13,3 +13,8 @@ Cargo.lock
|
||||
|
||||
# intellij files
|
||||
.idea/**
|
||||
|
||||
.history/
|
||||
|
||||
# For multipart example
|
||||
upload.png
|
||||
|
48
.travis.yml
48
.travis.yml
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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()),
|
||||
))
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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)]
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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```
|
||||
|
@ -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
21
async_ex2/Cargo.toml
Normal 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
3
async_ex2/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
This example illustrates how to use nested resource registration through application-level configuration.
|
||||
The endpoints do nothing.
|
||||
|
36
async_ex2/src/appconfig.rs
Normal file
36
async_ex2/src/appconfig.rs
Normal 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
16
async_ex2/src/bin/main.rs
Normal 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
15
async_ex2/src/common.rs
Normal 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>,
|
||||
}
|
2
async_ex2/src/handlers/mod.rs
Normal file
2
async_ex2/src/handlers/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod parts;
|
||||
pub mod products;
|
28
async_ex2/src/handlers/parts.rs
Normal file
28
async_ex2/src/handlers/parts.rs
Normal 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())
|
||||
}
|
56
async_ex2/src/handlers/products.rs
Normal file
56
async_ex2/src/handlers/products.rs
Normal 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
3
async_ex2/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod appconfig;
|
||||
pub mod common;
|
||||
pub mod handlers;
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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>
|
@ -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>
|
@ -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"
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
7
cookie-session/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
## Cookie session example
|
||||
|
||||
```sh
|
||||
cd cookie-session
|
||||
cargo run
|
||||
# Starting http server: 127.0.0.1:8080
|
||||
```
|
@ -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()
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
DATABASE_URL=file:test.db
|
||||
DATABASE_URL=test.db
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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"
|
@ -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>
|
||||
```
|
@ -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();
|
||||
}
|
@ -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"
|
||||
|
@ -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>
|
||||
```
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -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"
|
||||
|
120
json/src/main.rs
120
json/src/main.rs
@ -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
13
json_error/Cargo.toml
Normal 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
53
json_error/src/main.rs
Normal 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
17
jsonrpc/Cargo.toml
Normal 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
34
jsonrpc/README.md
Normal 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
137
jsonrpc/src/convention.rs
Normal 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
164
jsonrpc/src/main.rs
Normal 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();
|
||||
}
|
38
jsonrpc/tests/test_client.py
Normal file
38
jsonrpc/tests/test_client.py
Normal 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())
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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.
|
||||
|
@ -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()
|
||||
}
|
||||
|
70
middleware/src/read_body.rs
Normal file
70
middleware/src/read_body.rs
Normal 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))
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
@ -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(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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``
|
||||
|
@ -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
11
multipart/client.sh
Executable 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
BIN
multipart/example.png
Normal file
Binary file not shown.
@ -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()
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -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"))?)
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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
0
redis-session/README.md
Normal 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());
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,2 @@
|
||||
max_width = 89
|
||||
reorder_imports = true
|
||||
#wrap_comments = true
|
||||
fn_args_density = "Compressed"
|
||||
#use_small_heuristics = false
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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"] }
|
||||
|
||||
|
@ -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/)
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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"),
|
||||
)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user