Created
April 2, 2020 03:43
-
-
Save crmckenzie/24ff6db72213af049024522967eb50f7 to your computer and use it in GitHub Desktop.
actix-and-async.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
cargo.toml | |
[dependencies] | |
actix = "0.9.0" | |
actix-rt = "1.0.0" | |
actix-web = { version="2.0.0", features=["openssl"] } | |
actix-service = "1.0.5" | |
env_logger = "0.6.1" | |
bytes = "0.5.4" | |
futures = "0.3.4" | |
reqwest = {version = "0.10.4", features = ["blocking"] } | |
serde = { version = "^1.0", features = ["derive"] } | |
serde_derive = "1.0.105" | |
serde_json = "1.0.39" | |
time = "0.2.9" | |
*/ | |
use actix_web::{App, HttpResponse, HttpServer}; | |
use std::io; | |
#[derive(Clone)] | |
struct Config { | |
rest: reqwest::Client, | |
} | |
#[actix_rt::main] | |
async fn main() -> io::Result<()> { | |
std::env::set_var("RUST_LOG", "actix_web=info"); | |
env_logger::init(); | |
let endpoint = "127.0.0.1:3000"; | |
println!("Starting server at: {:?}", endpoint); | |
let config = Config { | |
rest: reqwest::Client::new() | |
}; | |
HttpServer::new(move || { | |
App::new() | |
.configure(|context| { | |
context.data(config.clone()); | |
}) | |
.service(error_body) | |
.service(simple) | |
.service(sync_async) | |
.service(sync_sync) | |
.service(async_async) | |
}) | |
.keep_alive(1) // Send keep-alive every 1 seconds. | |
.shutdown_timeout(60) // Allow a timeout grace-period so remaining work can flush. | |
.client_timeout(15) // Set client timeout to 15 seconds to send headers | |
.client_shutdown(15) // Set client shutdown timeout to 15 seconds before dropping request. | |
.bind(endpoint) | |
.expect("Demo API failed to start http server.") | |
.run() | |
.await | |
} | |
/* | |
curl http://localhost:3000/sync-async | |
Hangs indefinitely. | |
*/ | |
#[actix_web::get("/sync-async")] | |
fn sync_async(config: actix_web::web::Data<Config>) -> HttpResponse { | |
/* | |
This example simplifies a lot of complexity. | |
In reality the api call is buried deep in an model that | |
is implemented via synchronous function calls. For | |
this reason, it would be a big change to modify everything | |
to be async all the way down. | |
*/ | |
let rest_client = config.rest.clone(); | |
let http_response = futures::executor::block_on(exec_reqwest_async(rest_client)); | |
match http_response { | |
Ok(_) => HttpResponse::Ok().json("Success!"), | |
Err(_) => HttpResponse::InternalServerError().json("No good."), | |
} | |
} | |
async fn exec_reqwest_async(client: reqwest::Client) -> Result<String, reqwest::Error> { | |
println!("Executing reqwest asychronously!"); | |
let body = client.get("https://www.rust-lang.org").send() | |
.await?; | |
println!("Reqwest executed!"); | |
let text = body | |
.text() | |
.await?; | |
println!("Body read!"); | |
Ok(text) | |
} | |
/* | |
curl http://localhost:3000/async-async | |
"Success!" | |
*/ | |
#[actix_web::get("/async-async")] | |
async fn async_async() -> HttpResponse { | |
/* | |
This example simplifies a lot of complexity. | |
In reality the api call is buried deep in an model that | |
is implemented via synchronous function calls. | |
This route works but it requires that we reimplement | |
everything as async/await. Not cool. | |
*/ | |
let http_response = exec_reqwest_async(reqwest::Client::new()).await; | |
match http_response { | |
Ok(_) => HttpResponse::Ok().json("Success!"), | |
Err(_) => HttpResponse::InternalServerError().json("No good."), | |
} | |
} | |
/* | |
This method fails with: | |
```bash | |
thread 'actix-rt:worker:4' panicked at 'Cannot start a runtime from within a runtime. | |
This happens because a function (like `block_on`) attempted to block the current | |
thread while the thread is being used to drive asynchronous tasks.' | |
``` | |
*/ | |
/* | |
curl http://localhost:3000/sync-sync | |
curl: (52) Empty reply from server | |
*/ | |
#[actix_web::get("/sync-sync")] | |
fn sync_sync() -> HttpResponse { | |
let http_response = exec_reqwest_sync(); | |
match http_response { | |
Ok(_) => HttpResponse::Ok().json("Success!"), | |
Err(_) => HttpResponse::InternalServerError().json("No good."), | |
} | |
} | |
fn exec_reqwest_sync() -> Result<String, reqwest::Error> { | |
println!("Executing reqwest synchronously!"); | |
let client = reqwest::blocking::Client::new(); | |
let res = client.get("https://www.rust-lang.org") | |
.send()?; | |
println!("Reqwest executed!"); | |
Ok(res.text()?) | |
} | |
/* | |
curl http://localhost:3000/sync-sync-simple | |
Hello World% | |
*/ | |
#[actix_web::get("sync-sync-simple")] | |
fn simple() -> HttpResponse { | |
let result = futures::executor::block_on(hello("World".to_string())); | |
HttpResponse::Ok().body(result) | |
} | |
async fn hello(text: String) -> String { | |
format!("Hello {}", text) | |
} | |
#[derive(serde::Serialize)] | |
struct Error { | |
message: String | |
} | |
/* | |
This method proves that putting a body in the `json` method | |
is now preserved. This required custom middleware in actix 1.x | |
curl http://localhost:3000/error-body | |
{"message":"Rut-roh--this didn't go well. (or did it?)"} | |
*/ | |
#[actix_web::get("error-body")] | |
fn error_body() -> HttpResponse { | |
HttpResponse::InternalServerError().json(Error { | |
message: "Rut-roh--this didn't go well. (or did it?)".to_string() | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment