1
- use axum:: body:: Body ;
2
- use axum:: response:: IntoResponse ;
1
+ use axum:: response:: { Html , IntoResponse , Response } ;
3
2
use axum:: {
4
3
routing:: { delete, get, patch, post} ,
5
4
Json , Router ,
6
5
} ;
7
6
use deadpool_postgres:: Pool ;
8
7
9
- use once_cell:: sync:: Lazy ;
8
+ use http:: { header, StatusCode , Uri } ;
9
+ use rust_embed:: RustEmbed ;
10
10
use std:: env;
11
- use std:: path:: PathBuf ;
12
- use std:: str:: FromStr ;
13
- use tower:: service_fn;
14
11
use tower_http:: cors;
15
12
use tower_http:: cors:: CorsLayer ;
16
- use tower_http:: services:: ServeDir ;
17
13
use utoipa:: OpenApi ;
18
14
use utoipa_swagger_ui:: SwaggerUi ;
19
15
@@ -37,7 +33,11 @@ use crate::pipelines::{
37
33
use crate :: rest_utils:: not_found;
38
34
use crate :: udfs:: { create_udf, delete_udf, get_udfs, validate_udf} ;
39
35
use crate :: ApiDoc ;
40
- use arroyo_types:: { telemetry_enabled, API_ENDPOINT_ENV , ASSET_DIR_ENV } ;
36
+ use arroyo_types:: { telemetry_enabled, API_ENDPOINT_ENV } ;
37
+
38
+ #[ derive( RustEmbed ) ]
39
+ #[ folder = "../../webui/dist" ]
40
+ struct Assets ;
41
41
42
42
#[ derive( Clone ) ]
43
43
pub struct AppState {
@@ -62,30 +62,54 @@ pub async fn api_fallback() -> impl IntoResponse {
62
62
not_found ( "Route" )
63
63
}
64
64
65
- pub fn create_rest_app ( pool : Pool , controller_addr : & str ) -> Router {
66
- let asset_dir = env:: var ( ASSET_DIR_ENV ) . unwrap_or_else ( |_| "webui/dist" . to_string ( ) ) ;
65
+ async fn not_found_static ( ) -> Response {
66
+ ( StatusCode :: NOT_FOUND , "404" ) . into_response ( )
67
+ }
67
68
68
- static INDEX_HTML : Lazy < String > = Lazy :: new ( || {
69
- let asset_dir = env :: var ( ASSET_DIR_ENV ) . unwrap_or_else ( |_| "webui/dist" . to_string ( ) ) ;
69
+ async fn static_handler ( uri : Uri ) -> impl IntoResponse {
70
+ let path = uri . path ( ) . trim_start_matches ( '/' ) ;
70
71
71
- let endpoint = env:: var ( API_ENDPOINT_ENV ) . unwrap_or_else ( |_| String :: new ( ) ) ;
72
+ if path. is_empty ( ) || path == "index.html" {
73
+ return index_html ( ) . await ;
74
+ }
72
75
73
- std:: fs:: read_to_string ( PathBuf :: from_str ( & asset_dir) . unwrap ( )
74
- . join ( "index.html" ) )
75
- . expect ( "Could not find index.html in asset dir (you may need to build the console sources)" )
76
- . replace ( "{{API_ENDPOINT}}" , & endpoint)
77
- . replace ( "{{CLUSTER_ID}}" , & arroyo_server_common:: get_cluster_id ( ) )
78
- . replace ( "{{DISABLE_TELEMETRY}}" , if telemetry_enabled ( ) { "false" } else { "true" } )
79
- } ) ;
76
+ match Assets :: get ( path) {
77
+ Some ( content) => {
78
+ let mime = mime_guess:: from_path ( path) . first_or_octet_stream ( ) ;
80
79
81
- let fallback = service_fn ( |_: http:: Request < _ > | async move {
82
- let body = Body :: from ( INDEX_HTML . as_str ( ) ) ;
83
- let res = http:: Response :: new ( body) ;
84
- Ok :: < _ , _ > ( res)
85
- } ) ;
80
+ ( [ ( header:: CONTENT_TYPE , mime. as_ref ( ) ) ] , content. data ) . into_response ( )
81
+ }
82
+ None => {
83
+ if path. contains ( '.' ) {
84
+ return not_found_static ( ) . await ;
85
+ }
86
+
87
+ index_html ( ) . await
88
+ }
89
+ }
90
+ }
86
91
87
- let serve_dir = ServeDir :: new ( asset_dir) . not_found_service ( fallback) ;
92
+ async fn index_html ( ) -> Response {
93
+ match Assets :: get ( "index.html" ) {
94
+ Some ( content) => {
95
+ let endpoint = env:: var ( API_ENDPOINT_ENV ) . unwrap_or_else ( |_| String :: new ( ) ) ;
88
96
97
+ let replaced = String :: from_utf8 ( content. data . to_vec ( ) )
98
+ . expect ( "index.html is invalid UTF-8" )
99
+ . replace ( "{{API_ENDPOINT}}" , & endpoint)
100
+ . replace ( "{{CLUSTER_ID}}" , & arroyo_server_common:: get_cluster_id ( ) )
101
+ . replace (
102
+ "{{DISABLE_TELEMETRY}}" ,
103
+ if telemetry_enabled ( ) { "false" } else { "true" } ,
104
+ ) ;
105
+
106
+ Html ( replaced) . into_response ( )
107
+ }
108
+ None => not_found_static ( ) . await ,
109
+ }
110
+ }
111
+
112
+ pub fn create_rest_app ( pool : Pool , controller_addr : & str ) -> Router {
89
113
// TODO: enable in development only!!!
90
114
let cors = CorsLayer :: new ( )
91
115
. allow_methods ( cors:: Any )
@@ -146,8 +170,7 @@ pub fn create_rest_app(pool: Pool, controller_addr: &str) -> Router {
146
170
. url ( "/api/v1/api-docs/openapi.json" , ApiDoc :: openapi ( ) ) ,
147
171
)
148
172
. nest ( "/api/v1" , api_routes)
149
- . route_service ( "/" , fallback)
150
- . fallback_service ( serve_dir)
173
+ . fallback ( static_handler)
151
174
. with_state ( AppState {
152
175
controller_addr : controller_addr. to_string ( ) ,
153
176
pool,
0 commit comments