Skip to content

Commit ad419f0

Browse files
authored
Embed static files in binary for release builds (#603)
1 parent 237be60 commit ad419f0

File tree

7 files changed

+107
-54
lines changed

7 files changed

+107
-54
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ jobs:
8787
sudo service mosquitto start
8888
- name: Check Formatting
8989
run: cargo fmt -- --check
90+
- name: Build console
91+
run: |
92+
cd webui
93+
pnpm build
9094
- name: Build
9195
run: cargo build --all-features
9296
- name: Validate API

Cargo.lock

Lines changed: 46 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/arroyo-api/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ proc-macro2 = "1"
7575
postgres-types = { version = "*", features = ["derive"] }
7676
tokio-postgres = { version = "*", features = ["with-serde_json-1", "with-time-0_3", "with-uuid-1"] }
7777
deadpool-postgres = { version = "0.10" }
78+
79+
7880
futures = "0.3"
7981
futures-util = "0.3.28"
8082
time = "0.3"
@@ -84,6 +86,8 @@ uuid = "1.3.3"
8486
regress = "0.6.0"
8587
apache-avro = "0.16.0"
8688
toml = "0.8"
89+
rust-embed = { version = "6.8.1", features = ["axum"] }
90+
mime_guess = "2.0.4"
8791

8892
[build-dependencies]
8993
cornucopia = { version = "0.9" }

crates/arroyo-api/src/rest.rs

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
1-
use axum::body::Body;
2-
use axum::response::IntoResponse;
1+
use axum::response::{Html, IntoResponse, Response};
32
use axum::{
43
routing::{delete, get, patch, post},
54
Json, Router,
65
};
76
use deadpool_postgres::Pool;
87

9-
use once_cell::sync::Lazy;
8+
use http::{header, StatusCode, Uri};
9+
use rust_embed::RustEmbed;
1010
use std::env;
11-
use std::path::PathBuf;
12-
use std::str::FromStr;
13-
use tower::service_fn;
1411
use tower_http::cors;
1512
use tower_http::cors::CorsLayer;
16-
use tower_http::services::ServeDir;
1713
use utoipa::OpenApi;
1814
use utoipa_swagger_ui::SwaggerUi;
1915

@@ -37,7 +33,11 @@ use crate::pipelines::{
3733
use crate::rest_utils::not_found;
3834
use crate::udfs::{create_udf, delete_udf, get_udfs, validate_udf};
3935
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;
4141

4242
#[derive(Clone)]
4343
pub struct AppState {
@@ -62,30 +62,54 @@ pub async fn api_fallback() -> impl IntoResponse {
6262
not_found("Route")
6363
}
6464

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+
}
6768

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('/');
7071

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+
}
7275

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();
8079

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+
}
8691

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());
8896

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 {
89113
// TODO: enable in development only!!!
90114
let cors = CorsLayer::new()
91115
.allow_methods(cors::Any)
@@ -146,8 +170,7 @@ pub fn create_rest_app(pool: Pool, controller_addr: &str) -> Router {
146170
.url("/api/v1/api-docs/openapi.json", ApiDoc::openapi()),
147171
)
148172
.nest("/api/v1", api_routes)
149-
.route_service("/", fallback)
150-
.fallback_service(serve_dir)
173+
.fallback(static_handler)
151174
.with_state(AppState {
152175
controller_addr: controller_addr.to_string(),
153176
pool,

crates/arroyo-df/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ use datafusion_execution::FunctionRegistry;
6868
use std::time::{Duration, SystemTime};
6969
use std::{collections::HashMap, sync::Arc};
7070
use syn::Item;
71-
use tracing::{info, warn};
71+
use tracing::{debug, info, warn};
7272
use unicase::UniCase;
7373

7474
const DEFAULT_IDLE_TIME: Option<Duration> = Some(Duration::from_secs(5 * 60));
@@ -520,7 +520,7 @@ pub async fn parse_and_get_arrow_program(
520520
plan_rewrite.visit(&mut metadata)?;
521521
used_connections.extend(metadata.connection_ids.iter());
522522

523-
info!("Logical plan: {}", plan_rewrite.display_graphviz());
523+
debug!("Logical plan: {}", plan_rewrite.display_graphviz());
524524

525525
let sink = match sink_name {
526526
Some(sink_name) => {

crates/arroyo-df/src/rewriters.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ use datafusion_expr::{
3030
use std::collections::{HashMap, HashSet};
3131
use std::sync::Arc;
3232
use std::time::Duration;
33-
use tracing::info;
3433

3534
/// Rewrites a logical plan to move projections out of table scans
3635
/// and into a separate projection node which may include virtual fields,
@@ -149,20 +148,11 @@ impl<'a> SourceRewriter<'a> {
149148
)),
150149
});
151150

152-
info!(
153-
"table source schema: {:?}",
154-
table_source_extension.schema().fields()
155-
);
156-
157151
let (projection_input, projection) = if table.is_updating() {
158152
let mut projection_offsets = table_scan.projection.clone();
159153
if let Some(offsets) = projection_offsets.as_mut() {
160154
offsets.push(table.fields.len())
161155
}
162-
info!(
163-
"table source schema: {:?}",
164-
table_source_extension.schema().fields()
165-
);
166156
(
167157
LogicalPlan::Extension(Extension {
168158
node: Arc::new(DebeziumUnrollingExtension::try_new(table_source_extension)?),
@@ -188,8 +178,6 @@ impl<'a> SourceRewriter<'a> {
188178
) -> DFResult<LogicalPlan> {
189179
let input = self.projection(table_scan, table)?;
190180

191-
info!("table scan plan:\n{:?}", input);
192-
193181
let schema = input.schema().clone();
194182
let remote = LogicalPlan::Extension(Extension {
195183
node: Arc::new(RemoteTableExtension {

docker/Dockerfile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ COPY docker/install_deps.sh /install_deps.sh
5555
RUN sh /install_deps.sh run
5656

5757
COPY --from=builder /arroyo-bin ./
58-
COPY --from=builder /app/webui/dist ./dist
5958

6059
COPY docker/supervisord.conf /supervisord.conf
6160
COPY docker/entrypoint.sh /entrypoint.sh
@@ -78,7 +77,6 @@ RUN apt-get update && \
7877
apt-get -y install libsasl2-2 ca-certificates curl
7978

8079
COPY --from=builder /arroyo-bin ./
81-
COPY --from=builder /app/webui/dist ./dist
8280

8381
ENV PRODUCTION=true \
8482
ASSET_DIR="/app/dist" \
@@ -94,7 +92,6 @@ RUN apt-get update && \
9492
apt-get -y install libsasl2-2 ca-certificates curl
9593

9694
COPY --from=builder /arroyo-bin ./
97-
COPY --from=builder /app/webui/dist ./dist
9895

9996
ENV PRODUCTION=true \
10097
ASSET_DIR="/app/dist"

0 commit comments

Comments
 (0)