Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
mooware opened this issue Aug 17, 2019 · 24 comments
Closed

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

mooware opened this issue Aug 17, 2019 · 24 comments

Comments

@mooware
Copy link

mooware commented Aug 17, 2019

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
Copy link
Member

I believe it works if the feature is turned on in the awc crate manually. It's missing here:

actix-web/Cargo.toml

Lines 65 to 69 in 61e492e

# openssl
ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
# rustls
rust-tls = ["rustls", "actix-server/rust-tls"]

@fafhrd91
Copy link
Member

fixed in 1.0.6

@marccarre
Copy link

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:

  • Using rustls:

    [dependencies]
    actix-rt = "1.1.1"
    actix-web = { version = "3.0.0-alpha.2", features=["rustls"] }
    rustls = "0.17.0"
  • Using openssl:

    [dependencies]
    actix-rt = "1.1.1"
    actix-web = { version = "3.0.0-alpha.2", features=["openssl"] }
    openssl = "0.10.29"

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
Copy link

Pzixel commented May 15, 2020

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
Copy link

Pzixel commented May 15, 2020

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
Copy link
Member

@Pzixel
Copy link

Pzixel commented May 18, 2020

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

@ilyazub
Copy link

ilyazub commented Jun 2, 2020

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
Copy link

Pzixel commented Jun 2, 2020

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
Copy link

Pzixel commented Jun 2, 2020

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
Copy link
Member

robjtede commented Jun 2, 2020

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
Copy link

Pzixel commented Jun 2, 2020

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
Copy link
Member

robjtede commented Jun 2, 2020

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
Copy link

Pzixel commented Jun 2, 2020

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
Copy link

Pzixel commented Jun 2, 2020

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

Related to #1351

@Zhappa
Copy link

Zhappa commented Jun 2, 2020

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
Copy link

Pzixel commented Jun 2, 2020

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

image

@robjtede
Copy link
Member

robjtede commented Jun 9, 2020

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

@robjtede robjtede reopened this Jun 9, 2020
@robjtede
Copy link
Member

robjtede commented Jun 9, 2020

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

@Zhappa
Copy link

Zhappa commented Jun 10, 2020

actix/examples#330 created

@Tsingh315
Copy link

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
Copy link

Somebody please share example how to do it with actix_web 3

@kkalavantavanich
Copy link

FWIW, I was able to use openssl client by following Pzixel's comment: #1045 (comment). I never got it working it actix-web 3 + rustls + macos though.

@5pecia1
Copy link

5pecia1 commented Jun 1, 2022

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"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests