actix / actix-web

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.
https://actix.rs
Apache License 2.0
21.75k stars 1.68k forks source link

actix_web::client cannot use rust-tls for https #1045

Closed mooware closed 4 years ago

mooware commented 5 years ago

I tried to use actix_web::client::Client to send a request to an https url. It works with the "ssl" feature, but not with the "rust-tls" feature.

I condensed it to this sample code:

use actix_rt::System;
use actix_web::client::Client;
use futures::lazy;

fn main() {
    let resp = System::new("test").block_on(lazy(|| {
        Client::default()
            .get("https://www.rust-lang.org")
            .send()
    }));

    println!("Response: {:?}", resp.unwrap());
}

With "ssl" it works and prints the response headers. With "rust-tls" I get Connect(SslIsNotSupported).

mitsuhiko commented 5 years ago

I believe it works if the feature is turned on in the awc crate manually. It's missing here: https://github.com/actix/actix-web/blob/61e492e7e31fa1543f475b3cde465c89cc77f3b7/Cargo.toml#L65-L69

fafhrd91 commented 5 years ago

fixed in 1.0.6

marccarre commented 4 years ago

Just got bitten by Err(Connect(SslIsNotSupported)) too 😐. For posterity, and until this makes it to the documentation [1], please try the following (a.k.a. "enabling the SSL/TLS feature").

[1]: I am willing to open a PR to improve this. Where would you like this documented exactly?


In your Cargo.toml:


In your main.rs:

use actix_web::client::Client;

#[actix_rt::main]
async fn main() {
    let client = Client::default();

    // Create request builder and send request
    let response = client
        .get("https://www.rust-lang.org") // <--- notice the "s" in "https://..."
        .header("User-Agent", "Actix-web")
        .send()
        .await; // <- Send http request

    println!("Response: {:?}", response);
}

$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.12s
     Running `target/debug/rust-http-playground`
Response: Ok(
ClientResponse HTTP/2.0 200 OK
  headers:
    "strict-transport-security": "max-age=63072000"
    "x-amz-cf-id": "6gculLlBr5lYOjyFkFEErQpKKSY7w2xC70IvsL3ytGEk5HJoJMEMlQ=="
    "x-cache": "Miss from cloudfront"
    "x-xss-protection": "1; mode=block"
    "x-content-type-options": "nosniff"
    "via": "1.1 vegur, 1.1 77ffb7fa0ceed0e909a8f69baef40302.cloudfront.net (CloudFront)"
    "x-amz-cf-pop": "NRT20-C4"
    "content-security-policy": "default-src 'self'; frame-ancestors 'self'; img-src 'self' avatars.githubusercontent.com; frame-src 'self' player.vimeo.com"
    "content-length": "19220"
    "referrer-policy": "no-referrer, strict-origin-when-cross-origin"
    "vary": "Accept-Encoding"
    "server": "Rocket"
    "content-type": "text/html; charset=utf-8"
    "date": "Fri, 15 May 2020 07:32:08 GMT"
)
Pzixel commented 4 years ago

Unfortunately doesn't work to me in actix-web 2.0:

2020-05-15T13:42:33.392505900+03:00 DEBUG trust_dns_resolver::async_resolver::background - trust-dns resolver running
2020-05-15T13:42:33.398505100+03:00 DEBUG trust_dns_proto::xfer::dns_handle - querying: www.rust-lang.org A
2020-05-15T13:42:33.399506400+03:00 DEBUG trust_dns_resolver::name_server::name_server - reconnecting: NameServerConfig { socket_addr: V6([fec0:0:0:ffff::1]:53), protocol: Udp, tls_dns_name: None }
2020-05-15T13:42:33.399506400+03:00 DEBUG trust_dns_resolver::name_server::name_server - reconnecting: NameServerConfig { socket_addr: V6([fec0:0:0:ffff::2]:53), protocol: Udp, tls_dns_name: None }
2020-05-15T13:42:33.399506400+03:00 DEBUG trust_dns_resolver::name_server::connection_provider - connecting: Udp { socket_addr: V6([fec0:0:0:ffff::1]:53), timeout: 5s }
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer - enqueueing message: [Query { name: Name { is_fqdn: false, labels: [www, rust-lang, org] }, query_type: A, query_class: IN }]
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_resolver::name_server::connection_provider - connecting: Udp { socket_addr: V6([fec0:0:0:ffff::2]:53), timeout: 5s }
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer - enqueueing message: [Query { name: Name { is_fqdn: false, labels: [www, rust-lang, org] }, query_type: A, query_class: IN }]
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer::dns_exchange - connection established: UDP([fec0:0:0:ffff::1]:53)
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer::dns_exchange - sending message via: UDP([fec0:0:0:ffff::1]:53)
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer::dns_exchange - all handles closed, shutting down: UDP([fec0:0:0:ffff::1]:53)
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer::dns_exchange - io_stream is done, shutting down
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer::dns_exchange - connection established: UDP([fec0:0:0:ffff::2]:53)
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer::dns_exchange - sending message via: UDP([fec0:0:0:ffff::2]:53)
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer::dns_exchange - all handles closed, shutting down: UDP([fec0:0:0:ffff::2]:53)
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::xfer::dns_exchange - io_stream is done, shutting down
2020-05-15T13:42:33.400506700+03:00 DEBUG trust_dns_proto::udp::udp_stream - created socket successfully
2020-05-15T13:42:33.401506100+03:00 DEBUG trust_dns_proto::udp::udp_stream - created socket successfully
[src\clients.rs:28] response = Err(
    Connect(
        Timeout,
    ),
)

Windows 10 x64


In actix-web 3.0.0-alpha2 I still get Response: Err(Connect(Timeout)):

image


Same with "openssl"

Pzixel commented 4 years ago

I had to use reqwest insead, it works just fine to me. I'd love to not introduce extra dependencies but builtin client just doesn't work in my scenarios.

fafhrd91 commented 4 years ago

https://docs.rs/awc/2.0.0-alpha.1/awc/struct.Connector.html#method.rustls

Pzixel commented 4 years ago

Sorry, I didn't get what do you mean. I need to configure something to make it work?

ilyazub commented 4 years ago

Sorry, I didn't get what do you mean. I need to configure something to make it work?

@Pzixel I was able to configure https requests by following awc_https example.

// src/main.rs

use actix_web::client::{Client, Connector};
use openssl::ssl::{SslConnector, SslMethod};

#[actix_rt::main]
async fn main() {
    let builder = SslConnector::builder(SslMethod::tls()).unwrap();

    let client = Client::build()
        .connector(Connector::new().ssl(builder.build()).finish())
        .finish();

    // Create request builder and send request
    let response = client
        .get("https://www.rust-lang.org") // <--- notice the "s" in "https://..."
        .header("User-Agent", "Actix-web")
        .send()
        .await; // <- Send http request

    println!("Response: {:?}", response);
}

Which returns

ClientResponse HTTP/1.1 200 OK
  headers:
    "strict-transport-security": "max-age=63072000"
    "x-amz-cf-id": "nQUn97FTnU4iEG8giZxGeePvVyqrzp8jYsPspK2OvhrtFEMLIrdYiw=="
    "x-cache": "Miss from cloudfront"
    "x-xss-protection": "1; mode=block"
    "x-content-type-options": "nosniff"
    "via": "1.1 vegur, 1.1 650962b00c259fe47c193b15b2fe4b88.cloudfront.net (CloudFront)"
    "x-amz-cf-pop": "VIE50-C1"
    "content-security-policy": "default-src 'self'; frame-ancestors 'self'; img-src 'self' avatars.githubusercontent.com; frame-src 'self' player.vimeo.com"
    "content-length": "19220"
    "referrer-policy": "no-referrer, strict-origin-when-cross-origin"
    "vary": "Accept-Encoding"
    "server": "Rocket"
    "content-type": "text/html; charset=utf-8"
    "date": "Tue, 02 Jun 2020 10:51:59 GMT"
    "connection": "keep-alive"
)
Pzixel commented 4 years ago

Cool, I will try removing reqwest from my project then. Thanks. BRB with results (although not immediately, I need to switch to the task some moment later)

Pzixel commented 4 years ago

My first issue is how do I share client across threads? Arc<Client> doesn't seem to work:

error[E0277]: `std::rc::Rc<awc::ClientConfig>` cannot be sent between threads safely
  --> src\server.rs:28:18
   |
28 |     let server = HttpServer::new(move || {
   |                  ^^^^^^^^^^^^^^^ `std::rc::Rc<awc::ClientConfig>` cannot be sent between threads safely
   |
   = help: within `awc::Client`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<awc::ClientConfig>`
   = note: required because it appears within the type `awc::Client`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<awc::Client>`
   = note: required because it appears within the type `services::auth_service::AuthService`
   = note: required because it appears within the type `[closure@src\server.rs:28:34: 82:6 pool:diesel::r2d2::Pool<diesel::r2d2::ConnectionManager<diesel::PgConnection>>, everlasting_settings_service:&services::company_settings_service::CompanySettingsService, auth_service:services::auth_service::AuthService]`
   = note: required by `actix_web::server::HttpServer::<F, I, S, B>::new`

Or I should keep ClientConfig instead? It seems to be not public after all...

robjtede commented 4 years ago

It can't be shared between threads because it wraps an Rc so you'd need a mutex. I think creating one client per thread is acceptable.

Pzixel commented 4 years ago

But I need some hacky way to use them like thread_local. Since Client is just a newtype over Rc<ClientConfig> can we have a newtype for Arc<ClientConfig> or ClientConfig itself as public struct so I could construct Arc and then feed it into Client where I need?

Currently the only option is create a client per request (with entire config) or have thread_static values with client per thread, which doesn't look good either. I think I'd rather stick with reqwest for a while until it resolves

robjtede commented 4 years ago

What's wrong with:

HttpServer::new(move || {
    let client = actix_web::client::Client::default();
    App::new().data(client)
})

Which makes one client per thread without thread-local hacks.

Pzixel commented 4 years ago

I'm using it in middleware so it doesn't work like this. See:

pub fn run_server(
    application_url: &str,
    auth_service: AuthService,
    everlasting_settings_service: &'static CompanySettingsService,
    pool: Pool<ConnectionManager<PgConnection>>,
) -> io::Result<impl Future<Output = io::Result<()>>> {
    let server = HttpServer::new(move || {
        let integrations_service = IntegrationService::new(pool.clone());

        App::new()
            .data(integrations_service)
            .data(everlasting_settings_service)
            .authorized_route(
                API_PREFIX,
                web::get().to(handlers::get_about),
                auth_service.clone(),
            )
            .authorized_route(
                API_PREFIX.trim_end_matches('/'),
                web::get().to(handlers::get_about),
                auth_service.clone(),
            )
            .authorized_route(
                &[API_PREFIX, "v1/Integrations"].concat(),
                web::get().to(handlers::get_integrations),
                auth_service.clone(),
            )

...

impl<T, B> AppExt<T, B> for App<T, B>
where
    B: MessageBody,
    T: ServiceFactory<
        Config = (),
        Request = ServiceRequest,
        Response = ServiceResponse<B>,
        Error = actix_web::Error,
        InitError = (),
    >,
{
    fn authorized_route<P: IntoPattern>(
        self,
        path: P,
        route: Route,
        auth_service: AuthService,
    ) -> Self {
        let auth = HttpAuthentication::bearer(move |req, c| auth_service.validator(req, &c));
        self.service(resource(path).route(route).wrap(auth))
    }
}
Pzixel commented 4 years ago

Maybe somethink like this library could help: https://docs.rs/archery/0.3.0/archery/

Related to https://github.com/actix/actix-web/issues/1351

Zhappa commented 4 years ago

Sorry, I didn't get what do you mean. I need to configure something to make it work?

@Pzixel I was able to configure https requests by following awc_https example.

// src/main.rs

use actix_web::client::{Client, Connector};
use openssl::ssl::{SslConnector, SslMethod};

#[actix_rt::main]
async fn main() {
    let builder = SslConnector::builder(SslMethod::tls()).unwrap();

    let client = Client::build()
        .connector(Connector::new().ssl(builder.build()).finish())
        .finish();

    // Create request builder and send request
    let response = client
        .get("https://www.rust-lang.org") // <--- notice the "s" in "https://..."
        .header("User-Agent", "Actix-web")
        .send()
        .await; // <- Send http request

    println!("Response: {:?}", response);
}

Which returns

ClientResponse HTTP/1.1 200 OK
  headers:
    "strict-transport-security": "max-age=63072000"
    "x-amz-cf-id": "nQUn97FTnU4iEG8giZxGeePvVyqrzp8jYsPspK2OvhrtFEMLIrdYiw=="
    "x-cache": "Miss from cloudfront"
    "x-xss-protection": "1; mode=block"
    "x-content-type-options": "nosniff"
    "via": "1.1 vegur, 1.1 650962b00c259fe47c193b15b2fe4b88.cloudfront.net (CloudFront)"
    "x-amz-cf-pop": "VIE50-C1"
    "content-security-policy": "default-src 'self'; frame-ancestors 'self'; img-src 'self' avatars.githubusercontent.com; frame-src 'self' player.vimeo.com"
    "content-length": "19220"
    "referrer-policy": "no-referrer, strict-origin-when-cross-origin"
    "vary": "Accept-Encoding"
    "server": "Rocket"
    "content-type": "text/html; charset=utf-8"
    "date": "Tue, 02 Jun 2020 10:51:59 GMT"
    "connection": "keep-alive"
)

This is strange, but example never work on my machine, i always get something like:

Response on post: Err(
    Connect(
        Io(
            Custom {
                kind: Other,
                error: "the handshake failed: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed:ssl\\statem\\statem_clnt.c:1915:: unable to get local issuer certificate",
            },
        ),
    ),
)

The only way to make it work is to disable verification completely (which is not a case to work with)

async fn index(_req: HttpRequest) -> HttpResponse {
    let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
    builder.set_verify(SslVerifyMode::NONE);

    let client = Client::build()
        .connector(Connector::new().ssl(builder.build()).finish())
        .finish();

    let now = std::time::Instant::now();
    let payload =
        client
        .get("https://upload.wikimedia.org/wikipedia/commons/f/ff/Pizigani_1367_Chart_10MB.jpg")
        .send()
        .await
        .unwrap()
        .body()
        .limit(20_000_000)  // sets max allowable payload size
        .await
        .unwrap();

Can anybody verify this example works out of the box on his machine? (https://github.com/actix/examples/blob/22c8eaae87775d3da53ea3a73067c1a228a3a3a4/awc_https/src/main.rs#L8-L12)

Also my "rustup show":

stable-x86_64-pc-windows-gnu (default)
rustc 1.43.1 (8d69840ab 2020-05-04)

I using windows 10 x64

Pzixel commented 4 years ago

I can confirm it, I get the same error on freshly installed windows and OpenSSL:

image

robjtede commented 4 years ago

I can run that example without issues on macOS. Seems it's some bug with openssl on windows?

robjtede commented 4 years ago

Actaully @Zhappa or @Pzixel can you open a new issue for this, please.

Zhappa commented 4 years ago

https://github.com/actix/examples/issues/330 created

Tsingh315 commented 4 years ago

Is there a rustls example? I am getting connection timeout with rustls. Openssl is just impossible to compile in windows 10. Tried it for a day.

captain-yossarian commented 3 years ago

Somebody please share example how to do it with actix_web 3

kkalavantavanich commented 3 years ago

FWIW, I was able to use openssl client by following Pzixel's comment: https://github.com/actix/actix-web/issues/1045#issuecomment-637457941. I never got it working it actix-web 3 + rustls + macos though.

5pecia1 commented 2 years ago

I was able to successfully call with https in actix_web 4, awc 3.

actix = "0.13.0"
actix-web = "4"
awc = { version = "3.0.0", features = ["openssl"] }
openssl = "0.10.40"
victormongi commented 3 weeks ago

Help, anyone know how to setup TLS SSL / openssl with actix-web on windows 11? thank you

robjtede commented 3 weeks ago

@victormongi see the examples repo.