123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
- // SPDX-License-Identifier: Apache-2.0
- // SPDX-License-Identifier: MIT
- use bytes::Bytes;
- use reqwest::{header::HeaderName, redirect::Policy, Method};
- use serde::{Deserialize, Serialize};
- use serde_json::Value;
- use serde_repr::{Deserialize_repr, Serialize_repr};
- use std::{collections::HashMap, path::PathBuf, time::Duration};
- /// Client builder.
- #[derive(Default, Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct ClientBuilder {
- /// Max number of redirections to follow
- pub max_redirections: Option<usize>,
- /// Connect timeout in seconds for the request
- pub connect_timeout: Option<u64>,
- }
- impl ClientBuilder {
- /// Creates a new client builder with the default options.
- pub fn new() -> Self {
- Default::default()
- }
- /// Sets the maximum number of redirections.
- pub fn max_redirections(mut self, max_redirections: usize) -> Self {
- self.max_redirections = Some(max_redirections);
- self
- }
- /// Sets the connection timeout.
- pub fn connect_timeout(mut self, connect_timeout: u64) -> Self {
- self.connect_timeout = Some(connect_timeout);
- self
- }
- /// Builds the ClientOptions.
- pub fn build(self) -> crate::api::Result<Client> {
- let mut client_builder = reqwest::Client::builder();
- if let Some(max_redirections) = self.max_redirections {
- client_builder = client_builder.redirect(Policy::limited(max_redirections))
- }
- if let Some(connect_timeout) = self.connect_timeout {
- client_builder = client_builder.connect_timeout(Duration::from_secs(connect_timeout));
- }
- let client = client_builder.build()?;
- Ok(Client(client))
- }
- }
- /// The HTTP client.
- #[derive(Clone)]
- pub struct Client(reqwest::Client);
- impl Client {
- /// Executes an HTTP request
- ///
- /// The response will be transformed to String,
- /// If reading the response as binary, the byte array will be serialized using serde_json
- pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result<Response> {
- let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
- let mut request_builder = self.0.request(method, &request.url);
- if let Some(query) = request.query {
- request_builder = request_builder.query(&query);
- }
- if let Some(headers) = request.headers {
- for (header, header_value) in headers.iter() {
- request_builder =
- request_builder.header(HeaderName::from_bytes(header.as_bytes())?, header_value);
- }
- }
- if let Some(timeout) = request.timeout {
- request_builder = request_builder.timeout(Duration::from_secs(timeout));
- }
- let response = if let Some(body) = request.body {
- match body {
- Body::Bytes(data) => request_builder.body(Bytes::from(data)).send().await?,
- Body::Text(text) => request_builder.body(Bytes::from(text)).send().await?,
- Body::Json(json) => request_builder.json(&json).send().await?,
- Body::Form(form_body) => {
- let mut form = Vec::new();
- for (name, part) in form_body.0 {
- match part {
- FormPart::Bytes(bytes) => form.push((name, serde_json::to_string(&bytes)?)),
- FormPart::File(file_path) => form.push((name, serde_json::to_string(&file_path)?)),
- FormPart::Text(text) => form.push((name, text)),
- }
- }
- request_builder.form(&form).send().await?
- }
- }
- } else {
- request_builder.send().await?
- };
- let response = response.error_for_status()?;
- Ok(Response(
- request.response_type.unwrap_or(ResponseType::Json),
- response,
- ))
- }
- }
- #[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
- #[repr(u16)]
- #[non_exhaustive]
- /// The request's response type
- pub enum ResponseType {
- /// Read the response as JSON
- Json = 1,
- /// Read the response as text
- Text,
- /// Read the response as binary
- Binary,
- }
- /// FormBody data types.
- #[derive(Deserialize)]
- #[serde(untagged)]
- #[non_exhaustive]
- pub enum FormPart {
- /// A file path value.
- File(PathBuf),
- /// A string value.
- Text(String),
- /// A byte array value.
- Bytes(Vec<u8>),
- }
- /// Form body definition.
- #[derive(Deserialize)]
- pub struct FormBody(HashMap<String, FormPart>);
- impl FormBody {
- /// Creates a new form body.
- pub fn new(data: HashMap<String, FormPart>) -> Self {
- Self(data)
- }
- }
- /// A body for the request.
- #[derive(Deserialize)]
- #[serde(tag = "type", content = "payload")]
- #[non_exhaustive]
- pub enum Body {
- /// A multipart formdata body.
- Form(FormBody),
- /// A JSON body.
- Json(Value),
- /// A text string body.
- Text(String),
- /// A byte array body.
- Bytes(Vec<u8>),
- }
- /// The builder for a HTTP request.
- ///
- /// # Examples
- /// ```no_run
- /// use tauri::api::http::{ HttpRequestBuilder, ResponseType, ClientBuilder };
- /// async fn run() {
- /// let client = ClientBuilder::new()
- /// .max_redirections(3)
- /// .build()
- /// .unwrap();
- /// let mut request_builder = HttpRequestBuilder::new("GET", "http://example.com");
- /// let request = request_builder.response_type(ResponseType::Text);
- ///
- /// if let Ok(response) = client.send(request).await {
- /// println!("got response");
- /// } else {
- /// println!("Something Happened!");
- /// }
- /// }
- /// ```
- #[derive(Deserialize)]
- #[serde(rename_all = "camelCase")]
- pub struct HttpRequestBuilder {
- /// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE)
- pub method: String,
- /// The request URL
- pub url: String,
- /// The request query params
- pub query: Option<HashMap<String, String>>,
- /// The request headers
- pub headers: Option<HashMap<String, String>>,
- /// The request body
- pub body: Option<Body>,
- /// Timeout for the whole request
- pub timeout: Option<u64>,
- /// The response type (defaults to Json)
- pub response_type: Option<ResponseType>,
- }
- impl HttpRequestBuilder {
- /// Initializes a new instance of the HttpRequestrequest_builder.
- pub fn new(method: impl Into<String>, url: impl Into<String>) -> Self {
- Self {
- method: method.into(),
- url: url.into(),
- query: None,
- headers: None,
- body: None,
- timeout: None,
- response_type: None,
- }
- }
- /// Sets the request params.
- pub fn query(mut self, query: HashMap<String, String>) -> Self {
- self.query = Some(query);
- self
- }
- /// Sets the request headers.
- pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
- self.headers = Some(headers);
- self
- }
- /// Sets the request body.
- pub fn body(mut self, body: Body) -> Self {
- self.body = Some(body);
- self
- }
- /// Sets the general request timeout.
- pub fn timeout(mut self, timeout: u64) -> Self {
- self.timeout = Some(timeout);
- self
- }
- /// Sets the type of the response. Interferes with the way we read the response.
- pub fn response_type(mut self, response_type: ResponseType) -> Self {
- self.response_type = Some(response_type);
- self
- }
- }
- /// The HTTP response.
- pub struct Response(ResponseType, reqwest::Response);
- impl Response {
- /// Reads the response and returns its info.
- pub async fn read(self) -> crate::api::Result<ResponseData> {
- let url = self.1.url().to_string();
- let mut headers = HashMap::new();
- for (name, value) in self.1.headers() {
- headers.insert(name.as_str().to_string(), value.to_str()?.to_string());
- }
- let status = self.1.status().as_u16();
- let data = match self.0 {
- ResponseType::Json => self.1.json().await?,
- ResponseType::Text => Value::String(self.1.text().await?),
- ResponseType::Binary => Value::String(serde_json::to_string(&self.1.bytes().await?)?),
- };
- Ok(ResponseData {
- url,
- status,
- headers,
- data,
- })
- }
- }
- /// The response type.
- #[derive(Serialize)]
- #[serde(rename_all = "camelCase")]
- pub struct ResponseData {
- url: String,
- status: u16,
- headers: HashMap<String, String>,
- data: Value,
- }
|