tauri_plugin_localhost/
lib.rs1#![doc(
10 html_logo_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png",
11 html_favicon_url = "https://github.com/tauri-apps/tauri/raw/dev/app-icon.png"
12)]
13
14use std::collections::HashMap;
15
16use http::Uri;
17use tauri::{
18 plugin::{Builder as PluginBuilder, TauriPlugin},
19 Runtime,
20};
21use tiny_http::{Header, Response as HttpResponse, Server};
22
23pub struct Request {
24 url: String,
25}
26
27impl Request {
28 pub fn url(&self) -> &str {
29 &self.url
30 }
31}
32
33pub struct Response {
34 headers: HashMap<String, String>,
35}
36
37impl Response {
38 pub fn add_header<H: Into<String>, V: Into<String>>(&mut self, header: H, value: V) {
39 self.headers.insert(header.into(), value.into());
40 }
41}
42
43type OnRequest = Option<Box<dyn Fn(&Request, &mut Response) + Send + Sync>>;
44
45pub struct Builder {
46 port: u16,
47 host: Option<String>,
48 on_request: OnRequest,
49}
50
51impl Builder {
52 pub fn new(port: u16) -> Self {
53 Self {
54 port,
55 host: None,
56 on_request: None,
57 }
58 }
59
60 pub fn host<H: Into<String>>(mut self, host: H) -> Self {
62 self.host = Some(host.into());
63 self
64 }
65
66 pub fn on_request<F: Fn(&Request, &mut Response) + Send + Sync + 'static>(
67 mut self,
68 f: F,
69 ) -> Self {
70 self.on_request.replace(Box::new(f));
71 self
72 }
73
74 pub fn build<R: Runtime>(mut self) -> TauriPlugin<R> {
75 let port = self.port;
76 let host = self.host.unwrap_or("localhost".to_string());
77 let on_request = self.on_request.take();
78
79 PluginBuilder::new("localhost")
80 .setup(move |app, _api| {
81 let asset_resolver = app.asset_resolver();
82 std::thread::spawn(move || {
83 let server =
84 Server::http(format!("{host}:{port}")).expect("Unable to spawn server");
85 for req in server.incoming_requests() {
86 let path = req
87 .url()
88 .parse::<Uri>()
89 .map(|uri| uri.path().into())
90 .unwrap_or_else(|_| req.url().into());
91
92 #[allow(unused_mut)]
93 if let Some(mut asset) = asset_resolver.get(path) {
94 let request = Request {
95 url: req.url().into(),
96 };
97 let mut response = Response {
98 headers: Default::default(),
99 };
100
101 response.add_header("Content-Type", asset.mime_type);
102 if let Some(csp) = asset.csp_header {
103 response
104 .headers
105 .insert("Content-Security-Policy".into(), csp);
106 }
107
108 if let Some(on_request) = &on_request {
109 on_request(&request, &mut response);
110 }
111
112 let mut resp = HttpResponse::from_data(asset.bytes);
113 for (header, value) in response.headers {
114 if let Ok(h) = Header::from_bytes(header.as_bytes(), value) {
115 resp.add_header(h);
116 }
117 }
118 req.respond(resp).expect("unable to setup response");
119 }
120 }
121 });
122 Ok(())
123 })
124 .build()
125 }
126}