diff --git a/Cargo.lock b/Cargo.lock index 0818ca4..6cf7f3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -564,6 +564,7 @@ dependencies = [ "actix-service 2.0.2", "actix-utils 3.0.0", "futures-core", + "http", "log", "openssl", "pin-project-lite 0.2.8", @@ -638,7 +639,7 @@ dependencies = [ "actix-tls 2.0.0", "actix-utils 2.0.0", "actix-web-codegen 0.4.0", - "awc", + "awc 2.0.3", "bytes 0.5.6", "derive_more", "encoding_rs", @@ -1227,6 +1228,41 @@ dependencies = [ "serde_urlencoded", ] +[[package]] +name = "awc" +version = "3.0.0-beta.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a64a95bbf4905fd057ea45b70fdfeeb9c7ae09f03f091642c4e66a16ebb3ad5" +dependencies = [ + "actix-codec 0.4.2", + "actix-http 3.0.0-rc.1", + "actix-rt 2.6.0", + "actix-service 2.0.2", + "actix-tls 3.0.2", + "actix-utils 3.0.0", + "ahash", + "base64 0.13.0", + "bytes 1.1.0", + "cfg-if 1.0.0", + "cookie 0.16.0", + "derive_more", + "futures-core", + "futures-util", + "h2 0.3.11", + "http", + "itoa 1.0.1", + "log", + "mime", + "percent-encoding", + "pin-project-lite 0.2.8", + "rand 0.8.4", + "rustls 0.20.2", + "serde 1.0.136", + "serde_json", + "serde_urlencoded", + "tokio 1.16.1", +] + [[package]] name = "awc_examples" version = "2.0.0" @@ -1244,8 +1280,13 @@ dependencies = [ name = "awc_https" version = "0.1.0" dependencies = [ - "actix-web 3.3.3", - "openssl", + "actix-web 4.0.0-rc.1", + "awc 3.0.0-beta.20", + "env_logger 0.9.0", + "log", + "mime", + "rustls 0.20.2", + "webpki-roots 0.22.2", ] [[package]] @@ -6758,7 +6799,7 @@ dependencies = [ "actix-files 0.3.0", "actix-web 3.3.3", "actix-web-actors", - "awc", + "awc 2.0.3", "bytes 0.5.6", "env_logger 0.8.4", "futures", diff --git a/security/awc_https/Cargo.toml b/security/awc_https/Cargo.toml index dbe26b2..f7f6ed7 100644 --- a/security/awc_https/Cargo.toml +++ b/security/awc_https/Cargo.toml @@ -5,6 +5,11 @@ authors = ["dowwie "] edition = "2021" [dependencies] -actix-web = "4.0.0-beta.21" -awc = {version = "3.0.0-beta.19", features = ["openssl"]} -openssl = "0.10.38" +actix-web = "4.0.0-rc.1" +awc = { version = "3.0.0-beta.19", features = ["rustls"] } + +env_logger = "0.9" +log = "0.4" +mime = "0.3" +rustls = "0.20" +webpki-roots = "0.22" diff --git a/security/awc_https/README.md b/security/awc_https/README.md index ba0c36e..84828bb 100644 --- a/security/awc_https/README.md +++ b/security/awc_https/README.md @@ -1,9 +1,5 @@ -The goal of this example is to show you how to use the actix-web client (awc) -for https related communication. As of actix-web 2.0.0, one must be very -careful about setting up https communication. **You could use the default -awc api without configuring ssl but performance will be severely diminished**. +The goal of this example is to show you how to use the `awc` for secure HTTPS communication using Rustls. -This example downloads a 1MB image from wikipedia. +It uses best practices for efficient client set up and demonstrates how to increase the default payload limit. -To run: -> curl http://localhost:3000 -o image.jpg +This example downloads a 10MB image from Wikipedia when hitting a server route. `cargo run` this example and go to in your browser. diff --git a/security/awc_https/src/main.rs b/security/awc_https/src/main.rs index 375b848..986783d 100644 --- a/security/awc_https/src/main.rs +++ b/security/awc_https/src/main.rs @@ -1,40 +1,83 @@ -use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer}; -use awc::{Client, Connector}; -use openssl::ssl::{SslConnector, SslMethod}; +use std::{sync::Arc, time::Instant}; -async fn index(_req: HttpRequest) -> HttpResponse { - let builder = SslConnector::builder(SslMethod::tls()).unwrap(); +use actix_web::{get, middleware, web::Data, App, HttpResponse, HttpServer}; +use awc::{http::header, Client, Connector}; +use rustls::{ClientConfig, OwnedTrustAnchor, RootCertStore}; - let client = Client::builder() - .connector(Connector::new().openssl(builder.build())) - .finish(); +const MAP_URL: &str = + "https://upload.wikimedia.org/wikipedia/commons/f/ff/Pizigani_1367_Chart_10MB.jpg"; - let now = std::time::Instant::now(); - let payload = - client - .get("https://upload.wikimedia.org/wikipedia/commons/b/b9/Pizigani_1367_Chart_1MB.jpg") - .send() - .await - .unwrap() +#[get("/")] +async fn fetch_image(client: Data) -> HttpResponse { + let start = Instant::now(); + + let mut res = client.get(MAP_URL).send().await.unwrap(); + + if !res.status().is_success() { + log::error!("Wikipedia did not return expected image"); + return HttpResponse::InternalServerError().finish(); + } + + let payload = res .body() - .limit(20_000_000) // sets max allowable payload size + // expected image is larger than default body limit + .limit(20_000_000) // 20MB .await .unwrap(); - println!( - "awc time elapsed while reading bytes into memory: {} ms", - now.elapsed().as_millis() + log::info!( + "it took {}ms to download image to memory", + start.elapsed().as_millis() ); - HttpResponse::Ok().content_type("image/jpeg").body(payload) + HttpResponse::Ok() + .content_type(mime::IMAGE_JPEG) + .body(payload) } #[actix_web::main] async fn main() -> std::io::Result<()> { - let port = 3000; + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - HttpServer::new(|| App::new().service(web::resource("/").to(index))) - .bind(("0.0.0.0", port))? - .run() - .await + let client_tls_config = Arc::new(rustls_config()); + + log::info!("starting HTTP serer at http://localhost:8080"); + + HttpServer::new(move || { + // create client _inside_ `HttpServer::new` closure to have one per worker thread + let client = Client::builder() + // Wikipedia requires a User-Agent header to make requests + .add_default_header((header::USER_AGENT, "awc-example/1.0")) + // a "connector" wraps the stream into an encrypted connection + .connector(Connector::new().rustls(Arc::clone(&client_tls_config))) + .finish(); + + App::new() + .wrap(middleware::Logger::default()) + .app_data(Data::new(client)) + .service(fetch_image) + }) + .bind(("127.0.0.1", 8080))? + .workers(2) + .run() + .await +} + +/// Create simple rustls client config from root certificates. +fn rustls_config() -> ClientConfig { + let mut root_store = RootCertStore::empty(); + root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map( + |ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + }, + )); + + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth() }