Test von HTTPS Endpunkten mit hyper

hyper stellt eine schnelle HTTP Implementierung in Rust für Server und Clients zur Verfügung. Das Zusatz-Crate hyper-tls bringt die Implementierung von TLS mit.

Aufsetzend auf die asynchrone Laufzeitumgebung Tokio benötigen wir für Tests auch das tokio crate.

extern crate hyper;
extern crate hyper_tls;
extern crate tokio;

use hyper::rt::{Future, Stream};
use hyper::Client;
use hyper_tls::HttpsConnector;
use std::str;
use tokio::prelude::*;

In der Testumgebung müssen wir eine eigene Runtime instanzieren. Zum Auswerten des erfolgreichen Ablaufs des asynchronen Future verwende ich folgende Hilfsfunktion.

#[allow(dead_code)]
pub fn test_future<F>(f: F) -> Result<F::Item, F::Error>
where
    F: IntoFuture,
    F::Future: Send + 'static,
    F::Item: Send + 'static,
    F::Error: Send + 'static,
{
    let mut runtime = tokio::runtime::Runtime::new().expect("Unable to create a runtime");
    runtime.block_on(f.into_future())
}

Der eigentliche Test spezifiziert den HttpsConnector und baut einen Client.
Wir fragen hier per HTTP-GET eine kleine Textdatei ab.
Das Future gibt den HTTP-StatusCode als Nummer und die Textdatei als String als Ergebnis zurück.
Über concat2() wird hier der Body-Stream zusammengefasst.

#[test]
fn main_test() {
    let https = HttpsConnector::new(4).expect("TLS initialization failed");
    let client = Client::builder().build::<_, hyper::Body>(https);

    let fut = client
        .get(
            "https://mta-sts.gmail.com/.well-known/mta-sts.txt"
                .parse()
                .unwrap(),
        ).and_then(move |res| {
            let status = res.status().as_u16();
            res.into_body().concat2().and_then(move |body| {
                let s = str::from_utf8(&body).expect("mta-sts sends no utf-8");
                Ok((status, s.to_string()))
            })
        });
        

Der Aufruf unseres fut: Future und das matchen des Ergebnisses mit Überprüfung von status und body-Anfang. Ein evtl. Fehler würde den Test fehlschlagen lassen und den Fehler ausgeben.

    let r = test_future(fut);
    match r {
        Ok((status, body)) => {
            assert_eq!(status, 200);
            assert!(body.starts_with("version: STSv1"), format!("{}", body));
        }
        Err(e) => assert!(false, format!("error: {:?}", e)),
    }
}