slinger/
response.rs

1use crate::body::Body;
2#[cfg(feature = "cookie")]
3use crate::cookies;
4use crate::errors::{new_io_error, Error, Result};
5use crate::record::{HTTPRecord, RedirectRecord};
6#[cfg(feature = "tls")]
7use crate::tls::PeerCertificate;
8use crate::{Request, COLON_SPACE, CR_LF, SPACE};
9use bytes::Bytes;
10#[cfg(feature = "charset")]
11use encoding_rs::{Encoding, UTF_8};
12#[cfg(feature = "gzip")]
13use flate2::read::MultiGzDecoder;
14use http::{Method, Response as HttpResponse};
15#[cfg(feature = "charset")]
16use mime::Mime;
17#[cfg(feature = "gzip")]
18use std::io::Read;
19use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncReadExt, BufReader};
20use tokio::time::{timeout, Duration};
21
22/// A Response to a submitted `Request`.
23#[derive(Debug, Default, Clone)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
26pub struct Response {
27  #[cfg_attr(feature = "serde", serde(with = "http_serde::version"))]
28  #[cfg_attr(
29    feature = "schema",
30    schemars(
31      with = "String",
32      title = "HTTP version",
33      description = "The protocol version used in the HTTP response",
34      example = "HTTP/1.1"
35    )
36  )]
37  /// The HTTP version of the response.
38  pub version: http::Version,
39  #[cfg_attr(feature = "serde", serde(with = "http_serde::uri"))]
40  #[cfg_attr(
41    feature = "schema",
42    schemars(
43      with = "String",
44      title = "request URI",
45      description = "The original request URI that generated this response",
46      example = "https://example.com/api/v1/resource"
47    )
48  )]
49  /// The final URI of the response.
50  pub uri: http::Uri,
51  #[cfg_attr(feature = "serde", serde(with = "http_serde::status_code"))]
52  #[cfg_attr(
53    feature = "schema",
54    schemars(
55      title = "status code",
56      description = "The HTTP status code indicating the response status",
57      example = 200,
58      schema_with = "crate::serde_schema::status_code_schema"
59    )
60  )]
61  /// The status code of the response.
62  pub status_code: http::StatusCode,
63  #[cfg_attr(feature = "serde", serde(with = "http_serde::header_map"))]
64  #[cfg_attr(
65    feature = "schema",
66    schemars(
67      with = "std::collections::HashMap<String,String>",
68      title = "response headers",
69      description = "Key-value pairs of HTTP headers included in the response",
70      example = r#"{"Content-Type": "application/json", "Cache-Control": "max-age=3600"}"#
71    )
72  )]
73  /// The headers of the response.
74  pub headers: http::HeaderMap<http::HeaderValue>,
75  #[cfg_attr(feature = "serde", serde(skip))]
76  /// The extensions associated with the response.
77  pub extensions: http::Extensions,
78  #[cfg_attr(
79    feature = "schema",
80    schemars(
81      title = "response body",
82      description = "Optional body content received in the HTTP response",
83      example = r#"{"data": {"id": 123, "name": "example"}}"#,
84    )
85  )]
86  /// The body of the response.
87  pub body: Option<Body>,
88}
89
90impl PartialEq for Response {
91  fn eq(&self, other: &Self) -> bool {
92    self.version == other.version
93      && self.status_code == other.status_code
94      && self.headers == other.headers
95      && self.body.eq(&self.body)
96  }
97}
98
99impl<T> From<HttpResponse<T>> for Response
100where
101  T: Into<Body>,
102{
103  fn from(value: HttpResponse<T>) -> Self {
104    let (parts, body) = value.into_parts();
105    let body = body.into();
106    Self {
107      version: parts.version,
108      uri: Default::default(),
109      status_code: parts.status,
110      headers: parts.headers,
111      extensions: parts.extensions,
112      body: if body.is_empty() { None } else { Some(body) },
113    }
114  }
115}
116
117impl Response {
118  pub(crate) fn to_raw(&self) -> Bytes {
119    let mut http_response = Vec::new();
120    http_response.extend(format!("{:?}", self.version).as_bytes());
121    http_response.extend(SPACE);
122    http_response.extend(format!("{}", self.status_code).as_bytes());
123    http_response.extend(CR_LF);
124    for (k, v) in self.headers.iter() {
125      http_response.extend(k.as_str().as_bytes());
126      http_response.extend(COLON_SPACE);
127      http_response.extend(v.as_bytes());
128      http_response.extend(CR_LF);
129    }
130    http_response.extend(CR_LF);
131    // 添加body
132    if let Some(b) = self.body() {
133      if !b.is_empty() {
134        http_response.extend(b.as_ref());
135      }
136    }
137    Bytes::from(http_response)
138  }
139  /// An HTTP response builder
140  ///
141  /// This type can be used to construct an instance of `Response` through a
142  /// builder-like pattern.
143  pub fn builder() -> http::response::Builder {
144    http::response::Builder::new()
145  }
146}
147
148impl Response {
149  /// Retrieve the cookies contained in the response.
150  ///
151  /// Note that invalid 'Set-Cookie' headers will be ignored.
152  ///
153  /// # Optional
154  ///
155  /// This requires the optional `cookie` feature to be enabled.
156  #[cfg(feature = "cookie")]
157  #[cfg_attr(docsrs, doc(cfg(feature = "cookie")))]
158  pub fn cookies(&self) -> impl Iterator<Item = cookies::Cookie<'_>> {
159    cookies::extract_response_cookies(&self.headers).filter_map(|x| x.ok())
160  }
161
162  /// 获取编码并且尝试解码
163  #[cfg(feature = "charset")]
164  pub fn text_with_charset(&self, default_encoding: &str) -> Result<String> {
165    let body = if let Some(b) = self.body() {
166      b
167    } else {
168      return Ok(String::new());
169    };
170    let content_type = self
171      .headers
172      .get(http::header::CONTENT_TYPE)
173      .and_then(|value| value.to_str().ok())
174      .and_then(|value| value.parse::<Mime>().ok());
175    let header_encoding = content_type
176      .as_ref()
177      .and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
178      .unwrap_or(default_encoding);
179    let mut decode_text = String::new();
180    for encoding_name in &[header_encoding, default_encoding] {
181      let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
182      let (text, _, is_errors) = encoding.decode(body);
183      if !is_errors {
184        decode_text = text.to_string();
185        break;
186      }
187    }
188    Ok(decode_text)
189  }
190  /// Get the response text.
191  ///
192  /// This method decodes the response body with BOM sniffing
193  /// and with malformed sequences replaced with the REPLACEMENT CHARACTER.
194  /// Encoding is determined from the `charset` parameter of `Content-Type` header,
195  /// and defaults to `utf-8` if not presented.
196  ///
197  /// # Note
198  ///
199  /// If the `charset` feature is disabled the method will only attempt to decode the
200  /// response as UTF-8, regardless of the given `Content-Type`
201  ///
202  /// # Example
203  ///
204  /// ```rust
205  /// # extern crate slinger;
206  /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
207  /// let content = slinger::get("http://httpbin.org/range/26").await?.text()?;
208  /// # Ok(())
209  /// # }
210  /// ```
211  ///
212  pub fn text(&self) -> Result<String> {
213    #[cfg(feature = "charset")]
214    {
215      let default_encoding = "utf-8";
216      self.text_with_charset(default_encoding)
217    }
218    #[cfg(not(feature = "charset"))]
219    Ok(String::from_utf8_lossy(&self.body().clone().unwrap_or_default()).to_string())
220  }
221  /// Get the `StatusCode` of this `Response`.
222  ///
223  /// # Examples
224  ///
225  /// Checking for general status class:
226  ///
227  /// ```rust
228  /// # #[cfg(feature = "json")]
229  /// # fn run() -> Result<(), Box<dyn std::error::Error>> {
230  /// let resp = slinger::get("http://httpbin.org/get")?;
231  /// if resp.status().is_success() {
232  ///     println!("success!");
233  /// } else if resp.status().is_server_error() {
234  ///     println!("server error!");
235  /// } else {
236  ///     println!("Something else happened. Status: {:?}", resp.status());
237  /// }
238  /// # Ok(())
239  /// # }
240  /// ```
241  ///
242  /// Checking for specific status codes:
243  ///
244  /// ```rust
245  /// use slinger::Client;
246  /// use slinger::http::StatusCode;
247  /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
248  /// let client = Client::new();
249  ///
250  /// let resp = client.post("http://httpbin.org/post")
251  ///     .body("possibly too large")
252  ///     .send().await?;
253  ///
254  /// match resp.status_code() {
255  ///     StatusCode::OK => println!("success!"),
256  ///     StatusCode::PAYLOAD_TOO_LARGE => {
257  ///         println!("Request payload is too large!");
258  ///     }
259  ///     s => println!("Received response status: {s:?}"),
260  /// };
261  /// # Ok(())
262  /// # }
263  /// ```
264  #[inline]
265  pub fn status_code(&self) -> http::StatusCode {
266    self.status_code
267  }
268  /// Get the HTTP `Version` of this `Response`.
269  #[inline]
270  pub fn version(&self) -> http::Version {
271    self.version
272  }
273
274  /// Get the `Headers` of this `Response`.
275  ///
276  /// # Example
277  ///
278  /// Saving an etag when caching a file:
279  ///
280  /// ```
281  /// use slinger::Client;
282  /// use slinger::http::header::ETAG;
283  ///
284  /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
285  /// let client = Client::new();
286  ///
287  /// let mut resp = client.get("http://httpbin.org/cache").send().await?;
288  /// if resp.status_code().is_success() {
289  ///     if let Some(etag) = resp.headers().get(ETAG) {
290  ///         std::fs::write("etag", etag.as_bytes())?;
291  ///     }
292  /// }
293  /// # Ok(())
294  /// # }
295  /// ```
296  #[inline]
297  pub fn headers(&self) -> &http::HeaderMap {
298    &self.headers
299  }
300  /// Get a mutable reference to the `Headers` of this `Response`.
301  #[inline]
302  pub fn headers_mut(&mut self) -> &mut http::HeaderMap {
303    &mut self.headers
304  }
305  /// Get the content-length of the response, if it is known.
306  ///
307  /// Reasons it may not be known:
308  ///
309  /// - The server didn't send a `content-length` header.
310  /// - The response is gzipped and automatically decoded (thus changing
311  ///   the actual decoded length).
312  pub fn content_length(&self) -> Option<u64> {
313    self
314      .headers
315      .get(http::header::CONTENT_LENGTH)
316      .and_then(|x| x.to_str().ok()?.parse().ok())
317  }
318  /// Get the final `http::Uri` of this `Response`.
319  ///
320  /// # Example
321  ///
322  /// ```rust
323  /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
324  /// let resp = slinger::get("http://httpbin.org/redirect/1").await?;
325  /// assert_eq!(resp.uri().to_string().as_str(), "http://httpbin.org/get");
326  /// # Ok(())
327  /// # }
328  /// ```
329  #[inline]
330  pub fn uri(&self) -> &http::Uri {
331    &self.uri
332  }
333  #[inline]
334  pub(crate) fn url_mut(&mut self) -> &mut http::Uri {
335    &mut self.uri
336  }
337  /// Get the full response body as `Bytes`.
338  ///
339  /// # Example
340  ///
341  /// ```
342  /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
343  /// let resp = slinger::get("http://httpbin.org/ip").await?;
344  /// let body = resp.body();
345  /// println!("bytes: {body:?}");
346  /// # Ok(())
347  /// # }
348  /// ```
349  pub fn body(&self) -> &Option<Body> {
350    &self.body
351  }
352  /// private
353  pub fn body_mut(&mut self) -> &mut Option<Body> {
354    &mut self.body
355  }
356  /// Returns a reference to the associated extensions.
357  pub fn extensions(&self) -> &http::Extensions {
358    &self.extensions
359  }
360  /// Returns a mutable reference to the associated extensions.
361  pub fn extensions_mut(&mut self) -> &mut http::Extensions {
362    &mut self.extensions
363  }
364}
365
366/// 放一些响应中间过程记录,存起来方便获取
367impl Response {
368  /// Get the certificate to get this `Response`.
369  ///
370  /// # Example
371  ///
372  /// ```rust
373  /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
374  /// let resp = slinger::get("https://httpbin.org/").await?;
375  /// println!("httpbin.org certificate: {:?}", resp.certificate());
376  /// # Ok(())
377  /// # }
378  /// ```
379  ///
380  #[cfg(feature = "tls")]
381  pub fn certificate(&self) -> Option<&Vec<PeerCertificate>> {
382    self.extensions().get::<Vec<PeerCertificate>>()
383  }
384  /// Get the http record used to get this `Response`.
385  ///
386  /// # Example
387  ///
388  /// ```rust
389  /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
390  /// let resp = slinger::get("http://httpbin.org/redirect/1").await?;
391  /// println!("httpbin.org http: {:?}", resp.http_record());
392  /// # Ok(())
393  /// # }
394  /// ```
395  pub fn http_record(&self) -> Option<&Vec<HTTPRecord>> {
396    self.extensions().get::<Vec<HTTPRecord>>()
397  }
398  /// Get the request used to get this `Response`.
399  ///
400  /// # Example
401  ///
402  /// ```rust
403  /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
404  /// let resp = slinger::get("http://httpbin.org/redirect/1").await?;
405  /// println!("httpbin.org request: {:?}", resp.request());
406  /// # Ok(())
407  /// # }
408  /// ```
409  pub fn request(&self) -> Option<&Request> {
410    self.extensions().get::<Request>()
411  }
412  /// Get the redirect record used to get this `Response`.
413  ///
414  /// # Example
415  ///
416  /// ```rust
417  /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
418  /// let resp = slinger::get("http://httpbin.org/redirect-to?url=http://www.example.com/").await?;
419  /// println!("httpbin.org redirect: {:?}", resp.redirect_record());
420  /// # Ok(())
421  /// # }
422  /// ```
423  pub fn redirect_record(&self) -> Option<&RedirectRecord> {
424    self.extensions().get::<RedirectRecord>()
425  }
426}
427
428/// A builder to construct the properties of a `Response`.
429///
430/// To construct a `ResponseBuilder`, refer to the `Client` documentation.
431#[derive(Debug)]
432pub struct ResponseBuilder<T: AsyncRead + AsyncReadExt> {
433  builder: http::response::Builder,
434  reader: BufReader<T>,
435  config: ResponseConfig,
436}
437
438/// response config
439#[derive(Debug, Default)]
440pub struct ResponseConfig {
441  method: Method,
442  timeout: Option<Duration>,
443  unsafe_response: bool,
444  max_read: Option<u64>,
445}
446
447impl ResponseConfig {
448  /// new a response config
449  pub fn new(request: &Request, timeout: Option<Duration>) -> Self {
450    let method = request.method().clone();
451    let unsafe_response = request.is_unsafe();
452    ResponseConfig {
453      method,
454      timeout,
455      unsafe_response,
456      max_read: None,
457    }
458  }
459}
460
461impl<T: AsyncRead + Unpin + Sized> ResponseBuilder<T> {
462  /// Constructs a new response.
463  pub fn new(reader: BufReader<T>, config: ResponseConfig) -> ResponseBuilder<T> {
464    ResponseBuilder {
465      builder: Default::default(),
466      reader,
467      config,
468    }
469  }
470  async fn parser_version(&mut self) -> Result<(http::Version, http::StatusCode)> {
471    let (mut vf, mut sf) = (false, false);
472    let mut lines = Vec::new();
473    if let Ok(_length) = timeout(
474      self.config.timeout.unwrap_or(Duration::from_secs(30)),
475      self.reader.read_until(b'\n', &mut lines),
476    )
477    .await
478    .map_err(|e| Error::IO(std::io::Error::new(std::io::ErrorKind::TimedOut, e)))?
479    {
480      let mut version = http::Version::default();
481      let mut sc = http::StatusCode::default();
482      for (index, vc) in lines.splitn(3, |b| b == &b' ').enumerate() {
483        if vc.is_empty() {
484          return Err(new_io_error(
485            std::io::ErrorKind::InvalidData,
486            "invalid http version and status_code data",
487          ));
488        }
489        match index {
490          0 => {
491            version = match vc {
492              b"HTTP/0.9" => http::Version::HTTP_09,
493              b"HTTP/1.0" => http::Version::HTTP_10,
494              b"HTTP/1.1" => http::Version::HTTP_11,
495              b"HTTP/2.0" => http::Version::HTTP_2,
496              b"HTTP/3.0" => http::Version::HTTP_3,
497              _ => {
498                return Err(new_io_error(
499                  std::io::ErrorKind::InvalidData,
500                  "invalid http version",
501                ));
502              }
503            };
504            vf = true;
505          }
506          1 => {
507            sc = http::StatusCode::try_from(vc).map_err(|x| Error::Http(http::Error::from(x)))?;
508            sf = true;
509          }
510          _ => {}
511        }
512      }
513      if !(vf && sf) {
514        return Err(new_io_error(
515          std::io::ErrorKind::InvalidData,
516          "invalid http version and status_code data",
517        ));
518      }
519      Ok((version, sc))
520    } else {
521      Err(new_io_error(
522        std::io::ErrorKind::InvalidData,
523        "invalid http version and status_code data",
524      ))
525    }
526  }
527  async fn read_headers(&mut self) -> Result<http::HeaderMap> {
528    // 读取请求头
529    let mut headers = http::HeaderMap::new();
530    let mut header_line = Vec::new();
531    while let Ok(length) = timeout(
532      self.config.timeout.unwrap_or(Duration::from_secs(30)),
533      self.reader.read_until(b'\n', &mut header_line),
534    )
535    .await
536    .map_err(|e| Error::IO(std::io::Error::new(std::io::ErrorKind::TimedOut, e)))?
537    {
538      if length == 0 || header_line == b"\r\n" {
539        break;
540      }
541      if let Ok((Some(k), Some(v))) = parser_headers(&header_line) {
542        if headers.contains_key(&k) {
543          headers.append(k, v);
544        } else {
545          headers.insert(k, v);
546        }
547      };
548      header_line.clear();
549    }
550    Ok(headers)
551  }
552  async fn read_body(&mut self, header: &http::HeaderMap) -> Result<Vec<u8>> {
553    let mut body = Vec::new();
554    if matches!(self.config.method, Method::HEAD) {
555      return Ok(body);
556    }
557    let mut content_length: Option<u64> = header.get(http::header::CONTENT_LENGTH).and_then(|x| {
558      x.to_str()
559        .ok()?
560        .parse()
561        .ok()
562        .and_then(|l| if l == 0 { None } else { Some(l) })
563    });
564    if self.config.unsafe_response {
565      content_length = None;
566    }
567    if let Some(te) = header.get(http::header::TRANSFER_ENCODING) {
568      if te == "chunked" {
569        body = self.read_chunked_body().await?;
570      }
571    } else {
572      let limits = content_length.map(|x| {
573        if let Some(max) = self.config.max_read {
574          std::cmp::min(x, max)
575        } else {
576          x
577        }
578      });
579      let mut buffer = vec![0; 12]; // 定义一个缓冲区
580      let mut total_bytes_read = 0;
581      let timeout = self.config.timeout;
582      loop {
583        let size = if let Some(to) = timeout {
584          match tokio::time::timeout(to, self.reader.read(&mut buffer)).await {
585            Ok(size) => size,
586            Err(_) => break,
587          }
588        } else {
589          self.reader.read(&mut buffer).await
590        };
591        match size {
592          Ok(0) => break,
593          Ok(n) => {
594            body.extend_from_slice(&buffer[..n]);
595            total_bytes_read += n;
596            // 当有读取到数据的时候重置计时器
597          }
598          Err(ref err) if err.kind() == std::io::ErrorKind::WouldBlock => {
599            // 如果没有数据可读,但超时尚未到达,可以在这里等待或重试
600            // 当已经有数据了或者触发超时就跳出循环,防止防火墙一直把会话挂着不释放
601            if total_bytes_read > 0 {
602              break;
603            }
604          }
605          Err(_err) => break,
606        }
607        // 检查是否读取到了全部数据,如果是,则退出循环
608        if let Some(limit) = limits {
609          if total_bytes_read >= limit as usize {
610            break;
611          }
612        }
613      }
614    }
615    #[cfg(feature = "gzip")]
616    if let Some(ce) = header.get(http::header::CONTENT_ENCODING) {
617      if ce == "gzip" {
618        let mut gzip_body = Vec::new();
619        let mut d = MultiGzDecoder::new(&body[..]);
620        d.read_to_end(&mut gzip_body)?;
621        body = gzip_body;
622      }
623    }
624    Ok(body)
625  }
626
627  async fn read_chunked_body(&mut self) -> Result<Vec<u8>> {
628    let mut body: Vec<u8> = Vec::new();
629    loop {
630      let mut chunk: String = String::new();
631      loop {
632        let mut one_byte = vec![0; 1];
633        self.reader.read_exact(&mut one_byte).await?;
634        if one_byte[0] != 10 && one_byte[0] != 13 {
635          chunk.push(one_byte[0] as char);
636          break;
637        }
638      }
639      loop {
640        let mut one_byte = vec![0; 1];
641        self.reader.read_exact(&mut one_byte).await?;
642        if one_byte[0] == 10 || one_byte[0] == 13 {
643          self.reader.read_exact(&mut one_byte).await?;
644          break;
645        } else {
646          chunk.push(one_byte[0] as char)
647        }
648      }
649      if chunk == "0" || chunk.is_empty() {
650        break;
651      }
652      let chunk = usize::from_str_radix(&chunk, 16)?;
653      let mut chunk_of_bytes = vec![0; chunk];
654      self.reader.read_exact(&mut chunk_of_bytes).await?;
655      body.append(&mut chunk_of_bytes);
656    }
657    Ok(body)
658  }
659
660  /// Build a `Response`, which can be inspected, modified and executed with
661  /// `Client::execute()`.
662  pub async fn build(mut self) -> Result<(Response, T)> {
663    let (v, c) = self.parser_version().await?;
664    self.builder = self.builder.version(v).status(c);
665    let header = self.read_headers().await?;
666    // 读取body
667    let body = self.read_body(&header).await?;
668    if let Some(h) = self.builder.headers_mut() {
669      *h = header;
670    }
671    let resp = self.builder.body(body)?;
672    let response = resp.into();
673    let socket = self.reader.into_inner();
674    Ok((response, socket))
675  }
676}
677
678pub(crate) fn parser_headers(
679  buffer: &[u8],
680) -> Result<(Option<http::HeaderName>, Option<http::HeaderValue>)> {
681  let mut k = None;
682  let mut v = None;
683  let buffer = buffer.strip_suffix(CR_LF).unwrap_or(buffer);
684  for (index, h) in buffer.splitn(2, |s| s == &58).enumerate() {
685    let h = h.strip_prefix(SPACE).unwrap_or(h);
686    match index {
687      0 => match http::HeaderName::from_bytes(h) {
688        Ok(hk) => k = Some(hk),
689        Err(err) => {
690          return Err(Error::Http(http::Error::from(err)));
691        }
692      },
693      1 => match http::HeaderValue::from_bytes(h) {
694        Ok(hv) => v = Some(hv),
695        Err(err) => {
696          return Err(Error::Http(http::Error::from(err)));
697        }
698      },
699      _ => {}
700    }
701  }
702  Ok((k, v))
703}