algesten / ureq

A simple, safe HTTP client

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How to handle "native_tls::HandshakeError::WouldBlock"?

johan-bjareholt opened this issue · comments

I am getting the following error on a few requests:

ErrorKind::Io.msg("Unexpected native_tls::HandshakeError::WouldBlock")

It is racy and timing dependent. According to the description of the native_tls documentation, it doesn't sound like this issue should ever occur?

A stream interrupted midway through the handshake process due to a WouldBlock error.

Note that this is not a fatal error and it should be safe to call handshake at a later time once the stream is ready to perform I/O again.

https://docs.rs/native-tls/latest/native_tls/enum.HandshakeError.html#variant.WouldBlock

How are you supposed to handle this in ureq? To me I would expect this request to rather retry again and possibly timeout, but now it just instantly fails.

Huh that's surprising. I think you should never get this so long as the provided io performs blocking I/O (like a TcpStream).

Could you share your code?

Sorry for the late reply, been busy. Will try to create a minimal example.

I found the issue, it happens when there's a really really slow server and the request times out while trying to do a tls handshake.

If you run the server and then start two clients at the same time, one of them will fail with "TimedOut" and the other with "WouldBlock", as the first one that starts will timeout reading the response while the other one times out during the tls handshake.

So it's not really a "bug" per se, ureq does what it is supposed to, but the "WouldBlock" error type is very confusing.

client

static ROOT_CERT_PEM : &str = r#"-----BEGIN CERTIFICATE-----
MIIDcTCCAlmgAwIBAgIURAwA7tQhPBDBQ88AeTljaZjwCbUwDQYJKoZIhvcNAQEL
BQAwTzELMAkGA1UEBhMCU0UxDzANBgNVBAgMBlNjYW5pYTENMAsGA1UEBwwETHVu
ZDENMAsGA1UECgwEQXhpczERMA8GA1UEAwwIdGVzdC5jb20wHhcNMjMxMTEyMTUy
MTA5WhcNMjQxMTEyMTUyMTA5WjBPMQswCQYDVQQGEwJTRTEPMA0GA1UECAwGU2Nh
bmlhMQ0wCwYDVQQHDARMdW5kMQ0wCwYDVQQKDARBeGlzMREwDwYDVQQDDAh0ZXN0
LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIjbuyzMmEbv+RAt
mfq747V+lGTSxMMbNe/o721Ybjt3aSigrazmO8TL9Qy33bWwVGRuizZCcomyJAML
8qPqp5yB6t9u0+jdnOFneQE3PDp5wpel0zUfj9HBhMTbL0NnS3fwTOYS9jGgy2VH
wCWPqT+0TxKBaomR+6khFXwSBOd6xpOfGy2Kb4bdTvMmSgO4jDJ9gHWUnjMuMiWq
mFswIu9ZqxtJoD9iJTC+NG2NlMWZDzTy/Eo6OVyJf6uBUdPEWpDz9cURFvu1QuxL
mKA3wP8l6Xlq17hPnkStzmZnKJCiuToVCjzbjT2fzJy/S3Iid+i1vD6yEV+WogCX
VFFMbM8CAwEAAaNFMEMwEgYDVR0TAQH/BAgwBgEB/wIBAjAOBgNVHQ8BAf8EBAMC
AQYwHQYDVR0OBBYEFAv8JD8ZmZX4RI7lzFHi8u4yS6KHMA0GCSqGSIb3DQEBCwUA
A4IBAQAhQAKFj3IXHjE5TsolKZzFsVFQcph7h76kyhj6nPmdg0rEmb6Qtg4HO5YU
4A+dGx0nJGPLAnLYmDIc7Pn+ZChsZcbcZbm+JBuHKYrueuLvr30lxB5KsoXsqKdB
ME/uFgkwxqdCmNidT6oG2y2TIo+qZKqBWmIgxGdkU73NkvtgQbuVDg3tHYemj0ni
E0NPBpWN0N1tkUO/zjnDnfx2Qog2R1I+XUG3jlikJgXMGhSlsmYifCTHKzuqSb7l
6FrT3Y5nzeBpEL4U9sv0i3JYFHglEwzXUsNBYXg5ejlpXUu4XiTXKgPCqMByVAWx
7KpMXzfJ7W2Zf2J9qJkD24CrKaKi
-----END CERTIFICATE-----"#;

fn request() {
    let string = "abcde ".repeat(5);

    let root_cert = native_tls::Certificate::from_pem(ROOT_CERT_PEM.as_bytes()).unwrap();

    let tls_connector = native_tls::TlsConnector::builder()
        .add_root_certificate(root_cert)
        .build()
        .unwrap();
    let agent = ureq::AgentBuilder::new()
        .tls_connector(tls_connector.into())
        .build();
    let req = agent.post("https://192.168.0.8:5000/")
        .timeout(std::time::Duration::from_secs(1))
        .set("Expect", "100-continue");
    let res = req.send(string.as_bytes()).unwrap();
    println!("res2: {:?}", res);
    assert_eq!(res.status(), 200);
    let body = res.into_string().unwrap();
    assert_eq!("Hello, world!", body);
}

fn main() {
    request();
}

#[test]
fn test_a() {
    for i in 1..1000 {simultaneously run 
        println!("\nRun {i}\n");
        request();
    }
}

server

#!/usr/bin/env python3

from flask import Flask, request
from werkzeug import serving
from time import sleep

app = Flask(__name__)

@app.route('/', methods = ['POST'])
def a():
    body = request.data
    sleep(2)
    return "Hello, world!"


if __name__=='__main__':
    cert_chain_path = "Entity-chain.crt"
    priv_key_path = "Entity.key"
    ssl_ctx = (cert_chain_path, priv_key_path)

    app.use_reloader = False
    server = serving.make_server(
        host="0.0.0.0",
        port=5000,
        app=app,
        ssl_context=ssl_ctx)
    server.serve_forever()