Skip to content

Commit

Permalink
feat(rust): serve the web UI at '/'
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed Mar 6, 2024
1 parent 113547f commit e550ebd
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 15 deletions.
31 changes: 31 additions & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion rust/agama-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ macaddr = "1.0"
async-trait = "0.1.75"
axum = { version = "0.7.4", features = ["ws"] }
serde_json = "1.0.113"
tower-http = { version = "0.5.1", features = ["compression-br", "trace"] }
tower-http = { version = "0.5.1", features = ["compression-br", "fs", "trace"] }
tracing-subscriber = "0.3.18"
tracing-journald = "0.3.0"
tracing = "0.1.40"
Expand Down
24 changes: 22 additions & 2 deletions rust/agama-server/src/agama-web-server.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::process::{ExitCode, Termination};
use std::{
path::{Path, PathBuf},
process::{ExitCode, Termination},
};

use agama_lib::connection_to;
use agama_server::{
Expand All @@ -10,6 +13,8 @@ use tokio::sync::broadcast::channel;
use tracing_subscriber::prelude::*;
use utoipa::OpenApi;

const DEFAULT_WEB_UI_DIR: &'static str = "/usr/share/agama/web_ui";

#[derive(Subcommand, Debug)]
enum Commands {
/// Start the API server.
Expand All @@ -27,6 +32,9 @@ pub struct ServeArgs {
// Agama D-Bus address
#[arg(long, default_value = "unix:path=/run/agama/bus")]
dbus_address: String,
// Directory containing the web UI code.
#[arg(long)]
web_ui_dir: Option<PathBuf>,
}

#[derive(Parser, Debug)]
Expand All @@ -39,6 +47,17 @@ struct Cli {
pub command: Commands,
}

fn find_web_ui_dir() -> PathBuf {
if let Ok(home) = std::env::var("HOME") {
let path = Path::new(&home).join(".local/share/agama");
if path.exists() {
return path;
}
}

Path::new(DEFAULT_WEB_UI_DIR).into()
}

/// Start serving the API.
///
/// `args`: command-line arguments.
Expand All @@ -55,7 +74,8 @@ async fn serve_command(args: ServeArgs) -> anyhow::Result<()> {

let config = web::ServiceConfig::load()?;
let dbus = connection_to(&args.dbus_address).await?;
let service = web::service(config, tx, dbus).await?;
let web_ui_dir = args.web_ui_dir.unwrap_or(find_web_ui_dir());
let service = web::service(config, tx, dbus, web_ui_dir).await?;
axum::serve(listener, service)
.await
.expect("could not mount app on listener");
Expand Down
15 changes: 11 additions & 4 deletions rust/agama-server/src/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,25 @@ pub use config::ServiceConfig;
pub use docs::ApiDoc;
pub use event::{Event, EventsReceiver, EventsSender};
pub use service::MainServiceBuilder;
use std::path::Path;
use tokio_stream::StreamExt;

/// Returns a service that implements the web-based Agama API.
///
/// * `config`: service configuration.
/// * `events`: D-Bus connection.
pub async fn service(
/// * `events`: channel to send the events through the WebSocket.
/// * `dbus`: D-Bus connection.
/// * `web_ui_dir`: public directory containing the web UI.
pub async fn service<P>(
config: ServiceConfig,
events: EventsSender,
dbus: zbus::Connection,
) -> Result<Router, ServiceError> {
let router = MainServiceBuilder::new(events.clone())
web_ui_dir: P,
) -> Result<Router, ServiceError>
where
P: AsRef<Path>,
{
let router = MainServiceBuilder::new(events.clone(), web_ui_dir)
.add_service("/l10n", l10n_service(events.clone()))
.add_service("/software", software_service(dbus).await?)
.with_config(config)
Expand Down
34 changes: 31 additions & 3 deletions rust/agama-server/src/web/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,57 @@ use axum::{
routing::{get, post},
Router,
};
use std::convert::Infallible;
use std::{
convert::Infallible,
path::{Path, PathBuf},
};
use tower::Service;
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
use tower_http::{compression::CompressionLayer, services::ServeDir, trace::TraceLayer};

/// Builder for Agama main service.
///
/// It is responsible for building an axum service which includes:
///
/// * A static assets directory (`public_dir`).
/// * A websocket at the `/ws` path.
/// * An authentication endpointg at `/authenticate`.
/// * A 'ping' endpoint at '/ping'.
/// * A number of authenticated services that are added using the `add_service` function.
pub struct MainServiceBuilder {
config: ServiceConfig,
events: EventsSender,
router: Router<ServiceState>,
public_dir: PathBuf,
}

impl MainServiceBuilder {
pub fn new(events: EventsSender) -> Self {
/// Returns a new service builder.
///
/// * `events`: channel to send events through the WebSocket.
/// * `public_dir`: path to the public directory.
pub fn new<P>(events: EventsSender, public_dir: P) -> Self
where
P: AsRef<Path>,
{
let router = Router::new().route("/ws", get(super::ws::ws_handler));
let config = ServiceConfig::default();

Self {
events,
router,
config,
public_dir: PathBuf::from(public_dir.as_ref()),
}
}

pub fn with_config(self, config: ServiceConfig) -> Self {
Self { config, ..self }
}

/// Add an authenticated service.
///
/// * `path`: Path to mount the service on.
/// * `service`: Service to mount on the given `path`.
pub fn add_service<T>(self, path: &str, service: T) -> Self
where
T: Service<Request, Error = Infallible> + Clone + Send + 'static,
Expand All @@ -49,10 +74,13 @@ impl MainServiceBuilder {
config: self.config,
events: self.events,
};

let serve = ServeDir::new(self.public_dir);
self.router
.route_layer(middleware::from_extractor_with_state::<TokenClaims, _>(
state.clone(),
))
.nest_service("/", serve)
.route("/ping", get(super::http::ping))
.route("/authenticate", post(super::http::authenticate))
.layer(TraceLayer::new_for_http())
Expand Down
19 changes: 14 additions & 5 deletions rust/agama-server/tests/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,25 @@ use axum::{
Router,
};
use common::{body_to_string, DBusServer};
use std::error::Error;
use std::{error::Error, path::PathBuf};
use tokio::{sync::broadcast::channel, test};
use tower::ServiceExt;

async fn build_service() -> Router {
let (tx, _) = channel(16);
let server = DBusServer::new().start().await.unwrap();
service(ServiceConfig::default(), tx, server.connection())
.await
.unwrap()
service(
ServiceConfig::default(),
tx,
server.connection(),
public_dir(),
)
.await
.unwrap()
}

fn public_dir() -> PathBuf {
std::env::current_dir().unwrap().join("public")
}

#[test]
Expand All @@ -46,7 +55,7 @@ async fn access_protected_route(token: &str, jwt_secret: &str) -> Response {
jwt_secret: jwt_secret.to_string(),
};
let (tx, _) = channel(16);
let web_service = MainServiceBuilder::new(tx)
let web_service = MainServiceBuilder::new(tx, public_dir())
.add_service("/protected", get(protected))
.with_config(config)
.build();
Expand Down

0 comments on commit e550ebd

Please sign in to comment.