├── .gitignore ├── .vscode ├── settings.json └── launch.json ├── src ├── client.rs ├── lib.rs ├── response.rs ├── unix │ ├── response.rs │ ├── mod.rs │ └── request.rs ├── method.rs ├── windows │ ├── response.rs │ ├── callback.rs │ ├── request.rs │ ├── mod.rs │ └── err_code.rs └── prelude.rs ├── examples ├── https.rs ├── sequential.rs ├── parallel.rs ├── macos_bindgen.rs └── download.rs ├── LICENSE ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.cargo.noDefaultFeatures": true, 3 | "rust-analyzer.cargo.allTargets": true, 4 | // x86_64-apple-darwin 5 | // x86_64-pc-windows-msvc 6 | // x86_64-unknown-linux-gnu 7 | // "rust-analyzer.cargo.target": null, 8 | } -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Client { 3 | #[cfg(target_os = "windows")] 4 | pub(crate) h_session: crate::windows::Handle, 5 | #[cfg(target_os = "windows")] 6 | pub(crate) connections: 7 | std::sync::Mutex>>, 8 | } 9 | 10 | #[derive(Debug, Clone, Default)] 11 | pub struct ClientBuilder {} 12 | -------------------------------------------------------------------------------- /examples/https.rs: -------------------------------------------------------------------------------- 1 | use alhc::prelude::*; 2 | use alhc::*; 3 | 4 | use pollster::FutureExt; 5 | 6 | fn main() -> DynResult { 7 | let client = get_client_builder().build().unwrap(); 8 | let data = "Hello World!".repeat(256); 9 | 10 | let r = client 11 | .post("https://httpbin.org/post")? 12 | .header("user-agent", "alhc/0.2.0") 13 | .body_string(data) 14 | .block_on()? 15 | .recv_string() 16 | .block_on()?; 17 | 18 | println!("{r}"); 19 | 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | mod client; 4 | mod method; 5 | pub mod prelude; 6 | mod response; 7 | pub use client::*; 8 | pub use method::*; 9 | pub use response::*; 10 | #[cfg(target_os = "windows")] 11 | pub mod windows; 12 | 13 | #[cfg(unix)] 14 | pub mod unix; 15 | 16 | #[cfg(not(any(unix, target_os = "windows")))] 17 | compile_error!("ALHC is currently not supported on your target os."); 18 | 19 | #[cfg(not(feature = "anyhow"))] 20 | pub type DynResult = std::result::Result>; 21 | #[cfg(feature = "anyhow")] 22 | pub type DynResult = anyhow::Result; 23 | 24 | pub fn get_client_builder() -> impl prelude::CommonClientBuilder { 25 | ClientBuilder::default() 26 | } 27 | -------------------------------------------------------------------------------- /src/response.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use std::borrow::Cow; 4 | 5 | pub struct ResponseBody { 6 | pub(crate) data: Vec, 7 | pub(crate) code: u16, 8 | pub(crate) headers: HashMap, 9 | } 10 | 11 | impl ResponseBody { 12 | pub fn into_data(self) -> Vec { 13 | self.data 14 | } 15 | 16 | pub fn data(&self) -> &[u8] { 17 | &self.data 18 | } 19 | 20 | pub fn data_string(&self) -> Cow { 21 | String::from_utf8_lossy(&self.data) 22 | } 23 | 24 | #[cfg(feature = "serde")] 25 | pub fn data_json(self) -> crate::DynResult { 26 | Ok(serde_json::from_slice(&self.data)?) 27 | } 28 | 29 | pub fn status_code(&self) -> u16 { 30 | self.code 31 | } 32 | 33 | pub fn header(&self, header: &str) -> Option<&str> { 34 | self.headers 35 | .keys() 36 | .find(|x| x.eq_ignore_ascii_case(header)) 37 | .and_then(|x| self.headers.get(x).map(String::as_str)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Steve Xiao 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/unix/response.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use futures_lite::{AsyncRead, AsyncReadExt}; 4 | use isahc::AsyncBody; 5 | 6 | use crate::ResponseBody; 7 | 8 | pin_project_lite::pin_project! { 9 | pub struct CURLResponse { 10 | #[pin] 11 | pub(crate) res: AsyncBody, 12 | pub(crate) code: u16, 13 | pub(crate) headers: HashMap, 14 | } 15 | } 16 | 17 | impl AsyncRead for CURLResponse { 18 | fn poll_read( 19 | self: std::pin::Pin<&mut Self>, 20 | cx: &mut std::task::Context<'_>, 21 | buf: &mut [u8], 22 | ) -> std::task::Poll> { 23 | self.project().res.poll_read(cx, buf) 24 | } 25 | } 26 | 27 | #[cfg_attr(feature = "async_t", async_t::async_trait)] 28 | impl crate::prelude::CommonResponse for CURLResponse { 29 | async fn recv(mut self) -> std::io::Result { 30 | let mut data = Vec::with_capacity(1024 * 1024); 31 | self.read_to_end(&mut data).await?; 32 | Ok(ResponseBody { 33 | data, 34 | code: self.code, 35 | headers: self.headers, 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/unix/mod.rs: -------------------------------------------------------------------------------- 1 | //! Platform specific implementation for Unix (Linux and macOS) 2 | //! 3 | //! Currently using [`isahc` crate](https://github.com/sagebind/isahc) for compability, 4 | //! will be replaced by simpler code directly using [`curl` crate](https://github.com/alexcrichton/curl-rust). 5 | 6 | mod request; 7 | mod response; 8 | 9 | pub use request::CURLRequest; 10 | pub use response::CURLResponse; 11 | 12 | use isahc::HttpClient; 13 | use once_cell::sync::Lazy; 14 | 15 | use crate::{ 16 | prelude::{CommonClient, CommonClientBuilder}, 17 | Client, ClientBuilder, 18 | }; 19 | 20 | pub(super) static SHARED: Lazy = 21 | Lazy::new(|| HttpClient::new().expect("shared client failed to initialize")); 22 | 23 | impl CommonClient for Client { 24 | type ClientRequest = CURLRequest; 25 | 26 | fn request(&self, method: crate::Method, url: &str) -> crate::DynResult { 27 | Ok(CURLRequest::new( 28 | isahc::http::request::Builder::new() 29 | .method(method.as_str()) 30 | .uri(url), 31 | )) 32 | } 33 | } 34 | 35 | impl CommonClientBuilder for ClientBuilder { 36 | fn build(&self) -> crate::DynResult { 37 | Ok(Client {}) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/method.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub enum Method { 3 | GET, 4 | POST, 5 | HEAD, 6 | PATCH, 7 | PUT, 8 | TRACE, 9 | DELETE, 10 | CONNECT, 11 | OPTIONS, 12 | } 13 | 14 | impl Method { 15 | pub fn as_str(&self) -> &'static str { 16 | match self { 17 | Method::GET => "GET", 18 | Method::POST => "POST", 19 | Method::HEAD => "HEAD", 20 | Method::PATCH => "PATCH", 21 | Method::PUT => "PUT", 22 | Method::TRACE => "TRACE", 23 | Method::DELETE => "DELETE", 24 | Method::CONNECT => "CONNECT", 25 | Method::OPTIONS => "OPTIONS", 26 | } 27 | } 28 | 29 | // For windows only 30 | #[cfg(target_os = "windows")] 31 | pub(crate) fn as_raw_str_wide(&self) -> *const u16 { 32 | let data: &[u16] = match self { 33 | Method::GET => &[71, 69, 84, 0], 34 | Method::POST => &[80, 79, 83, 84, 0], 35 | Method::HEAD => &[72, 69, 65, 68, 0], 36 | Method::PATCH => &[80, 65, 84, 67, 72, 0], 37 | Method::PUT => &[80, 85, 84, 0], 38 | Method::TRACE => &[84, 82, 65, 67, 69, 0], 39 | Method::DELETE => &[68, 69, 76, 69, 84, 69, 0], 40 | Method::CONNECT => &[67, 79, 78, 78, 69, 67, 84, 0], 41 | Method::OPTIONS => &[79, 80, 84, 73, 79, 78, 83, 0], 42 | }; 43 | data.as_ptr() 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/sequential.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, time::Instant}; 2 | 3 | use alhc::prelude::*; 4 | use alhc::*; 5 | 6 | use pollster::FutureExt; 7 | 8 | fn main() { 9 | // tracing_subscriber::fmt() 10 | // .with_max_level(tracing::Level::DEBUG) 11 | // .init(); 12 | async { 13 | let client = get_client_builder().build().unwrap(); 14 | 15 | let mut success = 0; 16 | let mut failed = 0; 17 | 18 | println!("Sending httpbin"); 19 | 20 | for i in 0..10 { 21 | let instant = Instant::now(); 22 | println!("Requesting {}", i); 23 | let r = client 24 | .post("https://httpbin.org/anything")? 25 | .body_string("Hello World!".repeat(1000)) 26 | .await? 27 | .recv_string() 28 | .await; 29 | if let Err(err) = &r { 30 | println!("Request {} Error: {:?}", i, err); 31 | } else { 32 | let e = instant.elapsed().as_millis(); 33 | println!("Request {i} ok in {e}ms : {r:?}"); 34 | } 35 | 36 | match r { 37 | Ok(_) => { 38 | success += 1; 39 | println!("Request {} ok", i); 40 | } 41 | Err(err) => { 42 | failed += 1; 43 | println!("Request {} Error: {}", i, err); 44 | } 45 | } 46 | } 47 | 48 | println!( 49 | "Sent {} requests, {} succeed, {} failed", 50 | success + failed, 51 | success, 52 | failed 53 | ); 54 | 55 | Ok::<(), Box>(()) 56 | } 57 | .block_on() 58 | .unwrap(); 59 | } 60 | -------------------------------------------------------------------------------- /examples/parallel.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, sync::Arc, time::Instant}; 2 | 3 | use alhc::prelude::*; 4 | use alhc::*; 5 | 6 | use futures::future::join_all; 7 | use pollster::FutureExt; 8 | 9 | fn main() { 10 | // tracing_subscriber::fmt() 11 | // .with_max_level(Level::DEBUG) 12 | // .init(); 13 | async { 14 | let client = Arc::new(get_client_builder().build().unwrap()); 15 | 16 | let mut success = 0; 17 | let mut failed = 0; 18 | 19 | println!("Sending httpbin"); 20 | 21 | for (i, r) in join_all((0..10).map(|i| { 22 | let client = client.clone(); 23 | async move { 24 | let instant = Instant::now(); 25 | let r = client 26 | .post("http://httpbin.org/anything")? 27 | .body_string(format!("Requesting {}", i).repeat(4)); 28 | println!("Requesting {}", i); 29 | let r = r.await?.recv_string().await; 30 | if let Err(err) = &r { 31 | println!("Request {} Error: {}", i, err); 32 | } else { 33 | let e = instant.elapsed().as_millis(); 34 | println!("Request {} ok in {}ms", i, e); 35 | } 36 | Ok::>(r?) 37 | } 38 | })) 39 | .await 40 | .into_iter() 41 | .enumerate() 42 | { 43 | match r { 44 | Ok(_) => { 45 | success += 1; 46 | // println!("Request {} ok", i); 47 | } 48 | Err(err) => { 49 | failed += 1; 50 | println!("Request {} Error: {}", i, err); 51 | } 52 | } 53 | } 54 | 55 | println!( 56 | "Sent {} requests, {} succeed, {} failed", 57 | success + failed, 58 | success, 59 | failed 60 | ); 61 | 62 | DynResult::Ok(()) 63 | } 64 | .block_on() 65 | .unwrap(); 66 | } 67 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "alhc" 3 | version = "0.3.0-alpha.4" 4 | authors = ["Steve Xiao "] 5 | homepage = "https://github.com/Steve-xmh/alhc" 6 | repository = "https://github.com/Steve-xmh/alhc" 7 | description = "Async Lightweight HTTP Client: A async http client library that using system library to reduce binary size if possible. (Currently under heavy development)" 8 | keywords = ["http", "client", "request", "async"] 9 | categories = ["web-programming", "web-programming::http-client"] 10 | edition = "2021" 11 | readme = "README.md" 12 | license = "MIT" 13 | 14 | [package.metadata.docs.rs] 15 | targets = [ 16 | "x86_64-pc-windows-msvc", 17 | "x86_64-unknown-linux-gnu", 18 | "x86_64-apple-darwin", 19 | "i686-pc-windows-msvc", 20 | "i686-unknown-linux-gnu", 21 | "aarch64-unknown-linux-gnu", 22 | "aarch64-apple-darwin", 23 | ] 24 | 25 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 26 | 27 | [features] 28 | default = [] 29 | async_t_boxed = ["dep:async_t", "async_t/boxed"] 30 | serde = ["dep:serde", "dep:serde_json"] 31 | anyhow = ["dep:anyhow"] 32 | 33 | [target.'cfg(target_os = "windows")'.dependencies] 34 | windows-sys = { version = "0.52", features = [ 35 | "Win32_Foundation", 36 | "Win32_Networking_WinHttp", 37 | ]} 38 | 39 | 40 | [target.'cfg(unix)'.dependencies] 41 | once_cell = "1" 42 | 43 | [target.'cfg(unix)'.dependencies.isahc] 44 | version = "1.7" 45 | default-features = false 46 | 47 | [dev-dependencies] 48 | futures = "0.3" 49 | pollster = "0.3" 50 | smol = "2.0" 51 | tracing = "0.1.37" 52 | tracing-subscriber = "0.3.17" 53 | 54 | [dependencies] 55 | futures-lite = "2.3" 56 | pin-project-lite = "0.2" 57 | serde = { version = "1.0", optional = true } 58 | serde_json = { version = "1.0", optional = true } 59 | anyhow = { version = "1.0", optional = true } 60 | async_t = { version = "0.7", optional = true } 61 | # tracing = "0.1.37" 62 | 63 | # Some size optimization here 64 | [profile.release] 65 | lto = true 66 | codegen-units = 1 67 | panic = "abort" 68 | opt-level = "z" 69 | debug = false 70 | strip = true 71 | -------------------------------------------------------------------------------- /examples/macos_bindgen.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(target_os = "macos"))] 2 | fn main() {} 3 | 4 | #[cfg(target_os = "macos")] 5 | fn main() { 6 | // let output = std::process::Command::new("xcrun") 7 | // .arg("--sdk") 8 | // .arg("macosx") 9 | // .arg("--show-sdk-path") 10 | // .output() 11 | // .unwrap(); 12 | // let output = String::from_utf8_lossy(&output.stdout).to_string(); 13 | // let sysroot = output.trim(); 14 | 15 | // let bindgen_prefix = r#" 16 | // #![allow(unused)] 17 | // #![allow(non_snake_case)] 18 | 19 | // use core_foundation::runloop::*; 20 | // use core_foundation::data::*; 21 | // use core_foundation::dictionary::*; 22 | // use core_foundation::array::*; 23 | // use core_foundation::string::*; 24 | // use core_foundation::url::*; 25 | 26 | // #[repr(C)] 27 | // pub struct __CFAllocator(::core::ffi::c_void); 28 | // #[repr(C)] 29 | // pub struct __CFError(::core::ffi::c_void); 30 | // #[repr(C)] 31 | // pub struct __CFReadStream(::core::ffi::c_void); 32 | // #[repr(C)] 33 | // pub struct __CFWriteStream(::core::ffi::c_void); 34 | // #[repr(C)] 35 | // pub struct __CFHost(::core::ffi::c_void); 36 | // #[repr(C)] 37 | // pub struct __CFNetService(::core::ffi::c_void); 38 | // #[repr(C)] 39 | // pub struct __CFNetServiceMonitor(::core::ffi::c_void); 40 | // #[repr(C)] 41 | // pub struct __CFNetServiceBrowser(::core::ffi::c_void); 42 | // #[repr(C)] 43 | // pub struct __CFHTTPMessage(::core::ffi::c_void); 44 | // #[repr(C)] 45 | // pub struct _CFHTTPAuthentication(::core::ffi::c_void); 46 | // #[repr(C)] 47 | // pub struct __CFNetDiagnostic(::core::ffi::c_void); 48 | 49 | // "#; 50 | 51 | // let r = bindgen::Builder::default() 52 | // .header("./src/macos/wrapper.h") 53 | // .merge_extern_blocks(true) 54 | // .use_core() 55 | // .generate_comments(true) 56 | // .allowlist_file(".*CFNetwork.framework/Headers/.*") 57 | // .blocklist_item("_+.*") 58 | // .generate_block(true) 59 | // .sort_semantically(true) 60 | // .clang_arg(format!("-isysroot{}", sysroot)) 61 | // .generate() 62 | // .unwrap(); 63 | 64 | // let r = r.to_string(); 65 | 66 | // let r = r.replace( 67 | // "extern \"C\" {", 68 | // r#" 69 | // #[link(name = "CFNetwork", kind = "framework")] 70 | // extern "C" {"#, 71 | // ); 72 | 73 | // std::fs::write( 74 | // "./src/macos/cf_network.rs", 75 | // format!("{}{}", bindgen_prefix, r), 76 | // ) 77 | // .unwrap(); 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Async Lightweight HTTP Client (aka ALHC) 2 | 3 | > **WARNING** 4 | > 5 | > This library is still in development and **VERY UNSTABLE**, please don't use it in production environment. 6 | 7 | [github.com](https://github.com/Steve-xmh/alhc) 8 | [crates.io](https://crates.io/crates/alhc) 9 | [docs.rs](https://docs.rs/alhc) 10 | 11 | What if we need async but also lightweight http client without using such a large library like `reqwest`, `isahc` or `surf`? 12 | 13 | ALHC is a async http client library that using System library to reduce binary size and provide async request feature. 14 | 15 | HTTPS Example: 16 | 17 | ```rust 18 | use alhc::prelude::*; 19 | use alhc::*; 20 | 21 | use pollster::FutureExt; 22 | 23 | fn main() -> DynResult { 24 | let client = get_client_builder().build().unwrap(); 25 | 26 | let r = client 27 | .post("https://httpbin.org/anything")? 28 | .header("user-agent", "alhc/0.2.0") 29 | .body_string("Hello World!".repeat(20)) 30 | .block_on()? 31 | .recv_string() 32 | .block_on()?; 33 | 34 | println!("{r}"); 35 | 36 | Ok(()) 37 | } 38 | ``` 39 | 40 | Our little request example [`https`](./examples/https.rs) with release build can be 182 KB, which is smaller than `tinyget`'s `http` example. If we use rustc nightly feature plus `build-std` and `panic_immediate_abort`, it'll be incredibly 65 KB! 41 | 42 | Currently work in progress and support Windows (Using WinHTTP) and unix-like system (including macOS) (Using System libcurl by wraping [`isahc`](https://github.com/sagebind/isahc) crate (Will be replaced by simplier `curl` crate binding)). 43 | 44 | ## Platform Status 45 | 46 | | Name | Status | Note | 47 | | ------- | ------- | -------------------------------------------------------------------- | 48 | | Windows | Working | Maybe unstable (To be optimized) | 49 | | macOS | Working | Simple wrapper of [`isahc`](https://github.com/sagebind/isahc) crate | 50 | | Linux | Working | Simple wrapper of [`isahc`](https://github.com/sagebind/isahc) crate | 51 | 52 | ## Features 53 | 54 | - `async_t_boxed`: Use `async-trait` instead of `async-t`, which requires 1.75+ version of rustc but with zero-cost. Default is disabled. 55 | - `serde`: Can give you the ability of send/receive json data without manually call `serde_json`. Default is disabled. 56 | - `anyhow`: Use `Result` type from `anyhow` crate instead `Result>`. Default is disabled. 57 | 58 | ## Minimum binary size on unix-like platform guideline 59 | 60 | For Unix-like system like linux or macOS which have builtin libcurl on almost all desktop version, you have to install all the development packages that `curl` crate needs to dynamic link these libraries. For an example, on Ubuntu, you need to install `libcurl4-openssl-dev` and `zlib1g-dev` for a dynamic linkage. Else `curl` crate will build from source and static link them and heavily impact binary size. 61 | 62 | ## Compilation binary size comparison 63 | 64 | > Note: the size optimization argument is: `cargo +nightly run --release -Z build-std=core,alloc,std,panic_abort -Z build-std-features=panic_immediate_abort --target [TARGET] --example [EXAMPLE]` and some configuration in [`Cargo.toml`](./Cargo.toml) 65 | 66 | | Name | Windows (x86_64) | Windows (i686) | Windows (aarch64) | macOS (x86_64) | macOS (aarch64) | Linux (x86_64) | 67 | | --------------------------------------------------- | ---------------: | -------------: | ----------------: | -------------: | --------------: | -------------: | 68 | | example `https` | 468,992 | 402,944 | 296,960 | 4,078,936 | 4,395,400 | 18,051,064 | 69 | | example `https` release | 181,248 | 162,816 | 200,192 | 729,304 | 719,192 | 850,704 | 70 | | example `https` release with size optimization | 75,264 | 66,048 | 59,392 | 444,680 | 453,064 | 465,480 | 71 | | example `parallel` | 571,904 | 486,912 | 393,216 | 4,250,296 | 4,572,120 | 19,612,824 | 72 | | example `parallel` release | 190,464 | 170,496 | 229,888 | 737,536 | 735,752 | 862,992 | 73 | | example `parallel` release with size optimization | 80,896 | 71,680 | 66,560 | 452,912 | 453,112 | 469,576 | 74 | | example `sequential` | 472,064 | 405,504 | 302,080 | 4,069,368 | 4,373,352 | 18,048,624 | 75 | | example `sequential` release | 182,784 | 164,864 | 203,264 | 729,296 | 719,176 | 850,704 | 76 | | example `sequential` release with size optimization | 76,800 | 68,096 | 60,928 | 448,776 | 453,064 | 465,480 | 77 | -------------------------------------------------------------------------------- /examples/download.rs: -------------------------------------------------------------------------------- 1 | use std::{sync::Arc, time::Instant}; 2 | 3 | use alhc::prelude::*; 4 | use alhc::*; 5 | use futures::future::join_all; 6 | use pollster::FutureExt; 7 | 8 | fn main() -> DynResult { 9 | async { 10 | let download_url = Arc::new(std::env::args().last().unwrap_or_default()); 11 | 12 | if std::env::args().count() <= 1 || download_url.is_empty() { 13 | println!( 14 | "usage: {} ", 15 | std::env::args().collect::>().join(" ") 16 | ); 17 | return Ok(()); 18 | } 19 | 20 | let chunk_amount = 4; 21 | let client = Arc::new({ 22 | // c.set_timeout(Duration::from_secs(2)); 23 | get_client_builder().build().unwrap() 24 | }); 25 | 26 | println!("Downloading from url: {}", download_url); 27 | 28 | let head_resp = client.head(&download_url)?.await?.recv().await?; 29 | if let Some(content_length) = head_resp 30 | .header("Content-Length") 31 | .and_then(|x| x.parse::().ok()) 32 | { 33 | println!("Content Length: {} bytes", content_length); 34 | let time = Instant::now(); 35 | let mut chunk_jobs = Vec::with_capacity(chunk_amount); 36 | let chunk_size = content_length / chunk_amount; 37 | for i in 0..chunk_amount { 38 | let start_pos = i * chunk_size; 39 | let end_pos = if i == chunk_amount - 1 { 40 | content_length 41 | } else { 42 | start_pos + chunk_size 43 | }; 44 | let client = client.clone(); 45 | let download_url = download_url.clone(); 46 | chunk_jobs.push(async move { 47 | loop { 48 | let req = client.get(&download_url)?; 49 | let req_job = async { 50 | println!("Chunk {} has started: {}-{}", i, start_pos, end_pos); 51 | let time = Instant::now(); 52 | let req = req 53 | .header("Range", &format!("bytes={}-{}", start_pos, end_pos)) 54 | .header("User-Agent", "alhc/0.1.0"); 55 | let res = req.await?; 56 | let chunk_file = smol::fs::OpenOptions::new() 57 | .create(true) 58 | .truncate(true) 59 | .write(true) 60 | .open(format!("test.chunk.{}.tmp", i)) 61 | .await?; 62 | smol::io::copy(res, chunk_file).await?; 63 | let time = time.elapsed().as_secs_f64(); 64 | println!("Chunk {} has finished: {}s", i, time); 65 | DynResult::Ok(()) 66 | }; 67 | match req_job.await { 68 | Ok(_) => return DynResult::Ok(()), 69 | Err(e) => { 70 | println!("Chunk {} failed to fetch, retrying: {}", i, e); 71 | } 72 | } 73 | } 74 | }); 75 | } 76 | let r = join_all(chunk_jobs).await; 77 | let r = r.into_iter().all(|b| b.is_ok()); 78 | if r { 79 | println!("All chunk downloaded successfully, concating into one file"); 80 | let time = Instant::now(); 81 | let mut result = smol::fs::File::create("test.bin").await?; 82 | for i in 0..chunk_amount { 83 | println!("Concatenating chunk: {}", i); 84 | let chunk_file = smol::fs::File::open(format!("test.chunk.{}.tmp", i)).await?; 85 | smol::io::copy(chunk_file, &mut result).await?; 86 | } 87 | let time = time.elapsed().as_secs_f64(); 88 | println!("File finished to concat: {}s", time); 89 | } else { 90 | println!("Some of chunks failed to download, cleaning chunks"); 91 | } 92 | // Clean temp chunks 93 | for i in 0..chunk_amount { 94 | let _ = smol::fs::remove_file(format!("test.chunk.{}.tmp", i)).await; 95 | } 96 | let time = time.elapsed().as_secs_f64(); 97 | println!("File downloaded: {}s", time); 98 | } else { 99 | println!("Content Length: unknown"); 100 | 101 | let req = client.get(&download_url)?; 102 | let time = Instant::now(); 103 | let res = req.await?; 104 | let result_file = smol::fs::OpenOptions::new() 105 | .create(true) 106 | .truncate(true) 107 | .write(true) 108 | .open("test.bin") 109 | .await?; 110 | smol::io::copy(res, result_file).await?; 111 | let time = time.elapsed().as_secs_f64(); 112 | println!("File downloaded: {}s", time); 113 | } 114 | Ok(()) 115 | } 116 | .block_on() 117 | } 118 | -------------------------------------------------------------------------------- /src/unix/request.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | pin::Pin, 4 | task::{Context, Poll}, 5 | }; 6 | 7 | use futures_lite::{AsyncRead, Future, FutureExt}; 8 | use isahc::{AsyncBody, ResponseFuture}; 9 | 10 | use crate::{prelude::CommonRequest, DynResult}; 11 | 12 | use super::{response::CURLResponse, SHARED}; 13 | 14 | #[derive(Clone, Copy)] 15 | enum RequestState { 16 | Building, 17 | Recv, 18 | } 19 | 20 | pub struct CURLRequest { 21 | state: RequestState, 22 | req_builder: Option, 23 | body: Option>, 24 | res: Option>, 25 | } 26 | 27 | impl CURLRequest { 28 | pub(crate) fn new(req_builder: isahc::http::request::Builder) -> Self { 29 | Self { 30 | state: RequestState::Building, 31 | req_builder: Some(req_builder), 32 | body: None, 33 | res: None, 34 | } 35 | } 36 | } 37 | 38 | impl Future for CURLRequest { 39 | type Output = DynResult; 40 | 41 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 42 | match self.state { 43 | RequestState::Building => { 44 | if let Some(req_builder) = self.req_builder.take() { 45 | let body = self 46 | .body 47 | .take() 48 | .unwrap_or_else(|| Box::new(futures_lite::io::empty())); 49 | match req_builder.body(AsyncBody::from_reader(body)) { 50 | Ok(req) => { 51 | let res = SHARED.send_async(req); 52 | self.res = Some(res); 53 | self.state = RequestState::Recv; 54 | cx.waker().wake_by_ref(); 55 | Poll::Pending 56 | } 57 | Err(_) => Poll::Ready(Err({ 58 | #[cfg(not(feature = "anyhow"))] 59 | { 60 | Box::from("isahc error") 61 | } 62 | #[cfg(feature = "anyhow")] 63 | { 64 | anyhow::anyhow!("isahc error") 65 | } 66 | })), 67 | } 68 | } else { 69 | Poll::Ready(Err({ 70 | #[cfg(not(feature = "anyhow"))] 71 | { 72 | Box::from("already polled") 73 | } 74 | #[cfg(feature = "anyhow")] 75 | { 76 | anyhow::anyhow!("already polled") 77 | } 78 | })) 79 | } 80 | } 81 | RequestState::Recv => { 82 | if let Some(res) = &mut self.as_mut().res { 83 | match res.poll(cx) { 84 | Poll::Ready(Ok(res)) => { 85 | let code = res.status().as_u16(); 86 | let mut headers = HashMap::with_capacity(res.headers().len()); 87 | for (name, value) in res.headers().iter() { 88 | headers.insert( 89 | name.as_str().to_string(), 90 | String::from_utf8_lossy(value.as_bytes()).into_owned(), 91 | ); 92 | } 93 | Poll::Ready(Ok(CURLResponse { 94 | res: res.into_body(), 95 | code, 96 | headers, 97 | })) 98 | } 99 | Poll::Ready(Err(_)) => Poll::Ready(Err({ 100 | #[cfg(not(feature = "anyhow"))] 101 | { 102 | Box::from("isahc error") 103 | } 104 | #[cfg(feature = "anyhow")] 105 | { 106 | anyhow::anyhow!("isahc error") 107 | } 108 | })), 109 | Poll::Pending => Poll::Pending, 110 | } 111 | } else { 112 | Poll::Ready(Err({ 113 | #[cfg(not(feature = "anyhow"))] 114 | { 115 | Box::from("already polled") 116 | } 117 | #[cfg(feature = "anyhow")] 118 | { 119 | anyhow::anyhow!("already polled") 120 | } 121 | })) 122 | } 123 | } 124 | } 125 | } 126 | } 127 | 128 | impl CommonRequest for CURLRequest { 129 | fn body( 130 | mut self, 131 | new_body: impl AsyncRead + Unpin + Send + Sync + 'static, 132 | _body_size: usize, 133 | ) -> Self { 134 | self.body = Some(Box::new(new_body)); 135 | self 136 | } 137 | 138 | fn header(mut self, header: &str, value: &str) -> Self { 139 | let req_builder = self.req_builder.take(); 140 | if let Some(req_builder) = req_builder { 141 | self.req_builder = Some(req_builder.header(header, value)); 142 | } 143 | self 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/windows/response.rs: -------------------------------------------------------------------------------- 1 | use futures_lite::*; 2 | use std::{ 3 | collections::HashMap, 4 | pin::Pin, 5 | sync::{ 6 | mpsc::{Receiver, TryRecvError}, 7 | Arc, 8 | }, 9 | task::Poll, 10 | }; 11 | use windows_sys::Win32::Networking::WinHttp::{WinHttpQueryDataAvailable, WinHttpReadData}; 12 | 13 | use super::{err_code::resolve_io_error, Handle, NetworkContext, WinHTTPCallbackEvent, BUF_SIZE}; 14 | use crate::{prelude::*, ResponseBody}; 15 | 16 | pub struct WinHTTPResponse { 17 | pub(super) _connection: Arc, 18 | pub(super) h_request: Arc, 19 | pub(super) raw_headers: String, 20 | pub(super) ctx: Pin>, 21 | pub(super) buf: Pin>, 22 | pub(super) read_size: usize, 23 | pub(super) total_read_size: usize, 24 | pub(super) callback_receiver: Receiver, 25 | } 26 | 27 | #[cfg_attr(feature = "async_t", async_t::async_trait)] 28 | impl CommonResponse for WinHTTPResponse { 29 | async fn recv(mut self) -> std::io::Result { 30 | let mut data = Vec::with_capacity(256); 31 | self.read_to_end(&mut data).await?; 32 | data.shrink_to_fit(); 33 | let mut headers_lines = self.raw_headers.lines(); 34 | 35 | let status_code = headers_lines 36 | .next() 37 | .and_then(|x| x.split(' ').nth(1).map(|x| x.parse::().unwrap_or(0))) 38 | .unwrap_or(0); 39 | 40 | let mut parsed_headers: HashMap = 41 | HashMap::with_capacity(headers_lines.size_hint().1.unwrap_or(8)); 42 | 43 | for header in headers_lines { 44 | if let Some((key, value)) = header.split_once(": ") { 45 | let key = key.trim(); 46 | let value = value.trim(); 47 | if let Some(exist_header) = parsed_headers.get_mut(key) { 48 | exist_header.push_str("; "); 49 | exist_header.push_str(value); 50 | } else { 51 | parsed_headers.insert(key.to_owned(), value.to_owned()); 52 | } 53 | } 54 | } 55 | 56 | Ok(ResponseBody { 57 | data, 58 | code: status_code, 59 | headers: parsed_headers, 60 | }) 61 | } 62 | } 63 | 64 | impl AsyncRead for WinHTTPResponse { 65 | fn poll_read( 66 | mut self: Pin<&mut Self>, 67 | cx: &mut std::task::Context<'_>, 68 | buf: &mut [u8], 69 | ) -> Poll> { 70 | if self.ctx.as_mut().waker.is_none() { 71 | self.ctx.as_mut().waker = Some(cx.waker().clone()); 72 | let r = unsafe { WinHttpQueryDataAvailable(**self.h_request, std::ptr::null_mut()) }; 73 | if r == 0 { 74 | return Poll::Ready(Err(resolve_io_error())); 75 | } 76 | } 77 | if self.ctx.has_completed { 78 | return Poll::Ready(Ok(0)); 79 | } 80 | if self.ctx.buf_size != usize::MAX && self.read_size < self.ctx.buf_size { 81 | let read_size = self 82 | .ctx 83 | .buf_size 84 | .min(buf.len()) 85 | .min(self.ctx.buf_size - self.read_size); 86 | buf[..read_size].copy_from_slice(&self.buf[self.read_size..self.read_size + read_size]); 87 | self.read_size += read_size; 88 | self.total_read_size += read_size; 89 | return Poll::Ready(Ok(read_size)); 90 | } 91 | match self.callback_receiver.try_recv() { 92 | Ok(event) => { 93 | let result = match event { 94 | WinHTTPCallbackEvent::DataAvailable => { 95 | self.read_size = 0; 96 | self.ctx.buf_size = usize::MAX; 97 | let h_request = **self.h_request; 98 | let buf = self.buf.as_mut_slice(); 99 | let r = unsafe { 100 | WinHttpReadData( 101 | h_request, 102 | buf.as_mut_ptr() as _, 103 | buf.len() as _, 104 | std::ptr::null_mut(), 105 | ) 106 | }; 107 | if r == 0 { 108 | return Poll::Ready(Err(resolve_io_error())); 109 | } 110 | Poll::Pending 111 | } 112 | WinHTTPCallbackEvent::DataWritten => { 113 | if self.ctx.buf_size == 0 { 114 | Poll::Ready(Ok(0)) 115 | } else { 116 | let r = unsafe { 117 | WinHttpQueryDataAvailable(**self.h_request, std::ptr::null_mut()) 118 | }; 119 | if r == 0 { 120 | return Poll::Ready(Err(resolve_io_error())); 121 | } 122 | Poll::Pending 123 | } 124 | } 125 | WinHTTPCallbackEvent::Error(err) => Poll::Ready(Err(err)), 126 | _ => unreachable!(), 127 | }; 128 | cx.waker().wake_by_ref(); 129 | result 130 | } 131 | Err(TryRecvError::Empty) => Poll::Pending, 132 | Err(TryRecvError::Disconnected) => { 133 | Poll::Ready(Err(std::io::Error::other("channel has been disconnected"))) 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/windows/callback.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_void, OsString}, 3 | os::windows::ffi::OsStringExt, 4 | }; 5 | 6 | use windows_sys::Win32::{ 7 | Foundation::{GetLastError, ERROR_INSUFFICIENT_BUFFER}, 8 | Networking::WinHttp::*, 9 | }; 10 | 11 | use crate::windows::{ 12 | err_code::{resolve_io_error, resolve_io_error_from_error_code}, 13 | WinHTTPCallbackEvent, 14 | }; 15 | 16 | use super::NetworkContext; 17 | 18 | pub unsafe extern "system" fn status_callback( 19 | h_request: *mut c_void, 20 | dw_context: usize, 21 | dw_internet_status: u32, 22 | lpv_status_infomation: *mut c_void, 23 | dw_status_infomation_length: u32, 24 | ) { 25 | let ctx = dw_context as *mut NetworkContext; 26 | 27 | if let Some(ctx) = ctx.as_mut() { 28 | match dw_internet_status { 29 | WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE => { 30 | let _ = ctx 31 | .callback_sender 32 | .send(WinHTTPCallbackEvent::WriteCompleted); 33 | if let Some(waker) = &ctx.waker { 34 | waker.wake_by_ref(); 35 | } 36 | } 37 | WINHTTP_CALLBACK_STATUS_WRITE_COMPLETE => { 38 | let _ = ctx 39 | .callback_sender 40 | .send(WinHTTPCallbackEvent::WriteCompleted); 41 | if let Some(waker) = &ctx.waker { 42 | waker.wake_by_ref(); 43 | } 44 | } 45 | WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE => { 46 | let mut header_size = 0; 47 | 48 | let r = WinHttpQueryHeaders( 49 | h_request, 50 | WINHTTP_QUERY_RAW_HEADERS_CRLF, 51 | std::ptr::null(), 52 | std::ptr::null_mut(), 53 | &mut header_size, 54 | std::ptr::null_mut(), 55 | ); 56 | 57 | if r == 0 { 58 | let code = GetLastError(); 59 | if code != ERROR_INSUFFICIENT_BUFFER { 60 | let _ = ctx 61 | .callback_sender 62 | .send(WinHTTPCallbackEvent::Error(resolve_io_error())); 63 | if let Some(waker) = &ctx.waker { 64 | waker.wake_by_ref(); 65 | } 66 | return; 67 | } 68 | } 69 | 70 | let mut header_data = vec![0u16; header_size as _]; 71 | 72 | let r = WinHttpQueryHeaders( 73 | h_request, 74 | WINHTTP_QUERY_RAW_HEADERS_CRLF, 75 | std::ptr::null(), 76 | header_data.as_mut_ptr() as *mut _, 77 | &mut header_size, 78 | std::ptr::null_mut(), 79 | ); 80 | 81 | if r == 0 { 82 | let _ = ctx 83 | .callback_sender 84 | .send(WinHTTPCallbackEvent::Error(resolve_io_error())); 85 | if let Some(waker) = &ctx.waker { 86 | waker.wake_by_ref(); 87 | } 88 | return; 89 | } 90 | 91 | let header_data = OsString::from_wide(&header_data) 92 | .to_string_lossy() 93 | .trim_end_matches('\0') 94 | .to_string(); 95 | 96 | let _ = ctx 97 | .callback_sender 98 | .send(WinHTTPCallbackEvent::RawHeadersReceived(header_data)); 99 | 100 | if let Some(waker) = &ctx.waker { 101 | waker.wake_by_ref(); 102 | } 103 | } 104 | WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE => { 105 | if let Some(waker) = &ctx.waker { 106 | waker.wake_by_ref(); 107 | } 108 | } 109 | WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED => { 110 | if let Some(waker) = &ctx.waker { 111 | waker.wake_by_ref(); 112 | } 113 | } 114 | WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED => { 115 | if let Some(waker) = &ctx.waker { 116 | waker.wake_by_ref(); 117 | } 118 | } 119 | WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE => { 120 | let _ = ctx 121 | .callback_sender 122 | .send(WinHTTPCallbackEvent::DataAvailable); 123 | if let Some(waker) = &ctx.waker { 124 | waker.wake_by_ref(); 125 | } 126 | } 127 | WINHTTP_CALLBACK_STATUS_READ_COMPLETE => { 128 | ctx.buf_size = dw_status_infomation_length as usize; 129 | ctx.has_completed = ctx.buf_size == 0; 130 | let _ = ctx.callback_sender.send(WinHTTPCallbackEvent::DataWritten); 131 | if let Some(waker) = &ctx.waker { 132 | waker.wake_by_ref(); 133 | } 134 | } 135 | WINHTTP_CALLBACK_STATUS_REQUEST_ERROR => { 136 | let result = (lpv_status_infomation as *mut WINHTTP_ASYNC_RESULT) 137 | .as_ref() 138 | .unwrap(); 139 | 140 | if result.dwError != ERROR_WINHTTP_OPERATION_CANCELLED { 141 | let _ = ctx.callback_sender.send(WinHTTPCallbackEvent::Error( 142 | resolve_io_error_from_error_code(result.dwError as _), 143 | )); 144 | 145 | if let Some(waker) = &ctx.waker { 146 | waker.wake_by_ref(); 147 | } 148 | } 149 | } 150 | _other => { 151 | if let Some(waker) = &ctx.waker { 152 | waker.wake_by_ref(); 153 | } 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | use crate::{Method, ResponseBody}; 2 | use core::future::Future; 3 | use core::time::Duration; 4 | use futures_lite::io::Cursor; 5 | use futures_lite::AsyncRead; 6 | 7 | #[cfg(target_os = "windows")] 8 | pub type Request = crate::windows::WinHTTPRequest; 9 | #[cfg(target_os = "windows")] 10 | pub type Response = crate::windows::WinHTTPResponse; 11 | #[cfg(unix)] 12 | pub type Request = crate::unix::CURLRequest; 13 | #[cfg(unix)] 14 | pub type Response = crate::unix::CURLResponse; 15 | 16 | /// A trait that will be implemented by all request type in ALHC. 17 | /// 18 | /// All the request will implement [`Future`] 19 | /// and output a response with [`CommonResponse`] and [`AsyncRead`] trait 20 | /// implemented. 21 | /// 22 | /// To pass struct that implemented [`serde::ser::Serialize`] as a json body, 23 | /// you can enable `serde` feature and use [`CommonRequestSerdeExt::body_json`]. 24 | /// 25 | /// [`AsyncRead`] allows you to read data in chunks of bytes without load all in 26 | /// memory. 27 | /// 28 | /// [`CommonResponse`] provided some convenient methods can help you receive 29 | /// small data like text or JSON. 30 | pub trait CommonRequest: Future 31 | where 32 | Self: Sized, 33 | { 34 | /// Provide data as a body in request 35 | fn body(self, body: impl AsyncRead + Unpin + Send + Sync + 'static, body_size: usize) -> Self; 36 | /// Provide string data as a body in request 37 | fn body_string(self, body: String) -> Self { 38 | let len = body.len(); 39 | self.body(Cursor::new(body), len) 40 | } 41 | /// Provide binary data as a body in request 42 | fn body_bytes(self, body: Vec) -> Self { 43 | let len = body.len(); 44 | self.body(Cursor::new(body), len) 45 | } 46 | /// Add a header value, will keep exists same header. 47 | fn header(self, header: &str, value: &str) -> Self; 48 | /// Replace a header value, add if not exists. 49 | fn replace_header(self, header: &str, value: &str) -> Self { 50 | self.header(header, value) 51 | } 52 | } 53 | 54 | #[cfg(feature = "serde")] 55 | /// A trait that allows you to pass struct that implemented 56 | /// [`serde::ser::Serialize`] as a json body. 57 | pub trait CommonRequestSerdeExt: CommonRequest { 58 | fn body_json(self, body: &T) -> crate::DynResult { 59 | Ok(self.body_string(serde_json::to_string(body)?)) 60 | } 61 | } 62 | 63 | #[cfg(feature = "serde")] 64 | impl CommonRequestSerdeExt for R {} 65 | 66 | #[cfg_attr(feature = "async_t", async_t::async_trait)] 67 | #[cfg_attr(not(feature = "async_t"), allow(async_fn_in_trait))] 68 | /// A trait that will be implemented by all response type in ALHC. 69 | /// 70 | /// All the response will implement [`AsyncRead`], which allows you to read data 71 | /// in chunks of bytes without load all in memory. 72 | /// 73 | pub trait CommonResponse: AsyncRead 74 | where 75 | Self: Sized + Unpin, 76 | { 77 | /// Receive all data in memory and return a [`ResponseBody`] 78 | /// 79 | /// You can get binary data, status code or headers in it. 80 | async fn recv(self) -> std::io::Result; 81 | 82 | /// Convenient method to receive data as string. 83 | async fn recv_string(self) -> std::io::Result { 84 | Ok(self.recv().await?.data_string().into_owned()) 85 | } 86 | 87 | /// Convenient method to receive data as binary data. 88 | async fn recv_bytes(self) -> std::io::Result> { 89 | Ok(self.recv().await?.data) 90 | } 91 | } 92 | 93 | #[cfg(feature = "serde")] 94 | #[cfg_attr(feature = "async_t", async_t::async_trait)] 95 | #[cfg_attr(not(feature = "async_t"), allow(async_fn_in_trait))] 96 | /// A trait that allows you to receive json data and deserialize data into 97 | /// a struct that implemented [`serde::ser::Serialize`]. 98 | pub trait CommonResponseSerdeExt: CommonResponse { 99 | /// Convenient method to receive data as a json data and deserialize data 100 | /// into a struct. 101 | async fn recv_json(self) -> crate::DynResult { 102 | Ok(serde_json::from_str(&self.recv_string().await?)?) 103 | } 104 | } 105 | 106 | #[cfg(feature = "serde")] 107 | impl CommonResponseSerdeExt for R {} 108 | 109 | /// A trait that all [`Client`] will implement, allow you to send request and 110 | /// set some options for it. 111 | pub trait CommonClient { 112 | type ClientRequest: CommonRequest; 113 | /// Invoke a request with a method and a url, will return a 114 | /// [`CommonRequest`] implementation. 115 | fn request(&self, method: Method, url: &str) -> crate::DynResult; 116 | /// Set connection timeout for new client. 117 | /// 118 | /// Maybe no effect due to the implementation on platform. 119 | fn set_timeout(&mut self, _max_timeout: Duration) {} 120 | } 121 | 122 | /// Some convenient methods about [`CommonClient`]. 123 | pub trait CommonClientExt: CommonClient { 124 | /// A wrapper of `CommonClient::request(Method::GET, url)` 125 | fn get(&self, url: &str) -> crate::DynResult { 126 | self.request(Method::GET, url) 127 | } 128 | 129 | /// A wrapper of `CommonClient::request(Method::POST, url)` 130 | fn post(&self, url: &str) -> crate::DynResult { 131 | self.request(Method::POST, url) 132 | } 133 | 134 | /// A wrapper of `CommonClient::request(Method::PUT, url)` 135 | fn put(&self, url: &str) -> crate::DynResult { 136 | self.request(Method::PUT, url) 137 | } 138 | 139 | /// A wrapper of `CommonClient::request(Method::DELETE, url)` 140 | fn delete(&self, url: &str) -> crate::DynResult { 141 | self.request(Method::DELETE, url) 142 | } 143 | 144 | /// A wrapper of `CommonClient::request(Method::HEAD, url)` 145 | fn head(&self, url: &str) -> crate::DynResult { 146 | self.request(Method::HEAD, url) 147 | } 148 | 149 | /// A wrapper of `CommonClient::request(Method::PATCH, url)` 150 | fn patch(&self, url: &str) -> crate::DynResult { 151 | self.request(Method::PATCH, url) 152 | } 153 | 154 | /// A wrapper of `CommonClient::request(Method::OPTIONS, url)` 155 | fn options(&self, url: &str) -> crate::DynResult { 156 | self.request(Method::OPTIONS, url) 157 | } 158 | } 159 | 160 | impl CommonClientExt for C {} 161 | 162 | pub trait CommonClientBuilder { 163 | fn build(&self) -> crate::DynResult; 164 | } 165 | -------------------------------------------------------------------------------- /src/windows/request.rs: -------------------------------------------------------------------------------- 1 | use futures_lite::AsyncRead; 2 | use std::future::Future; 3 | use std::{fmt::Debug, sync::mpsc::TryRecvError}; 4 | use std::{pin::Pin, sync::Arc}; 5 | use windows_sys::Win32::Networking::WinHttp::{ 6 | WinHttpAddRequestHeaders, WINHTTP_ADDREQ_FLAG_REPLACE, 7 | }; 8 | 9 | use self::err_code::resolve_io_error; 10 | 11 | use super::*; 12 | 13 | use crate::prelude::*; 14 | 15 | pin_project_lite::pin_project! { 16 | pub struct WinHTTPRequest { 17 | pub(super) _connection: Arc, 18 | pub(super) h_request: Arc, 19 | #[pin] 20 | pub(super) body: Box, 21 | pub(super) body_len: usize, 22 | pub(super) callback_receiver: Receiver, 23 | pub(super) buf: Pin>, 24 | pub(super) ctx: Pin>, 25 | } 26 | } 27 | 28 | impl Debug for WinHTTPRequest { 29 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 30 | f.debug_struct("Request") 31 | .field("connection", &self._connection) 32 | .field("h_request", &self.h_request) 33 | .field("body_len", &self.body_len) 34 | .field("callback_receiver", &self.callback_receiver) 35 | .field("ctx", &self.ctx) 36 | .finish() 37 | } 38 | } 39 | 40 | impl CommonRequest for WinHTTPRequest { 41 | fn body( 42 | mut self, 43 | body: impl AsyncRead + Unpin + Send + Sync + 'static, 44 | body_size: usize, 45 | ) -> Self { 46 | self.body_len = body_size; 47 | self.body = Box::new(body); 48 | self 49 | } 50 | 51 | fn header(self, header: &str, value: &str) -> Self { 52 | let headers = format!("{}:{}", header, value); 53 | let headers = headers.to_utf16().as_ptr(); 54 | 55 | unsafe { 56 | WinHttpAddRequestHeaders(**self.h_request, headers, u32::MAX, WINHTTP_ADDREQ_FLAG_ADD); 57 | } 58 | 59 | self 60 | } 61 | 62 | fn replace_header(self, header: &str, value: &str) -> Self { 63 | let headers = format!("{}:{}", header, value); 64 | let headers = headers.to_utf16().as_ptr(); 65 | 66 | unsafe { 67 | WinHttpAddRequestHeaders( 68 | **self.h_request, 69 | headers, 70 | u32::MAX, 71 | WINHTTP_ADDREQ_FLAG_REPLACE, 72 | ); 73 | } 74 | 75 | self 76 | } 77 | } 78 | 79 | impl Future for WinHTTPRequest { 80 | type Output = futures_lite::io::Result; 81 | 82 | fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { 83 | if self.ctx.as_mut().waker.is_none() { 84 | self.ctx.as_mut().waker = Some(cx.waker().clone()); 85 | let send_result = unsafe { 86 | WinHttpSendRequest( 87 | **self.h_request, 88 | std::ptr::null(), 89 | 0, 90 | std::ptr::null(), 91 | 0, 92 | self.body_len as _, 93 | self.ctx.as_mut().get_unchecked_mut() as *mut _ as usize, 94 | ) 95 | }; 96 | if send_result == 0 { 97 | return Poll::Ready(Err(resolve_io_error())); 98 | } 99 | } 100 | match self.callback_receiver.try_recv() { 101 | Ok(event) => match event { 102 | WinHTTPCallbackEvent::WriteCompleted => { 103 | let project = self.project(); 104 | match project.body.poll_read(cx, project.buf.as_mut_slice()) { 105 | Poll::Ready(Ok(size)) => { 106 | if size == 0 { 107 | let h_request = ***project.h_request; 108 | let r = unsafe { 109 | WinHttpReceiveResponse(h_request, std::ptr::null_mut()) 110 | }; 111 | if r == 0 { 112 | return Poll::Ready(Err(resolve_io_error())); 113 | } 114 | } else { 115 | let h_request = ***project.h_request; 116 | let buf = project.buf.as_ptr(); 117 | let r = unsafe { 118 | WinHttpWriteData( 119 | h_request, 120 | buf as *const c_void, 121 | size as _, 122 | std::ptr::null_mut(), 123 | ) 124 | }; 125 | if r == 0 { 126 | return Poll::Ready(Err(resolve_io_error())); 127 | } 128 | } 129 | Poll::Pending 130 | } 131 | Poll::Ready(Err(err)) => Poll::Ready(Err(err)), 132 | Poll::Pending => Poll::Pending, 133 | } 134 | } 135 | WinHTTPCallbackEvent::RawHeadersReceived(raw_headers) => { 136 | let (ctx, mut rx) = NetworkContext::new(); 137 | let mut ctx = Box::pin(ctx); 138 | std::mem::swap(&mut ctx, &mut self.ctx); 139 | std::mem::swap(&mut rx, &mut self.callback_receiver); 140 | ctx.waker = None; 141 | ctx.buf_size = usize::MAX; 142 | Poll::Ready(Ok(WinHTTPResponse { 143 | _connection: self._connection.clone(), 144 | h_request: self.h_request.clone(), 145 | ctx, 146 | read_size: 0, 147 | total_read_size: 0, 148 | buf: Box::pin([0; BUF_SIZE]), 149 | raw_headers, 150 | callback_receiver: rx, 151 | })) 152 | } 153 | WinHTTPCallbackEvent::Error(err) => Poll::Ready(Err(err)), 154 | _ => unreachable!(), 155 | }, 156 | Err(TryRecvError::Empty) => Poll::Pending, 157 | Err(TryRecvError::Disconnected) => { 158 | Poll::Ready(Err(std::io::Error::other("channel has been disconnected"))) 159 | } 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug unit tests in library 'alhc'", 11 | "cargo": { 12 | "args": [ 13 | "test", 14 | "--no-run", 15 | "--lib", 16 | "--package=alhc" 17 | ], 18 | "filter": { 19 | "name": "alhc", 20 | "kind": "lib" 21 | } 22 | }, 23 | "args": [], 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | { 27 | "type": "lldb", 28 | "request": "launch", 29 | "name": "Debug example 'download'", 30 | "cargo": { 31 | "args": [ 32 | "build", 33 | "--example=download", 34 | "--package=alhc" 35 | ], 36 | "filter": { 37 | "name": "download", 38 | "kind": "example" 39 | } 40 | }, 41 | "args": [], 42 | "cwd": "${workspaceFolder}" 43 | }, 44 | { 45 | "type": "lldb", 46 | "request": "launch", 47 | "name": "Debug unit tests in example 'download'", 48 | "cargo": { 49 | "args": [ 50 | "test", 51 | "--no-run", 52 | "--example=download", 53 | "--package=alhc" 54 | ], 55 | "filter": { 56 | "name": "download", 57 | "kind": "example" 58 | } 59 | }, 60 | "args": [], 61 | "cwd": "${workspaceFolder}" 62 | }, 63 | { 64 | "type": "lldb", 65 | "request": "launch", 66 | "name": "Debug example 'https'", 67 | "cargo": { 68 | "args": [ 69 | "build", 70 | "--example=https", 71 | "--package=alhc" 72 | ], 73 | "filter": { 74 | "name": "https", 75 | "kind": "example" 76 | } 77 | }, 78 | "args": [], 79 | "cwd": "${workspaceFolder}" 80 | }, 81 | { 82 | "type": "lldb", 83 | "request": "launch", 84 | "name": "Debug unit tests in example 'https'", 85 | "cargo": { 86 | "args": [ 87 | "test", 88 | "--no-run", 89 | "--example=https", 90 | "--package=alhc" 91 | ], 92 | "filter": { 93 | "name": "https", 94 | "kind": "example" 95 | } 96 | }, 97 | "args": [], 98 | "cwd": "${workspaceFolder}" 99 | }, 100 | { 101 | "type": "lldb", 102 | "request": "launch", 103 | "name": "Debug example 'macos_bindgen'", 104 | "cargo": { 105 | "args": [ 106 | "build", 107 | "--example=macos_bindgen", 108 | "--package=alhc" 109 | ], 110 | "filter": { 111 | "name": "macos_bindgen", 112 | "kind": "example" 113 | } 114 | }, 115 | "args": [], 116 | "cwd": "${workspaceFolder}" 117 | }, 118 | { 119 | "type": "lldb", 120 | "request": "launch", 121 | "name": "Debug unit tests in example 'macos_bindgen'", 122 | "cargo": { 123 | "args": [ 124 | "test", 125 | "--no-run", 126 | "--example=macos_bindgen", 127 | "--package=alhc" 128 | ], 129 | "filter": { 130 | "name": "macos_bindgen", 131 | "kind": "example" 132 | } 133 | }, 134 | "args": [], 135 | "cwd": "${workspaceFolder}" 136 | }, 137 | { 138 | "type": "lldb", 139 | "request": "launch", 140 | "name": "Debug example 'parallel'", 141 | "cargo": { 142 | "args": [ 143 | "build", 144 | "--example=parallel", 145 | "--package=alhc" 146 | ], 147 | "filter": { 148 | "name": "parallel", 149 | "kind": "example" 150 | } 151 | }, 152 | "args": [], 153 | "cwd": "${workspaceFolder}" 154 | }, 155 | { 156 | "type": "lldb", 157 | "request": "launch", 158 | "name": "Debug unit tests in example 'parallel'", 159 | "cargo": { 160 | "args": [ 161 | "test", 162 | "--no-run", 163 | "--example=parallel", 164 | "--package=alhc" 165 | ], 166 | "filter": { 167 | "name": "parallel", 168 | "kind": "example" 169 | } 170 | }, 171 | "args": [], 172 | "cwd": "${workspaceFolder}" 173 | }, 174 | { 175 | "type": "lldb", 176 | "request": "launch", 177 | "name": "Debug example 'sequential'", 178 | "cargo": { 179 | "args": [ 180 | "build", 181 | "--example=sequential", 182 | "--package=alhc" 183 | ], 184 | "filter": { 185 | "name": "sequential", 186 | "kind": "example" 187 | } 188 | }, 189 | "args": [], 190 | "cwd": "${workspaceFolder}" 191 | }, 192 | { 193 | "type": "lldb", 194 | "request": "launch", 195 | "name": "Debug unit tests in example 'sequential'", 196 | "cargo": { 197 | "args": [ 198 | "test", 199 | "--no-run", 200 | "--example=sequential", 201 | "--package=alhc" 202 | ], 203 | "filter": { 204 | "name": "sequential", 205 | "kind": "example" 206 | } 207 | }, 208 | "args": [], 209 | "cwd": "${workspaceFolder}" 210 | } 211 | ] 212 | } -------------------------------------------------------------------------------- /src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | //! Platform specific implementation for Windows 2 | //! 3 | //! Currently using WinHTTP system library. 4 | //! 5 | //! Documentation: 6 | 7 | mod callback; 8 | mod err_code; 9 | mod request; 10 | mod response; 11 | 12 | pub use request::*; 13 | pub use response::*; 14 | 15 | use std::{ 16 | collections::HashMap, 17 | ffi::{c_void, OsString}, 18 | ops::Deref, 19 | os::windows::ffi::OsStringExt, 20 | ptr::slice_from_raw_parts, 21 | sync::{ 22 | mpsc::{Receiver, Sender}, 23 | Arc, Mutex, 24 | }, 25 | task::{Poll, Waker}, 26 | time::Duration, 27 | }; 28 | 29 | use crate::{prelude::*, Client, ClientBuilder, DynResult}; 30 | 31 | use windows_sys::Win32::{Foundation::GetLastError, Networking::WinHttp::*}; 32 | 33 | use crate::Method; 34 | 35 | trait ToWide { 36 | fn to_utf16(self) -> Vec; 37 | } 38 | 39 | impl ToWide for &str { 40 | fn to_utf16(self) -> Vec { 41 | self.encode_utf16().chain(Some(0)).collect::>() 42 | } 43 | } 44 | 45 | #[derive(Debug)] 46 | enum WinHTTPCallbackEvent { 47 | WriteCompleted, 48 | RawHeadersReceived(String), 49 | DataAvailable, 50 | DataWritten, 51 | Error(std::io::Error), 52 | } 53 | 54 | #[derive(Debug)] 55 | struct NetworkContext { 56 | waker: Option, 57 | buf_size: usize, 58 | has_completed: bool, 59 | callback_sender: Sender, 60 | } 61 | 62 | impl NetworkContext { 63 | fn new() -> (Self, Receiver) { 64 | let (tx, rx) = std::sync::mpsc::channel(); 65 | ( 66 | Self { 67 | waker: None, 68 | buf_size: 0, 69 | has_completed: false, 70 | callback_sender: tx, 71 | }, 72 | rx, 73 | ) 74 | } 75 | } 76 | 77 | // According to WinHTTP documention, buffer should be at least 8KB. 78 | // https://learn.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-winhttpreaddata#remarks 79 | const BUF_SIZE: usize = 8 * 1024; 80 | 81 | #[derive(Clone, Debug)] 82 | pub(crate) struct Handle(*mut c_void); 83 | 84 | unsafe impl Send for Handle {} 85 | unsafe impl Sync for Handle {} 86 | 87 | impl From<*mut c_void> for Handle { 88 | fn from(h: *mut c_void) -> Self { 89 | Self(h) 90 | } 91 | } 92 | 93 | impl Deref for Handle { 94 | type Target = *mut c_void; 95 | 96 | fn deref(&self) -> &Self::Target { 97 | &self.0 98 | } 99 | } 100 | 101 | impl Drop for Handle { 102 | fn drop(&mut self) { 103 | unsafe { 104 | let nil = std::ptr::null::(); 105 | WinHttpSetOption( 106 | self as *mut Self as *mut _, 107 | WINHTTP_OPTION_CONTEXT_VALUE, 108 | &nil as *const _ as *const c_void, 109 | std::mem::size_of::<*const c_void>() as _, 110 | ); 111 | if WinHttpCloseHandle(self.0) == 0 { 112 | panic!( 113 | "Can't close handle for {:?}: {:08X}", 114 | self.0, 115 | GetLastError() 116 | ); 117 | } 118 | } 119 | } 120 | } 121 | 122 | impl Client { 123 | pub(crate) fn get_or_connect_connection(&self, hostname: &str) -> std::io::Result> { 124 | unsafe { 125 | let mut connections = self.connections.lock().unwrap(); 126 | if let Some(conn) = connections.get(hostname).cloned() { 127 | Ok(conn) 128 | } else { 129 | let hostname_w = hostname.to_utf16(); 130 | let h_connection = WinHttpConnect( 131 | *self.h_session, 132 | hostname_w.as_ptr(), 133 | INTERNET_DEFAULT_PORT, 134 | 0, 135 | ); 136 | 137 | if h_connection.is_null() { 138 | return Err(err_code::resolve_io_error()); 139 | } 140 | 141 | let conn: Arc = Arc::new(h_connection.into()); 142 | 143 | connections.insert(hostname.to_owned(), conn.clone()); 144 | 145 | Ok(conn) 146 | } 147 | } 148 | } 149 | } 150 | 151 | impl CommonClient for Client { 152 | type ClientRequest = WinHTTPRequest; 153 | 154 | fn set_timeout(&mut self, max_timeout: Duration) { 155 | unsafe { 156 | let max_timeout = max_timeout.as_millis() as std::os::raw::c_int; 157 | WinHttpSetTimeouts( 158 | *self.h_session, 159 | max_timeout, 160 | max_timeout, 161 | max_timeout, 162 | max_timeout, 163 | ); 164 | } 165 | } 166 | 167 | fn request(&self, method: Method, url: &str) -> crate::DynResult { 168 | unsafe { 169 | let url = url.to_utf16(); 170 | 171 | let mut component = URL_COMPONENTS { 172 | dwStructSize: std::mem::size_of::() as _, 173 | dwSchemeLength: u32::MAX, 174 | dwHostNameLength: u32::MAX, 175 | dwUrlPathLength: u32::MAX, 176 | dwExtraInfoLength: u32::MAX, 177 | ..std::mem::zeroed() 178 | }; 179 | 180 | let r = WinHttpCrackUrl(url.as_ptr(), 0, 0, &mut component); // TODO: Error handling 181 | 182 | if r == 0 { 183 | #[cfg(not(feature = "anyhow"))] 184 | return Err(Box::new(std::io::Error::last_os_error())); 185 | #[cfg(feature = "anyhow")] 186 | anyhow::bail!("Failed on WinHttpCrackUrl: {}", GetLastError()) 187 | } 188 | 189 | let host_name = 190 | slice_from_raw_parts(component.lpszHostName, component.dwHostNameLength as _); 191 | let host_name = OsString::from_wide(host_name.as_ref().unwrap()) 192 | .to_string_lossy() 193 | .to_string(); 194 | 195 | let conn = self.get_or_connect_connection(&host_name)?; 196 | 197 | let url_path = 198 | slice_from_raw_parts(component.lpszUrlPath, component.dwUrlPathLength as _); 199 | let url_path = OsString::from_wide(url_path.as_ref().unwrap()) 200 | .to_string_lossy() 201 | .to_string(); 202 | 203 | let url_path_w = url_path.to_utf16(); 204 | 205 | let h_request = WinHttpOpenRequest( 206 | **conn, 207 | method.as_raw_str_wide(), 208 | url_path_w.as_ptr(), 209 | std::ptr::null(), 210 | std::ptr::null(), 211 | std::ptr::null_mut(), 212 | WINHTTP_FLAG_SECURE, 213 | ); 214 | 215 | if h_request.is_null() { 216 | #[cfg(not(feature = "anyhow"))] 217 | return Err(Box::new(std::io::Error::last_os_error())); 218 | #[cfg(feature = "anyhow")] 219 | anyhow::bail!("Failed on WinHttpOpenRequest: {}", GetLastError()) 220 | } 221 | 222 | let r = WinHttpSetStatusCallback( 223 | h_request, 224 | Some(callback::status_callback), 225 | WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 226 | 0, 227 | ); 228 | 229 | if r.map(|x| (x as usize) == usize::MAX).unwrap_or(false) { 230 | #[cfg(not(feature = "anyhow"))] 231 | return Err(Box::new(std::io::Error::last_os_error())); 232 | #[cfg(feature = "anyhow")] 233 | anyhow::bail!("Failed on WinHttpSetStatusCallback: {}", GetLastError()) 234 | } 235 | 236 | let (ctx, rx) = NetworkContext::new(); 237 | 238 | Ok(WinHTTPRequest { 239 | _connection: conn, 240 | body: Box::new(futures_lite::io::empty()), 241 | body_len: 0, 242 | ctx: Box::pin(ctx), 243 | h_request: Arc::new(h_request.into()), 244 | callback_receiver: rx, 245 | buf: Box::pin([0; BUF_SIZE]), 246 | }) 247 | } 248 | } 249 | } 250 | 251 | impl CommonClientBuilder for ClientBuilder { 252 | fn build(&self) -> DynResult { 253 | unsafe { 254 | let h_session = WinHttpOpen( 255 | std::ptr::null(), 256 | WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, 257 | std::ptr::null(), 258 | std::ptr::null(), 259 | WINHTTP_FLAG_ASYNC, 260 | ); 261 | WinHttpSetOption( 262 | h_session, 263 | WINHTTP_OPTION_HTTP2_KEEPALIVE, 264 | &15000u32 as *const _ as *const c_void, 265 | 4, 266 | ); 267 | Ok(Client { 268 | h_session: h_session.into(), 269 | connections: Mutex::new(HashMap::with_capacity(16)), 270 | }) 271 | } 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/windows/err_code.rs: -------------------------------------------------------------------------------- 1 | use std::io::ErrorKind; 2 | 3 | use windows_sys::Win32::{ 4 | Foundation::{GetLastError, WIN32_ERROR}, 5 | Networking::WinHttp::*, 6 | }; 7 | 8 | pub fn resolve_io_error_from_error_code(code: WIN32_ERROR) -> std::io::Error { 9 | match code { 10 | ERROR_WINHTTP_AUTODETECTION_FAILED => std::io::Error::new( 11 | ErrorKind::Other, 12 | "ERROR_WINHTTP_AUTODETECTION_FAILED: 12180", 13 | ), 14 | ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR => std::io::Error::new( 15 | ErrorKind::Other, 16 | "ERROR_WINHTTP_AUTO_PROXY_SERVICE_ERROR: 12178", 17 | ), 18 | ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT => std::io::Error::new( 19 | ErrorKind::Other, 20 | "ERROR_WINHTTP_BAD_AUTO_PROXY_SCRIPT: 12166", 21 | ), 22 | ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN => std::io::Error::new( 23 | ErrorKind::Other, 24 | "ERROR_WINHTTP_CANNOT_CALL_AFTER_OPEN: 12103", 25 | ), 26 | ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND => std::io::Error::new( 27 | ErrorKind::Other, 28 | "ERROR_WINHTTP_CANNOT_CALL_AFTER_SEND: 12102", 29 | ), 30 | ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN => std::io::Error::new( 31 | ErrorKind::Other, 32 | "ERROR_WINHTTP_CANNOT_CALL_BEFORE_OPEN: 12100", 33 | ), 34 | ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND => std::io::Error::new( 35 | ErrorKind::Other, 36 | "ERROR_WINHTTP_CANNOT_CALL_BEFORE_SEND: 12101", 37 | ), 38 | ERROR_WINHTTP_CANNOT_CONNECT => std::io::Error::new( 39 | ErrorKind::NotConnected, 40 | "ERROR_WINHTTP_CANNOT_CONNECT: 12029", 41 | ), 42 | ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW => std::io::Error::new( 43 | ErrorKind::OutOfMemory, 44 | "ERROR_WINHTTP_CHUNKED_ENCODING_HEADER_SIZE_OVERFLOW: 12183", 45 | ), 46 | ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED => std::io::Error::new( 47 | ErrorKind::Other, 48 | "ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED: 12044", 49 | ), 50 | ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED_PROXY => std::io::Error::new( 51 | ErrorKind::Other, 52 | "ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED_PROXY: 12187", 53 | ), 54 | ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY => std::io::Error::new( 55 | ErrorKind::Other, 56 | "ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY: 12186", 57 | ), 58 | ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY => std::io::Error::new( 59 | ErrorKind::Other, 60 | "ERROR_WINHTTP_CLIENT_CERT_NO_PRIVATE_KEY: 12185", 61 | ), 62 | ERROR_WINHTTP_CONNECTION_ERROR => std::io::Error::new( 63 | ErrorKind::ConnectionAborted, 64 | "ERROR_WINHTTP_CONNECTION_ERROR: 12030", 65 | ), 66 | ERROR_WINHTTP_FEATURE_DISABLED => { 67 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_FEATURE_DISABLED: 12192") 68 | } 69 | ERROR_WINHTTP_GLOBAL_CALLBACK_FAILED => std::io::Error::new( 70 | ErrorKind::Other, 71 | "ERROR_WINHTTP_GLOBAL_CALLBACK_FAILED: 12191", 72 | ), 73 | ERROR_WINHTTP_HEADER_ALREADY_EXISTS => std::io::Error::new( 74 | ErrorKind::Other, 75 | "ERROR_WINHTTP_HEADER_ALREADY_EXISTS: 12155", 76 | ), 77 | ERROR_WINHTTP_HEADER_COUNT_EXCEEDED => std::io::Error::new( 78 | ErrorKind::Other, 79 | "ERROR_WINHTTP_HEADER_COUNT_EXCEEDED: 12181", 80 | ), 81 | ERROR_WINHTTP_HEADER_NOT_FOUND => { 82 | std::io::Error::new(ErrorKind::NotFound, "ERROR_WINHTTP_HEADER_NOT_FOUND: 12150") 83 | } 84 | ERROR_WINHTTP_HEADER_SIZE_OVERFLOW => std::io::Error::new( 85 | ErrorKind::OutOfMemory, 86 | "ERROR_WINHTTP_HEADER_SIZE_OVERFLOW: 12182", 87 | ), 88 | ERROR_WINHTTP_HTTP_PROTOCOL_MISMATCH => std::io::Error::new( 89 | ErrorKind::Other, 90 | "ERROR_WINHTTP_HTTP_PROTOCOL_MISMATCH: 12190", 91 | ), 92 | ERROR_WINHTTP_INCORRECT_HANDLE_STATE => std::io::Error::new( 93 | ErrorKind::Other, 94 | "ERROR_WINHTTP_INCORRECT_HANDLE_STATE: 12019", 95 | ), 96 | ERROR_WINHTTP_INCORRECT_HANDLE_TYPE => std::io::Error::new( 97 | ErrorKind::Other, 98 | "ERROR_WINHTTP_INCORRECT_HANDLE_TYPE: 12018", 99 | ), 100 | ERROR_WINHTTP_INTERNAL_ERROR => { 101 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_INTERNAL_ERROR: 12004") 102 | } 103 | ERROR_WINHTTP_INVALID_HEADER => std::io::Error::new( 104 | ErrorKind::InvalidData, 105 | "ERROR_WINHTTP_INVALID_HEADER: 12153", 106 | ), 107 | ERROR_WINHTTP_INVALID_OPTION => std::io::Error::new( 108 | ErrorKind::InvalidInput, 109 | "ERROR_WINHTTP_INVALID_OPTION: 12009", 110 | ), 111 | ERROR_WINHTTP_INVALID_QUERY_REQUEST => std::io::Error::new( 112 | ErrorKind::Other, 113 | "ERROR_WINHTTP_INVALID_QUERY_REQUEST: 12154", 114 | ), 115 | ERROR_WINHTTP_INVALID_SERVER_RESPONSE => std::io::Error::new( 116 | ErrorKind::Other, 117 | "ERROR_WINHTTP_INVALID_SERVER_RESPONSE: 12152", 118 | ), 119 | ERROR_WINHTTP_INVALID_URL => { 120 | std::io::Error::new(ErrorKind::InvalidInput, "ERROR_WINHTTP_INVALID_URL: 12005") 121 | } 122 | ERROR_WINHTTP_LOGIN_FAILURE => { 123 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_LOGIN_FAILURE: 12015") 124 | } 125 | ERROR_WINHTTP_NAME_NOT_RESOLVED => { 126 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_NAME_NOT_RESOLVED: 12007") 127 | } 128 | ERROR_WINHTTP_NOT_INITIALIZED => { 129 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_NOT_INITIALIZED: 12172") 130 | } 131 | ERROR_WINHTTP_OPERATION_CANCELLED => { 132 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_OPERATION_CANCELLED: 12017") 133 | } 134 | ERROR_WINHTTP_OPTION_NOT_SETTABLE => { 135 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_OPTION_NOT_SETTABLE: 12011") 136 | } 137 | ERROR_WINHTTP_OUT_OF_HANDLES => { 138 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_OUT_OF_HANDLES: 12001") 139 | } 140 | ERROR_WINHTTP_REDIRECT_FAILED => { 141 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_REDIRECT_FAILED: 12156") 142 | } 143 | ERROR_WINHTTP_RESEND_REQUEST => { 144 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_RESEND_REQUEST: 12032") 145 | } 146 | ERROR_WINHTTP_RESERVED_189 => { 147 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_RESERVED_189: 12189") 148 | } 149 | ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW => std::io::Error::new( 150 | ErrorKind::OutOfMemory, 151 | "ERROR_WINHTTP_RESPONSE_DRAIN_OVERFLOW: 12184", 152 | ), 153 | ERROR_WINHTTP_SCRIPT_EXECUTION_ERROR => std::io::Error::new( 154 | ErrorKind::Other, 155 | "ERROR_WINHTTP_SCRIPT_EXECUTION_ERROR: 12177", 156 | ), 157 | ERROR_WINHTTP_SECURE_CERT_CN_INVALID => std::io::Error::new( 158 | ErrorKind::Other, 159 | "ERROR_WINHTTP_SECURE_CERT_CN_INVALID: 12038", 160 | ), 161 | ERROR_WINHTTP_SECURE_CERT_DATE_INVALID => std::io::Error::new( 162 | ErrorKind::Other, 163 | "ERROR_WINHTTP_SECURE_CERT_DATE_INVALID: 12037", 164 | ), 165 | ERROR_WINHTTP_SECURE_CERT_REVOKED => { 166 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_SECURE_CERT_REVOKED: 12170") 167 | } 168 | ERROR_WINHTTP_SECURE_CERT_REV_FAILED => std::io::Error::new( 169 | ErrorKind::Other, 170 | "ERROR_WINHTTP_SECURE_CERT_REV_FAILED: 12057", 171 | ), 172 | ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE => std::io::Error::new( 173 | ErrorKind::Other, 174 | "ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE: 12179", 175 | ), 176 | ERROR_WINHTTP_SECURE_CHANNEL_ERROR => std::io::Error::new( 177 | ErrorKind::Other, 178 | "ERROR_WINHTTP_SECURE_CHANNEL_ERROR: 12157", 179 | ), 180 | ERROR_WINHTTP_SECURE_FAILURE => { 181 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_SECURE_FAILURE: 12175") 182 | } 183 | ERROR_WINHTTP_SECURE_FAILURE_PROXY => std::io::Error::new( 184 | ErrorKind::Other, 185 | "ERROR_WINHTTP_SECURE_FAILURE_PROXY: 12188", 186 | ), 187 | ERROR_WINHTTP_SECURE_INVALID_CA => { 188 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_SECURE_INVALID_CA: 12045") 189 | } 190 | ERROR_WINHTTP_SECURE_INVALID_CERT => { 191 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_SECURE_INVALID_CERT: 12169") 192 | } 193 | ERROR_WINHTTP_SHUTDOWN => { 194 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_SHUTDOWN: 12012") 195 | } 196 | ERROR_WINHTTP_TIMEOUT => { 197 | std::io::Error::new(ErrorKind::TimedOut, "ERROR_WINHTTP_TIMEOUT: 12002") 198 | } 199 | ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT => std::io::Error::new( 200 | ErrorKind::Other, 201 | "ERROR_WINHTTP_UNABLE_TO_DOWNLOAD_SCRIPT: 12167", 202 | ), 203 | ERROR_WINHTTP_UNHANDLED_SCRIPT_TYPE => std::io::Error::new( 204 | ErrorKind::Other, 205 | "ERROR_WINHTTP_UNHANDLED_SCRIPT_TYPE: 12176", 206 | ), 207 | ERROR_WINHTTP_UNRECOGNIZED_SCHEME => { 208 | std::io::Error::new(ErrorKind::Other, "ERROR_WINHTTP_UNRECOGNIZED_SCHEME: 12006") 209 | } 210 | 211 | other => std::io::Error::from_raw_os_error(other as _), 212 | } 213 | } 214 | 215 | pub fn resolve_io_error() -> std::io::Error { 216 | resolve_io_error_from_error_code(unsafe { GetLastError() }) 217 | } 218 | --------------------------------------------------------------------------------