Skip to content

Commit 81705bb

Browse files
authored
feat(updater): add method to set request headers closes #3896 (#3931)
1 parent 38e330f commit 81705bb

9 files changed

Lines changed: 147 additions & 29 deletions

File tree

.changes/http-api-header-method.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Added `tauri::api::http::HttpRequestBuilder#header` method.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
**Breaking change:** The `tauri::api::http::HttpRequestBuilder#headers` method now takes `header::HeaderMap` instead of a `HashMap`.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
**Breaking change:** The `tauri::api::http::Response#headers` method now returns `&header::HeaderMap` instead of `&HashMap`.

.changes/updater-headers.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"tauri": patch
3+
---
4+
5+
Allow setting app updater request headers via `AppHandle::updater().header()`.

core/tauri/src/api/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ pub enum Error {
9090
/// Unknown program name.
9191
#[error("unknown program name: {0}")]
9292
UnknownProgramName(String),
93+
/// HTTP error.
94+
#[error(transparent)]
95+
Http(#[from] http::Error),
9396
}
9497

9598
#[cfg(feature = "cli")]

core/tauri/src/api/http.rs

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@
44

55
//! Types and functions related to HTTP request.
66
7-
use http::{header::HeaderName, Method};
8-
pub use http::{HeaderMap, StatusCode};
9-
use serde::{Deserialize, Serialize};
7+
use http::Method;
8+
pub use http::StatusCode;
9+
use serde::{Deserialize, Deserializer, Serialize};
1010
use serde_json::Value;
1111
use serde_repr::{Deserialize_repr, Serialize_repr};
1212
use url::Url;
1313

1414
use std::{collections::HashMap, time::Duration};
1515

16+
#[cfg(feature = "reqwest-client")]
17+
pub use reqwest::header;
18+
19+
#[cfg(not(feature = "reqwest-client"))]
20+
pub use attohttpc::header;
21+
22+
use header::{HeaderName, HeaderValue};
23+
1624
/// The builder of [`Client`].
1725
#[derive(Debug, Clone, Default, Deserialize)]
1826
#[serde(rename_all = "camelCase")]
@@ -107,11 +115,8 @@ impl Client {
107115
}
108116

109117
if let Some(headers) = request.headers {
110-
for (header, header_value) in headers.iter() {
111-
request_builder = request_builder.header(
112-
HeaderName::from_bytes(header.as_bytes())?,
113-
header_value.as_bytes(),
114-
);
118+
for (name, value) in headers.0.iter() {
119+
request_builder = request_builder.header(name, value);
115120
}
116121
}
117122

@@ -183,16 +188,12 @@ impl Client {
183188
};
184189
}
185190

186-
let mut http_request = request_builder.build()?;
187191
if let Some(headers) = request.headers {
188-
for (header, value) in headers.iter() {
189-
http_request.headers_mut().insert(
190-
HeaderName::from_bytes(header.as_bytes())?,
191-
http::header::HeaderValue::from_bytes(value.as_bytes())?,
192-
);
193-
}
192+
request_builder = request_builder.headers(headers.0);
194193
}
195194

195+
let http_request = request_builder.build()?;
196+
196197
let response = self.0.execute(http_request).await?;
197198

198199
Ok(Response(
@@ -252,6 +253,34 @@ pub enum Body {
252253
Bytes(Vec<u8>),
253254
}
254255

256+
/// A set of HTTP headers.
257+
#[derive(Debug, Default)]
258+
pub struct HeaderMap(header::HeaderMap);
259+
260+
impl<'de> Deserialize<'de> for HeaderMap {
261+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
262+
where
263+
D: Deserializer<'de>,
264+
{
265+
let map = HashMap::<String, String>::deserialize(deserializer)?;
266+
let mut headers = header::HeaderMap::default();
267+
for (key, value) in map {
268+
if let (Ok(key), Ok(value)) = (
269+
header::HeaderName::from_bytes(key.as_bytes()),
270+
header::HeaderValue::from_str(&value),
271+
) {
272+
headers.insert(key, value);
273+
} else {
274+
return Err(serde::de::Error::custom(format!(
275+
"invalid header `{}` `{}`",
276+
key, value
277+
)));
278+
}
279+
}
280+
Ok(Self(headers))
281+
}
282+
}
283+
255284
/// The builder for a HTTP request.
256285
///
257286
/// # Examples
@@ -281,7 +310,7 @@ pub struct HttpRequestBuilder {
281310
/// The request query params
282311
pub query: Option<HashMap<String, String>>,
283312
/// The request headers
284-
pub headers: Option<HashMap<String, String>>,
313+
pub headers: Option<HeaderMap>,
285314
/// The request body
286315
pub body: Option<Body>,
287316
/// Timeout for the whole request
@@ -311,10 +340,28 @@ impl HttpRequestBuilder {
311340
self
312341
}
313342

343+
/// Adds a header.
344+
pub fn header<K, V>(mut self, key: K, value: V) -> crate::api::Result<Self>
345+
where
346+
HeaderName: TryFrom<K>,
347+
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
348+
HeaderValue: TryFrom<V>,
349+
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
350+
{
351+
let key: Result<HeaderName, http::Error> = key.try_into().map_err(Into::into);
352+
let value: Result<HeaderValue, http::Error> = value.try_into().map_err(Into::into);
353+
self
354+
.headers
355+
.get_or_insert_with(Default::default)
356+
.0
357+
.insert(key?, value?);
358+
Ok(self)
359+
}
360+
314361
/// Sets the request headers.
315362
#[must_use]
316-
pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
317-
self.headers = Some(headers);
363+
pub fn headers(mut self, headers: header::HeaderMap) -> Self {
364+
self.headers.replace(HeaderMap(headers));
318365
self
319366
}
320367

@@ -356,7 +403,7 @@ impl Response {
356403
}
357404

358405
/// Get the headers of this Response.
359-
pub fn headers(&self) -> &HeaderMap {
406+
pub fn headers(&self) -> &header::HeaderMap {
360407
self.1.headers()
361408
}
362409

core/tauri/src/updater/core.rs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,16 @@ use crate::{
1313
AppHandle, Manager, Runtime,
1414
};
1515
use base64::decode;
16-
use http::StatusCode;
16+
use http::{
17+
header::{HeaderName, HeaderValue},
18+
HeaderMap, StatusCode,
19+
};
1720
use minisign_verify::{PublicKey, Signature};
1821
use tauri_utils::{platform::current_exe, Env};
1922

2023
#[cfg(feature = "updater")]
2124
use std::io::Seek;
2225
use std::{
23-
collections::HashMap,
2426
env, fmt,
2527
io::{Cursor, Read},
2628
path::{Path, PathBuf},
@@ -212,6 +214,7 @@ pub struct UpdateBuilder<R: Runtime> {
212214
pub executable_path: Option<PathBuf>,
213215
should_install: Option<Box<dyn FnOnce(&str, &str) -> bool + Send>>,
214216
timeout: Option<Duration>,
217+
headers: HeaderMap,
215218
}
216219

217220
impl<R: Runtime> fmt::Debug for UpdateBuilder<R> {
@@ -223,6 +226,7 @@ impl<R: Runtime> fmt::Debug for UpdateBuilder<R> {
223226
.field("target", &self.target)
224227
.field("executable_path", &self.executable_path)
225228
.field("timeout", &self.timeout)
229+
.field("headers", &self.headers)
226230
.finish()
227231
}
228232
}
@@ -238,6 +242,7 @@ impl<R: Runtime> UpdateBuilder<R> {
238242
current_version: env!("CARGO_PKG_VERSION").into(),
239243
should_install: None,
240244
timeout: None,
245+
headers: Default::default(),
241246
}
242247
}
243248

@@ -295,6 +300,20 @@ impl<R: Runtime> UpdateBuilder<R> {
295300
self
296301
}
297302

303+
/// Add a `Header` to the request.
304+
pub fn header<K, V>(mut self, key: K, value: V) -> Result<Self>
305+
where
306+
HeaderName: TryFrom<K>,
307+
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
308+
HeaderValue: TryFrom<V>,
309+
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
310+
{
311+
let key: std::result::Result<HeaderName, http::Error> = key.try_into().map_err(Into::into);
312+
let value: std::result::Result<HeaderValue, http::Error> = value.try_into().map_err(Into::into);
313+
self.headers.insert(key?, value?);
314+
Ok(self)
315+
}
316+
298317
pub async fn build(mut self) -> Result<Update<R>> {
299318
let mut remote_release: Option<RemoteRelease> = None;
300319

@@ -336,6 +355,10 @@ impl<R: Runtime> UpdateBuilder<R> {
336355
}
337356
}
338357

358+
// we want JSON only
359+
let mut headers = self.headers;
360+
headers.insert("Accept", HeaderValue::from_str("application/json").unwrap());
361+
339362
// Allow fallback if more than 1 urls is provided
340363
let mut last_error: Option<Error> = None;
341364
for url in &self.urls {
@@ -351,11 +374,7 @@ impl<R: Runtime> UpdateBuilder<R> {
351374
.replace("{{target}}", &target)
352375
.replace("{{arch}}", arch);
353376

354-
// we want JSON only
355-
let mut headers = HashMap::new();
356-
headers.insert("Accept".into(), "application/json".into());
357-
358-
let mut request = HttpRequestBuilder::new("GET", &fixed_link)?.headers(headers);
377+
let mut request = HttpRequestBuilder::new("GET", &fixed_link)?.headers(headers.clone());
359378
if let Some(timeout) = self.timeout {
360379
request = request.timeout(timeout);
361380
}
@@ -408,6 +427,8 @@ impl<R: Runtime> UpdateBuilder<R> {
408427
version::is_greater(&self.current_version, &final_release.version).unwrap_or(false)
409428
};
410429

430+
headers.remove("Accept");
431+
411432
// create our new updater
412433
Ok(Update {
413434
app: self.app,
@@ -423,6 +444,7 @@ impl<R: Runtime> UpdateBuilder<R> {
423444
#[cfg(target_os = "windows")]
424445
with_elevated_task: final_release.with_elevated_task,
425446
timeout: self.timeout,
447+
headers,
426448
})
427449
}
428450
}
@@ -460,6 +482,8 @@ pub struct Update<R: Runtime> {
460482
with_elevated_task: bool,
461483
/// Request timeout
462484
timeout: Option<Duration>,
485+
/// Request headers
486+
headers: HeaderMap,
463487
}
464488

465489
impl<R: Runtime> Clone for Update<R> {
@@ -478,6 +502,7 @@ impl<R: Runtime> Clone for Update<R> {
478502
#[cfg(target_os = "windows")]
479503
with_elevated_task: self.with_elevated_task,
480504
timeout: self.timeout,
505+
headers: self.headers.clone(),
481506
}
482507
}
483508
}
@@ -502,9 +527,15 @@ impl<R: Runtime> Update<R> {
502527
}
503528

504529
// set our headers
505-
let mut headers = HashMap::new();
506-
headers.insert("Accept".into(), "application/octet-stream".into());
507-
headers.insert("User-Agent".into(), "tauri/updater".into());
530+
let mut headers = self.headers.clone();
531+
headers.insert(
532+
"Accept",
533+
HeaderValue::from_str("application/octet-stream").unwrap(),
534+
);
535+
headers.insert(
536+
"User-Agent",
537+
HeaderValue::from_str("tauri/updater").unwrap(),
538+
);
508539

509540
let client = ClientBuilder::new().build()?;
510541
// Create our request

core/tauri/src/updater/error.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ pub enum Error {
6565
/// The updater responded with an invalid signature type.
6666
#[error("the updater response field `{0}` type is invalid, expected {1} but found {2}")]
6767
InvalidResponseType(&'static str, &'static str, serde_json::Value),
68+
/// HTTP error.
69+
#[error(transparent)]
70+
Http(#[from] http::Error),
6871
}
6972

7073
pub type Result<T = ()> = std::result::Result<T, Error>;

core/tauri/src/updater/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ mod error;
448448

449449
use std::time::Duration;
450450

451+
use http::header::{HeaderName, HeaderValue};
452+
451453
pub use self::error::Error;
452454
/// Alias for [`std::result::Result`] using our own [`Error`].
453455
pub type Result<T> = std::result::Result<T, Error>;
@@ -557,6 +559,18 @@ impl<R: Runtime> UpdateBuilder<R> {
557559
self
558560
}
559561

562+
/// Add a `Header` to the request.
563+
pub fn header<K, V>(mut self, key: K, value: V) -> Result<Self>
564+
where
565+
HeaderName: TryFrom<K>,
566+
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
567+
HeaderValue: TryFrom<V>,
568+
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
569+
{
570+
self.inner = self.inner.header(key, value)?;
571+
Ok(self)
572+
}
573+
560574
/// Check if an update is available.
561575
///
562576
/// # Examples

0 commit comments

Comments
 (0)