nyquest_backend_winhttp/blocking/
client.rs1use 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#[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 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 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 if !headers_str.is_empty() {
60 request.add_headers(&headers_str).into_nyquest()?;
61 }
62
63 match prepared_body {
65 PreparedBody::None => unsafe {
66 request.send(std::ptr::null(), 0, 0).into_nyquest()?;
67 },
68 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 request.receive_response().into_nyquest()?;
85
86 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#[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}