Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save crmckenzie/24ff6db72213af049024522967eb50f7 to your computer and use it in GitHub Desktop.
Save crmckenzie/24ff6db72213af049024522967eb50f7 to your computer and use it in GitHub Desktop.
actix-and-async.rs
/*
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