Skip to main content

nyquest_backend_winhttp/blocking/
client.rs

1//! Blocking WinHTTP client implementation.
2
3use std::sync::Arc;
4
5use nyquest_interface::blocking::{BlockingBackend, BlockingClient, Request};
6use nyquest_interface::client::ClientOptions;
7use nyquest_interface::Result as NyquestResult;
8
9use super::response::WinHttpBlockingResponse;
10use crate::error::WinHttpResultExt;
11use crate::request::{
12    create_request, method_to_cwstr, prepare_additional_headers, prepare_body, PreparedBody,
13};
14use crate::session::WinHttpSession;
15use crate::url::{concat_url, ParsedUrl};
16use crate::WinHttpBackend;
17
18#[cfg(feature = "blocking-stream")]
19use crate::stream::{DataOrStream, StreamWriter};
20#[cfg(feature = "blocking-stream")]
21use nyquest_interface::blocking::BoxedStream;
22
23/// Blocking WinHTTP client.
24#[derive(Clone)]
25pub struct WinHttpBlockingClient {
26    session: Arc<WinHttpSession>,
27}
28
29impl WinHttpBlockingClient {
30    pub(crate) fn new(options: ClientOptions) -> NyquestResult<Self> {
31        let session = WinHttpSession::new(options, false).into_nyquest()?;
32        Ok(Self { session })
33    }
34}
35
36impl BlockingClient for WinHttpBlockingClient {
37    type Response = WinHttpBlockingResponse;
38
39    fn request(&self, req: Request) -> NyquestResult<Self::Response> {
40        // Create connection and request handles
41        let (connection, request) = {
42            let url = concat_url(self.session.base_cwurl.as_deref(), &req.relative_uri)?;
43            let parsed_url = ParsedUrl::parse(&url).ok_or(nyquest_interface::Error::InvalidUrl)?;
44            let method = method_to_cwstr(&req.method);
45            create_request(&self.session, &parsed_url, &method).into_nyquest()?
46        };
47
48        // Prepare headers and body
49        let prepared_body = prepare_body(req.body, get_stream_content_length);
50        let headers_str = prepare_additional_headers(
51            &req.additional_headers,
52            &self.session.options,
53            &prepared_body,
54        );
55
56        let body_len = prepared_body.body_len();
57
58        // Add headers
59        if !headers_str.is_empty() {
60            request.add_headers(&headers_str).into_nyquest()?;
61        }
62
63        // Send the request
64        match prepared_body {
65            PreparedBody::None => unsafe {
66                request.send(std::ptr::null(), 0, 0).into_nyquest()?;
67            },
68            // SAFETY: Since the request handle is in blocking mode, WinHTTP
69            // will not read the body data until send() returns.
70            PreparedBody::Complete { data, .. } => unsafe {
71                request.send(data.as_ptr(), data.len(), 0).into_nyquest()?;
72            },
73            #[cfg(feature = "blocking-stream")]
74            PreparedBody::Stream { stream_parts, .. } => {
75                self.send_streaming_request(&request, stream_parts, body_len)?;
76            }
77            #[cfg(not(feature = "blocking-stream"))]
78            PreparedBody::Stream { .. } => {
79                unreachable!("streaming requires blocking-stream feature")
80            }
81        }
82
83        // Receive response
84        request.receive_response().into_nyquest()?;
85
86        // Get status code
87        let status = request.query_status_code().into_nyquest()?;
88        let content_length = request.query_content_length();
89
90        Ok(WinHttpBlockingResponse::new(
91            self.session.clone(),
92            connection,
93            request,
94            status,
95            content_length,
96            self.session.options.max_response_buffer_size,
97        ))
98    }
99}
100
101#[cfg(feature = "blocking-stream")]
102impl WinHttpBlockingClient {
103    fn send_streaming_request(
104        &self,
105        request: &crate::handle::RequestHandle,
106        stream_parts: Vec<DataOrStream<BoxedStream>>,
107        content_length: Option<u64>,
108    ) -> NyquestResult<()> {
109        use crate::error::WinHttpResultExt as _;
110
111        let mut writer = if let Some(len) = content_length {
112            request.send_with_total_length(len, 0).into_nyquest()?;
113            StreamWriter::new(stream_parts, false)
114        } else {
115            request.send_chunked(0).into_nyquest()?;
116            StreamWriter::new(stream_parts, true)
117        };
118
119        while !writer.is_finished() {
120            use std::io::Read as _;
121            use std::task::Poll;
122
123            let (buf, mut range) = match writer
124                .poll_take_buffer(|stream, buf| Poll::Ready(stream.read(buf)))
125            {
126                Poll::Ready(Ok(res)) => res,
127                Poll::Ready(Err(e)) => return Err(e.into()),
128                Poll::Pending => {
129                    unreachable!("poll_take_buffer should never return Pending in blocking mode")
130                }
131            };
132            while !range.is_empty() {
133                let data = &buf[range.start..range.end];
134                let written = unsafe { request.write_data(data).into_nyquest()? };
135                range.start += written as usize;
136            }
137            writer.advance(buf);
138        }
139
140        Ok(())
141    }
142}
143
144/// Extracts content length from a BoxedStream if it's a sized stream.
145#[cfg(feature = "blocking-stream")]
146fn get_stream_content_length(stream: &BoxedStream) -> Option<u64> {
147    match stream {
148        BoxedStream::Sized { content_length, .. } => Some(*content_length),
149        BoxedStream::Unsized { .. } => None,
150    }
151}
152
153#[cfg(not(feature = "blocking-stream"))]
154fn get_stream_content_length(_stream: &impl Sized) -> Option<u64> {
155    None
156}
157
158impl BlockingBackend for WinHttpBackend {
159    type BlockingClient = WinHttpBlockingClient;
160
161    fn create_blocking_client(
162        &self,
163        options: ClientOptions,
164    ) -> NyquestResult<Self::BlockingClient> {
165        WinHttpBlockingClient::new(options)
166    }
167}