rlink/runtime/worker/
web_server.rs1use std::convert::Infallible;
2use std::net::SocketAddr;
3use std::path::PathBuf;
4use std::str::FromStr;
5use std::sync::Arc;
6
7use hyper::http::header;
8use hyper::service::{make_service_fn, service_fn};
9use hyper::{Body, Method, Request, Response};
10use hyper::{Server, StatusCode};
11use rand::Rng;
12
13use crate::channel::{bounded, Sender};
14use crate::core::cluster::StdResponse;
15use crate::utils::fs::read_binary;
16use crate::utils::http::server::{as_ok_json, page_not_found};
17use crate::utils::thread::async_runtime_multi;
18
19pub(crate) fn web_launch(context: Arc<crate::runtime::context::Context>) -> String {
20 let (tx, rx) = bounded(1);
21
22 std::thread::Builder::new()
23 .name("WebUI".to_string())
24 .spawn(move || {
25 async_runtime_multi("web", 2).block_on(async move {
26 let ip = context.bind_ip.clone();
27 let web_context = Arc::new(WebContext { context });
28 serve_with_rand_port(web_context, ip, tx).await;
29 });
30 })
31 .unwrap();
32
33 let bind_addr: SocketAddr = rx.recv().unwrap();
34 format!("http://{}", bind_addr.to_string())
35}
36
37struct WebContext {
38 context: Arc<crate::runtime::context::Context>,
39}
40
41async fn serve_with_rand_port(
42 web_context: Arc<WebContext>,
43 bind_id: String,
44 bind_addr_tx: Sender<SocketAddr>,
45) {
46 let mut rng = rand::thread_rng();
47 for _ in 0..30 {
48 let port = rng.gen_range(10000..30000);
49 let address = format!("{}:{}", bind_id.as_str(), port);
50 let socket_addr = SocketAddr::from_str(address.as_str()).unwrap();
51
52 let serve_result = serve(web_context.clone(), &socket_addr, bind_addr_tx.clone()).await;
53 match serve_result {
54 Ok(_) => error!("server stop"),
55 Err(e) => info!("try bind failure> {}", e),
56 }
57 }
58
59 error!("no port can be bound");
60}
61
62async fn serve(
63 web_context: Arc<WebContext>,
64 bind_addr: &SocketAddr,
65 bind_addr_tx: Sender<SocketAddr>,
66) -> anyhow::Result<()> {
67 let make_service = make_service_fn(move |_conn| {
69 let web_context = web_context.clone();
70 async move {
71 Ok::<_, Infallible>(service_fn(move |req| {
72 let web_context = web_context.clone();
73 route(req, web_context)
74 }))
75 }
76 });
77
78 let server = Server::try_bind(bind_addr)?.serve(make_service);
80
81 bind_addr_tx.send(bind_addr.clone()).unwrap();
82
83 if let Err(e) = server.await {
85 eprintln!("server error: {}", e);
86 }
87
88 Ok(())
89}
90
91async fn route(req: Request<Body>, web_context: Arc<WebContext>) -> anyhow::Result<Response<Body>> {
92 let path = req.uri().path();
93 let method = req.method();
94
95 if path.starts_with("/api/") {
96 if Method::GET.eq(method) {
97 match path {
98 "/api/threads" => get_thread_infos(req, web_context).await,
99 "/api/client/log/enable" => enable_client_log(req, web_context).await,
100 "/api/client/log/disable" => disable_client_log(req, web_context).await,
101 "/api/server/log/enable" => enable_server_log(req, web_context).await,
102 "/api/server/log/disable" => disable_server_log(req, web_context).await,
103 _ => page_not_found().await,
104 }
105 } else {
106 page_not_found().await
107 }
108 } else {
109 if Method::GET.eq(method) {
110 static_file(req, web_context).await
111 } else {
112 page_not_found().await
113 }
114 }
115}
116
117async fn enable_client_log(
118 _req: Request<Body>,
119 _context: Arc<WebContext>,
120) -> anyhow::Result<Response<Body>> {
121 crate::pub_sub::network::client::enable_log();
122 as_ok_json(&StdResponse::ok(Some(true)))
123}
124
125async fn disable_client_log(
126 _req: Request<Body>,
127 _context: Arc<WebContext>,
128) -> anyhow::Result<Response<Body>> {
129 crate::pub_sub::network::client::disable_log();
130 as_ok_json(&StdResponse::ok(Some(false)))
131}
132
133async fn enable_server_log(
134 _req: Request<Body>,
135 _context: Arc<WebContext>,
136) -> anyhow::Result<Response<Body>> {
137 crate::pub_sub::network::server::enable_log();
138 as_ok_json(&StdResponse::ok(Some(true)))
139}
140
141async fn disable_server_log(
142 _req: Request<Body>,
143 _context: Arc<WebContext>,
144) -> anyhow::Result<Response<Body>> {
145 crate::pub_sub::network::server::disable_log();
146 as_ok_json(&StdResponse::ok(Some(false)))
147}
148
149async fn get_thread_infos(
150 _req: Request<Body>,
151 _context: Arc<WebContext>,
152) -> anyhow::Result<Response<Body>> {
153 let c = crate::utils::thread::get_thread_infos();
154 as_ok_json(&StdResponse::ok(Some(c)))
155}
156
157async fn static_file(
158 req: Request<Body>,
159 context: Arc<WebContext>,
160) -> anyhow::Result<Response<Body>> {
161 let path = {
162 let mut path = req.uri().path();
163 if path.is_empty() || "/".eq(path) {
164 path = "/index.html";
165 };
166
167 &path[1..path.len()]
168 };
169
170 let static_file_path = {
171 let path = PathBuf::from_str(path)?;
172
173 let dashboard_path = context.context.dashboard_path.as_str();
174 let base_path = PathBuf::from_str(dashboard_path)?;
175
176 let n = base_path.join(path);
177 n
178 };
179
180 let ext = {
181 let ext_pos = path.rfind(".").ok_or(anyhow!("file ext name not found"))?;
182 &path[ext_pos + 1..path.len()]
183 };
184
185 let context_type = match ext {
186 "html" => "text/html; charset=utf-8",
187 "js" => "application/javascript",
188 "css" => "text/css",
189 "ico" => "image/x-icon",
190 "gif" => "image/gif",
191 "png" => "image/png",
192 "svg" => "image/svg+xml",
193 "woff" => "application/font-woff",
194 _ => "",
195 };
196
197 match read_binary(&static_file_path) {
198 Ok(context) => Response::builder()
199 .header(header::CONTENT_TYPE, context_type)
200 .status(StatusCode::OK)
201 .body(Body::from(context))
202 .map_err(|e| anyhow!(e)),
203 Err(e) => {
204 error!(
205 "static file not found. file path: {:?}, error: {}",
206 static_file_path, e
207 );
208 page_not_found().await
209 }
210 }
211}