| 1 | use std::convert::TryFrom; |
| 2 | use std::fmt; |
| 3 | use std::future::Future; |
| 4 | use std::time::Duration; |
| 5 | |
| 6 | use serde::Serialize; |
| 7 | #[cfg (feature = "json" )] |
| 8 | use serde_json; |
| 9 | |
| 10 | use super::body::Body; |
| 11 | use super::client::{Client, Pending}; |
| 12 | #[cfg (feature = "multipart" )] |
| 13 | use super::multipart; |
| 14 | use super::response::Response; |
| 15 | #[cfg (feature = "multipart" )] |
| 16 | use crate::header::CONTENT_LENGTH; |
| 17 | use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE}; |
| 18 | use crate::{Method, Url}; |
| 19 | use http::{request::Parts, Request as HttpRequest, Version}; |
| 20 | |
| 21 | /// A request which can be executed with `Client::execute()`. |
| 22 | pub struct Request { |
| 23 | method: Method, |
| 24 | url: Url, |
| 25 | headers: HeaderMap, |
| 26 | body: Option<Body>, |
| 27 | timeout: Option<Duration>, |
| 28 | version: Version, |
| 29 | } |
| 30 | |
| 31 | /// A builder to construct the properties of a `Request`. |
| 32 | /// |
| 33 | /// To construct a `RequestBuilder`, refer to the `Client` documentation. |
| 34 | #[must_use = "RequestBuilder does nothing until you 'send' it" ] |
| 35 | pub struct RequestBuilder { |
| 36 | client: Client, |
| 37 | request: crate::Result<Request>, |
| 38 | } |
| 39 | |
| 40 | impl Request { |
| 41 | /// Constructs a new request. |
| 42 | #[inline ] |
| 43 | pub fn new(method: Method, url: Url) -> Self { |
| 44 | Request { |
| 45 | method, |
| 46 | url, |
| 47 | headers: HeaderMap::new(), |
| 48 | body: None, |
| 49 | timeout: None, |
| 50 | version: Version::default(), |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | /// Get the method. |
| 55 | #[inline ] |
| 56 | pub fn method(&self) -> &Method { |
| 57 | &self.method |
| 58 | } |
| 59 | |
| 60 | /// Get a mutable reference to the method. |
| 61 | #[inline ] |
| 62 | pub fn method_mut(&mut self) -> &mut Method { |
| 63 | &mut self.method |
| 64 | } |
| 65 | |
| 66 | /// Get the url. |
| 67 | #[inline ] |
| 68 | pub fn url(&self) -> &Url { |
| 69 | &self.url |
| 70 | } |
| 71 | |
| 72 | /// Get a mutable reference to the url. |
| 73 | #[inline ] |
| 74 | pub fn url_mut(&mut self) -> &mut Url { |
| 75 | &mut self.url |
| 76 | } |
| 77 | |
| 78 | /// Get the headers. |
| 79 | #[inline ] |
| 80 | pub fn headers(&self) -> &HeaderMap { |
| 81 | &self.headers |
| 82 | } |
| 83 | |
| 84 | /// Get a mutable reference to the headers. |
| 85 | #[inline ] |
| 86 | pub fn headers_mut(&mut self) -> &mut HeaderMap { |
| 87 | &mut self.headers |
| 88 | } |
| 89 | |
| 90 | /// Get the body. |
| 91 | #[inline ] |
| 92 | pub fn body(&self) -> Option<&Body> { |
| 93 | self.body.as_ref() |
| 94 | } |
| 95 | |
| 96 | /// Get a mutable reference to the body. |
| 97 | #[inline ] |
| 98 | pub fn body_mut(&mut self) -> &mut Option<Body> { |
| 99 | &mut self.body |
| 100 | } |
| 101 | |
| 102 | /// Get the timeout. |
| 103 | #[inline ] |
| 104 | pub fn timeout(&self) -> Option<&Duration> { |
| 105 | self.timeout.as_ref() |
| 106 | } |
| 107 | |
| 108 | /// Get a mutable reference to the timeout. |
| 109 | #[inline ] |
| 110 | pub fn timeout_mut(&mut self) -> &mut Option<Duration> { |
| 111 | &mut self.timeout |
| 112 | } |
| 113 | |
| 114 | /// Get the http version. |
| 115 | #[inline ] |
| 116 | pub fn version(&self) -> Version { |
| 117 | self.version |
| 118 | } |
| 119 | |
| 120 | /// Get a mutable reference to the http version. |
| 121 | #[inline ] |
| 122 | pub fn version_mut(&mut self) -> &mut Version { |
| 123 | &mut self.version |
| 124 | } |
| 125 | |
| 126 | /// Attempt to clone the request. |
| 127 | /// |
| 128 | /// `None` is returned if the request can not be cloned, i.e. if the body is a stream. |
| 129 | pub fn try_clone(&self) -> Option<Request> { |
| 130 | let body = match self.body.as_ref() { |
| 131 | Some(body) => Some(body.try_clone()?), |
| 132 | None => None, |
| 133 | }; |
| 134 | let mut req = Request::new(self.method().clone(), self.url().clone()); |
| 135 | *req.timeout_mut() = self.timeout().copied(); |
| 136 | *req.headers_mut() = self.headers().clone(); |
| 137 | *req.version_mut() = self.version(); |
| 138 | req.body = body; |
| 139 | Some(req) |
| 140 | } |
| 141 | |
| 142 | pub(super) fn pieces( |
| 143 | self, |
| 144 | ) -> ( |
| 145 | Method, |
| 146 | Url, |
| 147 | HeaderMap, |
| 148 | Option<Body>, |
| 149 | Option<Duration>, |
| 150 | Version, |
| 151 | ) { |
| 152 | ( |
| 153 | self.method, |
| 154 | self.url, |
| 155 | self.headers, |
| 156 | self.body, |
| 157 | self.timeout, |
| 158 | self.version, |
| 159 | ) |
| 160 | } |
| 161 | } |
| 162 | |
| 163 | impl RequestBuilder { |
| 164 | pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder { |
| 165 | let mut builder = RequestBuilder { client, request }; |
| 166 | |
| 167 | let auth = builder |
| 168 | .request |
| 169 | .as_mut() |
| 170 | .ok() |
| 171 | .and_then(|req| extract_authority(&mut req.url)); |
| 172 | |
| 173 | if let Some((username, password)) = auth { |
| 174 | builder.basic_auth(username, password) |
| 175 | } else { |
| 176 | builder |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /// Assemble a builder starting from an existing `Client` and a `Request`. |
| 181 | pub fn from_parts(client: Client, request: Request) -> RequestBuilder { |
| 182 | RequestBuilder { |
| 183 | client, |
| 184 | request: crate::Result::Ok(request), |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | /// Add a `Header` to this Request. |
| 189 | pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder |
| 190 | where |
| 191 | HeaderName: TryFrom<K>, |
| 192 | <HeaderName as TryFrom<K>>::Error: Into<http::Error>, |
| 193 | HeaderValue: TryFrom<V>, |
| 194 | <HeaderValue as TryFrom<V>>::Error: Into<http::Error>, |
| 195 | { |
| 196 | self.header_sensitive(key, value, false) |
| 197 | } |
| 198 | |
| 199 | /// Add a `Header` to this Request with ability to define if `header_value` is sensitive. |
| 200 | fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder |
| 201 | where |
| 202 | HeaderName: TryFrom<K>, |
| 203 | <HeaderName as TryFrom<K>>::Error: Into<http::Error>, |
| 204 | HeaderValue: TryFrom<V>, |
| 205 | <HeaderValue as TryFrom<V>>::Error: Into<http::Error>, |
| 206 | { |
| 207 | let mut error = None; |
| 208 | if let Ok(ref mut req) = self.request { |
| 209 | match <HeaderName as TryFrom<K>>::try_from(key) { |
| 210 | Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) { |
| 211 | Ok(mut value) => { |
| 212 | // We want to potentially make an non-sensitive header |
| 213 | // to be sensitive, not the reverse. So, don't turn off |
| 214 | // a previously sensitive header. |
| 215 | if sensitive { |
| 216 | value.set_sensitive(true); |
| 217 | } |
| 218 | req.headers_mut().append(key, value); |
| 219 | } |
| 220 | Err(e) => error = Some(crate::error::builder(e.into())), |
| 221 | }, |
| 222 | Err(e) => error = Some(crate::error::builder(e.into())), |
| 223 | }; |
| 224 | } |
| 225 | if let Some(err) = error { |
| 226 | self.request = Err(err); |
| 227 | } |
| 228 | self |
| 229 | } |
| 230 | |
| 231 | /// Add a set of Headers to the existing ones on this Request. |
| 232 | /// |
| 233 | /// The headers will be merged in to any already set. |
| 234 | pub fn headers(mut self, headers: crate::header::HeaderMap) -> RequestBuilder { |
| 235 | if let Ok(ref mut req) = self.request { |
| 236 | crate::util::replace_headers(req.headers_mut(), headers); |
| 237 | } |
| 238 | self |
| 239 | } |
| 240 | |
| 241 | /// Enable HTTP basic authentication. |
| 242 | /// |
| 243 | /// ```rust |
| 244 | /// # use reqwest::Error; |
| 245 | /// |
| 246 | /// # async fn run() -> Result<(), Error> { |
| 247 | /// let client = reqwest::Client::new(); |
| 248 | /// let resp = client.delete("http://httpbin.org/delete" ) |
| 249 | /// .basic_auth("admin" , Some("good password" )) |
| 250 | /// .send() |
| 251 | /// .await?; |
| 252 | /// # Ok(()) |
| 253 | /// # } |
| 254 | /// ``` |
| 255 | pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder |
| 256 | where |
| 257 | U: fmt::Display, |
| 258 | P: fmt::Display, |
| 259 | { |
| 260 | let header_value = crate::util::basic_auth(username, password); |
| 261 | self.header_sensitive(crate::header::AUTHORIZATION, header_value, true) |
| 262 | } |
| 263 | |
| 264 | /// Enable HTTP bearer authentication. |
| 265 | pub fn bearer_auth<T>(self, token: T) -> RequestBuilder |
| 266 | where |
| 267 | T: fmt::Display, |
| 268 | { |
| 269 | let header_value = format!("Bearer {token}" ); |
| 270 | self.header_sensitive(crate::header::AUTHORIZATION, header_value, true) |
| 271 | } |
| 272 | |
| 273 | /// Set the request body. |
| 274 | pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder { |
| 275 | if let Ok(ref mut req) = self.request { |
| 276 | *req.body_mut() = Some(body.into()); |
| 277 | } |
| 278 | self |
| 279 | } |
| 280 | |
| 281 | /// Enables a request timeout. |
| 282 | /// |
| 283 | /// The timeout is applied from when the request starts connecting until the |
| 284 | /// response body has finished. It affects only this request and overrides |
| 285 | /// the timeout configured using `ClientBuilder::timeout()`. |
| 286 | pub fn timeout(mut self, timeout: Duration) -> RequestBuilder { |
| 287 | if let Ok(ref mut req) = self.request { |
| 288 | *req.timeout_mut() = Some(timeout); |
| 289 | } |
| 290 | self |
| 291 | } |
| 292 | |
| 293 | /// Sends a multipart/form-data body. |
| 294 | /// |
| 295 | /// ``` |
| 296 | /// # use reqwest::Error; |
| 297 | /// |
| 298 | /// # async fn run() -> Result<(), Error> { |
| 299 | /// let client = reqwest::Client::new(); |
| 300 | /// let form = reqwest::multipart::Form::new() |
| 301 | /// .text("key3", "value3") |
| 302 | /// .text("key4", "value4"); |
| 303 | /// |
| 304 | /// |
| 305 | /// let response = client.post("your url") |
| 306 | /// .multipart(form) |
| 307 | /// .send() |
| 308 | /// .await?; |
| 309 | /// # Ok(()) |
| 310 | /// # } |
| 311 | /// ``` |
| 312 | /// |
| 313 | /// In additional the request's body, the Content-Type and Content-Length fields are |
| 314 | /// appropriately set. |
| 315 | #[cfg (feature = "multipart" )] |
| 316 | #[cfg_attr (docsrs, doc(cfg(feature = "multipart" )))] |
| 317 | pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder { |
| 318 | let mut builder = self.header( |
| 319 | CONTENT_TYPE, |
| 320 | format!("multipart/form-data; boundary={}" , multipart.boundary()).as_str(), |
| 321 | ); |
| 322 | |
| 323 | builder = match multipart.compute_length() { |
| 324 | Some(length) => builder.header(CONTENT_LENGTH, length), |
| 325 | None => builder, |
| 326 | }; |
| 327 | |
| 328 | if let Ok(ref mut req) = builder.request { |
| 329 | *req.body_mut() = Some(multipart.stream()) |
| 330 | } |
| 331 | builder |
| 332 | } |
| 333 | |
| 334 | /// Modify the query string of the URL. |
| 335 | /// |
| 336 | /// Modifies the URL of this request, adding the parameters provided. |
| 337 | /// This method appends and does not overwrite. This means that it can |
| 338 | /// be called multiple times and that existing query parameters are not |
| 339 | /// overwritten if the same key is used. The key will simply show up |
| 340 | /// twice in the query string. |
| 341 | /// Calling `.query(&[("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`. |
| 342 | /// |
| 343 | /// # Note |
| 344 | /// This method does not support serializing a single key-value |
| 345 | /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such |
| 346 | /// as `.query(&[("key", "val")])`. It's also possible to serialize structs |
| 347 | /// and maps into a key-value pair. |
| 348 | /// |
| 349 | /// # Errors |
| 350 | /// This method will fail if the object you provide cannot be serialized |
| 351 | /// into a query string. |
| 352 | pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder { |
| 353 | let mut error = None; |
| 354 | if let Ok(ref mut req) = self.request { |
| 355 | let url = req.url_mut(); |
| 356 | let mut pairs = url.query_pairs_mut(); |
| 357 | let serializer = serde_urlencoded::Serializer::new(&mut pairs); |
| 358 | |
| 359 | if let Err(err) = query.serialize(serializer) { |
| 360 | error = Some(crate::error::builder(err)); |
| 361 | } |
| 362 | } |
| 363 | if let Ok(ref mut req) = self.request { |
| 364 | if let Some("" ) = req.url().query() { |
| 365 | req.url_mut().set_query(None); |
| 366 | } |
| 367 | } |
| 368 | if let Some(err) = error { |
| 369 | self.request = Err(err); |
| 370 | } |
| 371 | self |
| 372 | } |
| 373 | |
| 374 | /// Set HTTP version |
| 375 | pub fn version(mut self, version: Version) -> RequestBuilder { |
| 376 | if let Ok(ref mut req) = self.request { |
| 377 | req.version = version; |
| 378 | } |
| 379 | self |
| 380 | } |
| 381 | |
| 382 | /// Send a form body. |
| 383 | /// |
| 384 | /// Sets the body to the url encoded serialization of the passed value, |
| 385 | /// and also sets the `Content-Type: application/x-www-form-urlencoded` |
| 386 | /// header. |
| 387 | /// |
| 388 | /// ```rust |
| 389 | /// # use reqwest::Error; |
| 390 | /// # use std::collections::HashMap; |
| 391 | /// # |
| 392 | /// # async fn run() -> Result<(), Error> { |
| 393 | /// let mut params = HashMap::new(); |
| 394 | /// params.insert("lang" , "rust" ); |
| 395 | /// |
| 396 | /// let client = reqwest::Client::new(); |
| 397 | /// let res = client.post("http://httpbin.org" ) |
| 398 | /// .form(¶ms) |
| 399 | /// .send() |
| 400 | /// .await?; |
| 401 | /// # Ok(()) |
| 402 | /// # } |
| 403 | /// ``` |
| 404 | /// |
| 405 | /// # Errors |
| 406 | /// |
| 407 | /// This method fails if the passed value cannot be serialized into |
| 408 | /// url encoded format |
| 409 | pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder { |
| 410 | let mut error = None; |
| 411 | if let Ok(ref mut req) = self.request { |
| 412 | match serde_urlencoded::to_string(form) { |
| 413 | Ok(body) => { |
| 414 | req.headers_mut() |
| 415 | .entry(CONTENT_TYPE) |
| 416 | .or_insert(HeaderValue::from_static( |
| 417 | "application/x-www-form-urlencoded" , |
| 418 | )); |
| 419 | *req.body_mut() = Some(body.into()); |
| 420 | } |
| 421 | Err(err) => error = Some(crate::error::builder(err)), |
| 422 | } |
| 423 | } |
| 424 | if let Some(err) = error { |
| 425 | self.request = Err(err); |
| 426 | } |
| 427 | self |
| 428 | } |
| 429 | |
| 430 | /// Send a JSON body. |
| 431 | /// |
| 432 | /// # Optional |
| 433 | /// |
| 434 | /// This requires the optional `json` feature enabled. |
| 435 | /// |
| 436 | /// # Errors |
| 437 | /// |
| 438 | /// Serialization can fail if `T`'s implementation of `Serialize` decides to |
| 439 | /// fail, or if `T` contains a map with non-string keys. |
| 440 | #[cfg (feature = "json" )] |
| 441 | #[cfg_attr (docsrs, doc(cfg(feature = "json" )))] |
| 442 | pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder { |
| 443 | let mut error = None; |
| 444 | if let Ok(ref mut req) = self.request { |
| 445 | match serde_json::to_vec(json) { |
| 446 | Ok(body) => { |
| 447 | if !req.headers().contains_key(CONTENT_TYPE) { |
| 448 | req.headers_mut() |
| 449 | .insert(CONTENT_TYPE, HeaderValue::from_static("application/json" )); |
| 450 | } |
| 451 | *req.body_mut() = Some(body.into()); |
| 452 | } |
| 453 | Err(err) => error = Some(crate::error::builder(err)), |
| 454 | } |
| 455 | } |
| 456 | if let Some(err) = error { |
| 457 | self.request = Err(err); |
| 458 | } |
| 459 | self |
| 460 | } |
| 461 | |
| 462 | // This was a shell only meant to help with rendered documentation. |
| 463 | // However, docs.rs can now show the docs for the wasm platforms, so this |
| 464 | // is no longer needed. |
| 465 | // |
| 466 | // You should not otherwise depend on this function. It's deprecation |
| 467 | // is just to nudge people to reduce breakage. It may be removed in a |
| 468 | // future patch version. |
| 469 | #[doc (hidden)] |
| 470 | #[cfg_attr (target_arch = "wasm32" , deprecated)] |
| 471 | pub fn fetch_mode_no_cors(self) -> RequestBuilder { |
| 472 | self |
| 473 | } |
| 474 | |
| 475 | /// Build a `Request`, which can be inspected, modified and executed with |
| 476 | /// `Client::execute()`. |
| 477 | pub fn build(self) -> crate::Result<Request> { |
| 478 | self.request |
| 479 | } |
| 480 | |
| 481 | /// Build a `Request`, which can be inspected, modified and executed with |
| 482 | /// `Client::execute()`. |
| 483 | /// |
| 484 | /// This is similar to [`RequestBuilder::build()`], but also returns the |
| 485 | /// embedded `Client`. |
| 486 | pub fn build_split(self) -> (Client, crate::Result<Request>) { |
| 487 | (self.client, self.request) |
| 488 | } |
| 489 | |
| 490 | /// Constructs the Request and sends it to the target URL, returning a |
| 491 | /// future Response. |
| 492 | /// |
| 493 | /// # Errors |
| 494 | /// |
| 495 | /// This method fails if there was an error while sending request, |
| 496 | /// redirect loop was detected or redirect limit was exhausted. |
| 497 | /// |
| 498 | /// # Example |
| 499 | /// |
| 500 | /// ```no_run |
| 501 | /// # use reqwest::Error; |
| 502 | /// # |
| 503 | /// # async fn run() -> Result<(), Error> { |
| 504 | /// let response = reqwest::Client::new() |
| 505 | /// .get("https://hyper.rs" ) |
| 506 | /// .send() |
| 507 | /// .await?; |
| 508 | /// # Ok(()) |
| 509 | /// # } |
| 510 | /// ``` |
| 511 | pub fn send(self) -> impl Future<Output = Result<Response, crate::Error>> { |
| 512 | match self.request { |
| 513 | Ok(req) => self.client.execute_request(req), |
| 514 | Err(err) => Pending::new_err(err), |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | /// Attempt to clone the RequestBuilder. |
| 519 | /// |
| 520 | /// `None` is returned if the RequestBuilder can not be cloned, |
| 521 | /// i.e. if the request body is a stream. |
| 522 | /// |
| 523 | /// # Examples |
| 524 | /// |
| 525 | /// ``` |
| 526 | /// # use reqwest::Error; |
| 527 | /// # |
| 528 | /// # fn run() -> Result<(), Error> { |
| 529 | /// let client = reqwest::Client::new(); |
| 530 | /// let builder = client.post("http://httpbin.org/post" ) |
| 531 | /// .body("from a &str!" ); |
| 532 | /// let clone = builder.try_clone(); |
| 533 | /// assert!(clone.is_some()); |
| 534 | /// # Ok(()) |
| 535 | /// # } |
| 536 | /// ``` |
| 537 | pub fn try_clone(&self) -> Option<RequestBuilder> { |
| 538 | self.request |
| 539 | .as_ref() |
| 540 | .ok() |
| 541 | .and_then(|req| req.try_clone()) |
| 542 | .map(|req| RequestBuilder { |
| 543 | client: self.client.clone(), |
| 544 | request: Ok(req), |
| 545 | }) |
| 546 | } |
| 547 | } |
| 548 | |
| 549 | impl fmt::Debug for Request { |
| 550 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 551 | fmt_request_fields(&mut f.debug_struct("Request" ), self).finish() |
| 552 | } |
| 553 | } |
| 554 | |
| 555 | impl fmt::Debug for RequestBuilder { |
| 556 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| 557 | let mut builder: DebugStruct<'_, '_> = f.debug_struct(name:"RequestBuilder" ); |
| 558 | match self.request { |
| 559 | Ok(ref req: &Request) => fmt_request_fields(&mut builder, req).finish(), |
| 560 | Err(ref err: &Error) => builder.field(name:"error" , value:err).finish(), |
| 561 | } |
| 562 | } |
| 563 | } |
| 564 | |
| 565 | fn fmt_request_fields<'a, 'b>( |
| 566 | f: &'a mut fmt::DebugStruct<'a, 'b>, |
| 567 | req: &Request, |
| 568 | ) -> &'a mut fmt::DebugStruct<'a, 'b> { |
| 569 | f.field("method" , &req.method) |
| 570 | .field("url" , &req.url) |
| 571 | .field(name:"headers" , &req.headers) |
| 572 | } |
| 573 | |
| 574 | /// Check the request URL for a "username:password" type authority, and if |
| 575 | /// found, remove it from the URL and return it. |
| 576 | pub(crate) fn extract_authority(url: &mut Url) -> Option<(String, Option<String>)> { |
| 577 | use percent_encoding::percent_decode; |
| 578 | |
| 579 | if url.has_authority() { |
| 580 | let username: String = percent_decode(url.username().as_bytes()) |
| 581 | .decode_utf8() |
| 582 | .ok()? |
| 583 | .into(); |
| 584 | let password = url.password().and_then(|pass| { |
| 585 | percent_decode(pass.as_bytes()) |
| 586 | .decode_utf8() |
| 587 | .ok() |
| 588 | .map(String::from) |
| 589 | }); |
| 590 | if !username.is_empty() || password.is_some() { |
| 591 | url.set_username("" ) |
| 592 | .expect("has_authority means set_username shouldn't fail" ); |
| 593 | url.set_password(None) |
| 594 | .expect("has_authority means set_password shouldn't fail" ); |
| 595 | return Some((username, password)); |
| 596 | } |
| 597 | } |
| 598 | |
| 599 | None |
| 600 | } |
| 601 | |
| 602 | impl<T> TryFrom<HttpRequest<T>> for Request |
| 603 | where |
| 604 | T: Into<Body>, |
| 605 | { |
| 606 | type Error = crate::Error; |
| 607 | |
| 608 | fn try_from(req: HttpRequest<T>) -> crate::Result<Self> { |
| 609 | let (parts: Parts, body: T) = req.into_parts(); |
| 610 | let Parts { |
| 611 | method: Method, |
| 612 | uri: Uri, |
| 613 | headers: HeaderMap, |
| 614 | version: Version, |
| 615 | .. |
| 616 | } = parts; |
| 617 | let url: Url = Url::parse(&uri.to_string()).map_err(op:crate::error::builder)?; |
| 618 | Ok(Request { |
| 619 | method, |
| 620 | url, |
| 621 | headers, |
| 622 | body: Some(body.into()), |
| 623 | timeout: None, |
| 624 | version, |
| 625 | }) |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | impl TryFrom<Request> for HttpRequest<Body> { |
| 630 | type Error = crate::Error; |
| 631 | |
| 632 | fn try_from(req: Request) -> crate::Result<Self> { |
| 633 | let Request { |
| 634 | method: Method, |
| 635 | url: Url, |
| 636 | headers: HeaderMap, |
| 637 | body: Option, |
| 638 | version: Version, |
| 639 | .. |
| 640 | } = req; |
| 641 | |
| 642 | let mut req: Request = HttpRequest::builder() |
| 643 | .version(version) |
| 644 | .method(method) |
| 645 | .uri(url.as_str()) |
| 646 | .body(body.unwrap_or_else(Body::empty)) |
| 647 | .map_err(op:crate::error::builder)?; |
| 648 | |
| 649 | *req.headers_mut() = headers; |
| 650 | Ok(req) |
| 651 | } |
| 652 | } |
| 653 | |
| 654 | #[cfg (test)] |
| 655 | mod tests { |
| 656 | #![cfg (not(feature = "rustls-tls-manual-roots-no-provider" ))] |
| 657 | |
| 658 | use super::{Client, HttpRequest, Request, RequestBuilder, Version}; |
| 659 | use crate::Method; |
| 660 | use serde::Serialize; |
| 661 | use std::collections::BTreeMap; |
| 662 | use std::convert::TryFrom; |
| 663 | |
| 664 | #[test ] |
| 665 | fn add_query_append() { |
| 666 | let client = Client::new(); |
| 667 | let some_url = "https://google.com/" ; |
| 668 | let r = client.get(some_url); |
| 669 | |
| 670 | let r = r.query(&[("foo" , "bar" )]); |
| 671 | let r = r.query(&[("qux" , 3)]); |
| 672 | |
| 673 | let req = r.build().expect("request is valid" ); |
| 674 | assert_eq!(req.url().query(), Some("foo=bar&qux=3" )); |
| 675 | } |
| 676 | |
| 677 | #[test ] |
| 678 | fn add_query_append_same() { |
| 679 | let client = Client::new(); |
| 680 | let some_url = "https://google.com/" ; |
| 681 | let r = client.get(some_url); |
| 682 | |
| 683 | let r = r.query(&[("foo" , "a" ), ("foo" , "b" )]); |
| 684 | |
| 685 | let req = r.build().expect("request is valid" ); |
| 686 | assert_eq!(req.url().query(), Some("foo=a&foo=b" )); |
| 687 | } |
| 688 | |
| 689 | #[test ] |
| 690 | fn add_query_struct() { |
| 691 | #[derive (Serialize)] |
| 692 | struct Params { |
| 693 | foo: String, |
| 694 | qux: i32, |
| 695 | } |
| 696 | |
| 697 | let client = Client::new(); |
| 698 | let some_url = "https://google.com/" ; |
| 699 | let r = client.get(some_url); |
| 700 | |
| 701 | let params = Params { |
| 702 | foo: "bar" .into(), |
| 703 | qux: 3, |
| 704 | }; |
| 705 | |
| 706 | let r = r.query(¶ms); |
| 707 | |
| 708 | let req = r.build().expect("request is valid" ); |
| 709 | assert_eq!(req.url().query(), Some("foo=bar&qux=3" )); |
| 710 | } |
| 711 | |
| 712 | #[test ] |
| 713 | fn add_query_map() { |
| 714 | let mut params = BTreeMap::new(); |
| 715 | params.insert("foo" , "bar" ); |
| 716 | params.insert("qux" , "three" ); |
| 717 | |
| 718 | let client = Client::new(); |
| 719 | let some_url = "https://google.com/" ; |
| 720 | let r = client.get(some_url); |
| 721 | |
| 722 | let r = r.query(¶ms); |
| 723 | |
| 724 | let req = r.build().expect("request is valid" ); |
| 725 | assert_eq!(req.url().query(), Some("foo=bar&qux=three" )); |
| 726 | } |
| 727 | |
| 728 | #[test ] |
| 729 | fn test_replace_headers() { |
| 730 | use http::HeaderMap; |
| 731 | |
| 732 | let mut headers = HeaderMap::new(); |
| 733 | headers.insert("foo" , "bar" .parse().unwrap()); |
| 734 | headers.append("foo" , "baz" .parse().unwrap()); |
| 735 | |
| 736 | let client = Client::new(); |
| 737 | let req = client |
| 738 | .get("https://hyper.rs" ) |
| 739 | .header("im-a" , "keeper" ) |
| 740 | .header("foo" , "pop me" ) |
| 741 | .headers(headers) |
| 742 | .build() |
| 743 | .expect("request build" ); |
| 744 | |
| 745 | assert_eq!(req.headers()["im-a" ], "keeper" ); |
| 746 | |
| 747 | let foo = req.headers().get_all("foo" ).iter().collect::<Vec<_>>(); |
| 748 | assert_eq!(foo.len(), 2); |
| 749 | assert_eq!(foo[0], "bar" ); |
| 750 | assert_eq!(foo[1], "baz" ); |
| 751 | } |
| 752 | |
| 753 | #[test ] |
| 754 | fn normalize_empty_query() { |
| 755 | let client = Client::new(); |
| 756 | let some_url = "https://google.com/" ; |
| 757 | let empty_query: &[(&str, &str)] = &[]; |
| 758 | |
| 759 | let req = client |
| 760 | .get(some_url) |
| 761 | .query(empty_query) |
| 762 | .build() |
| 763 | .expect("request build" ); |
| 764 | |
| 765 | assert_eq!(req.url().query(), None); |
| 766 | assert_eq!(req.url().as_str(), "https://google.com/" ); |
| 767 | } |
| 768 | |
| 769 | #[test ] |
| 770 | fn try_clone_reusable() { |
| 771 | let client = Client::new(); |
| 772 | let builder = client |
| 773 | .post("http://httpbin.org/post" ) |
| 774 | .header("foo" , "bar" ) |
| 775 | .body("from a &str!" ); |
| 776 | let req = builder |
| 777 | .try_clone() |
| 778 | .expect("clone successful" ) |
| 779 | .build() |
| 780 | .expect("request is valid" ); |
| 781 | assert_eq!(req.url().as_str(), "http://httpbin.org/post" ); |
| 782 | assert_eq!(req.method(), Method::POST); |
| 783 | assert_eq!(req.headers()["foo" ], "bar" ); |
| 784 | } |
| 785 | |
| 786 | #[test ] |
| 787 | fn try_clone_no_body() { |
| 788 | let client = Client::new(); |
| 789 | let builder = client.get("http://httpbin.org/get" ); |
| 790 | let req = builder |
| 791 | .try_clone() |
| 792 | .expect("clone successful" ) |
| 793 | .build() |
| 794 | .expect("request is valid" ); |
| 795 | assert_eq!(req.url().as_str(), "http://httpbin.org/get" ); |
| 796 | assert_eq!(req.method(), Method::GET); |
| 797 | assert!(req.body().is_none()); |
| 798 | } |
| 799 | |
| 800 | #[test ] |
| 801 | #[cfg (feature = "stream" )] |
| 802 | fn try_clone_stream() { |
| 803 | let chunks: Vec<Result<_, ::std::io::Error>> = vec![Ok("hello" ), Ok(" " ), Ok("world" )]; |
| 804 | let stream = futures_util::stream::iter(chunks); |
| 805 | let client = Client::new(); |
| 806 | let builder = client |
| 807 | .get("http://httpbin.org/get" ) |
| 808 | .body(super::Body::wrap_stream(stream)); |
| 809 | let clone = builder.try_clone(); |
| 810 | assert!(clone.is_none()); |
| 811 | } |
| 812 | |
| 813 | #[test ] |
| 814 | fn convert_url_authority_into_basic_auth() { |
| 815 | let client = Client::new(); |
| 816 | let some_url = "https://Aladdin:open sesame@localhost/" ; |
| 817 | |
| 818 | let req = client.get(some_url).build().expect("request build" ); |
| 819 | |
| 820 | assert_eq!(req.url().as_str(), "https://localhost/" ); |
| 821 | assert_eq!( |
| 822 | req.headers()["authorization" ], |
| 823 | "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" |
| 824 | ); |
| 825 | } |
| 826 | |
| 827 | #[test ] |
| 828 | fn test_basic_auth_sensitive_header() { |
| 829 | let client = Client::new(); |
| 830 | let some_url = "https://localhost/" ; |
| 831 | |
| 832 | let req = client |
| 833 | .get(some_url) |
| 834 | .basic_auth("Aladdin" , Some("open sesame" )) |
| 835 | .build() |
| 836 | .expect("request build" ); |
| 837 | |
| 838 | assert_eq!(req.url().as_str(), "https://localhost/" ); |
| 839 | assert_eq!( |
| 840 | req.headers()["authorization" ], |
| 841 | "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" |
| 842 | ); |
| 843 | assert!(req.headers()["authorization" ].is_sensitive()); |
| 844 | } |
| 845 | |
| 846 | #[test ] |
| 847 | fn test_bearer_auth_sensitive_header() { |
| 848 | let client = Client::new(); |
| 849 | let some_url = "https://localhost/" ; |
| 850 | |
| 851 | let req = client |
| 852 | .get(some_url) |
| 853 | .bearer_auth("Hold my bear" ) |
| 854 | .build() |
| 855 | .expect("request build" ); |
| 856 | |
| 857 | assert_eq!(req.url().as_str(), "https://localhost/" ); |
| 858 | assert_eq!(req.headers()["authorization" ], "Bearer Hold my bear" ); |
| 859 | assert!(req.headers()["authorization" ].is_sensitive()); |
| 860 | } |
| 861 | |
| 862 | #[test ] |
| 863 | fn test_explicit_sensitive_header() { |
| 864 | let client = Client::new(); |
| 865 | let some_url = "https://localhost/" ; |
| 866 | |
| 867 | let mut header = http::HeaderValue::from_static("in plain sight" ); |
| 868 | header.set_sensitive(true); |
| 869 | |
| 870 | let req = client |
| 871 | .get(some_url) |
| 872 | .header("hiding" , header) |
| 873 | .build() |
| 874 | .expect("request build" ); |
| 875 | |
| 876 | assert_eq!(req.url().as_str(), "https://localhost/" ); |
| 877 | assert_eq!(req.headers()["hiding" ], "in plain sight" ); |
| 878 | assert!(req.headers()["hiding" ].is_sensitive()); |
| 879 | } |
| 880 | |
| 881 | #[test ] |
| 882 | fn convert_from_http_request() { |
| 883 | let http_request = HttpRequest::builder() |
| 884 | .method("GET" ) |
| 885 | .uri("http://localhost/" ) |
| 886 | .header("User-Agent" , "my-awesome-agent/1.0" ) |
| 887 | .body("test test test" ) |
| 888 | .unwrap(); |
| 889 | let req: Request = Request::try_from(http_request).unwrap(); |
| 890 | assert!(req.body().is_some()); |
| 891 | let test_data = b"test test test" ; |
| 892 | assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..])); |
| 893 | let headers = req.headers(); |
| 894 | assert_eq!(headers.get("User-Agent" ).unwrap(), "my-awesome-agent/1.0" ); |
| 895 | assert_eq!(req.method(), Method::GET); |
| 896 | assert_eq!(req.url().as_str(), "http://localhost/" ); |
| 897 | } |
| 898 | |
| 899 | #[test ] |
| 900 | fn set_http_request_version() { |
| 901 | let http_request = HttpRequest::builder() |
| 902 | .method("GET" ) |
| 903 | .uri("http://localhost/" ) |
| 904 | .header("User-Agent" , "my-awesome-agent/1.0" ) |
| 905 | .version(Version::HTTP_11) |
| 906 | .body("test test test" ) |
| 907 | .unwrap(); |
| 908 | let req: Request = Request::try_from(http_request).unwrap(); |
| 909 | assert!(req.body().is_some()); |
| 910 | let test_data = b"test test test" ; |
| 911 | assert_eq!(req.body().unwrap().as_bytes(), Some(&test_data[..])); |
| 912 | let headers = req.headers(); |
| 913 | assert_eq!(headers.get("User-Agent" ).unwrap(), "my-awesome-agent/1.0" ); |
| 914 | assert_eq!(req.method(), Method::GET); |
| 915 | assert_eq!(req.url().as_str(), "http://localhost/" ); |
| 916 | assert_eq!(req.version(), Version::HTTP_11); |
| 917 | } |
| 918 | |
| 919 | #[test ] |
| 920 | fn builder_split_reassemble() { |
| 921 | let builder = { |
| 922 | let client = Client::new(); |
| 923 | client.get("http://example.com" ) |
| 924 | }; |
| 925 | let (client, inner) = builder.build_split(); |
| 926 | let request = inner.unwrap(); |
| 927 | let builder = RequestBuilder::from_parts(client, request); |
| 928 | builder.build().unwrap(); |
| 929 | } |
| 930 | |
| 931 | /* |
| 932 | use {body, Method}; |
| 933 | use super::Client; |
| 934 | use header::{Host, Headers, ContentType}; |
| 935 | use std::collections::HashMap; |
| 936 | use serde_urlencoded; |
| 937 | use serde_json; |
| 938 | |
| 939 | #[test] |
| 940 | fn basic_get_request() { |
| 941 | let client = Client::new().unwrap(); |
| 942 | let some_url = "https://google.com/"; |
| 943 | let r = client.get(some_url).unwrap().build(); |
| 944 | |
| 945 | assert_eq!(r.method, Method::Get); |
| 946 | assert_eq!(r.url.as_str(), some_url); |
| 947 | } |
| 948 | |
| 949 | #[test] |
| 950 | fn basic_head_request() { |
| 951 | let client = Client::new().unwrap(); |
| 952 | let some_url = "https://google.com/"; |
| 953 | let r = client.head(some_url).unwrap().build(); |
| 954 | |
| 955 | assert_eq!(r.method, Method::Head); |
| 956 | assert_eq!(r.url.as_str(), some_url); |
| 957 | } |
| 958 | |
| 959 | #[test] |
| 960 | fn basic_post_request() { |
| 961 | let client = Client::new().unwrap(); |
| 962 | let some_url = "https://google.com/"; |
| 963 | let r = client.post(some_url).unwrap().build(); |
| 964 | |
| 965 | assert_eq!(r.method, Method::Post); |
| 966 | assert_eq!(r.url.as_str(), some_url); |
| 967 | } |
| 968 | |
| 969 | #[test] |
| 970 | fn basic_put_request() { |
| 971 | let client = Client::new().unwrap(); |
| 972 | let some_url = "https://google.com/"; |
| 973 | let r = client.put(some_url).unwrap().build(); |
| 974 | |
| 975 | assert_eq!(r.method, Method::Put); |
| 976 | assert_eq!(r.url.as_str(), some_url); |
| 977 | } |
| 978 | |
| 979 | #[test] |
| 980 | fn basic_patch_request() { |
| 981 | let client = Client::new().unwrap(); |
| 982 | let some_url = "https://google.com/"; |
| 983 | let r = client.patch(some_url).unwrap().build(); |
| 984 | |
| 985 | assert_eq!(r.method, Method::Patch); |
| 986 | assert_eq!(r.url.as_str(), some_url); |
| 987 | } |
| 988 | |
| 989 | #[test] |
| 990 | fn basic_delete_request() { |
| 991 | let client = Client::new().unwrap(); |
| 992 | let some_url = "https://google.com/"; |
| 993 | let r = client.delete(some_url).unwrap().build(); |
| 994 | |
| 995 | assert_eq!(r.method, Method::Delete); |
| 996 | assert_eq!(r.url.as_str(), some_url); |
| 997 | } |
| 998 | |
| 999 | #[test] |
| 1000 | fn add_header() { |
| 1001 | let client = Client::new().unwrap(); |
| 1002 | let some_url = "https://google.com/"; |
| 1003 | let mut r = client.post(some_url).unwrap(); |
| 1004 | |
| 1005 | let header = Host { |
| 1006 | hostname: "google.com".to_string(), |
| 1007 | port: None, |
| 1008 | }; |
| 1009 | |
| 1010 | // Add a copy of the header to the request builder |
| 1011 | let r = r.header(header.clone()).build(); |
| 1012 | |
| 1013 | // then check it was actually added |
| 1014 | assert_eq!(r.headers.get::<Host>(), Some(&header)); |
| 1015 | } |
| 1016 | |
| 1017 | #[test] |
| 1018 | fn add_headers() { |
| 1019 | let client = Client::new().unwrap(); |
| 1020 | let some_url = "https://google.com/"; |
| 1021 | let mut r = client.post(some_url).unwrap(); |
| 1022 | |
| 1023 | let header = Host { |
| 1024 | hostname: "google.com".to_string(), |
| 1025 | port: None, |
| 1026 | }; |
| 1027 | |
| 1028 | let mut headers = Headers::new(); |
| 1029 | headers.set(header); |
| 1030 | |
| 1031 | // Add a copy of the headers to the request builder |
| 1032 | let r = r.headers(headers.clone()).build(); |
| 1033 | |
| 1034 | // then make sure they were added correctly |
| 1035 | assert_eq!(r.headers, headers); |
| 1036 | } |
| 1037 | |
| 1038 | #[test] |
| 1039 | fn add_headers_multi() { |
| 1040 | let client = Client::new().unwrap(); |
| 1041 | let some_url = "https://google.com/"; |
| 1042 | let mut r = client.post(some_url).unwrap(); |
| 1043 | |
| 1044 | let header = Host { |
| 1045 | hostname: "google.com".to_string(), |
| 1046 | port: None, |
| 1047 | }; |
| 1048 | |
| 1049 | let mut headers = Headers::new(); |
| 1050 | headers.set(header); |
| 1051 | |
| 1052 | // Add a copy of the headers to the request builder |
| 1053 | let r = r.headers(headers.clone()).build(); |
| 1054 | |
| 1055 | // then make sure they were added correctly |
| 1056 | assert_eq!(r.headers, headers); |
| 1057 | } |
| 1058 | |
| 1059 | #[test] |
| 1060 | fn add_body() { |
| 1061 | let client = Client::new().unwrap(); |
| 1062 | let some_url = "https://google.com/"; |
| 1063 | let mut r = client.post(some_url).unwrap(); |
| 1064 | |
| 1065 | let body = "Some interesting content"; |
| 1066 | |
| 1067 | let r = r.body(body).build(); |
| 1068 | |
| 1069 | let buf = body::read_to_string(r.body.unwrap()).unwrap(); |
| 1070 | |
| 1071 | assert_eq!(buf, body); |
| 1072 | } |
| 1073 | |
| 1074 | #[test] |
| 1075 | fn add_form() { |
| 1076 | let client = Client::new().unwrap(); |
| 1077 | let some_url = "https://google.com/"; |
| 1078 | let mut r = client.post(some_url).unwrap(); |
| 1079 | |
| 1080 | let mut form_data = HashMap::new(); |
| 1081 | form_data.insert("foo", "bar"); |
| 1082 | |
| 1083 | let r = r.form(&form_data).unwrap().build(); |
| 1084 | |
| 1085 | // Make sure the content type was set |
| 1086 | assert_eq!(r.headers.get::<ContentType>(), |
| 1087 | Some(&ContentType::form_url_encoded())); |
| 1088 | |
| 1089 | let buf = body::read_to_string(r.body.unwrap()).unwrap(); |
| 1090 | |
| 1091 | let body_should_be = serde_urlencoded::to_string(&form_data).unwrap(); |
| 1092 | assert_eq!(buf, body_should_be); |
| 1093 | } |
| 1094 | |
| 1095 | #[test] |
| 1096 | fn add_json() { |
| 1097 | let client = Client::new().unwrap(); |
| 1098 | let some_url = "https://google.com/"; |
| 1099 | let mut r = client.post(some_url).unwrap(); |
| 1100 | |
| 1101 | let mut json_data = HashMap::new(); |
| 1102 | json_data.insert("foo", "bar"); |
| 1103 | |
| 1104 | let r = r.json(&json_data).unwrap().build(); |
| 1105 | |
| 1106 | // Make sure the content type was set |
| 1107 | assert_eq!(r.headers.get::<ContentType>(), Some(&ContentType::json())); |
| 1108 | |
| 1109 | let buf = body::read_to_string(r.body.unwrap()).unwrap(); |
| 1110 | |
| 1111 | let body_should_be = serde_json::to_string(&json_data).unwrap(); |
| 1112 | assert_eq!(buf, body_should_be); |
| 1113 | } |
| 1114 | |
| 1115 | #[test] |
| 1116 | fn add_json_fail() { |
| 1117 | use serde::{Serialize, Serializer}; |
| 1118 | use serde::ser::Error; |
| 1119 | struct MyStruct; |
| 1120 | impl Serialize for MyStruct { |
| 1121 | fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error> |
| 1122 | where S: Serializer |
| 1123 | { |
| 1124 | Err(S::Error::custom("nope")) |
| 1125 | } |
| 1126 | } |
| 1127 | |
| 1128 | let client = Client::new().unwrap(); |
| 1129 | let some_url = "https://google.com/"; |
| 1130 | let mut r = client.post(some_url).unwrap(); |
| 1131 | let json_data = MyStruct{}; |
| 1132 | assert!(r.json(&json_data).unwrap_err().is_serialization()); |
| 1133 | } |
| 1134 | */ |
| 1135 | } |
| 1136 | |