leyline-http 0.1.5

HTTP client that impersonates Chrome, Firefox, and Safari on the wire: TLS (JA3/JA4), HTTP/2, and HTTP/3 fingerprints
Documentation
use super::{TransportBody, TransportResponse, adopt, status};
use crate::core::body_stream::BodyStream;
use crate::core::error::{Error, Kind, Result};
use crate::core::response::{HttpVersion, ResponseTiming};
use crate::h2::client::{H2ResponseEx, ResponseBody};
use crate::h2::connection::PseudoHeaders;
use crate::header_str::HeaderStr;
use crate::pool::TlsInfo;
use crate::util::request_target;

const STANDARD_METHODS: [&str; 9] = [
    "GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "TRACE", "CONNECT",
];

fn method_header(method: &str) -> HeaderStr {
    STANDARD_METHODS
        .iter()
        .copied()
        .find(|known| *known == method)
        .map_or_else(|| HeaderStr::from(method), HeaderStr::from_static)
}

pub(super) fn request_pseudo(method: &str, url: &url::Url) -> Result<PseudoHeaders> {
    if url.scheme() != "https" {
        return Err(Error::new(Kind::Config).with_message("HTTP/2 requires an https:// URL"));
    }
    let host = url
        .host_str()
        .ok_or_else(|| Error::new(Kind::Config).with_message("no host in URL"))?;
    let port = url.port_or_known_default().unwrap_or(443);
    Ok(PseudoHeaders {
        method: method_header(method),
        scheme: HeaderStr::from_static("https"),
        authority: HeaderStr::from(crate::util::authority(url, host, port)),
        path: HeaderStr::from(request_target(url)),
        protocol: None,
    })
}

pub(super) fn transport_response(
    resp: H2ResponseEx,
    tls: TlsInfo,
    timing: ResponseTiming,
    url: &url::Url,
) -> Result<TransportResponse> {
    let body = match resp.body {
        ResponseBody::Buffered(b) => TransportBody::Buffered(b),
        ResponseBody::Streaming(rx) => TransportBody::Streaming(BodyStream::new(rx)),
    };
    Ok(TransportResponse {
        status: status(resp.status)?,
        headers: adopt(resp.headers),
        trailers: adopt(resp.trailers.unwrap_or_default()),
        body,
        final_url: url.clone(),
        version: HttpVersion::Http2,
        tls: Some(tls),
        timing,
    })
}