1 // Copyright (c) 2023 Huawei Device Co., Ltd.
2 // Licensed under the Apache License, Version 2.0 (the "License");
3 // you may not use this file except in compliance with the License.
4 // You may obtain a copy of the License at
5 //
6 // http://www.apache.org/licenses/LICENSE-2.0
7 //
8 // Unless required by applicable law or agreed to in writing, software
9 // distributed under the License is distributed on an "AS IS" BASIS,
10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 // See the License for the specific language governing permissions and
12 // limitations under the License.
13
14 //! `ylong_http_client` `Request` adapter.
15
16 use core::ops::{Deref, DerefMut};
17 use core::pin::Pin;
18 use core::task::{Context, Poll};
19 use std::io::Cursor;
20 use std::sync::Arc;
21
22 use ylong_http::body::async_impl::ReusableReader;
23 use ylong_http::body::MultiPartBase;
24 use ylong_http::request::uri::PercentEncoder as PerEncoder;
25 use ylong_http::request::{Request as Req, RequestBuilder as ReqBuilder};
26
27 use crate::error::{ErrorKind, HttpClientError};
28 use crate::runtime::{AsyncRead, ReadBuf};
29 use crate::util::interceptor::Interceptors;
30 use crate::util::monitor::TimeGroup;
31 use crate::util::request::RequestArc;
32
33 /// A structure that represents an HTTP `Request`. It contains a request line,
34 /// some HTTP headers and a HTTP body.
35 ///
36 /// An HTTP request is made by a client, to a named host, which is located on a
37 /// server. The aim of the request is to access a resource on the server.
38 ///
39 /// This structure is based on `ylong_http::Request<T>`.
40 ///
41 /// # Examples
42 ///
43 /// ```
44 /// use ylong_http_client::async_impl::{Body, Request};
45 ///
46 /// let request = Request::builder().body(Body::empty());
47 /// ```
48 pub struct Request {
49 pub(crate) inner: Req<Body>,
50 pub(crate) time_group: TimeGroup,
51 }
52
53 impl Request {
54 /// Creates a new, default `RequestBuilder`.
55 ///
56 /// # Default
57 ///
58 /// - The method of this `RequestBuilder` is `GET`.
59 /// - The URL of this `RequestBuilder` is `/`.
60 /// - The HTTP version of this `RequestBuilder` is `HTTP/1.1`.
61 /// - No headers are in this `RequestBuilder`.
62 ///
63 /// # Examples
64 ///
65 /// ```
66 /// use ylong_http_client::async_impl::Request;
67 ///
68 /// let builder = Request::builder();
69 /// ```
builder() -> RequestBuilder70 pub fn builder() -> RequestBuilder {
71 RequestBuilder::new()
72 }
73
time_group_mut(&mut self) -> &mut TimeGroup74 pub(crate) fn time_group_mut(&mut self) -> &mut TimeGroup {
75 &mut self.time_group
76 }
77 }
78
79 impl Deref for Request {
80 type Target = Req<Body>;
81
deref(&self) -> &Self::Target82 fn deref(&self) -> &Self::Target {
83 &self.inner
84 }
85 }
86
87 impl DerefMut for Request {
deref_mut(&mut self) -> &mut Self::Target88 fn deref_mut(&mut self) -> &mut Self::Target {
89 &mut self.inner
90 }
91 }
92
93 /// A structure which used to build an HTTP `Request`.
94 ///
95 /// # Examples
96 ///
97 /// ```
98 /// use ylong_http_client::async_impl::{Body, RequestBuilder};
99 ///
100 /// let request = RequestBuilder::new()
101 /// .method("GET")
102 /// .version("HTTP/1.1")
103 /// .url("http://www.example.com")
104 /// .header("Content-Type", "application/octet-stream")
105 /// .body(Body::empty());
106 /// ```
107 #[derive(Default)]
108 pub struct RequestBuilder(ReqBuilder);
109
110 impl RequestBuilder {
111 /// Creates a new, default `RequestBuilder`.
112 ///
113 /// # Default
114 ///
115 /// - The method of this `RequestBuilder` is `GET`.
116 /// - The URL of this `RequestBuilder` is `/`.
117 /// - The HTTP version of this `RequestBuilder` is `HTTP/1.1`.
118 /// - No headers are in this `RequestBuilder`.
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use ylong_http_client::async_impl::RequestBuilder;
124 ///
125 /// let builder = RequestBuilder::new();
126 /// ```
new() -> Self127 pub fn new() -> Self {
128 Self(ReqBuilder::new())
129 }
130
131 /// Sets the `Method` of the `Request`.
132 ///
133 /// # Examples
134 ///
135 /// ```
136 /// use ylong_http_client::async_impl::RequestBuilder;
137 ///
138 /// let builder = RequestBuilder::new().method("GET");
139 /// ```
method(self, method: &str) -> Self140 pub fn method(self, method: &str) -> Self {
141 Self(self.0.method(method))
142 }
143
144 /// Sets the `Url` of the `Request`.
145 ///
146 /// # Examples
147 ///
148 /// ```
149 /// use ylong_http_client::async_impl::RequestBuilder;
150 ///
151 /// let builder = RequestBuilder::new().url("www.example.com");
152 /// ```
url(self, url: &str) -> Self153 pub fn url(self, url: &str) -> Self {
154 Self(self.0.url(url))
155 }
156
157 /// Sets the `Version` of the `Request`. Uses `Version::HTTP11` by default.
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// use ylong_http_client::async_impl::RequestBuilder;
163 ///
164 /// let builder = RequestBuilder::new().version("HTTP/1.1");
165 /// ```
version(mut self, version: &str) -> Self166 pub fn version(mut self, version: &str) -> Self {
167 self.0 = self.0.version(version);
168 self
169 }
170
171 /// Adds a `Header` to `Request`. Overwrites `HeaderValue` if the
172 /// `HeaderName` already exists.
173 ///
174 /// # Examples
175 ///
176 /// ```
177 /// use ylong_http_client::async_impl::RequestBuilder;
178 ///
179 /// let builder = RequestBuilder::new().header("Content-Type", "application/octet-stream");
180 /// ```
header(mut self, name: &str, value: &str) -> Self181 pub fn header(mut self, name: &str, value: &str) -> Self {
182 self.0 = self.0.header(name, value);
183 self
184 }
185
186 /// Adds a `Header` to `Request`. Appends `HeaderValue` to the end of
187 /// previous `HeaderValue` if the `HeaderName` already exists.
188 ///
189 /// # Examples
190 ///
191 /// ```
192 /// use ylong_http_client::async_impl::RequestBuilder;
193 ///
194 /// let builder = RequestBuilder::new().append_header("Content-Type", "application/octet-stream");
195 /// ```
append_header(mut self, name: &str, value: &str) -> Self196 pub fn append_header(mut self, name: &str, value: &str) -> Self {
197 self.0 = self.0.append_header(name, value);
198 self
199 }
200
201 /// Tries to create a `Request` based on the incoming `body`.
202 ///
203 /// # Examples
204 ///
205 /// ```
206 /// use ylong_http_client::async_impl::{Body, RequestBuilder};
207 ///
208 /// let request = RequestBuilder::new().body(Body::empty());
209 /// ```
body(self, body: Body) -> Result<Request, HttpClientError>210 pub fn body(self, body: Body) -> Result<Request, HttpClientError> {
211 let mut builder = self;
212 match body.inner {
213 BodyKind::Slice(ref slice) => {
214 builder = builder.header(
215 "Content-Length",
216 format!("{}", slice.get_ref().len()).as_str(),
217 );
218 }
219 BodyKind::Multipart(ref multipart) => {
220 let value = format!(
221 "multipart/form-data; boundary={}",
222 multipart.multipart().boundary()
223 );
224
225 builder = builder.header("Content-Type", value.as_str());
226
227 if let Some(size) = multipart.multipart().total_bytes() {
228 builder = builder.header("Content-Length", format!("{size}").as_str());
229 }
230 }
231 _ => {}
232 }
233
234 builder
235 .0
236 .body(body)
237 .map(|inner| Request {
238 inner,
239 time_group: TimeGroup::default(),
240 })
241 .map_err(|e| HttpClientError::from_error(ErrorKind::Build, e))
242 }
243 }
244
245 /// A structure that represents body of HTTP request.
246 ///
247 /// There are many kinds of body supported:
248 ///
249 /// - Empty: an empty body.
250 /// - Slice: a body whose content comes from a memory slice.
251 /// - Stream: a body whose content comes from a stream.
252 /// - Multipart: a body whose content can transfer into a `Multipart`.
253 ///
254 /// # Examples
255 ///
256 /// ```
257 /// use ylong_http_client::async_impl::Body;
258 ///
259 /// let body = Body::empty();
260 /// ```
261 pub struct Body {
262 inner: BodyKind,
263 }
264
265 pub(crate) enum BodyKind {
266 Empty,
267 Slice(Cursor<Vec<u8>>),
268 Stream(Box<dyn ReusableReader + Send + Sync + Unpin>),
269 Multipart(Box<dyn MultiPartBase + Send + Sync + Unpin>),
270 }
271
272 impl Body {
273 /// Creates an empty HTTP body.
274 ///
275 /// # Examples
276 ///
277 /// ```
278 /// use ylong_http_client::async_impl::Body;
279 ///
280 /// let body = Body::empty();
281 /// ```
empty() -> Self282 pub fn empty() -> Self {
283 Body::new(BodyKind::Empty)
284 }
285
286 /// Creates an HTTP body that based on memory slice.
287 ///
288 /// This kind of body is **reusable**.
289 ///
290 /// # Example
291 ///
292 /// ```
293 /// use ylong_http_client::async_impl::Body;
294 ///
295 /// let body = Body::slice("HelloWorld");
296 /// ```
slice<T>(slice: T) -> Self where T: Into<Vec<u8>>,297 pub fn slice<T>(slice: T) -> Self
298 where
299 T: Into<Vec<u8>>,
300 {
301 Body::new(BodyKind::Slice(Cursor::new(slice.into())))
302 }
303
304 /// Creates an HTTP body that based on an asynchronous stream.
305 ///
306 /// This kind of body is not **reusable**.
307 ///
308 /// # Examples
309 ///
310 /// ```
311 /// use ylong_http_client::async_impl::Body;
312 ///
313 /// let body = Body::stream("HelloWorld".as_bytes());
314 /// ```
stream<T>(stream: T) -> Self where T: ReusableReader + Send + Sync + Unpin + 'static,315 pub fn stream<T>(stream: T) -> Self
316 where
317 T: ReusableReader + Send + Sync + Unpin + 'static,
318 {
319 Body::new(BodyKind::Stream(
320 Box::new(stream) as Box<dyn ReusableReader + Send + Sync + Unpin>
321 ))
322 }
323
324 /// Creates an HTTP body that based on a structure which implements
325 /// `MultiPartBase`.
326 ///
327 /// This kind of body is not **reusable**.
328 ///
329 /// # Examples
330 ///
331 /// ```
332 /// use ylong_http_client::async_impl::{Body, MultiPart};
333 ///
334 /// async fn create_multipart_body(multipart: MultiPart) {
335 /// let body = Body::multipart(multipart);
336 /// }
337 /// ```
multipart<T>(stream: T) -> Self where T: MultiPartBase + Send + Sync + Unpin + 'static,338 pub fn multipart<T>(stream: T) -> Self
339 where
340 T: MultiPartBase + Send + Sync + Unpin + 'static,
341 {
342 Body::new(BodyKind::Multipart(
343 Box::new(stream) as Box<dyn MultiPartBase + Send + Sync + Unpin>
344 ))
345 }
346 }
347
348 impl Body {
new(inner: BodyKind) -> Self349 pub(crate) fn new(inner: BodyKind) -> Self {
350 Self { inner }
351 }
352
353 #[cfg(feature = "http2")]
is_empty(&self) -> bool354 pub(crate) fn is_empty(&self) -> bool {
355 matches!(self.inner, BodyKind::Empty)
356 }
357
reuse(&mut self) -> std::io::Result<()>358 pub(crate) async fn reuse(&mut self) -> std::io::Result<()> {
359 match self.inner {
360 BodyKind::Empty => Ok(()),
361 BodyKind::Slice(ref mut slice) => {
362 slice.set_position(0);
363 Ok(())
364 }
365 BodyKind::Stream(ref mut stream) => stream.reuse().await,
366 BodyKind::Multipart(ref mut multipart) => multipart.reuse().await,
367 }
368 }
369 }
370
371 impl AsyncRead for Body {
poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll<std::io::Result<()>>372 fn poll_read(
373 self: Pin<&mut Self>,
374 cx: &mut Context<'_>,
375 buf: &mut ReadBuf<'_>,
376 ) -> Poll<std::io::Result<()>> {
377 let this = self.get_mut();
378 match this.inner {
379 BodyKind::Empty => Poll::Ready(Ok(())),
380 BodyKind::Slice(ref mut slice) => {
381 #[cfg(feature = "tokio_base")]
382 return Pin::new(slice).poll_read(cx, buf);
383 #[cfg(feature = "ylong_base")]
384 return poll_read_cursor(slice, buf);
385 }
386 BodyKind::Stream(ref mut stream) => Pin::new(stream).poll_read(cx, buf),
387 BodyKind::Multipart(ref mut multipart) => Pin::new(multipart).poll_read(cx, buf),
388 }
389 }
390 }
391
392 /// HTTP url percent encoding implementation.
393 ///
394 /// # Examples
395 ///
396 /// ```
397 /// use ylong_http_client::async_impl::PercentEncoder;
398 ///
399 /// let url = "https://www.example.com/data/测试文件.txt";
400 /// let encoded = PercentEncoder::encode(url).unwrap();
401 /// assert_eq!(
402 /// encoded,
403 /// "https://www.example.com/data/%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.txt"
404 /// );
405 /// ```
406 pub struct PercentEncoder;
407
408 impl PercentEncoder {
409 /// Percent-coding entry.
encode(url: &str) -> Result<String, HttpClientError>410 pub fn encode(url: &str) -> Result<String, HttpClientError> {
411 PerEncoder::parse(url).map_err(|e| HttpClientError::from_error(ErrorKind::Other, e))
412 }
413 }
414
415 pub(crate) struct Message {
416 pub(crate) request: RequestArc,
417 pub(crate) interceptor: Arc<Interceptors>,
418 }
419
420 #[cfg(feature = "ylong_base")]
poll_read_cursor( cursor: &mut Cursor<Vec<u8>>, buf: &mut ylong_runtime::io::ReadBuf<'_>, ) -> Poll<std::io::Result<()>>421 fn poll_read_cursor(
422 cursor: &mut Cursor<Vec<u8>>,
423 buf: &mut ylong_runtime::io::ReadBuf<'_>,
424 ) -> Poll<std::io::Result<()>> {
425 let pos = cursor.position();
426 let data = (*cursor).get_ref();
427
428 if pos > data.len() as u64 {
429 return Poll::Ready(Ok(()));
430 }
431
432 let start = pos as usize;
433 let target = std::cmp::min(data.len() - start, buf.remaining());
434 let end = start + target;
435 buf.append(&(data.as_slice())[start..end]);
436 cursor.set_position(end as u64);
437
438 Poll::Ready(Ok(()))
439 }
440
441 #[cfg(test)]
442 mod ut_client_request {
443 use crate::async_impl::{Body, PercentEncoder, RequestBuilder};
444
445 /// UT test cases for `RequestBuilder::default`.
446 ///
447 /// # Brief
448 /// 1. Creates a `RequestBuilder` by `RequestBuilder::default`.
449 /// 2. Checks if result is correct.
450 #[test]
ut_client_request_builder_default()451 fn ut_client_request_builder_default() {
452 let builder = RequestBuilder::default().append_header("name", "value");
453 let request = builder.body(Body::empty());
454 assert!(request.is_ok());
455 #[cfg(feature = "http2")]
456 assert!(request.unwrap().body().is_empty());
457
458 let request = RequestBuilder::default()
459 .append_header("name", "value")
460 .url("http://")
461 .body(Body::empty());
462 assert!(request.is_err());
463 }
464
465 /// UT test cases for `RequestBuilder::body`.
466 ///
467 /// # Brief
468 /// 1. Creates a `RequestBuilder` by `RequestBuilder::body`.
469 /// 2. Checks if result is correct.
470 #[cfg(feature = "ylong_base")]
471 #[test]
ut_client_request_builder_body()472 fn ut_client_request_builder_body() {
473 use std::pin::Pin;
474
475 use ylong_http::body::{MultiPart, Part};
476 use ylong_runtime::futures::poll_fn;
477 use ylong_runtime::io::ReadBuf;
478
479 use crate::runtime::AsyncRead;
480
481 let mp = MultiPart::new().part(
482 Part::new()
483 .name("name")
484 .file_name("example.txt")
485 .mime("application/octet-stream")
486 .stream("1234".as_bytes())
487 .length(Some(4)),
488 );
489 let mut request = RequestBuilder::default().body(Body::multipart(mp)).unwrap();
490 let handle = ylong_runtime::spawn(async move {
491 let mut buf = vec![0u8; 50];
492 let mut v_size = vec![];
493 let mut v_str = vec![];
494
495 loop {
496 let mut read_buf = ReadBuf::new(&mut buf);
497 poll_fn(|cx| Pin::new(request.body_mut()).poll_read(cx, &mut read_buf))
498 .await
499 .unwrap();
500
501 let len = read_buf.filled().len();
502 if len == 0 {
503 break;
504 }
505 v_size.push(len);
506 v_str.extend_from_slice(&buf[..len]);
507 }
508 assert_eq!(v_size, vec![50, 50, 50, 50, 50, 11]);
509 });
510 ylong_runtime::block_on(handle).unwrap();
511 }
512
513 /// UT test cases for `PercentEncoder::encode`.
514 ///
515 /// # Brief
516 /// 1. Creates a `PercentEncoder`.
517 /// 2. Checks if result is correct.
518 #[test]
ut_client_percent_encoder_encode()519 fn ut_client_percent_encoder_encode() {
520 let url = "https://www.example.com/data/测试文件.txt";
521 let encoded = PercentEncoder::encode(url).unwrap();
522 assert_eq!(
523 encoded,
524 "https://www.example.com/data/%E6%B5%8B%E8%AF%95%E6%96%87%E4%BB%B6.txt"
525 );
526 }
527 }
528