1
0
mirror of https://github.com/actix/examples synced 2024-11-23 22:41:07 +01:00

upgrade to 2.0 alpha.3

This commit is contained in:
Nikolay Kim 2019-12-07 23:59:24 +06:00
parent e7f7175b7b
commit 3127352797
100 changed files with 1075 additions and 1107 deletions

View File

@ -1,8 +1,6 @@
[workspace] [workspace]
members = [ members = [
"./", # "actix_redis",
"actix_redis",
"actix_todo",
"async_db", "async_db",
"async_ex1", "async_ex1",
"async_ex2", "async_ex2",
@ -21,24 +19,28 @@ members = [
"juniper", "juniper",
"middleware", "middleware",
"multipart", "multipart",
"protobuf", "openssl",
# "protobuf",
"r2d2", "r2d2",
"redis-session", # "redis-session",
"rustls", "rustls",
"server-sent-events", "server-sent-events",
"simple-auth-server", "simple-auth-server",
"state", "state",
"static_index", "static_index",
"template_askama", "template_askama",
"template_tera",
"template_yarte",
"template_handlebars", "template_handlebars",
"tls", "template_tera",
"udp-echo", # "template_yarte",
"todo",
# "udp-echo",
"unix-socket", "unix-socket",
"web-cors/backend", # "web-cors/backend",
"websocket", # "websocket",
"websocket-chat", # "websocket-chat",
"websocket-chat-broker", # "websocket-chat-broker",
"websocket-tcp-chat", # "websocket-tcp-chat",
] ]
[patch.crates-io]
actix-http = { git = "https://github.com/actix/actix-web.git" }

View File

@ -1,168 +0,0 @@
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 crate::db;
use crate::session::{self, FlashMessage};
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()
.then(move |res| match res {
Ok(tasks) => {
let mut context = Context::new();
context.insert("tasks", &tasks);
//Session is set during operations on other endpoints
//that can redirect to index
if let Some(flash) = session::get_flash(&session)? {
context.insert("msg", &(flash.kind, flash.message));
session::clear_flash(&session);
}
let rendered =
tmpl.render("index.html.tera", &context).map_err(|e| {
error::ErrorInternalServerError(e.description().to_owned())
})?;
Ok(HttpResponse::Ok().body(rendered))
}
Err(e) => Err(e),
})
}
#[derive(Deserialize)]
pub struct CreateForm {
description: String,
}
pub fn create(
params: web::Form<CreateForm>,
pool: web::Data<db::PgPool>,
session: Session,
) -> impl Future<Item = HttpResponse, Error = Error> {
if params.description.is_empty() {
Either::A(
session::set_flash(
&session,
FlashMessage::error("Description cannot be empty"),
)
.map(|_| redirect_to("/"))
.into_future(),
)
} else {
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),
}),
)
}
}
#[derive(Deserialize)]
pub struct UpdateParams {
id: i32,
}
#[derive(Deserialize)]
pub struct UpdateForm {
_method: String,
}
pub fn update(
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" => 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);
Either::B(err(error::ErrorBadRequest(msg)))
}
}
}
fn toggle(
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()
.then(move |res| match res {
Ok(_) => Ok(redirect_to("/")),
Err(e) => Err(e),
})
}
fn delete(
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()
.then(move |res| match res {
Ok(_) => {
session::set_flash(
&session,
FlashMessage::success("Task was deleted."),
)?;
Ok(redirect_to("/"))
}
Err(e) => Err(e),
})
}
fn redirect_to(location: &str) -> HttpResponse {
HttpResponse::Found()
.header(http::header::LOCATION, location)
.finish()
}
pub fn bad_request<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
let new_resp = NamedFile::open("static/errors/400.html")?
.set_status_code(res.status())
.respond_to(res.request())?;
Ok(ErrorHandlerResponse::Response(
res.into_response(new_resp.into_body()),
))
}
pub fn not_found<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
let new_resp = NamedFile::open("static/errors/404.html")?
.set_status_code(res.status())
.respond_to(res.request())?;
Ok(ErrorHandlerResponse::Response(
res.into_response(new_resp.into_body()),
))
}
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(res.status())
.respond_to(res.request())?;
Ok(ErrorHandlerResponse::Response(
res.into_response(new_resp.into_body()),
))
}

View File

@ -1,18 +1,18 @@
[package] [package]
name = "async_db" name = "async_db"
version = "0.1.0" version = "2.0.0"
authors = ["Darin Gordon <dkcdkg@gmail.com>"] authors = ["Darin Gordon <dkcdkg@gmail.com>"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix-rt = "0.2.2" actix-rt = "1.0.0-alpha.3"
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
dotenv = "0.10" dotenv = "0.10"
env_logger = "0.5" env_logger = "0.6"
failure = "0.1.1" failure = "0.1.1"
futures = "0.1" futures = "0.3.1"
num_cpus = "1.10.0" num_cpus = "1.10.0"
r2d2 = "0.8.2" r2d2 = "0.8.2"
r2d2_sqlite = "0.8.0" r2d2_sqlite = "0.8.0"

View File

@ -1,6 +1,6 @@
use actix_web::{web, Error as AWError}; use actix_web::{web, Error as AWError};
use failure::Error; use failure::Error;
use futures::Future; use futures::{Future, TryFutureExt};
use r2d2; use r2d2;
use r2d2_sqlite; use r2d2_sqlite;
use rusqlite::NO_PARAMS; use rusqlite::NO_PARAMS;
@ -26,7 +26,7 @@ pub enum Queries {
pub fn execute( pub fn execute(
pool: &Pool, pool: &Pool,
query: Queries, query: Queries,
) -> impl Future<Item = Vec<WeatherAgg>, Error = AWError> { ) -> impl Future<Output = Result<Vec<WeatherAgg>, AWError>> {
let pool = pool.clone(); let pool = pool.clone();
web::block(move || match query { web::block(move || match query {
Queries::GetTopTenHottestYears => get_hottest_years(pool.get()?), Queries::GetTopTenHottestYears => get_hottest_years(pool.get()?),
@ -34,7 +34,7 @@ pub fn execute(
Queries::GetTopTenHottestMonths => get_hottest_months(pool.get()?), Queries::GetTopTenHottestMonths => get_hottest_months(pool.get()?),
Queries::GetTopTenColdestMonths => get_coldest_months(pool.get()?), Queries::GetTopTenColdestMonths => get_coldest_months(pool.get()?),
}) })
.from_err() .map_err(AWError::from)
} }
fn get_hottest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> { fn get_hottest_years(conn: Connection) -> Result<Vec<WeatherAgg>, Error> {

View File

@ -14,64 +14,42 @@ This project illustrates two examples:
use std::io; use std::io;
use actix_web::{middleware, web, App, Error as AWError, HttpResponse, HttpServer}; use actix_web::{middleware, web, App, Error as AWError, HttpResponse, HttpServer};
use futures::future::{join_all, ok as fut_ok, Future}; use futures::future::join_all;
use r2d2_sqlite; use r2d2_sqlite::{self, SqliteConnectionManager};
use r2d2_sqlite::SqliteConnectionManager;
mod db; mod db;
use db::{Pool, Queries, WeatherAgg}; use db::{Pool, Queries};
/// Version 1: Calls 4 queries in sequential order, as an asynchronous handler /// Version 1: Calls 4 queries in sequential order, as an asynchronous handler
fn asyncio_weather( async fn asyncio_weather(db: web::Data<Pool>) -> Result<HttpResponse, AWError> {
db: web::Data<Pool>, let result = vec![
) -> impl Future<Item = HttpResponse, Error = AWError> { db::execute(&db, Queries::GetTopTenHottestYears).await?,
let mut result: Vec<Vec<WeatherAgg>> = vec![]; db::execute(&db, Queries::GetTopTenColdestYears).await?,
db::execute(&db, Queries::GetTopTenHottestMonths).await?,
db::execute(&db, Queries::GetTopTenColdestMonths).await?,
];
db::execute(&db, Queries::GetTopTenHottestYears) Ok(HttpResponse::Ok().json(result))
.from_err()
.and_then(move |res| {
result.push(res);
db::execute(&db, Queries::GetTopTenColdestYears)
.from_err()
.and_then(move |res| {
result.push(res);
db::execute(&db, Queries::GetTopTenHottestMonths)
.from_err()
.and_then(move |res| {
result.push(res);
db::execute(&db, Queries::GetTopTenColdestMonths)
.from_err()
.and_then(move |res| {
result.push(res);
fut_ok(result)
})
})
})
})
.and_then(|res| Ok(HttpResponse::Ok().json(res)))
} }
/// Version 2: Calls 4 queries in parallel, as an asynchronous handler /// Version 2: Calls 4 queries in parallel, as an asynchronous handler
/// Returning Error types turn into None values in the response /// Returning Error types turn into None values in the response
fn parallel_weather( async fn parallel_weather(db: web::Data<Pool>) -> Result<HttpResponse, AWError> {
db: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = AWError> {
let fut_result = vec![ let fut_result = vec![
Box::new(db::execute(&db, Queries::GetTopTenHottestYears)), Box::pin(db::execute(&db, Queries::GetTopTenHottestYears)),
Box::new(db::execute(&db, Queries::GetTopTenColdestYears)), Box::pin(db::execute(&db, Queries::GetTopTenColdestYears)),
Box::new(db::execute(&db, Queries::GetTopTenHottestMonths)), Box::pin(db::execute(&db, Queries::GetTopTenHottestMonths)),
Box::new(db::execute(&db, Queries::GetTopTenColdestMonths)), Box::pin(db::execute(&db, Queries::GetTopTenColdestMonths)),
]; ];
let result: Result<Vec<_>, _> = join_all(fut_result).await.into_iter().collect();
join_all(fut_result) Ok(HttpResponse::Ok().json(result.map_err(AWError::from)?))
.map_err(AWError::from)
.map(|result| HttpResponse::Ok().json(result))
} }
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix_rt::System::new("parallel_db_example");
// Start N db executor actors (N = number of cores avail) // Start N db executor actors (N = number of cores avail)
let manager = SqliteConnectionManager::file("weather.db"); let manager = SqliteConnectionManager::file("weather.db");
@ -80,20 +58,18 @@ fn main() -> io::Result<()> {
// Start http server // Start http server
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
// store db pool as Data object
.data(pool.clone()) .data(pool.clone())
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service( .service(
web::resource("/asyncio_weather") web::resource("/asyncio_weather").route(web::get().to(asyncio_weather)),
.route(web::get().to_async(asyncio_weather)),
) )
.service( .service(
web::resource("/parallel_weather") web::resource("/parallel_weather")
.route(web::get().to_async(parallel_weather)), .route(web::get().to(parallel_weather)),
) )
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.start(); .start()
.await
println!("Started http server: 127.0.0.1:8080");
sys.run()
} }

View File

@ -1,18 +1,18 @@
[package] [package]
name = "awc_examples" name = "awc_examples"
version = "0.1.0" version = "2.0.0"
authors = ["dowwie <dkcdkg@gmail.com>"] authors = ["dowwie <dkcdkg@gmail.com>"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix-rt = "0.2.2" actix-rt = "1.0.0-alpha.3"
actix-web = { version="1.0.0", features=["ssl"] } actix-web = { version="2.0.0-alpha.3", features=["openssl"] }
futures = "0.1" futures = "0.3.1"
serde = "1.0.43" serde = "1.0.43"
serde_derive = "1.0.43" serde_derive = "1.0.43"
serde_json = "1.0.16" serde_json = "1.0.16"
validator = "0.6.3" validator = "0.6.3"
validator_derive = "0.6.5" validator_derive = "0.6.5"
env_logger = "0.5.9" env_logger = "0.6"

View File

@ -26,7 +26,7 @@ use actix_web::{
web::{self, BytesMut}, web::{self, BytesMut},
App, Error, HttpResponse, HttpServer, App, Error, HttpResponse, HttpServer,
}; };
use futures::{Future, Stream}; use futures::StreamExt;
use validator::Validate; use validator::Validate;
#[derive(Debug, Validate, Deserialize, Serialize)] #[derive(Debug, Validate, Deserialize, Serialize)]
@ -50,56 +50,51 @@ struct HttpBinResponse {
} }
/// validate data, post json to httpbin, get it back in the response body, return deserialized /// validate data, post json to httpbin, get it back in the response body, return deserialized
fn step_x( async fn step_x(data: SomeData, client: &Client) -> Result<SomeData, Error> {
data: SomeData, // validate data
client: &Client, data.validate().map_err(ErrorBadRequest)?;
) -> impl Future<Item = SomeData, Error = Error> {
let validation = futures::future::result(data.validate()).map_err(ErrorBadRequest); let mut res = client
let post_response = client
.post("https://httpbin.org/post") .post("https://httpbin.org/post")
.send_json(&data) .send_json(&data)
.map_err(Error::from) // <- convert SendRequestError to an Error .await
.and_then(|resp| { .map_err(Error::from)?; // <- convert SendRequestError to an Error
resp.from_err()
.fold(BytesMut::new(), |mut acc, chunk| {
acc.extend_from_slice(&chunk);
Ok::<_, Error>(acc)
})
.map(|body| {
let body: HttpBinResponse = serde_json::from_slice(&body).unwrap();
body.json
})
});
validation.and_then(|_| post_response) let mut body = BytesMut::new();
while let Some(chunk) = res.next().await {
body.extend_from_slice(&chunk?);
}
let body: HttpBinResponse = serde_json::from_slice(&body).unwrap();
Ok(body.json)
} }
fn create_something( async fn create_something(
some_data: web::Json<SomeData>, some_data: web::Json<SomeData>,
client: web::Data<Client>, client: web::Data<Client>,
) -> impl Future<Item = HttpResponse, Error = Error> { ) -> Result<HttpResponse, Error> {
step_x(some_data.into_inner(), &client).and_then(move |some_data_2| { let some_data_2 = step_x(some_data.into_inner(), &client).await?;
step_x(some_data_2, &client).and_then(move |some_data_3| { let some_data_3 = step_x(some_data_2, &client).await?;
step_x(some_data_3, &client).and_then(|d| { let d = step_x(some_data_3, &client).await?;
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(serde_json::to_string(&d).unwrap())) .body(serde_json::to_string(&d).unwrap()))
})
})
})
} }
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let endpoint = "127.0.0.1:8080"; let endpoint = "127.0.0.1:8080";
println!("Starting server at: {:?}", endpoint); println!("Starting server at: {:?}", endpoint);
HttpServer::new(|| { HttpServer::new(|| {
App::new().data(Client::default()).service( App::new()
web::resource("/something").route(web::post().to_async(create_something)), .data(Client::default())
) .service(web::resource("/something").route(web::post().to(create_something)))
}) })
.bind(endpoint)? .bind(endpoint)?
.run() .start()
.await
} }

View File

@ -6,13 +6,13 @@ edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix-rt = "0.2.2" actix-rt = "1.0.0-alpha.3"
actix-web = { version="1.0.0", features=["ssl"] } actix-web = { version="2.0.0-alpha.3", features=["openssl"] }
actix-multipart = "0.1.1" actix-multipart = "0.2.0-alpha.2"
actix-service = "0.4.1" actix-service = "1.0.0-alpha.3"
bytes = "0.4.12" bytes = "0.5.2"
env_logger = "0.6.1" env_logger = "0.6.1"
futures = "0.1" futures = "0.3.1"
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
serde_derive = "1.0.90" serde_derive = "1.0.90"
serde_json = "1.0.39" serde_json = "1.0.39"

View File

@ -8,27 +8,27 @@ pub fn config_app(cfg: &mut web::ServiceConfig) {
web::scope("/products") web::scope("/products")
.service( .service(
web::resource("") web::resource("")
.route(web::get().to_async(products::get_products)) .route(web::get().to(products::get_products))
.route(web::post().to_async(products::add_product)), .route(web::post().to(products::add_product)),
) )
.service( .service(
web::scope("/{product_id}") web::scope("/{product_id}")
.service( .service(
web::resource("") web::resource("")
.route(web::get().to_async(products::get_product_detail)) .route(web::get().to(products::get_product_detail))
.route(web::delete().to_async(products::remove_product)), .route(web::delete().to(products::remove_product)),
) )
.service( .service(
web::scope("/parts") web::scope("/parts")
.service( .service(
web::resource("") web::resource("")
.route(web::get().to_async(parts::get_parts)) .route(web::get().to(parts::get_parts))
.route(web::post().to_async(parts::add_part)), .route(web::post().to(parts::add_part)),
) )
.service( .service(
web::resource("/{part_id}") web::resource("/{part_id}")
.route(web::get().to_async(parts::get_part_detail)) .route(web::get().to(parts::get_part_detail))
.route(web::delete().to_async(parts::remove_part)), .route(web::delete().to(parts::remove_part)),
), ),
), ),
), ),

View File

@ -2,7 +2,8 @@ use actix_web::{middleware, App, HttpServer};
use async_ex2::appconfig::config_app; use async_ex2::appconfig::config_app;
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init(); env_logger::init();
@ -12,5 +13,6 @@ fn main() -> std::io::Result<()> {
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

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

View File

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

View File

@ -1,16 +1,17 @@
[package] [package]
name = "basics" name = "basics"
version = "1.0.0" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-rt = "0.2.2" actix-rt = "1.0.0-alpha.3"
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-files = "0.1.1" actix-files = "0.2.0-alpha.3"
actix-session = "0.2.0" actix-session = "0.3.0-alpha.3"
actix-utils = "1.0.0-alpha.3"
futures = "0.1.25" futures = "0.3.1"
env_logger = "0.5" env_logger = "0.5"
bytes = "0.4" bytes = "0.5"

View File

@ -5,24 +5,23 @@ use std::{env, io};
use actix_files as fs; use actix_files as fs;
use actix_session::{CookieSession, Session}; use actix_session::{CookieSession, Session};
use actix_utils::mpsc;
use actix_web::http::{header, Method, StatusCode}; use actix_web::http::{header, Method, StatusCode};
use actix_web::{ use actix_web::{
error, guard, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, error, guard, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
Result, Result,
}; };
use bytes::Bytes; use bytes::Bytes;
use futures::unsync::mpsc;
use futures::{future::ok, Future, Stream};
/// favicon handler /// favicon handler
#[get("/favicon")] #[get("/favicon")]
fn favicon() -> Result<fs::NamedFile> { async fn favicon() -> Result<fs::NamedFile> {
Ok(fs::NamedFile::open("static/favicon.ico")?) Ok(fs::NamedFile::open("static/favicon.ico")?)
} }
/// simple index handler /// simple index handler
#[get("/welcome")] #[get("/welcome")]
fn welcome(session: Session, req: HttpRequest) -> Result<HttpResponse> { async fn welcome(session: Session, req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req); println!("{:?}", req);
// session // session
@ -42,32 +41,22 @@ fn welcome(session: Session, req: HttpRequest) -> Result<HttpResponse> {
} }
/// 404 handler /// 404 handler
fn p404() -> Result<fs::NamedFile> { async fn p404() -> Result<fs::NamedFile> {
Ok(fs::NamedFile::open("static/404.html")?.set_status_code(StatusCode::NOT_FOUND)) Ok(fs::NamedFile::open("static/404.html")?.set_status_code(StatusCode::NOT_FOUND))
} }
/// async handler /// response body
fn index_async(req: HttpRequest) -> impl Future<Item = HttpResponse, Error = Error> { async fn response_body(path: web::Path<String>) -> HttpResponse {
println!("{:?}", req);
ok(HttpResponse::Ok()
.content_type("text/html")
.body(format!("Hello {}!", req.match_info().get("name").unwrap())))
}
/// async body
fn index_async_body(path: web::Path<String>) -> HttpResponse {
let text = format!("Hello {}!", *path); let text = format!("Hello {}!", *path);
let (tx, rx_body) = mpsc::unbounded(); let (tx, rx_body) = mpsc::channel();
let _ = tx.unbounded_send(Bytes::from(text.as_bytes())); let _ = tx.send(Ok::<_, Error>(Bytes::from(text)));
HttpResponse::Ok() HttpResponse::Ok().streaming(rx_body)
.streaming(rx_body.map_err(|_| error::ErrorBadRequest("bad request")))
} }
/// handler with path parameters like `/user/{name}/` /// handler with path parameters like `/user/{name}/`
fn with_param(req: HttpRequest, path: web::Path<(String,)>) -> HttpResponse { async fn with_param(req: HttpRequest, path: web::Path<(String,)>) -> HttpResponse {
println!("{:?}", req); println!("{:?}", req);
HttpResponse::Ok() HttpResponse::Ok()
@ -75,10 +64,10 @@ fn with_param(req: HttpRequest, path: web::Path<(String,)>) -> HttpResponse {
.body(format!("Hello {}!", path.0)) .body(format!("Hello {}!", path.0))
} }
fn main() -> io::Result<()> { #[actix_rt::main]
env::set_var("RUST_LOG", "actix_web=debug"); async fn main() -> io::Result<()> {
env::set_var("RUST_LOG", "actix_web=debug;actix_server=info");
env_logger::init(); env_logger::init();
let sys = actix_rt::System::new("basic-example");
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
@ -92,14 +81,9 @@ fn main() -> io::Result<()> {
.service(welcome) .service(welcome)
// with path parameters // with path parameters
.service(web::resource("/user/{name}").route(web::get().to(with_param))) .service(web::resource("/user/{name}").route(web::get().to(with_param)))
// async handler // async response body
.service( .service(
web::resource("/async/{name}").route(web::get().to_async(index_async)), web::resource("/async-body/{name}").route(web::get().to(response_body)),
)
// async handler
.service(
web::resource("/async-body/{name}")
.route(web::get().to(index_async_body)),
) )
.service( .service(
web::resource("/test").to(|req: HttpRequest| match *req.method() { web::resource("/test").to(|req: HttpRequest| match *req.method() {
@ -109,10 +93,12 @@ fn main() -> io::Result<()> {
}), }),
) )
.service(web::resource("/error").to(|| { .service(web::resource("/error").to(|| {
async {
error::InternalError::new( error::InternalError::new(
io::Error::new(io::ErrorKind::Other, "test"), io::Error::new(io::ErrorKind::Other, "test"),
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
) )
}
})) }))
// static files // static files
.service(fs::Files::new("/static", "static").show_files_listing()) .service(fs::Files::new("/static", "static").show_files_listing())
@ -137,8 +123,6 @@ fn main() -> io::Result<()> {
) )
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.start(); .start()
.await
println!("Starting http server: 127.0.0.1:8080");
sys.run()
} }

View File

@ -1,11 +1,12 @@
[package] [package]
name = "cookie-auth" name = "cookie-auth"
version = "0.1.0" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-identity = "0.1.0" actix-identity = "0.2.0-alpha.1"
actix-rt = "1.0.0-alpha.3"
env_logger = "0.6" env_logger = "0.6"

View File

@ -2,24 +2,25 @@ use actix_identity::Identity;
use actix_identity::{CookieIdentityPolicy, IdentityService}; use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{middleware, web, App, HttpResponse, HttpServer}; use actix_web::{middleware, web, App, HttpResponse, HttpServer};
fn index(id: Identity) -> String { async fn index(id: Identity) -> String {
format!( format!(
"Hello {}", "Hello {}",
id.identity().unwrap_or_else(|| "Anonymous".to_owned()) id.identity().unwrap_or_else(|| "Anonymous".to_owned())
) )
} }
fn login(id: Identity) -> HttpResponse { async fn login(id: Identity) -> HttpResponse {
id.remember("user1".to_owned()); id.remember("user1".to_owned());
HttpResponse::Found().header("location", "/").finish() HttpResponse::Found().header("location", "/").finish()
} }
fn logout(id: Identity) -> HttpResponse { async fn logout(id: Identity) -> HttpResponse {
id.forget(); id.forget();
HttpResponse::Found().header("location", "/").finish() HttpResponse::Found().header("location", "/").finish()
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
@ -37,5 +38,6 @@ fn main() -> std::io::Result<()> {
.service(web::resource("/").route(web::get().to(index))) .service(web::resource("/").route(web::get().to(index)))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

@ -1,15 +1,15 @@
[package] [package]
name = "cookie-session" name = "cookie-session"
version = "0.1.0" version = "1.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-session = "0.2.0" actix-session = "0.3.0-alpha.3"
actix-rt = "0.2.5" actix-rt = "1.0.0-alpha.3"
futures = "0.1" futures = "0.3.1"
time = "0.1" time = "0.1"
env_logger = "0.6" env_logger = "0.6"

View File

@ -9,7 +9,7 @@ use actix_session::{CookieSession, Session};
use actix_web::{middleware::Logger, web, App, HttpRequest, HttpServer, Result}; use actix_web::{middleware::Logger, web, App, HttpRequest, HttpServer, Result};
/// simple index handler with session /// simple index handler with session
fn index(session: Session, req: HttpRequest) -> Result<&'static str> { async fn index(session: Session, req: HttpRequest) -> Result<&'static str> {
println!("{:?}", req); println!("{:?}", req);
// RequestSession trait is used for session access // RequestSession trait is used for session access
@ -25,10 +25,11 @@ fn index(session: Session, req: HttpRequest) -> Result<&'static str> {
Ok("welcome!") Ok("welcome!")
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
let sys = actix_rt::System::new("cookie-session"); println!("Starting http server: 127.0.0.1:8080");
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
@ -39,8 +40,6 @@ fn main() -> std::io::Result<()> {
.service(web::resource("/").to(index)) .service(web::resource("/").to(index))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.start(); .start()
.await
println!("Starting http server: 127.0.0.1:8080");
sys.run()
} }

View File

@ -1,16 +1,17 @@
[package] [package]
name = "diesel-example" name = "diesel-example"
version = "0.1.0" version = "1.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0" actix-rt = "1.0.0-alpha.3"
actix-web = "2.0.0-alpha.3"
bytes = "0.4" bytes = "0.4"
env_logger = "0.6" env_logger = "0.6"
futures = "0.1" futures = "0.3.1"
uuid = { version = "0.5", features = ["serde", "v4"] } uuid = { version = "0.5", features = ["serde", "v4"] }
serde = "1.0" serde = "1.0"
serde_json = "1.0" serde_json = "1.0"

View File

@ -14,8 +14,7 @@ use bytes::BytesMut;
use diesel::prelude::*; use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager}; use diesel::r2d2::{self, ConnectionManager};
use dotenv; use dotenv;
use futures::future::{err, Either}; use futures::StreamExt;
use futures::{Future, Stream};
mod models; mod models;
mod schema; mod schema;
@ -43,15 +42,15 @@ fn query(
} }
/// Async request handler /// Async request handler
fn add( async fn add(
name: web::Path<String>, name: web::Path<String>,
pool: web::Data<Pool>, pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = Error> { ) -> Result<HttpResponse, Error> {
// run diesel blocking code // run diesel blocking code
web::block(move || query(name.into_inner(), pool)).then(|res| match res { Ok(web::block(move || query(name.into_inner(), pool))
Ok(user) => Ok(HttpResponse::Ok().json(user)), .await
Err(_) => Ok(HttpResponse::InternalServerError().into()), .map(|user| HttpResponse::Ok().json(user))
}) .map_err(|_| HttpResponse::InternalServerError())?)
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
@ -62,62 +61,53 @@ struct MyUser {
const MAX_SIZE: usize = 262_144; // max payload size is 256k const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// This handler manually load request payload and parse json object /// This handler manually load request payload and parse json object
fn index_add( async fn index_add(
pl: web::Payload, mut pl: web::Payload,
pool: web::Data<Pool>, pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = Error> { ) -> Result<HttpResponse, Error> {
pl let mut body = BytesMut::new();
// `Future::from_err` acts like `?` in that it coerces the error type from // pl.next() gets next item from asynchronous stream of byte chunks
// the future into the final error type // it resolves to `Option<Result<Bytes, Error>>` object
.from_err() // `None` - indicaets end of stream
// `fold` will asynchronously read each chunk of the request body and while let Some(chunk) = pl.next().await {
// call supplied closure, then it resolves to result of closure let chunk = chunk?;
.fold(BytesMut::new(), move |mut body, chunk| {
// limit max size of in-memory payload // limit max size of in-memory payload
if (body.len() + chunk.len()) > MAX_SIZE { if (body.len() + chunk.len()) > MAX_SIZE {
Err(error::ErrorBadRequest("overflow")) return Err(error::ErrorBadRequest("overflow"));
} else {
body.extend_from_slice(&chunk);
Ok(body)
} }
}) body.extend_from_slice(&chunk);
// `Future::and_then` can be used to merge an asynchronous workflow with a }
// synchronous workflow
//
// Douman NOTE:
// The return value in this closure helps, to clarify result for compiler
// as otheriwse it cannot understand it
.and_then(move |body| {
// body is loaded, now we can deserialize serde-json // body is loaded, now we can deserialize serde-json
let r_obj = serde_json::from_slice::<MyUser>(&body); let r_obj = serde_json::from_slice::<MyUser>(&body);
// Send to the db for create // Send to the db for create return response to peer
match r_obj { match r_obj {
Ok(obj) => { Ok(obj) => {
Either::A(web::block(move || query(obj.name, pool)).then(|res| { let user = web::block(move || query(obj.name, pool))
match res { .await
Ok(user) => Ok(HttpResponse::Ok().json(user)), .map_err(|_| Error::from(HttpResponse::InternalServerError()))?;
Err(_) => Ok(HttpResponse::InternalServerError().into()), Ok(HttpResponse::Ok().json(user))
} }
})) Err(_) => Err(error::ErrorBadRequest("Json Decode Failed")),
} }
Err(_) => Either::B(err(error::ErrorBadRequest("Json Decode Failed"))),
}
})
} }
fn add2( async fn add2(
item: web::Json<MyUser>, item: web::Json<MyUser>,
pool: web::Data<Pool>, pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = Error> { ) -> Result<HttpResponse, Error> {
// run diesel blocking code // run diesel blocking code
web::block(move || query(item.into_inner().name, pool)).then(|res| match res { let user = web::block(move || query(item.into_inner().name, pool))
Ok(user) => Ok(HttpResponse::Ok().json(user)), .await
Err(_) => Ok(HttpResponse::InternalServerError().into()), .map_err(|_| HttpResponse::InternalServerError())?; // convert diesel error to http response
})
Ok(HttpResponse::Ok().json(user))
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
@ -153,14 +143,15 @@ fn main() -> std::io::Result<()> {
.into() .into()
}), }),
) )
.route(web::post().to_async(add2)), .route(web::post().to(add2)),
) )
// Manual parsing would allow custom error construction, use of // Manual parsing would allow custom error construction, use of
// other parsers *beside* json (for example CBOR, protobuf, xml), and allows // other parsers *beside* json (for example CBOR, protobuf, xml), and allows
// an application to standardise on a single parser implementation. // an application to standardise on a single parser implementation.
.service(web::resource("/add").route(web::post().to_async(index_add))) .service(web::resource("/add").route(web::post().to(index_add)))
.service(web::resource("/add/{name}").route(web::get().to_async(add))) .service(web::resource("/add/{name}").route(web::get().to(add)))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

@ -1,14 +1,14 @@
[package] [package]
name = "error_handling" name = "error_handling"
version = "0.1.0" version = "1.0.0"
authors = ["dowwie <dkcdkg@gmail.com>"] authors = ["dowwie <dkcdkg@gmail.com>"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"
derive_more = "0.14.0" derive_more = "0.99.2"
futures = "0.1.23" futures = "0.3.1"
rand = "0.5.4" rand = "0.5.4"
env_logger = "0.6" env_logger = "0.6"

View File

@ -14,7 +14,6 @@ http errors will be chosen, each with an equal chance of being selected:
use actix_web::{web, App, Error, HttpResponse, HttpServer, ResponseError}; use actix_web::{web, App, Error, HttpResponse, HttpServer, ResponseError};
use derive_more::Display; // naming it clearly for illustration purposes use derive_more::Display; // naming it clearly for illustration purposes
use futures::future::{err, ok, Future};
use rand::{ use rand::{
distributions::{Distribution, Standard}, distributions::{Distribution, Standard},
thread_rng, Rng, thread_rng, Rng,
@ -71,32 +70,33 @@ impl ResponseError for CustomError {
} }
/// randomly returns either () or one of the 4 CustomError variants /// randomly returns either () or one of the 4 CustomError variants
fn do_something_random() -> impl Future<Item = (), Error = CustomError> { async fn do_something_random() -> Result<(), CustomError> {
let mut rng = thread_rng(); let mut rng = thread_rng();
// 20% chance that () will be returned by this function // 20% chance that () will be returned by this function
if rng.gen_bool(2.0 / 10.0) { if rng.gen_bool(2.0 / 10.0) {
ok(()) Ok(())
} else { } else {
err(rand::random::<CustomError>()) Err(rand::random::<CustomError>())
} }
} }
fn do_something() -> impl Future<Item = HttpResponse, Error = Error> { async fn do_something() -> Result<HttpResponse, Error> {
do_something_random().from_err().and_then(|_| { do_something_random().await?;
HttpResponse::Ok().body("Nothing interesting happened. Try again.")
}) Ok(HttpResponse::Ok().body("Nothing interesting happened. Try again."))
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
HttpServer::new(move || { HttpServer::new(move || {
App::new().service( App::new()
web::resource("/something").route(web::get().to_async(do_something)), .service(web::resource("/something").route(web::get().to(do_something)))
)
}) })
.bind("127.0.0.1:8088")? .bind("127.0.0.1:8088")?
.run() .start()
.await
} }

View File

@ -1,12 +1,12 @@
[package] [package]
name = "form-example" name = "form-example"
version = "0.1.0" version = "1.0.0"
authors = ["Gorm Casper <gcasper@gmail.com>"] authors = ["Gorm Casper <gcasper@gmail.com>"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix-web = "1.0.8" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"

View File

@ -9,14 +9,16 @@ struct AppState {
foo: String, foo: String,
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.configure(app_config) .configure(app_config)
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }
fn app_config(config: &mut web::ServiceConfig) { fn app_config(config: &mut web::ServiceConfig) {
@ -28,11 +30,11 @@ fn app_config(config: &mut web::ServiceConfig) {
.service(web::resource("/").route(web::get().to(index))) .service(web::resource("/").route(web::get().to(index)))
.service(web::resource("/post1").route(web::post().to(handle_post_1))) .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("/post2").route(web::post().to(handle_post_2)))
.service(web::resource("/post3").route(web::post().to(handle_post_3))) .service(web::resource("/post3").route(web::post().to(handle_post_3))),
); );
} }
fn index() -> Result<HttpResponse> { async fn index() -> Result<HttpResponse> {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(include_str!("../static/form.html"))) .body(include_str!("../static/form.html")))
@ -44,14 +46,14 @@ pub struct MyParams {
} }
/// Simple handle POST request /// Simple handle POST request
fn handle_post_1(params: web::Form<MyParams>) -> Result<HttpResponse> { async fn handle_post_1(params: web::Form<MyParams>) -> Result<HttpResponse> {
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("text/plain") .content_type("text/plain")
.body(format!("Your name is {}", params.name))) .body(format!("Your name is {}", params.name)))
} }
/// State and POST Params /// State and POST Params
fn handle_post_2( async fn handle_post_2(
state: web::Data<AppState>, state: web::Data<AppState>,
params: web::Form<MyParams>, params: web::Form<MyParams>,
) -> HttpResponse { ) -> HttpResponse {
@ -62,7 +64,7 @@ fn handle_post_2(
} }
/// Request and POST Params /// Request and POST Params
fn handle_post_3(req: HttpRequest, params: web::Form<MyParams>) -> impl Responder { async fn handle_post_3(req: HttpRequest, params: web::Form<MyParams>) -> impl Responder {
println!("Handling POST request: {:?}", req); println!("Handling POST request: {:?}", req);
HttpResponse::Ok() HttpResponse::Ok()
@ -74,10 +76,10 @@ fn handle_post_3(req: HttpRequest, params: web::Form<MyParams>) -> impl Responde
mod tests { mod tests {
use super::*; use super::*;
use actix_web::body::{Body , ResponseBody}; use actix_web::body::{Body, ResponseBody};
use actix_web::dev::{HttpResponseBuilder, Service, ServiceResponse}; use actix_web::dev::{HttpResponseBuilder, Service, ServiceResponse};
use actix_web::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; use actix_web::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
use actix_web::test::{self, block_on, TestRequest}; use actix_web::test::{self, TestRequest};
use actix_web::web::Form; use actix_web::web::Form;
trait BodyTest { trait BodyTest {
@ -99,47 +101,42 @@ mod tests {
} }
} }
#[test] #[actix_rt::test]
fn handle_post_1_unit_test() { async fn handle_post_1_unit_test() {
let params = Form(MyParams { let params = Form(MyParams {
name: "John".to_string(), name: "John".to_string(),
}); });
let result = handle_post_1(params); let resp = handle_post_1(params).await.unwrap();
let resp = block_on(result).unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain") HeaderValue::from_static("text/plain")
); );
assert_eq!( assert_eq!(resp.body().as_str(), "Your name is John");
resp.body().as_str(),
"Your name is John"
);
} }
#[test] #[actix_rt::test]
fn handle_post_1_integration_test() { async fn handle_post_1_integration_test() {
let mut app = test::init_service(App::new().configure(app_config)); let mut app = test::init_service(App::new().configure(app_config)).await;
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri("/post1") .uri("/post1")
.set_form(&MyParams {name: "John".to_string()}) .set_form(&MyParams {
name: "John".to_string(),
})
.to_request(); .to_request();
let resp: ServiceResponse = block_on(app.call(req)).unwrap(); let resp: ServiceResponse = app.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain") HeaderValue::from_static("text/plain")
); );
assert_eq!( assert_eq!(resp.response().body().as_str(), "Your name is John");
resp.response().body().as_str(),
"Your name is John"
);
} }
#[test] #[actix_rt::test]
fn handle_post_2_unit_test() { async fn handle_post_2_unit_test() {
let state = TestRequest::default() let state = TestRequest::default()
.data(AppState { .data(AppState {
foo: "bar".to_string(), foo: "bar".to_string(),
@ -150,8 +147,7 @@ mod tests {
let params = Form(MyParams { let params = Form(MyParams {
name: "John".to_string(), name: "John".to_string(),
}); });
let result = handle_post_2(state, params); let resp = handle_post_2(state, params).await;
let resp = block_on(result).unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
@ -164,14 +160,16 @@ mod tests {
); );
} }
#[test] #[actix_rt::test]
fn handle_post_2_integration_test() { async fn handle_post_2_integration_test() {
let mut app = test::init_service(App::new().configure(app_config)); let mut app = test::init_service(App::new().configure(app_config)).await;
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri("/post2") .uri("/post2")
.set_form(&MyParams {name: "John".to_string()}) .set_form(&MyParams {
name: "John".to_string(),
})
.to_request(); .to_request();
let resp: ServiceResponse = block_on(app.call(req)).unwrap(); let resp: ServiceResponse = app.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
@ -184,16 +182,18 @@ mod tests {
); );
} }
#[test] #[actix_rt::test]
fn handle_post_3_unit_test() { async fn handle_post_3_unit_test() {
let req = TestRequest::default().to_http_request(); let req = TestRequest::default().to_http_request();
let params = Form(MyParams { let params = Form(MyParams {
name: "John".to_string(), name: "John".to_string(),
}); });
let result = handle_post_3(req.clone(), params); let result = handle_post_3(req.clone(), params).await;
let resp = match block_on(result.respond_to(&req)) { let resp = match result.respond_to(&req).await {
Ok(t) => t, Ok(t) => t,
Err(_) => HttpResponseBuilder::new(StatusCode::INTERNAL_SERVER_ERROR).finish(), Err(_) => {
HttpResponseBuilder::new(StatusCode::INTERNAL_SERVER_ERROR).finish()
}
}; };
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
@ -201,29 +201,25 @@ mod tests {
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain") HeaderValue::from_static("text/plain")
); );
assert_eq!( assert_eq!(resp.body().as_str(), "Your name is John");
resp.body().as_str(),
"Your name is John"
);
} }
#[test] #[actix_rt::test]
fn handle_post_3_integration_test() { async fn handle_post_3_integration_test() {
let mut app = test::init_service(App::new().configure(app_config)); let mut app = test::init_service(App::new().configure(app_config)).await;
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri("/post3") .uri("/post3")
.set_form(&MyParams {name: "John".to_string()}) .set_form(&MyParams {
name: "John".to_string(),
})
.to_request(); .to_request();
let resp: ServiceResponse = block_on(app.call(req)).unwrap(); let resp: ServiceResponse = app.call(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!( assert_eq!(
resp.headers().get(CONTENT_TYPE).unwrap(), resp.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("text/plain") HeaderValue::from_static("text/plain")
); );
assert_eq!( assert_eq!(resp.response().body().as_str(), "Your name is John");
resp.response().body().as_str(),
"Your name is John"
);
} }
} }

View File

@ -1,14 +1,16 @@
[package] [package]
name = "actix-graphql-demo" name = "actix-graphql-demo"
version = "0.1.0" version = "2.0.0"
authors = ["Dwi Sulfahnur <hello@dwisulfahnur.com>"] authors = ["Dwi Sulfahnur <hello@dwisulfahnur.com>"]
edition = "2018" edition = "2018"
workspace = ".."
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
actix-web = "1.0" actix-web = "2.0.0-alpha.3"
futures = "0.1" actix-rt = "1.0.0-alpha.3"
futures = "0.3.1"
juniper = "0.13" juniper = "0.13"

View File

@ -1,44 +1,42 @@
use std::sync::Arc; use std::sync::Arc;
use actix_web::{Error, HttpResponse, web}; use actix_web::{web, Error, HttpResponse};
use futures::Future;
use juniper::http::graphiql::graphiql_source; use juniper::http::graphiql::graphiql_source;
use juniper::http::GraphQLRequest; use juniper::http::GraphQLRequest;
use crate::db::Pool; use crate::db::Pool;
use crate::schemas::root::{Context, create_schema, Schema}; use crate::schemas::root::{create_schema, Context, Schema};
pub fn graphql( pub async fn graphql(
pool: web::Data<Pool>, pool: web::Data<Pool>,
schema: web::Data<Arc<Schema>>, schema: web::Data<Arc<Schema>>,
data: web::Json<GraphQLRequest>, data: web::Json<GraphQLRequest>,
) -> impl Future<Item=HttpResponse, Error=Error> { ) -> Result<HttpResponse, Error> {
let ctx = Context { let ctx = Context {
dbpool: pool.get_ref().to_owned(), dbpool: pool.get_ref().to_owned(),
}; };
web::block(move || { let res = web::block(move || {
let res = data.execute(&schema, &ctx); let res = data.execute(&schema, &ctx);
Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?) Ok::<_, serde_json::error::Error>(serde_json::to_string(&res)?)
}) })
.map_err(Error::from) .await
.and_then(|res| { .map_err(Error::from)?;
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(res)) .body(res))
})
} }
pub fn graphql_playground() -> HttpResponse { pub async fn graphql_playground() -> HttpResponse {
HttpResponse::Ok() HttpResponse::Ok()
.content_type("text/html; charset=utf-8") .content_type("text/html; charset=utf-8")
.body(graphiql_source("/graphql")) .body(graphiql_source("/graphql"))
} }
pub fn register(config: &mut web::ServiceConfig) { pub fn register(config: &mut web::ServiceConfig) {
let schema = std::sync::Arc::new(create_schema()); let schema = std::sync::Arc::new(create_schema());
config config
.data(schema) .data(schema)
.route("/graphql", web::post().to_async(graphql)) .route("/graphql", web::post().to(graphql))
.route("/graphiql", web::get().to(graphql_playground)); .route("/graphiql", web::get().to(graphql_playground));
} }

View File

@ -4,16 +4,17 @@ extern crate r2d2;
extern crate r2d2_mysql; extern crate r2d2_mysql;
extern crate serde_json; extern crate serde_json;
use actix_web::{App, HttpServer, middleware, web}; use actix_web::{middleware, web, App, HttpServer};
use crate::db::get_db_pool; use crate::db::get_db_pool;
use crate::handlers::{register}; use crate::handlers::register;
mod db;
mod handlers; mod handlers;
mod schemas; mod schemas;
mod db;
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
std::env::set_var("RUST_LOG", "actix_web=info,info"); std::env::set_var("RUST_LOG", "actix_web=info,info");
env_logger::init(); env_logger::init();
@ -24,8 +25,9 @@ fn main() -> std::io::Result<()> {
.data(pool.clone()) .data(pool.clone())
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.configure(register) .configure(register)
.default_service(web::to(|| "404")) .default_service(web::to(|| async { "404" }))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

@ -1,3 +1,3 @@
pub mod product;
pub mod root; pub mod root;
pub mod user; pub mod user;
pub mod product;

View File

@ -1,5 +1,5 @@
use juniper; use juniper;
use mysql::{Error as DBError, from_row, params, Row}; use mysql::{from_row, params, Error as DBError, Row};
use crate::schemas::root::Context; use crate::schemas::root::Context;
use crate::schemas::user::User; use crate::schemas::user::User;
@ -36,7 +36,7 @@ impl Product {
); );
if let Err(err) = user { if let Err(err) = user {
None None
}else{ } else {
let (id, name, email) = from_row(user.unwrap().unwrap()); let (id, name, email) = from_row(user.unwrap().unwrap());
Some(User { id, name, email }) Some(User { id, name, email })
} }

View File

@ -1,6 +1,6 @@
use juniper::{FieldError, FieldResult, RootNode};
use juniper; use juniper;
use mysql::{Error as DBError, from_row, params, Row}; use juniper::{FieldError, FieldResult, RootNode};
use mysql::{from_row, params, Error as DBError, Row};
use crate::db::Pool; use crate::db::Pool;
@ -8,7 +8,7 @@ use super::product::{Product, ProductInput};
use super::user::{User, UserInput}; use super::user::{User, UserInput};
pub struct Context { pub struct Context {
pub dbpool: Pool pub dbpool: Pool,
} }
impl juniper::Context for Context {} impl juniper::Context for Context {}
@ -20,13 +20,18 @@ impl QueryRoot {
#[graphql(description = "List of all users")] #[graphql(description = "List of all users")]
fn users(context: &Context) -> FieldResult<Vec<User>> { fn users(context: &Context) -> FieldResult<Vec<User>> {
let mut conn = context.dbpool.get().unwrap(); let mut conn = context.dbpool.get().unwrap();
let users = conn.prep_exec("select * from user", ()) let users = conn
.prep_exec("select * from user", ())
.map(|result| { .map(|result| {
result.map(|x| x.unwrap()).map(|mut row| { result
.map(|x| x.unwrap())
.map(|mut row| {
let (id, name, email) = from_row(row); let (id, name, email) = from_row(row);
User { id, name, email } User { id, name, email }
}).collect() })
}).unwrap(); .collect()
})
.unwrap();
Ok(users) Ok(users)
} }
@ -34,10 +39,8 @@ impl QueryRoot {
fn user(context: &Context, id: String) -> FieldResult<User> { fn user(context: &Context, id: String) -> FieldResult<User> {
let mut conn = context.dbpool.get().unwrap(); let mut conn = context.dbpool.get().unwrap();
let user: Result<Option<Row>, DBError> = conn.first_exec( let user: Result<Option<Row>, DBError> =
"SELECT * FROM user WHERE id=:id", conn.first_exec("SELECT * FROM user WHERE id=:id", params! {"id" => id});
params! {"id" => id},
);
if let Err(err) = user { if let Err(err) = user {
return Err(FieldError::new( return Err(FieldError::new(
@ -53,23 +56,31 @@ impl QueryRoot {
#[graphql(description = "List of all users")] #[graphql(description = "List of all users")]
fn products(context: &Context) -> FieldResult<Vec<Product>> { fn products(context: &Context) -> FieldResult<Vec<Product>> {
let mut conn = context.dbpool.get().unwrap(); let mut conn = context.dbpool.get().unwrap();
let products = conn.prep_exec("select * from product", ()) let products = conn
.prep_exec("select * from product", ())
.map(|result| { .map(|result| {
result.map(|x| x.unwrap()).map(|mut row| { result
.map(|x| x.unwrap())
.map(|mut row| {
let (id, user_id, name, price) = from_row(row); let (id, user_id, name, price) = from_row(row);
Product { id, user_id, name, price } Product {
}).collect() id,
}).unwrap(); user_id,
name,
price,
}
})
.collect()
})
.unwrap();
Ok(products) Ok(products)
} }
#[graphql(description = "Get Single user reference by user ID")] #[graphql(description = "Get Single user reference by user ID")]
fn product(context: &Context, id: String) -> FieldResult<Product> { fn product(context: &Context, id: String) -> FieldResult<Product> {
let mut conn = context.dbpool.get().unwrap(); let mut conn = context.dbpool.get().unwrap();
let product: Result<Option<Row>, DBError> = conn.first_exec( let product: Result<Option<Row>, DBError> =
"SELECT * FROM user WHERE id=:id", conn.first_exec("SELECT * FROM user WHERE id=:id", params! {"id" => id});
params! {"id" => id},
);
if let Err(err) = product { if let Err(err) = product {
return Err(FieldError::new( return Err(FieldError::new(
"Product Not Found", "Product Not Found",
@ -78,11 +89,15 @@ impl QueryRoot {
} }
let (id, user_id, name, price) = from_row(product.unwrap().unwrap()); let (id, user_id, name, price) = from_row(product.unwrap().unwrap());
Ok(Product { id, user_id, name, price }) Ok(Product {
id,
user_id,
name,
price,
})
} }
} }
pub struct MutationRoot; pub struct MutationRoot;
#[juniper::object(Context = Context)] #[juniper::object(Context = Context)]
@ -101,17 +116,15 @@ impl MutationRoot {
); );
match insert { match insert {
Ok(opt_row) => { Ok(opt_row) => Ok(User {
Ok(User {
id: new_id, id: new_id,
name: user.name, name: user.name,
email: user.email, email: user.email,
}) }),
}
Err(err) => { Err(err) => {
let msg = match err { let msg = match err {
DBError::MySqlError(err) => err.message, DBError::MySqlError(err) => err.message,
_ => "internal error".to_owned() _ => "internal error".to_owned(),
}; };
Err(FieldError::new( Err(FieldError::new(
"Failed to create new user", "Failed to create new user",
@ -136,18 +149,16 @@ impl MutationRoot {
); );
match insert { match insert {
Ok(opt_row) => { Ok(opt_row) => Ok(Product {
Ok(Product {
id: new_id, id: new_id,
user_id: product.user_id, user_id: product.user_id,
name: product.name, name: product.name,
price: product.price, price: product.price,
}) }),
}
Err(err) => { Err(err) => {
let msg = match err { let msg = match err {
DBError::MySqlError(err) => err.message, DBError::MySqlError(err) => err.message,
_ => "internal error".to_owned() _ => "internal error".to_owned(),
}; };
Err(FieldError::new( Err(FieldError::new(
"Failed to create new product", "Failed to create new product",

View File

@ -19,27 +19,42 @@ pub struct UserInput {
pub email: String, pub email: String,
} }
#[juniper::object(Context = Context)] #[juniper::object(Context = Context)]
impl User { impl User {
fn id(&self) -> &str { &self.id } fn id(&self) -> &str {
&self.id
}
fn name(&self) -> &str { fn name(&self) -> &str {
&self.name &self.name
} }
fn email(&self) -> &str { &self.email } fn email(&self) -> &str {
&self.email
}
fn products(&self, context: &Context) -> Vec<Product> { fn products(&self, context: &Context) -> Vec<Product> {
let mut conn = context.dbpool.get().unwrap(); let mut conn = context.dbpool.get().unwrap();
let products = conn.prep_exec( let products = conn
"select * from product where user_id=:user_id", params! { .prep_exec(
"select * from product where user_id=:user_id",
params! {
"user_id" => &self.id "user_id" => &self.id
}) },
)
.map(|result| { .map(|result| {
result.map(|x| x.unwrap()).map(|mut row| { result
.map(|x| x.unwrap())
.map(|mut row| {
let (id, user_id, name, price) = from_row(row); let (id, user_id, name, price) = from_row(row);
Product { id, user_id, name, price } Product {
}).collect() id,
}).unwrap(); user_id,
name,
price,
}
})
.collect()
})
.unwrap();
products products
} }
} }

View File

@ -1,10 +1,11 @@
[package] [package]
name = "hello-world" name = "hello-world"
version = "0.1.0" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"
env_logger = "0.6" env_logger = "0.6"

View File

@ -1,11 +1,12 @@
use actix_web::{middleware, web, App, HttpRequest, HttpServer}; use actix_web::{middleware, web, App, HttpRequest, HttpServer};
fn index(req: HttpRequest) -> &'static str { async fn index(req: HttpRequest) -> &'static str {
println!("REQ: {:?}", req); println!("REQ: {:?}", req);
"Hello world!" "Hello world!"
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
@ -13,28 +14,27 @@ fn main() -> std::io::Result<()> {
App::new() App::new()
// enable logger // enable logger
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service(web::resource("/index.html").to(|| "Hello world!")) .service(web::resource("/index.html").to(|| async { "Hello world!" }))
.service(web::resource("/").to(index)) .service(web::resource("/").to(index))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use actix_web::dev::Service; use actix_web::dev::Service;
use actix_web::{test, web, App, http, Error}; use actix_web::{http, test, web, App, Error};
#[test] #[actix_rt::test]
fn test_index() -> Result<(), Error> { async fn test_index() -> Result<(), Error> {
let app = App::new().route("/", web::get().to(index)); let app = App::new().route("/", web::get().to(index));
let mut app = test::init_service(app); let mut app = test::init_service(app).await;
let req = test::TestRequest::get() let req = test::TestRequest::get().uri("/").to_request();
.uri("/") let resp = app.call(req).await.unwrap();
.to_request();
let resp = test::block_on(app.call(req)).unwrap();
assert_eq!(resp.status(), http::StatusCode::OK); assert_eq!(resp.status(), http::StatusCode::OK);

View File

@ -1,14 +1,14 @@
[package] [package]
name = "http-proxy" name = "http-proxy"
version = "0.1.0" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rotem Yaari <vmalloc@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>", "Rotem Yaari <vmalloc@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-rt = "0.2" actix-rt = "1.0.0-alpha.3"
actix-web = { version = "1.0.0", features=["ssl"] } actix-web = { version = "2.0.0-alpha.3", features=["openssl"] }
clap = "2.32.0" clap = "2.32.0"
futures = "0.1.25" futures = "0.3.1"
failure = "0.1.3" failure = "0.1.3"
url = "1.7.1" url = "1.7.1"

View File

@ -1,17 +1,16 @@
use std::net::ToSocketAddrs;
use actix_web::client::Client; use actix_web::client::Client;
use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use clap::{value_t, Arg}; use clap::{value_t, Arg};
use futures::stream::Stream;
use futures::Future;
use std::net::ToSocketAddrs;
use url::Url; use url::Url;
fn forward( async fn forward(
req: HttpRequest, req: HttpRequest,
body: web::Bytes, body: web::Bytes,
url: web::Data<Url>, url: web::Data<Url>,
client: web::Data<Client>, client: web::Data<Client>,
) -> impl Future<Item = HttpResponse, Error = Error> { ) -> Result<HttpResponse, Error> {
let mut new_url = url.get_ref().clone(); let mut new_url = url.get_ref().clone();
new_url.set_path(req.uri().path()); new_url.set_path(req.uri().path());
new_url.set_query(req.uri().query()); new_url.set_query(req.uri().query());
@ -27,10 +26,8 @@ fn forward(
forwarded_req forwarded_req
}; };
forwarded_req let mut res = forwarded_req.send_body(body).await.map_err(Error::from)?;
.send_body(body)
.map_err(Error::from)
.map(|mut res| {
let mut client_resp = HttpResponse::build(res.status()); let mut client_resp = HttpResponse::build(res.status());
// Remove `Connection` as per // Remove `Connection` as per
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection#Directives // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection#Directives
@ -39,16 +36,12 @@ fn forward(
{ {
client_resp.header(header_name.clone(), header_value.clone()); client_resp.header(header_name.clone(), header_value.clone());
} }
res.body()
.into_stream() Ok(client_resp.body(res.body().await?))
.concat2()
.map(move |b| client_resp.body(b))
.map_err(|e| e.into())
})
.flatten()
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
let matches = clap::App::new("HTTP Proxy") let matches = clap::App::new("HTTP Proxy")
.arg( .arg(
Arg::with_name("listen_addr") Arg::with_name("listen_addr")
@ -102,9 +95,10 @@ fn main() -> std::io::Result<()> {
.data(Client::new()) .data(Client::new())
.data(forward_url.clone()) .data(forward_url.clone())
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.default_service(web::route().to_async(forward)) .default_service(web::route().to(forward))
}) })
.bind((listen_addr, listen_port))? .bind((listen_addr, listen_port))?
.system_exit() .system_exit()
.run() .start()
.await
} }

View File

@ -6,10 +6,11 @@ workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"
bytes = "0.4" bytes = "0.5.2"
futures = "0.1" futures = "0.3.1"
env_logger = "*" env_logger = "*"
serde = "1.0" serde = "1.0"

View File

@ -1,8 +1,8 @@
use actix_web::{ use actix_web::{
error, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, error, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer,
}; };
use bytes::BytesMut; use bytes::{Bytes, BytesMut};
use futures::{Future, Stream}; use futures::StreamExt;
use json::JsonValue; use json::JsonValue;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@ -13,13 +13,13 @@ struct MyObj {
} }
/// This handler uses json extractor /// This handler uses json extractor
fn index(item: web::Json<MyObj>) -> HttpResponse { async fn index(item: web::Json<MyObj>) -> HttpResponse {
println!("model: {:?}", &item); println!("model: {:?}", &item);
HttpResponse::Ok().json(item.0) // <- send response HttpResponse::Ok().json(item.0) // <- send response
} }
/// This handler uses json extractor with limit /// This handler uses json extractor with limit
fn extract_item(item: web::Json<MyObj>, req: HttpRequest) -> HttpResponse { async fn extract_item(item: web::Json<MyObj>, req: HttpRequest) -> HttpResponse {
println!("request: {:?}", req); println!("request: {:?}", req);
println!("model: {:?}", item); println!("model: {:?}", item);
@ -29,37 +29,25 @@ fn extract_item(item: web::Json<MyObj>, req: HttpRequest) -> HttpResponse {
const MAX_SIZE: usize = 262_144; // max payload size is 256k const MAX_SIZE: usize = 262_144; // max payload size is 256k
/// This handler manually load request payload and parse json object /// This handler manually load request payload and parse json object
fn index_manual( async fn index_manual(mut payload: web::Payload) -> Result<HttpResponse, Error> {
payload: web::Payload,
) -> impl Future<Item = HttpResponse, Error = Error> {
// payload is a stream of Bytes objects // payload is a stream of Bytes objects
payload let mut body = BytesMut::new();
// `Future::from_err` acts like `?` in that it coerces the error type from while let Some(chunk) = payload.next().await {
// the future into the final error type let chunk = chunk?;
.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| {
// limit max size of in-memory payload // limit max size of in-memory payload
if (body.len() + chunk.len()) > MAX_SIZE { if (body.len() + chunk.len()) > MAX_SIZE {
Err(error::ErrorBadRequest("overflow")) return Err(error::ErrorBadRequest("overflow"));
} else {
body.extend_from_slice(&chunk);
Ok(body)
} }
}) body.extend_from_slice(&chunk);
// `Future::and_then` can be used to merge an asynchronous workflow with a }
// synchronous workflow
.and_then(|body| {
// body is loaded, now we can deserialize serde-json // body is loaded, now we can deserialize serde-json
let obj = serde_json::from_slice::<MyObj>(&body)?; let obj = serde_json::from_slice::<MyObj>(&body)?;
Ok(HttpResponse::Ok().json(obj)) // <- send response Ok(HttpResponse::Ok().json(obj)) // <- send response
})
} }
/// This handler manually load request payload and parse json-rust /// This handler manually load request payload and parse json-rust
fn index_mjsonrust(pl: web::Payload) -> impl Future<Item = HttpResponse, Error = Error> { async fn index_mjsonrust(body: Bytes) -> Result<HttpResponse, Error> {
pl.concat2().from_err().and_then(|body| {
// body is loaded, now we can deserialize json-rust // body is loaded, now we can deserialize json-rust
let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result
let injson: JsonValue = match result { let injson: JsonValue = match result {
@ -69,10 +57,10 @@ fn index_mjsonrust(pl: web::Payload) -> impl Future<Item = HttpResponse, Error =
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(injson.dump())) .body(injson.dump()))
})
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
@ -85,23 +73,22 @@ fn main() -> std::io::Result<()> {
.service( .service(
web::resource("/extractor2") web::resource("/extractor2")
.data(web::JsonConfig::default().limit(1024)) // <- limit size of the payload (resource level) .data(web::JsonConfig::default().limit(1024)) // <- limit size of the payload (resource level)
.route(web::post().to_async(extract_item)), .route(web::post().to(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("/manual").route(web::post().to(index_manual)))
.service(web::resource("/mjsonrust").route(web::post().to(index_mjsonrust)))
.service(web::resource("/").route(web::post().to(index))) .service(web::resource("/").route(web::post().to(index)))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use actix_web::dev::Service; use actix_web::dev::Service;
use actix_web::{test, web, App, http}; use actix_web::{http, test, web, App};
#[test] #[test]
fn test_index() -> Result<(), Error> { fn test_index() -> Result<(), Error> {
@ -110,7 +97,10 @@ mod tests {
let req = test::TestRequest::post() let req = test::TestRequest::post()
.uri("/") .uri("/")
.set_json(&MyObj { name: "my-name".to_owned(), number: 43 }) .set_json(&MyObj {
name: "my-name".to_owned(),
number: 43,
})
.to_request(); .to_request();
let resp = test::block_on(app.call(req)).unwrap(); let resp = test::block_on(app.call(req)).unwrap();

View File

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

View File

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

View File

@ -1,16 +1,16 @@
[package] [package]
name = "jsonrpc-example" name = "jsonrpc-example"
version = "0.1.0" version = "2.0.0"
authors = ["mohanson <mohanson@outlook.com>"] authors = ["mohanson <mohanson@outlook.com>"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix = "0.8.2" actix-web = "2.0.0-alpha.3"
actix-web = "1.0.0" actix-rt = "1.0.0-alpha.3"
bytes = "0.5"
env_logger = "0.6" env_logger = "0.6"
futures = "0.1.23" futures = "0.3.1"
futures-timer = "0.1"
log = "0.4" log = "0.4"
serde = "1.0" serde = "1.0"
serde_derive = "1.0" serde_derive = "1.0"

View File

@ -1,11 +1,12 @@
use std::error; use std::error;
use std::sync::Arc; use std::pin::Pin;
use std::sync::RwLock; use std::sync::{Arc, RwLock};
use std::time::Duration; use std::time::Duration;
use actix_rt::time::delay_for;
use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
use futures::{future, Future, Stream}; use bytes::Bytes;
use futures_timer::Delay; use futures::{Future, FutureExt};
use serde_json; use serde_json;
use serde_json::Value; use serde_json::Value;
@ -13,11 +14,7 @@ use serde_json::Value;
mod convention; mod convention;
/// The main handler for JSONRPC server. /// The main handler for JSONRPC server.
fn rpc_handler( async fn rpc_handler(req: HttpRequest, body: Bytes) -> Result<HttpResponse, Error> {
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()) { let reqjson: convention::Request = match serde_json::from_slice(body.as_ref()) {
Ok(ok) => ok, Ok(ok) => ok,
Err(_) => { Err(_) => {
@ -36,7 +33,7 @@ fn rpc_handler(
let mut result = convention::Response::default(); let mut result = convention::Response::default();
result.id = reqjson.id.clone(); result.id = reqjson.id.clone();
match rpc_select(&app_state, reqjson.method.as_str(), reqjson.params) { match rpc_select(&app_state, reqjson.method.as_str(), reqjson.params).await {
Ok(ok) => result.result = ok, Ok(ok) => result.result = ok,
Err(e) => result.error = Some(e), Err(e) => result.error = Some(e),
} }
@ -44,10 +41,9 @@ fn rpc_handler(
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("application/json") .content_type("application/json")
.body(result.dump())) .body(result.dump()))
})
} }
fn rpc_select( async fn rpc_select(
app_state: &AppState, app_state: &AppState,
method: &str, method: &str,
params: Vec<Value>, params: Vec<Value>,
@ -66,7 +62,7 @@ fn rpc_select(
.read() .read()
.unwrap() .unwrap()
.wait(params[0].as_u64().unwrap()) .wait(params[0].as_u64().unwrap())
.wait() .await
{ {
Ok(ok) => Ok(Value::from(ok)), Ok(ok) => Ok(Value::from(ok)),
Err(e) => Err(convention::ErrorData::new(500, &format!("{:?}", e)[..])), Err(e) => Err(convention::ErrorData::new(500, &format!("{:?}", e)[..])),
@ -89,7 +85,7 @@ pub trait ImplNetwork {
fn wait( fn wait(
&self, &self,
d: u64, d: u64,
) -> Box<dyn Future<Item = String, Error = Box<dyn error::Error>>>; ) -> Pin<Box<dyn Future<Output = Result<String, Box<dyn error::Error>>>>>;
fn get(&self) -> u32; fn get(&self) -> u32;
fn inc(&mut self); fn inc(&mut self);
@ -113,12 +109,12 @@ impl ImplNetwork for ObjNetwork {
fn wait( fn wait(
&self, &self,
d: u64, d: u64,
) -> Box<dyn Future<Item = String, Error = Box<dyn error::Error>>> { ) -> Pin<Box<dyn Future<Output = Result<String, Box<dyn error::Error>>>>> {
if let Err(e) = Delay::new(Duration::from_secs(d)).wait() { async move {
let e: Box<dyn error::Error> = Box::new(e); delay_for(Duration::from_secs(d)).await;
return Box::new(future::err(e)); Ok(String::from("pong"))
}; }
Box::new(future::ok(String::from("pong"))) .boxed_local()
} }
fn get(&self) -> u32 { fn get(&self) -> u32 {
@ -141,24 +137,22 @@ impl AppState {
} }
} }
fn main() { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "info"); std::env::set_var("RUST_LOG", "info");
env_logger::init(); env_logger::init();
let network = Arc::new(RwLock::new(ObjNetwork::new())); let network = Arc::new(RwLock::new(ObjNetwork::new()));
let sys = actix::System::new("actix_jrpc");
HttpServer::new(move || { HttpServer::new(move || {
let app_state = AppState::new(network.clone()); let app_state = AppState::new(network.clone());
App::new() App::new()
.data(app_state) .data(app_state)
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service(web::resource("/").route(web::post().to_async(rpc_handler))) .service(web::resource("/").route(web::post().to(rpc_handler)))
}) })
.bind("127.0.0.1:8080") .bind("127.0.0.1:8080")
.unwrap() .unwrap()
.workers(1) .start()
.start(); .await
let _ = sys.run();
} }

View File

@ -1,13 +1,15 @@
[package] [package]
name = "middleware-example" name = "middleware-example"
version = "0.1.0" version = "1.0.0"
authors = ["Gorm Casper <gcasper@gmail.com>", "Sven-Hendrik Haase <svenstaro@gmail.com>"] authors = ["Gorm Casper <gcasper@gmail.com>", "Sven-Hendrik Haase <svenstaro@gmail.com>"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix-service = "0.4.1" actix-service = "1.0.0-alpha.3"
actix-web = "1.0.0" actix-rt = "1.0.0-alpha.3"
futures = "0.1.25" actix-web = "2.0.0-alpha.3"
futures = "0.3.1"
env_logger = "0.6" env_logger = "0.6"
bytes = "0.4" bytes = "0.5"
pin-project = "0.4.6"

View File

@ -1,17 +1,18 @@
use actix_web::{web, App, HttpServer};
use actix_service::Service; use actix_service::Service;
use futures::future::Future; use actix_web::{web, App, HttpServer};
use futures::future::FutureExt;
#[allow(dead_code)]
mod redirect;
#[allow(dead_code)] #[allow(dead_code)]
mod read_request_body; mod read_request_body;
#[allow(dead_code)] #[allow(dead_code)]
mod read_response_body; mod read_response_body;
#[allow(dead_code)] #[allow(dead_code)]
mod redirect;
#[allow(dead_code)]
mod simple; mod simple;
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=debug"); std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init(); env_logger::init();
@ -30,14 +31,15 @@ fn main() -> std::io::Result<()> {
}) })
}) })
.service(web::resource("/login").to(|| { .service(web::resource("/login").to(|| {
async {
"You are on /login. Go to src/redirect.rs to change this behavior." "You are on /login. Go to src/redirect.rs to change this behavior."
}
}))
.service(web::resource("/").to(|| {
async { "Hello, middleware! Check the console where the server is run." }
})) }))
.service(
web::resource("/").to(|| {
"Hello, middleware! Check the console where the server is run."
}),
)
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

@ -1,12 +1,13 @@
use std::cell::RefCell;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use actix_web::error::PayloadError;
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error, HttpMessage}; use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error, HttpMessage};
use bytes::BytesMut; use bytes::BytesMut;
use futures::future::{ok, FutureResult}; use futures::future::{ok, Future, Ready};
use futures::stream::Stream; use futures::stream::StreamExt;
use futures::{Future, Poll};
use std::cell::RefCell;
use std::rc::Rc;
pub struct Logging; pub struct Logging;
@ -21,7 +22,7 @@ where
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = LoggingMiddleware<S>; type Transform = LoggingMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
ok(LoggingMiddleware { ok(LoggingMiddleware {
@ -45,26 +46,27 @@ where
type Request = ServiceRequest; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>; type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready() self.service.poll_ready(cx)
} }
fn call(&mut self, mut req: ServiceRequest) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let mut svc = self.service.clone(); let mut svc = self.service.clone();
Box::new( Box::pin(async move {
req.take_payload() let mut body = BytesMut::new();
.fold(BytesMut::new(), move |mut body, chunk| { let mut stream = req.take_payload();
body.extend_from_slice(&chunk); while let Some(chunk) = stream.next().await {
Ok::<_, PayloadError>(body) body.extend_from_slice(&chunk?);
}
println!("request body: {:?}", body);
let res = svc.call(req).await?;
println!("response: {:?}", res.headers());
Ok(res)
}) })
.map_err(|e| e.into())
.and_then(move |bytes| {
println!("request body: {:?}", bytes);
svc.call(req).and_then(|res| Ok(res))
}),
)
} }
} }

View File

@ -1,11 +1,13 @@
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use actix_web::body::{BodySize, MessageBody, ResponseBody}; use actix_web::body::{BodySize, MessageBody, ResponseBody};
use std::marker::PhantomData;
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error}; use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::Async; use futures::future::{ok, Ready};
use futures::future::{ok, FutureResult};
use futures::{Future, Poll};
pub struct Logging; pub struct Logging;
@ -19,12 +21,10 @@ where
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = LoggingMiddleware<S>; type Transform = LoggingMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
ok(LoggingMiddleware { ok(LoggingMiddleware { service })
service,
})
} }
} }
@ -42,8 +42,8 @@ where
type Error = Error; type Error = Error;
type Future = WrapperStream<S, B>; type Future = WrapperStream<S, B>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready() self.service.poll_ready(cx)
} }
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
@ -54,11 +54,13 @@ where
} }
} }
#[pin_project::pin_project]
pub struct WrapperStream<S, B> pub struct WrapperStream<S, B>
where where
B: MessageBody, B: MessageBody,
S: Service, S: Service,
{ {
#[pin]
fut: S::Future, fut: S::Future,
_t: PhantomData<(B,)>, _t: PhantomData<(B,)>,
} }
@ -68,18 +70,19 @@ where
B: MessageBody, B: MessageBody,
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>, S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
{ {
type Item = ServiceResponse<BodyLogger<B>>; type Output = Result<ServiceResponse<BodyLogger<B>>, Error>;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = futures::try_ready!(self.fut.poll()); let res = futures::ready!(self.project().fut.poll(cx));
Ok(Async::Ready(res.map_body(move |_, body| { Poll::Ready(res.map(|res| {
res.map_body(move |_, body| {
ResponseBody::Body(BodyLogger { ResponseBody::Body(BodyLogger {
body, body,
body_accum: BytesMut::new(), body_accum: BytesMut::new(),
}) })
}))) })
}))
} }
} }
@ -99,13 +102,15 @@ impl<B: MessageBody> MessageBody for BodyLogger<B> {
self.body.size() self.body.size()
} }
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> { fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, Error>>> {
match self.body.poll_next()? { match self.body.poll_next(cx) {
Async::Ready(Some(chunk)) => { Poll::Ready(Some(Ok(chunk))) => {
self.body_accum.extend_from_slice(&chunk); self.body_accum.extend_from_slice(&chunk);
Ok(Async::Ready(Some(chunk))) Poll::Ready(Some(Ok(chunk)))
} }
val => Ok(val), Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
} }
} }
} }

View File

@ -1,8 +1,9 @@
use std::task::{Context, Poll};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::dev::{ServiceRequest, ServiceResponse};
use actix_web::{http, Error, HttpResponse}; use actix_web::{http, Error, HttpResponse};
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, Either, Ready};
use futures::Poll;
pub struct CheckLogin; pub struct CheckLogin;
@ -16,7 +17,7 @@ where
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = CheckLoginMiddleware<S>; type Transform = CheckLoginMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
ok(CheckLoginMiddleware { service }) ok(CheckLoginMiddleware { service })
@ -34,10 +35,10 @@ where
type Request = ServiceRequest; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
type Future = Either<S::Future, FutureResult<Self::Response, Self::Error>>; type Future = Either<S::Future, Ready<Result<Self::Response, Self::Error>>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready() self.service.poll_ready(cx)
} }
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
@ -46,13 +47,13 @@ where
let is_logged_in = false; // Change this to see the change in outcome in the browser let is_logged_in = false; // Change this to see the change in outcome in the browser
if is_logged_in { if is_logged_in {
Either::A(self.service.call(req)) Either::Left(self.service.call(req))
} else { } else {
// Don't forward to /login if we are already on /login // Don't forward to /login if we are already on /login
if req.path() == "/login" { if req.path() == "/login" {
Either::A(self.service.call(req)) Either::Left(self.service.call(req))
} else { } else {
Either::B(ok(req.into_response( Either::Right(ok(req.into_response(
HttpResponse::Found() HttpResponse::Found()
.header(http::header::LOCATION, "/login") .header(http::header::LOCATION, "/login")
.finish() .finish()

View File

@ -1,7 +1,10 @@
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error}; use actix_web::{dev::ServiceRequest, dev::ServiceResponse, Error};
use futures::future::{ok, FutureResult}; use futures::future::{ok, Ready};
use futures::{Future, Poll}; use futures::Future;
// There are two steps in middleware processing. // There are two steps in middleware processing.
// 1. Middleware initialization, middleware factory gets called with // 1. Middleware initialization, middleware factory gets called with
@ -23,7 +26,7 @@ where
type Error = Error; type Error = Error;
type InitError = (); type InitError = ();
type Transform = SayHiMiddleware<S>; type Transform = SayHiMiddleware<S>;
type Future = FutureResult<Self::Transform, Self::InitError>; type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
ok(SayHiMiddleware { service }) ok(SayHiMiddleware { service })
@ -43,18 +46,22 @@ where
type Request = ServiceRequest; type Request = ServiceRequest;
type Response = ServiceResponse<B>; type Response = ServiceResponse<B>;
type Error = Error; type Error = Error;
type Future = Box<dyn Future<Item = Self::Response, Error = Self::Error>>; type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.service.poll_ready() self.service.poll_ready(cx)
} }
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
println!("Hi from start. You requested: {}", req.path()); println!("Hi from start. You requested: {}", req.path());
Box::new(self.service.call(req).and_then(|res| { let fut = self.service.call(req);
Box::pin(async move {
let res = fut.await?;
println!("Hi from response"); println!("Hi from response");
Ok(res) Ok(res)
})) })
} }
} }

View File

@ -13,3 +13,4 @@ readme = "README.md"
futures = "0.3.1" futures = "0.3.1"
actix-multipart = "0.2.0-alpha.3" actix-multipart = "0.2.0-alpha.3"
actix-web = "2.0.0-alpha.3" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"

View File

@ -1,7 +1,7 @@
use std::io::Write;
use actix_multipart::Multipart; use actix_multipart::Multipart;
use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
use futures::{StreamExt}; use futures::StreamExt;
use std::io::Write;
async fn save_file(mut payload: Multipart) -> Result<HttpResponse, Error> { async fn save_file(mut payload: Multipart) -> Result<HttpResponse, Error> {
// iterate over multipart stream // iterate over multipart stream
@ -38,19 +38,21 @@ fn index() -> HttpResponse {
HttpResponse::Ok().body(html) HttpResponse::Ok().body(html)
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
std::fs::create_dir_all("./tmp").unwrap(); std::fs::create_dir_all("./tmp").unwrap();
let ip = "0.0.0.0:3000"; let ip = "0.0.0.0:3000";
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new().wrap(middleware::Logger::default()).service(
.wrap(middleware::Logger::default())
.service(
web::resource("/") web::resource("/")
.route(web::get().to(index)) .route(web::get().to(index))
.route(web::post().to(save_file)), .route(web::post().to(save_file)),
) )
}) })
.bind(ip)? .bind(ip)?
.run() .start()
.await
} }

12
openssl/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "openssl-example"
version = "0.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
edition = "2018"
workspace = ".."
[dependencies]
actix-rt = "1.0.0-alpha.3"
actix-web = { version="2.0.0-alpha.3", features=["openssl"] }
env_logger = "0.6"
openssl = "0.10"

View File

@ -4,18 +4,19 @@ use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServ
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
/// simple handle /// simple handle
fn index(req: HttpRequest) -> Result<HttpResponse, Error> { async fn index(req: HttpRequest) -> Result<HttpResponse, Error> {
println!("{:?}", req); println!("{:?}", req);
Ok(HttpResponse::Ok() Ok(HttpResponse::Ok()
.content_type("text/plain") .content_type("text/plain")
.body("Welcome!")) .body("Welcome!"))
} }
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=debug"); std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init(); env_logger::init();
let sys = actix_rt::System::new("tls-example"); println!("Started http server: 127.0.0.1:8443");
// load ssl keys // load ssl keys
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
@ -37,9 +38,7 @@ fn main() -> io::Result<()> {
.finish() .finish()
}))) })))
}) })
.bind_ssl("127.0.0.1:8443", builder)? .bind_openssl("127.0.0.1:8443", builder)?
.start(); .start()
.await
println!("Started http server: 127.0.0.1:8443");
sys.run()
} }

View File

@ -1,15 +1,15 @@
[package] [package]
name = "r2d2-example" name = "r2d2-example"
version = "0.1.0" version = "1.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix-rt = "0.2" actix-rt = "1.0.0-alpha.3"
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
futures = "0.1" futures = "0.3.1"
env_logger = "0.6" env_logger = "0.6"
uuid = { version = "0.7", features = ["v4"] } uuid = { version = "0.7", features = ["v4"] }

View File

@ -2,18 +2,17 @@
use std::io; use std::io;
use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer}; use actix_web::{middleware, web, App, Error, HttpResponse, HttpServer};
use futures::Future;
use r2d2::Pool; use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager; use r2d2_sqlite::SqliteConnectionManager;
use uuid; use uuid;
/// Async request handler. Ddb pool is stored in application state. /// Async request handler. Ddb pool is stored in application state.
fn index( async fn index(
path: web::Path<String>, path: web::Path<String>,
db: web::Data<Pool<SqliteConnectionManager>>, db: web::Data<Pool<SqliteConnectionManager>>,
) -> impl Future<Item = HttpResponse, Error = Error> { ) -> Result<HttpResponse, Error> {
// execute sync code in threadpool // execute sync code in threadpool
web::block(move || { let res = web::block(move || {
let conn = db.get().unwrap(); let conn = db.get().unwrap();
let uuid = format!("{}", uuid::Uuid::new_v4()); let uuid = format!("{}", uuid::Uuid::new_v4());
@ -27,16 +26,16 @@ fn index(
row.get::<_, String>(0) row.get::<_, String>(0)
}) })
}) })
.then(|res| match res { .await
Ok(user) => Ok(HttpResponse::Ok().json(user)), .map(|user| HttpResponse::Ok().json(user))
Err(_) => Ok(HttpResponse::InternalServerError().into()), .map_err(|_| HttpResponse::InternalServerError())?;
}) Ok(res)
} }
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=debug"); std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init(); env_logger::init();
let sys = actix_rt::System::new("r2d2-example");
// r2d2 pool // r2d2 pool
let manager = SqliteConnectionManager::file("test.db"); let manager = SqliteConnectionManager::file("test.db");
@ -47,10 +46,9 @@ fn main() -> io::Result<()> {
App::new() App::new()
.data(pool.clone()) // <- store db pool in app state .data(pool.clone()) // <- store db pool in app state
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.route("/{name}", web::get().to_async(index)) .route("/{name}", web::get().to(index))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.start(); .start()
.await
sys.run()
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rustls-example" name = "rustls-example"
version = "0.1.0" version = "1.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
@ -11,6 +11,7 @@ path = "src/main.rs"
[dependencies] [dependencies]
env_logger = "0.5" env_logger = "0.5"
rustls = "0.15" rustls = "0.16"
actix-web = { version = "1.0.0", features=["rust-tls"] } actix-web = { version = "2.0.0-alpha.3", features=["rustls"] }
actix-files = "0.1.0" actix-files = "0.2.0-alpha.3"
actix-rt = "1.0.0-alpha.3"

View File

@ -2,19 +2,20 @@ use std::fs::File;
use std::io::BufReader; use std::io::BufReader;
use actix_files::Files; use actix_files::Files;
use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer}; use actix_web::{middleware, web, App, HttpRequest, HttpResponse, HttpServer};
use rustls::internal::pemfile::{certs, rsa_private_keys}; use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{NoClientAuth, ServerConfig}; use rustls::{NoClientAuth, ServerConfig};
/// simple handle /// simple handle
fn index(req: HttpRequest) -> Result<HttpResponse, Error> { async fn index(req: HttpRequest) -> HttpResponse {
println!("{:?}", req); println!("{:?}", req);
Ok(HttpResponse::Ok() HttpResponse::Ok()
.content_type("text/plain") .content_type("text/plain")
.body("Welcome!")) .body("Welcome!")
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
if std::env::var("RUST_LOG").is_err() { if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
} }
@ -43,5 +44,6 @@ fn main() -> std::io::Result<()> {
.service(Files::new("/static", "static")) .service(Files::new("/static", "static"))
}) })
.bind_rustls("127.0.0.1:8443", config)? .bind_rustls("127.0.0.1:8443", config)?
.run() .start()
.await
} }

View File

@ -1,13 +1,13 @@
[package] [package]
name = "server-sent-events" name = "server-sent-events"
version = "0.1.0" version = "1.0.0"
authors = ["Arve Seljebu"] authors = ["Arve Seljebu"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-rt = "0.2" actix-rt = "1.0.0-alpha.3"
actix-web = "1.0" actix-web = "2.0.0-alpha.3"
env_logger = "0.6" env_logger = "0.6"
futures = "0.1" futures = "0.3.1"
tokio = "0.1" tokio = "0.2"

View File

@ -1,17 +1,17 @@
use actix_rt::Arbiter; use std::pin::Pin;
use actix_web::error::ErrorInternalServerError; use std::sync::Mutex;
use std::task::{Context, Poll};
use std::time::Duration;
use actix_web::web::{Bytes, Data, Path}; use actix_web::web::{Bytes, Data, Path};
use actix_web::{web, App, Error, HttpResponse, HttpServer, Responder}; use actix_web::{web, App, Error, HttpResponse, HttpServer, Responder};
use env_logger; use env_logger;
use tokio::prelude::*; use futures::{Stream, StreamExt};
use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::sync::mpsc::{channel, Receiver, Sender};
use tokio::timer::Interval; use tokio::time::{interval_at, Instant};
use std::sync::Mutex; #[actix_rt::main]
use std::time::{Duration, Instant}; async fn main() -> std::io::Result<()> {
fn main() {
env_logger::init(); env_logger::init();
let data = Broadcaster::create(); let data = Broadcaster::create();
@ -22,13 +22,12 @@ fn main() {
.route("/events", web::get().to(new_client)) .route("/events", web::get().to(new_client))
.route("/broadcast/{msg}", web::get().to(broadcast)) .route("/broadcast/{msg}", web::get().to(broadcast))
}) })
.bind("127.0.0.1:8080") .bind("127.0.0.1:8080")?
.expect("Unable to bind port") .start()
.run() .await
.unwrap();
} }
fn index() -> impl Responder { async fn index() -> impl Responder {
let content = include_str!("index.html"); let content = include_str!("index.html");
HttpResponse::Ok() HttpResponse::Ok()
@ -36,7 +35,7 @@ fn index() -> impl Responder {
.body(content) .body(content)
} }
fn new_client(broadcaster: Data<Mutex<Broadcaster>>) -> impl Responder { async fn new_client(broadcaster: Data<Mutex<Broadcaster>>) -> impl Responder {
let rx = broadcaster.lock().unwrap().new_client(); let rx = broadcaster.lock().unwrap().new_client();
HttpResponse::Ok() HttpResponse::Ok()
@ -45,7 +44,10 @@ fn new_client(broadcaster: Data<Mutex<Broadcaster>>) -> impl Responder {
.streaming(rx) .streaming(rx)
} }
fn broadcast(msg: Path<String>, broadcaster: Data<Mutex<Broadcaster>>) -> impl Responder { async fn broadcast(
msg: Path<String>,
broadcaster: Data<Mutex<Broadcaster>>,
) -> impl Responder {
broadcaster.lock().unwrap().send(&msg.into_inner()); broadcaster.lock().unwrap().send(&msg.into_inner());
HttpResponse::Ok().body("msg sent") HttpResponse::Ok().body("msg sent")
@ -73,14 +75,12 @@ impl Broadcaster {
} }
fn spawn_ping(me: Data<Mutex<Self>>) { fn spawn_ping(me: Data<Mutex<Self>>) {
let task = Interval::new(Instant::now(), Duration::from_secs(10)) actix_rt::spawn(async move {
.for_each(move |_| { let mut task = interval_at(Instant::now(), Duration::from_secs(10));
while let Some(_) = task.next().await {
me.lock().unwrap().remove_stale_clients(); me.lock().unwrap().remove_stale_clients();
Ok(()) }
}) })
.map_err(|e| panic!("interval errored; err={:?}", e));
Arbiter::spawn(task);
} }
fn remove_stale_clients(&mut self) { fn remove_stale_clients(&mut self) {
@ -119,10 +119,16 @@ impl Broadcaster {
struct Client(Receiver<Bytes>); struct Client(Receiver<Bytes>);
impl Stream for Client { impl Stream for Client {
type Item = Bytes; type Item = Result<Bytes, Error>;
type Error = Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { fn poll_next(
self.0.poll().map_err(ErrorInternalServerError) mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
match Pin::new(&mut self.0).poll_next(cx) {
Poll::Ready(Some(v)) => Poll::Ready(Some(Ok(v))),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
} }
} }

View File

@ -1,20 +1,21 @@
[package] [package]
name = "simple-auth-server" name = "simple-auth-server"
version = "0.1.0" version = "2.0.0"
authors = ["mygnu <tech@hgill.io>"] authors = ["mygnu <tech@hgill.io>"]
edition = "2018" edition = "2018"
workspace = ".." workspace = ".."
[dependencies] [dependencies]
actix-identity = "0.1.0" actix-identity = "0.2.0-alpha.1"
actix-web = "1.0.3" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"
argonautica = "0.2.0" argonautica = "0.2.0"
chrono = { version = "0.4.6", features = ["serde"] } chrono = { version = "0.4.6", features = ["serde"] }
derive_more = "0.15.0" derive_more = "0.99.0"
diesel = { version = "1.4.2", features = ["postgres","uuidv07", "r2d2", "chrono"] } diesel = { version = "1.4.2", features = ["postgres","uuidv07", "r2d2", "chrono"] }
dotenv = "0.14.1" dotenv = "0.14.1"
env_logger = "0.6" env_logger = "0.6"
futures = "0.1" futures = "0.3.1"
r2d2 = "0.8" r2d2 = "0.8"
lazy_static = "1.3.0" lazy_static = "1.3.0"
serde = "1.0" serde = "1.0"
@ -22,4 +23,3 @@ serde_derive = "1.0"
serde_json = "1.0" serde_json = "1.0"
sparkpost = "0.5.2" sparkpost = "0.5.2"
uuid = { version = "0.7", features = ["serde", "v4"] } uuid = { version = "0.7", features = ["serde", "v4"] }

View File

@ -1,3 +1,5 @@
use std::pin::Pin;
use actix_identity::Identity; use actix_identity::Identity;
use actix_web::{ use actix_web::{
dev::Payload, error::BlockingError, web, Error, FromRequest, HttpRequest, dev::Payload, error::BlockingError, web, Error, FromRequest, HttpRequest,
@ -5,7 +7,7 @@ use actix_web::{
}; };
use diesel::prelude::*; use diesel::prelude::*;
use diesel::PgConnection; use diesel::PgConnection;
use futures::Future; use futures::future::Future;
use crate::errors::ServiceError; use crate::errors::ServiceError;
use crate::models::{Pool, SlimUser, User}; use crate::models::{Pool, SlimUser, User};
@ -24,29 +26,34 @@ pub type LoggedUser = SlimUser;
impl FromRequest for LoggedUser { impl FromRequest for LoggedUser {
type Config = (); type Config = ();
type Error = Error; type Error = Error;
type Future = Result<LoggedUser, Error>; type Future = Pin<Box<dyn Future<Output = Result<LoggedUser, Error>>>>;
fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future { fn from_request(req: &HttpRequest, pl: &mut Payload) -> Self::Future {
if let Some(identity) = Identity::from_request(req, pl)?.identity() { let fut = Identity::from_request(req, pl);
Box::pin(async move {
if let Some(identity) = fut.await?.identity() {
let user: LoggedUser = serde_json::from_str(&identity)?; let user: LoggedUser = serde_json::from_str(&identity)?;
return Ok(user); return Ok(user);
} };
Err(ServiceError::Unauthorized.into()) Err(ServiceError::Unauthorized.into())
})
} }
} }
pub fn logout(id: Identity) -> HttpResponse { pub async fn logout(id: Identity) -> HttpResponse {
id.forget(); id.forget();
HttpResponse::Ok().finish() HttpResponse::Ok().finish()
} }
pub fn login( pub async fn login(
auth_data: web::Json<AuthData>, auth_data: web::Json<AuthData>,
id: Identity, id: Identity,
pool: web::Data<Pool>, pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = ServiceError> { ) -> Result<HttpResponse, ServiceError> {
web::block(move || query(auth_data.into_inner(), pool)).then( let res = web::block(move || query(auth_data.into_inner(), pool)).await;
move |res: Result<SlimUser, BlockingError<ServiceError>>| match res {
match res {
Ok(user) => { Ok(user) => {
let user_string = serde_json::to_string(&user).unwrap(); let user_string = serde_json::to_string(&user).unwrap();
id.remember(user_string); id.remember(user_string);
@ -56,11 +63,10 @@ pub fn login(
BlockingError::Error(service_error) => Err(service_error), BlockingError::Error(service_error) => Err(service_error),
BlockingError::Canceled => Err(ServiceError::InternalServerError), BlockingError::Canceled => Err(ServiceError::InternalServerError),
}, },
}, }
)
} }
pub fn get_me(logged_user: LoggedUser) -> HttpResponse { pub async fn get_me(logged_user: LoggedUser) -> HttpResponse {
HttpResponse::Ok().json(logged_user) HttpResponse::Ok().json(logged_user)
} }
/// Diesel query /// Diesel query

View File

@ -1,6 +1,5 @@
use actix_web::{error::BlockingError, web, HttpResponse}; use actix_web::{error::BlockingError, web, HttpResponse};
use diesel::{prelude::*, PgConnection}; use diesel::{prelude::*, PgConnection};
use futures::Future;
use crate::email_service::send_invitation; use crate::email_service::send_invitation;
use crate::errors::ServiceError; use crate::errors::ServiceError;
@ -11,20 +10,22 @@ pub struct InvitationData {
pub email: String, pub email: String,
} }
pub fn post_invitation( pub async fn post_invitation(
invitation_data: web::Json<InvitationData>, invitation_data: web::Json<InvitationData>,
pool: web::Data<Pool>, pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = ServiceError> { ) -> Result<HttpResponse, ServiceError> {
// run diesel blocking code // run diesel blocking code
web::block(move || create_invitation(invitation_data.into_inner().email, pool)).then( let res =
|res| match res { web::block(move || create_invitation(invitation_data.into_inner().email, pool))
.await;
match res {
Ok(_) => Ok(HttpResponse::Ok().finish()), Ok(_) => Ok(HttpResponse::Ok().finish()),
Err(err) => match err { Err(err) => match err {
BlockingError::Error(service_error) => Err(service_error), BlockingError::Error(service_error) => Err(service_error),
BlockingError::Canceled => Err(ServiceError::InternalServerError), BlockingError::Canceled => Err(ServiceError::InternalServerError),
}, },
}, }
)
} }
fn create_invitation( fn create_invitation(

View File

@ -17,7 +17,8 @@ mod register_handler;
mod schema; mod schema;
mod utils; mod utils;
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok(); dotenv::dotenv().ok();
std::env::set_var( std::env::set_var(
"RUST_LOG", "RUST_LOG",
@ -52,22 +53,23 @@ fn main() -> std::io::Result<()> {
// everything under '/api/' route // everything under '/api/' route
.service( .service(
web::scope("/api") web::scope("/api")
.service(web::resource("/invitation").route(
web::post().to_async(invitation_handler::post_invitation),
))
.service( .service(
web::resource("/register/{invitation_id}").route( web::resource("/invitation")
web::post().to_async(register_handler::register_user), .route(web::post().to(invitation_handler::post_invitation)),
), )
.service(
web::resource("/register/{invitation_id}")
.route(web::post().to(register_handler::register_user)),
) )
.service( .service(
web::resource("/auth") web::resource("/auth")
.route(web::post().to_async(auth_handler::login)) .route(web::post().to(auth_handler::login))
.route(web::delete().to(auth_handler::logout)) .route(web::delete().to(auth_handler::logout))
.route(web::get().to(auth_handler::get_me)), .route(web::get().to(auth_handler::get_me)),
), ),
) )
}) })
.bind("127.0.0.1:3000")? .bind("127.0.0.1:3000")?
.run() .start()
.await
} }

View File

@ -1,6 +1,5 @@
use actix_web::{error::BlockingError, web, HttpResponse}; use actix_web::{error::BlockingError, web, HttpResponse};
use diesel::prelude::*; use diesel::prelude::*;
use futures::Future;
use crate::errors::ServiceError; use crate::errors::ServiceError;
use crate::models::{Invitation, Pool, SlimUser, User}; use crate::models::{Invitation, Pool, SlimUser, User};
@ -11,25 +10,27 @@ pub struct UserData {
pub password: String, pub password: String,
} }
pub fn register_user( pub async fn register_user(
invitation_id: web::Path<String>, invitation_id: web::Path<String>,
user_data: web::Json<UserData>, user_data: web::Json<UserData>,
pool: web::Data<Pool>, pool: web::Data<Pool>,
) -> impl Future<Item = HttpResponse, Error = ServiceError> { ) -> Result<HttpResponse, ServiceError> {
web::block(move || { let res = web::block(move || {
query( query(
invitation_id.into_inner(), invitation_id.into_inner(),
user_data.into_inner().password, user_data.into_inner().password,
pool, pool,
) )
}) })
.then(|res| match res { .await;
match res {
Ok(user) => Ok(HttpResponse::Ok().json(&user)), Ok(user) => Ok(HttpResponse::Ok().json(&user)),
Err(err) => match err { Err(err) => match err {
BlockingError::Error(service_error) => Err(service_error), BlockingError::Error(service_error) => Err(service_error),
BlockingError::Canceled => Err(ServiceError::InternalServerError), BlockingError::Canceled => Err(ServiceError::InternalServerError),
}, },
}) }
} }
fn query( fn query(

View File

@ -1,11 +1,12 @@
[package] [package]
name = "state" name = "state"
version = "0.1.0" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
futures = "0.1.25" actix-rt = "1.0.0-alpha.3"
futures = "0.3.1"
env_logger = "0.6" env_logger = "0.6"

View File

@ -17,14 +17,15 @@ use std::sync::Mutex;
use actix_web::{middleware, web, App, HttpRequest, HttpResponse, HttpServer}; use actix_web::{middleware, web, App, HttpRequest, HttpResponse, HttpServer};
/// simple handle /// simple handle
fn index(state: web::Data<Mutex<usize>>, req: HttpRequest) -> HttpResponse { async fn index(state: web::Data<Mutex<usize>>, req: HttpRequest) -> HttpResponse {
println!("{:?}", req); println!("{:?}", req);
*(state.lock().unwrap()) += 1; *(state.lock().unwrap()) += 1;
HttpResponse::Ok().body(format!("Num of requests: {}", state.lock().unwrap())) HttpResponse::Ok().body(format!("Num of requests: {}", state.lock().unwrap()))
} }
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
@ -40,5 +41,6 @@ fn main() -> io::Result<()> {
.service(web::resource("/").to(index)) .service(web::resource("/").to(index))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

@ -1,13 +1,14 @@
[package] [package]
name = "static_index" name = "static_index"
version = "0.1.0" version = "2.0.0"
authors = ["Jose Marinez <digeratus@gmail.com>"] authors = ["Jose Marinez <digeratus@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
futures = "0.1" futures = "0.3.1"
env_logger = "0.5" env_logger = "0.6"
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-files = "0.1.1" actix-files = "0.2.0-alpha.3"
actix-rt = "1.0.0-alpha.3"

View File

@ -1,7 +1,8 @@
use actix_files as fs; use actix_files as fs;
use actix_web::{middleware, App, HttpServer}; use actix_web::{middleware, App, HttpServer};
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
@ -15,5 +16,6 @@ fn main() -> std::io::Result<()> {
) )
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

@ -1,12 +1,13 @@
[package] [package]
name = "template-askama" name = "template-askama"
version = "0.1.0" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"
env_logger = "0.6" env_logger = "0.6"
askama = "0.8" askama = "0.8"

View File

@ -14,7 +14,7 @@ struct UserTemplate<'a> {
#[template(path = "index.html")] #[template(path = "index.html")]
struct Index; struct Index;
fn index(query: web::Query<HashMap<String, String>>) -> Result<HttpResponse> { async fn index(query: web::Query<HashMap<String, String>>) -> Result<HttpResponse> {
let s = if let Some(name) = query.get("name") { let s = if let Some(name) = query.get("name") {
UserTemplate { UserTemplate {
name, name,
@ -28,11 +28,13 @@ fn index(query: web::Query<HashMap<String, String>>) -> Result<HttpResponse> {
Ok(HttpResponse::Ok().content_type("text/html").body(s)) Ok(HttpResponse::Ok().content_type("text/html").body(s))
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
// start http server // start http server
HttpServer::new(move || { HttpServer::new(move || {
App::new().service(web::resource("/").route(web::get().to(index))) App::new().service(web::resource("/").route(web::get().to(index)))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

@ -1,10 +1,11 @@
[package] [package]
name = "template_handlebars" name = "template_handlebars"
version = "0.1.0" version = "1.0.0"
authors = ["Alexandru Tiniuc <tiniuc.alexandru@gmail.com>"] authors = ["Alexandru Tiniuc <tiniuc.alexandru@gmail.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"
handlebars = "2.0.0-beta.2" handlebars = "2.0.0-beta.2"
serde_json = "1.0" serde_json = "1.0"

View File

@ -13,7 +13,7 @@ use std::io;
// Macro documentation can be found in the actix_web_codegen crate // Macro documentation can be found in the actix_web_codegen crate
#[get("/")] #[get("/")]
fn index(hb: web::Data<Handlebars>) -> HttpResponse { async fn index(hb: web::Data<Handlebars>) -> HttpResponse {
let data = json!({ let data = json!({
"name": "Handlebars" "name": "Handlebars"
}); });
@ -23,7 +23,10 @@ fn index(hb: web::Data<Handlebars>) -> HttpResponse {
} }
#[get("/{user}/{data}")] #[get("/{user}/{data}")]
fn user(hb: web::Data<Handlebars>, info: web::Path<(String, String)>) -> HttpResponse { async fn user(
hb: web::Data<Handlebars>,
info: web::Path<(String, String)>,
) -> HttpResponse {
let data = json!({ let data = json!({
"user": info.0, "user": info.0,
"data": info.1 "data": info.1
@ -33,7 +36,8 @@ fn user(hb: web::Data<Handlebars>, info: web::Path<(String, String)>) -> HttpRes
HttpResponse::Ok().body(body) HttpResponse::Ok().body(body)
} }
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
// Handlebars uses a repository for the compiled templates. This object must be // Handlebars uses a repository for the compiled templates. This object must be
// shared between the application threads, and is therefore passed to the // shared between the application threads, and is therefore passed to the
// Application Builder as an atomic reference-counted pointer. // Application Builder as an atomic reference-counted pointer.
@ -50,5 +54,6 @@ fn main() -> io::Result<()> {
.service(user) .service(user)
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "template-tera" name = "template-tera"
version = "0.1.0" version = "2.0.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
@ -8,4 +8,5 @@ edition = "2018"
[dependencies] [dependencies]
env_logger = "0.6" env_logger = "0.6"
tera = "0.11" tera = "0.11"
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"

View File

@ -6,7 +6,7 @@ use std::collections::HashMap;
use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer}; use actix_web::{error, middleware, web, App, Error, HttpResponse, HttpServer};
// store tera template in application state // store tera template in application state
fn index( async fn index(
tmpl: web::Data<tera::Tera>, tmpl: web::Data<tera::Tera>,
query: web::Query<HashMap<String, String>>, query: web::Query<HashMap<String, String>>,
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
@ -24,7 +24,8 @@ fn index(
Ok(HttpResponse::Ok().content_type("text/html").body(s)) Ok(HttpResponse::Ok().content_type("text/html").body(s))
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info"); std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init(); env_logger::init();
@ -38,5 +39,6 @@ fn main() -> std::io::Result<()> {
.service(web::resource("/").route(web::get().to(index))) .service(web::resource("/").route(web::get().to(index)))
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .start()
.await
} }

View File

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

View File

@ -1,17 +1,18 @@
[package] [package]
authors = ["Dan Munckton <dangit@munckfish.net>"] authors = ["Dan Munckton <dangit@munckfish.net>"]
name = "actix-todo" name = "actix-todo"
version = "0.1.0" version = "2.0.0"
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
actix-web = "1.0.0" actix-web = "2.0.0-alpha.3"
actix-files = "0.1.1" actix-files = "0.2.0-alpha.3"
actix-session = "0.2.0" actix-session = "0.3.0-alpha.3"
actix-rt = "1.0.0-alpha.3"
dotenv = "0.13.0" dotenv = "0.13.0"
env_logger = "0.5.10" env_logger = "0.5.10"
futures = "0.1.22" futures = "0.3.1"
log = "0.4.3" log = "0.4.3"
serde = "1.0.69" serde = "1.0.69"
serde_derive = "1.0.69" serde_derive = "1.0.69"

135
todo/src/api.rs Normal file
View File

@ -0,0 +1,135 @@
use actix_files::NamedFile;
use actix_session::Session;
use actix_web::middleware::errhandlers::ErrorHandlerResponse;
use actix_web::{dev, error, http, web, Error, HttpResponse, Result};
use tera::{Context, Tera};
use crate::db;
use crate::session::{self, FlashMessage};
pub async fn index(
pool: web::Data<db::PgPool>,
tmpl: web::Data<Tera>,
session: Session,
) -> Result<HttpResponse, Error> {
let tasks = web::block(move || db::get_all_tasks(&pool)).await?;
let mut context = Context::new();
context.insert("tasks", &tasks);
//Session is set during operations on other endpoints
//that can redirect to index
if let Some(flash) = session::get_flash(&session)? {
context.insert("msg", &(flash.kind, flash.message));
session::clear_flash(&session);
}
let rendered = tmpl
.render("index.html.tera", &context)
.map_err(|e| error::ErrorInternalServerError(e.description().to_owned()))?;
Ok(HttpResponse::Ok().body(rendered))
}
#[derive(Deserialize)]
pub struct CreateForm {
description: String,
}
pub async fn create(
params: web::Form<CreateForm>,
pool: web::Data<db::PgPool>,
session: Session,
) -> Result<HttpResponse, Error> {
if params.description.is_empty() {
session::set_flash(
&session,
FlashMessage::error("Description cannot be empty"),
)?;
Ok(redirect_to("/"))
} else {
web::block(move || db::create_task(params.into_inner().description, &pool))
.await?;
session::set_flash(&session, FlashMessage::success("Task successfully added"))?;
Ok(redirect_to("/"))
}
}
#[derive(Deserialize)]
pub struct UpdateParams {
id: i32,
}
#[derive(Deserialize)]
pub struct UpdateForm {
_method: String,
}
pub async fn update(
db: web::Data<db::PgPool>,
params: web::Path<UpdateParams>,
form: web::Form<UpdateForm>,
session: Session,
) -> Result<HttpResponse, Error> {
match form._method.as_ref() {
"put" => toggle(db, params).await,
"delete" => delete(db, params, session).await,
unsupported_method => {
let msg = format!("Unsupported HTTP method: {}", unsupported_method);
Err(error::ErrorBadRequest(msg))
}
}
}
async fn toggle(
pool: web::Data<db::PgPool>,
params: web::Path<UpdateParams>,
) -> Result<HttpResponse, Error> {
web::block(move || db::toggle_task(params.id, &pool)).await?;
Ok(redirect_to("/"))
}
async fn delete(
pool: web::Data<db::PgPool>,
params: web::Path<UpdateParams>,
session: Session,
) -> Result<HttpResponse, Error> {
web::block(move || db::delete_task(params.id, &pool)).await?;
session::set_flash(&session, FlashMessage::success("Task was deleted."))?;
Ok(redirect_to("/"))
}
fn redirect_to(location: &str) -> HttpResponse {
HttpResponse::Found()
.header(http::header::LOCATION, location)
.finish()
}
pub fn bad_request<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
let new_resp = NamedFile::open("static/errors/400.html")?
.set_status_code(res.status())
.into_response(res.request())?;
Ok(ErrorHandlerResponse::Response(
res.into_response(new_resp.into_body()),
))
}
pub fn not_found<B>(res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
let new_resp = NamedFile::open("static/errors/404.html")?
.set_status_code(res.status())
.into_response(res.request())?;
Ok(ErrorHandlerResponse::Response(
res.into_response(new_resp.into_body()),
))
}
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(res.status())
.into_response(res.request())?;
Ok(ErrorHandlerResponse::Response(
res.into_response(new_resp.into_body()),
))
}

View File

@ -24,7 +24,8 @@ mod session;
static SESSION_SIGNING_KEY: &[u8] = &[0; 32]; static SESSION_SIGNING_KEY: &[u8] = &[0; 32];
fn main() -> io::Result<()> { #[actix_rt::main]
async fn main() -> io::Result<()> {
dotenv().ok(); dotenv().ok();
env::set_var("RUST_LOG", "actix_todo=debug,actix_web=info"); env::set_var("RUST_LOG", "actix_todo=debug,actix_web=info");
@ -54,14 +55,12 @@ fn main() -> io::Result<()> {
.wrap(Logger::default()) .wrap(Logger::default())
.wrap(session_store) .wrap(session_store)
.wrap(error_handlers) .wrap(error_handlers)
.service(web::resource("/").route(web::get().to_async(api::index))) .service(web::resource("/").route(web::get().to(api::index)))
.service(web::resource("/todo").route(web::post().to_async(api::create))) .service(web::resource("/todo").route(web::post().to(api::create)))
.service( .service(web::resource("/todo/{id}").route(web::post().to(api::update)))
web::resource("/todo/{id}").route(web::post().to_async(api::update)),
)
.service(fs::Files::new("/static", "static/")) .service(fs::Files::new("/static", "static/"))
}; };
debug!("Starting server"); debug!("Starting server");
HttpServer::new(app).bind("localhost:8088")?.run() HttpServer::new(app).bind("localhost:8088")?.start().await
} }

View File

@ -1,12 +1,11 @@
[package] [package]
name = "unix-socket" name = "unix-socket"
version = "0.1.0" version = "1.0.0"
authors = ["Messense Lv <messense@icloud.com>"] authors = ["Messense Lv <messense@icloud.com>"]
workspace = ".." workspace = ".."
edition = "2018" edition = "2018"
[dependencies] [dependencies]
env_logger = "0.5" env_logger = "0.6"
tokio-uds = "0.2" actix-web = "2.0.0-alpha.3"
actix-rt = "1.0.0-alpha.3"
actix-web = { version = "1.0.5", features = ["uds"] }

View File

@ -1,10 +1,12 @@
use actix_web::{middleware, web, App, HttpRequest, HttpServer}; use actix_web::{middleware, web, App, HttpRequest, HttpServer};
fn index(_req: HttpRequest) -> &'static str { async fn index(_req: HttpRequest) -> &'static str {
"Hello world!" "Hello world!"
} }
fn main() -> std::io::Result<()> { #[actix_rt::main]
#[cfg(unix)]
async fn main() -> std::io::Result<()> {
::std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); ::std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info");
env_logger::init(); env_logger::init();
@ -13,10 +15,18 @@ fn main() -> std::io::Result<()> {
// enable logger - always register actix-web Logger middleware last // enable logger - always register actix-web Logger middleware last
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service( .service(
web::resource("/index.html").route(web::get().to(|| "Hello world!")), web::resource("/index.html")
.route(web::get().to(|| async { "Hello world!" })),
) )
.service(web::resource("/").to(index)) .service(web::resource("/").to(index))
}) })
.bind_uds("/tmp/actix-uds.socket")? .bind_uds("/tmp/actix-uds.socket")?
.run() .start()
.await
}
#[cfg(not(unix))]
fn main() -> std::io::Result<()> {
println!("not supported");
Ok(())
} }