initial commit
This commit is contained in:
commit
a6c0f916b7
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.direnv
|
||||||
|
.vscode
|
||||||
|
/target
|
4310
Cargo.lock
generated
Normal file
4310
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
46
Cargo.toml
Normal file
46
Cargo.toml
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
[package]
|
||||||
|
name = "hello"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
[[bin]]
|
||||||
|
name = "server"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "0.3"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
axum = { version = "0", features = ["http2"] }
|
||||||
|
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
|
||||||
|
tracing-opentelemetry = { version = "0.22", optional = true }
|
||||||
|
opentelemetry = { version = "0.21", optional = true }
|
||||||
|
opentelemetry_sdk = { version = "0.21", optional = true }
|
||||||
|
opentelemetry-stdout = { version = "0.2", features = ["trace"], optional = true }
|
||||||
|
|
||||||
|
sea-orm = { version = "0", features = [ "sqlx-sqlite", "runtime-tokio-rustls", "macros", "mock", "with-chrono", "with-json", "with-uuid" ] }
|
||||||
|
|
||||||
|
tera = "1"
|
||||||
|
serde = "1"
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
|
include_dir = { version = "0.7", features = ["metadata", "glob"], optional = true }
|
||||||
|
tower = { version = "0.4", optional = true }
|
||||||
|
mime_guess = { version = "2", optional = true }
|
||||||
|
|
||||||
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
|
webrtc = "0.10.1"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = [ "embed-templates" ]
|
||||||
|
otlp = [ "dep:tracing-opentelemetry", "dep:opentelemetry", "dep:opentelemetry_sdk" ]
|
||||||
|
debug = [ "dep:opentelemetry-stdout" ]
|
||||||
|
|
||||||
|
embed-templates = [ "dep:include_dir" ]
|
||||||
|
|
||||||
|
embed-static = [ ]
|
||||||
|
serve-static = ["embed-static", "dep:mime_guess", "dep:tower" ]
|
||||||
|
extract-static = ["embed-static"]
|
65
flake.lock
generated
Normal file
65
flake.lock
generated
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"fenix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1708928609,
|
||||||
|
"narHash": "sha256-LcXC2NP/TzHMmJThZGG1e+7rht5HeuZK5WOirIDg+lU=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"rev": "e928fb6b5179ebd032c19afac5c461ccc0b6de55",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1708807242,
|
||||||
|
"narHash": "sha256-sRTRkhMD4delO/hPxxi+XwLqPn8BuUq6nnj4JqLwOu0=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "73de017ef2d18a04ac4bfd0c02650007ccb31c2a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "nixpkgs",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"fenix": "fenix",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rust-analyzer-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1708878562,
|
||||||
|
"narHash": "sha256-IBHMNEe3lspVdIzjpM2OVZiBFmFw1DKtdgVN5G41pRc=",
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"rev": "5346002d07d09badaf37949bec68012d963d61fc",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"ref": "nightly",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
86
flake.nix
Normal file
86
flake.nix
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
fenix = {
|
||||||
|
url = "github:nix-community/fenix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
nixpkgs.url = "nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
outputs = { nixpkgs, fenix, ... }:
|
||||||
|
let
|
||||||
|
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
|
||||||
|
foreachSystem = nixpkgs.lib.genAttrs systems;
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
packages = foreachSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
rustPlatform = pkgs.makeRustPlatform {
|
||||||
|
cargo = fenix.packages."${pkgs.stdenv.system}".complete.toolchain;
|
||||||
|
rustc = fenix.packages."${pkgs.stdenv.system}".complete.toolchain;
|
||||||
|
};
|
||||||
|
buildTools = with pkgs; [ pkg-config ];
|
||||||
|
libraries = with pkgs; [ sqlite ] ++ (nixpkgs.lib.optional pkgs.stdenv.isDarwin darwin.apple_sdk.frameworks.Security);
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
hello = rustPlatform.buildRustPackage rec {
|
||||||
|
pname = "hello";
|
||||||
|
version = "1.0.0";
|
||||||
|
|
||||||
|
nativeBuildInputs = buildTools;
|
||||||
|
buildInputs = libraries;
|
||||||
|
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
cargoLock = {
|
||||||
|
lockFile = ./Cargo.lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
meta = with nixpkgs.lib; {
|
||||||
|
description = "rust project scaffold";
|
||||||
|
homepage = "https://git.jeffthecoder.xyz/public/os-flakes";
|
||||||
|
license = licenses.unlicense;
|
||||||
|
maintainers = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
default = hello;
|
||||||
|
});
|
||||||
|
devShells = foreachSystem
|
||||||
|
(system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
developmentTools = with pkgs; [
|
||||||
|
# bpf-linker
|
||||||
|
# cargo-espflash
|
||||||
|
# cargo-generate
|
||||||
|
# cargo-make
|
||||||
|
# cargo-mobile2
|
||||||
|
# cargo-tauri
|
||||||
|
# cargo-watch
|
||||||
|
# TODO: cargo-xcode
|
||||||
|
# TODO: create-tauri-app
|
||||||
|
# cargo-espflash
|
||||||
|
# TODO: kopium
|
||||||
|
# TODO: ldproxy
|
||||||
|
# TODO: paperclip
|
||||||
|
sea-orm-cli
|
||||||
|
# perseus-cli
|
||||||
|
# trunk
|
||||||
|
# wasm-bindgen-cli
|
||||||
|
] ++ (with fenix.packages."${system}".complete; [ rust-analyzer rust-src ]);
|
||||||
|
in
|
||||||
|
with pkgs; rec {
|
||||||
|
default = packages."${system}".default.overrideAttrs (prevAttrs: {
|
||||||
|
nativeBuildInputs = prevAttrs.nativeBuildInputs ++ developmentTools;
|
||||||
|
|
||||||
|
RUST_SRC_PATH = stdenv.mkDerivation {
|
||||||
|
inherit (rustc) src;
|
||||||
|
inherit (rustc.src) name;
|
||||||
|
phases = ["unpackPhase" "installPhase"];
|
||||||
|
installPhase = ''cp -r library $out'';
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
57
src/bin/server.rs
Normal file
57
src/bin/server.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(clap::Parser)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Opts {
|
||||||
|
#[command(subcommand)]
|
||||||
|
subcommand: Option<Subcommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Subcommand)]
|
||||||
|
enum Subcommand {
|
||||||
|
Serve {
|
||||||
|
#[clap(long, short = 'l', default_value = "127.0.0.1", env)]
|
||||||
|
listen_address: String,
|
||||||
|
#[clap(long, short = 'p', default_value = "3000", env)]
|
||||||
|
listen_port: u16,
|
||||||
|
},
|
||||||
|
|
||||||
|
#[cfg(feature = "extract-static")]
|
||||||
|
Extract { destination: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Subcommand {
|
||||||
|
fn default() -> Self {
|
||||||
|
Subcommand::Serve {
|
||||||
|
listen_address: "127.0.0.1".into(),
|
||||||
|
listen_port: 3000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let opts = Opts::parse();
|
||||||
|
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
match opts.subcommand.unwrap_or_default() {
|
||||||
|
Subcommand::Serve {
|
||||||
|
listen_address,
|
||||||
|
listen_port,
|
||||||
|
} => {
|
||||||
|
let service = hello::web::routes::make_service()?;
|
||||||
|
tracing::info!(listen_address, listen_port, "app is service");
|
||||||
|
let listener =
|
||||||
|
tokio::net::TcpListener::bind(format!("{listen_address}:{listen_port}")).await?;
|
||||||
|
axum::serve(listener, service).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "extract-static")]
|
||||||
|
Subcommand::Extract { destination } => {
|
||||||
|
staticfiles::extract(destination).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
3
src/lib.rs
Normal file
3
src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod web;
|
||||||
|
pub mod types;
|
||||||
|
pub mod task;
|
51
src/task.rs
Normal file
51
src/task.rs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
use webrtc::{ice_transport::{ice_candidate::RTCIceCandidate, ice_candidate_type::RTCIceCandidateType, ice_server::RTCIceServer}, *};
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref MY_ADDRESS: Mutex<String> = Mutex::new(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn collect_local_address() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let SERVER_CONFIG: peer_connection::configuration::RTCConfiguration = peer_connection::configuration::RTCConfiguration {
|
||||||
|
ice_servers: vec![RTCIceServer {
|
||||||
|
urls: vec!["stun:nhz.jeffthecoder.xyz:3479".to_string()],
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let api = webrtc::api::APIBuilder::new().build();
|
||||||
|
let mut pc = api.new_peer_connection(SERVER_CONFIG.clone()).await?;
|
||||||
|
|
||||||
|
|
||||||
|
pc.on_ice_candidate(Box::new(|candicate: Option<RTCIceCandidate>|{
|
||||||
|
Box::pin(async {
|
||||||
|
tracing::info!(?candicate, "on_ice_candidate");
|
||||||
|
if let Some(c) = candicate {
|
||||||
|
if c.typ == RTCIceCandidateType::Srflx {
|
||||||
|
tracing::info!("+ {}", c.address);
|
||||||
|
let mut address = MY_ADDRESS.lock().await;
|
||||||
|
*address = c.address.clone();
|
||||||
|
} else {
|
||||||
|
tracing::info!(" {}", c.address);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
tracing::debug!("gather done");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
let offer = pc.create_offer(None).await?;
|
||||||
|
pc.set_local_description(offer).await?;
|
||||||
|
|
||||||
|
let _dc = pc.create_data_channel("some_label", None).await?;
|
||||||
|
|
||||||
|
let mut gather_complete = pc.gathering_complete_promise().await;
|
||||||
|
|
||||||
|
gather_complete.recv().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
11
src/types.rs
Normal file
11
src/types.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(Serialize, Default)]
|
||||||
|
pub struct PageContext {
|
||||||
|
pub address: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug)]
|
||||||
|
pub struct AppState {
|
||||||
|
pub templates: tera::Tera,
|
||||||
|
}
|
3
src/web.rs
Normal file
3
src/web.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod templates;
|
||||||
|
pub mod staticfiles;
|
||||||
|
pub mod routes;
|
75
src/web/routes.rs
Normal file
75
src/web/routes.rs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
response::{ErrorResponse, Html, Redirect},
|
||||||
|
routing::{get, post, IntoMakeService},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
web::templates::load_templates,
|
||||||
|
types::{AppState, PageContext},
|
||||||
|
task::{MY_ADDRESS, collect_local_address},
|
||||||
|
};
|
||||||
|
|
||||||
|
async fn root(State(state): State<Arc<RwLock<AppState>>>) -> axum::response::Result<Html<String>> {
|
||||||
|
let state = state.read().await;
|
||||||
|
|
||||||
|
let address_guard = MY_ADDRESS.lock().await;
|
||||||
|
let address = address_guard.clone();
|
||||||
|
drop(address_guard);
|
||||||
|
|
||||||
|
let ctx = match tera::Context::from_serialize(&PageContext {
|
||||||
|
address,
|
||||||
|
}) {
|
||||||
|
Ok(ctx) => ctx,
|
||||||
|
Err(err) => return Err(ErrorResponse::from(format!("{err}"))),
|
||||||
|
};
|
||||||
|
match state.templates.render("index.html", &ctx) {
|
||||||
|
Ok(result) => Ok(Html::from(result)),
|
||||||
|
Err(err) => Err(ErrorResponse::from(format!("{err}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Default)]
|
||||||
|
struct ReloadResult {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
error: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reload(State(state): State<Arc<RwLock<AppState>>>) -> Result<Redirect, Json<ReloadResult>> {
|
||||||
|
let mut state = state.write_owned().await;
|
||||||
|
if let Err(err) = state.templates.full_reload() {
|
||||||
|
return Err(Json(ReloadResult {
|
||||||
|
error: Some(err.to_string()),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
drop(state);
|
||||||
|
if let Err(err) = collect_local_address().await {
|
||||||
|
return Err(Json(ReloadResult {
|
||||||
|
error: Some(err.to_string()),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Redirect::to("/"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_service() -> Result<IntoMakeService<Router<()>>, Box<dyn std::error::Error>> {
|
||||||
|
let templates = load_templates()?;
|
||||||
|
|
||||||
|
let router = Router::new();
|
||||||
|
|
||||||
|
#[cfg(feature = "serve-static")]
|
||||||
|
let router = router.route_service("/static/*path", staticfiles::StaticFiles::strip("/static/"));
|
||||||
|
|
||||||
|
Ok(router
|
||||||
|
.route("/", get(root))
|
||||||
|
.route("/reload", post(reload))
|
||||||
|
.with_state(Arc::new(RwLock::new(crate::types::AppState {
|
||||||
|
templates: templates,
|
||||||
|
})))
|
||||||
|
.into_make_service())
|
||||||
|
}
|
155
src/web/staticfiles.rs
Normal file
155
src/web/staticfiles.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
#[cfg(feature = "embed-static")]
|
||||||
|
static STATIC_DIR: include_dir::Dir<'_> = include_dir::include_dir!("$CARGO_MANIFEST_DIR/static");
|
||||||
|
|
||||||
|
#[cfg(feature="extract-static")]
|
||||||
|
pub async fn extract<P: AsRef<std::path::Path>>(to: P) -> std::io::Result<()> {
|
||||||
|
let base_path = to.as_ref();
|
||||||
|
let mut dirs = vec![STATIC_DIR.clone()];
|
||||||
|
tracing::info!("extracing static assets...");
|
||||||
|
|
||||||
|
while let Some(dir) = dirs.pop() {
|
||||||
|
for entry in dir.entries() {
|
||||||
|
let path = base_path.join(entry.path());
|
||||||
|
|
||||||
|
match entry {
|
||||||
|
DirEntry::Dir(d) => {
|
||||||
|
tracing::trace!(dir=?d, "directory been put into queue");
|
||||||
|
tokio::fs::create_dir_all(&path).await?;
|
||||||
|
dirs.insert(0, d.clone());
|
||||||
|
}
|
||||||
|
DirEntry::File(f) => {
|
||||||
|
tracing::trace!(?path, "file extracted");
|
||||||
|
tokio::fs::write(path, f.contents()).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="serve-static")]
|
||||||
|
pub use with_service::router;
|
||||||
|
#[cfg(feature="serve-static")]
|
||||||
|
pub use with_service::StaticFiles;
|
||||||
|
|
||||||
|
#[cfg(feature="serve-static")]
|
||||||
|
mod with_service {
|
||||||
|
use std::{convert::Infallible, task::Poll};
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::{Bytes, Full},
|
||||||
|
extract::Path,
|
||||||
|
http::{Request, StatusCode},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
routing::{MethodFilter, MethodRouter},
|
||||||
|
};
|
||||||
|
use futures::Future;
|
||||||
|
use include_dir::Dir;
|
||||||
|
|
||||||
|
use super::STATIC_DIR;
|
||||||
|
|
||||||
|
async fn head(Path(static_path): Path<String>) -> Response {
|
||||||
|
if super::STATIC_DIR.contains(static_path) {
|
||||||
|
(StatusCode::OK, "").into_response()
|
||||||
|
} else {
|
||||||
|
(StatusCode::NOT_FOUND, "").into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get(Path(static_path): Path<String>) -> Response {
|
||||||
|
if let Some(file) = super::STATIC_DIR.get_file(static_path) {
|
||||||
|
(StatusCode::OK, file.contents()).into_response()
|
||||||
|
} else {
|
||||||
|
(StatusCode::NOT_FOUND, "").into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ResponseFuture {
|
||||||
|
OpenFileFuture(&'static [u8], String),
|
||||||
|
NotFoundFuture,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for ResponseFuture {
|
||||||
|
type Output = Result<Response, Infallible>;
|
||||||
|
|
||||||
|
fn poll(
|
||||||
|
self: std::pin::Pin<&mut Self>,
|
||||||
|
_: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Self::Output> {
|
||||||
|
match self.clone() {
|
||||||
|
ResponseFuture::OpenFileFuture(content, mime_type) => {
|
||||||
|
Poll::Ready(Ok(Response::builder()
|
||||||
|
.status(StatusCode::OK)
|
||||||
|
.header("Content-Type", mime_type.clone())
|
||||||
|
.body(axum::body::boxed(Full::new(Bytes::copy_from_slice(
|
||||||
|
content,
|
||||||
|
))))
|
||||||
|
.unwrap()))
|
||||||
|
}
|
||||||
|
ResponseFuture::NotFoundFuture => {
|
||||||
|
Poll::Ready(Ok(StatusCode::NOT_FOUND.into_response()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct StaticFiles(Dir<'static>, String);
|
||||||
|
|
||||||
|
impl<B> tower::Service<Request<B>> for StaticFiles
|
||||||
|
where
|
||||||
|
B: Send + 'static,
|
||||||
|
{
|
||||||
|
type Response = Response;
|
||||||
|
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
type Future = ResponseFuture;
|
||||||
|
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
_: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
|
std::task::Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request<B>) -> Self::Future {
|
||||||
|
let path = if let Some(path) = req.uri().path().strip_prefix(&self.1) {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
return ResponseFuture::NotFoundFuture;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mime_type = mime_guess::from_path(path)
|
||||||
|
.first()
|
||||||
|
.map_or("application/octet-stream".to_string(), |m| {
|
||||||
|
m.essence_str().to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
tracing::info!(path, "trying to get static");
|
||||||
|
if let Some(file) = self.0.get_file(path) {
|
||||||
|
ResponseFuture::OpenFileFuture(file.contents(), mime_type.to_string())
|
||||||
|
} else {
|
||||||
|
ResponseFuture::NotFoundFuture
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StaticFiles {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
StaticFiles(STATIC_DIR.clone(), "/".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn strip<S: ToString>(prefix: S) -> Self {
|
||||||
|
StaticFiles(STATIC_DIR.clone(), prefix.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn router() -> MethodRouter {
|
||||||
|
MethodRouter::new()
|
||||||
|
.on(MethodFilter::HEAD, head)
|
||||||
|
.on(MethodFilter::GET, get)
|
||||||
|
}
|
||||||
|
}
|
30
src/web/templates.rs
Normal file
30
src/web/templates.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#[cfg(feature = "embed-templates")]
|
||||||
|
static TEMPLATES_DIR: include_dir::Dir<'_> = include_dir::include_dir!("$CARGO_MANIFEST_DIR/templates");
|
||||||
|
|
||||||
|
pub fn load_templates() -> Result<tera::Tera, Box<dyn std::error::Error>> {
|
||||||
|
let mut templates = tera::Tera::parse("templates/**/*.html")?;
|
||||||
|
|
||||||
|
#[cfg(feature = "embed-templates")]
|
||||||
|
{
|
||||||
|
let template_names: std::collections::HashSet<_> = templates
|
||||||
|
.get_template_names()
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect();
|
||||||
|
for entry in TEMPLATES_DIR.find("**/*.html")? {
|
||||||
|
if let Some(file) = entry.as_file() {
|
||||||
|
let path = file.path();
|
||||||
|
let path = path.to_string_lossy().to_string();
|
||||||
|
if template_names.contains(&path) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(content) = file.contents_utf8() {
|
||||||
|
templates.add_raw_template(&path, content)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.build_inheritance_chains()?;
|
||||||
|
|
||||||
|
Ok(templates)
|
||||||
|
}
|
4
static/style.css
Normal file
4
static/style.css
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
23
templates/_layout.html
Normal file
23
templates/_layout.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
{% block head %}
|
||||||
|
<title>{% block fulltitle %}{% block title %}{% endblock %} - {% block sitename %}Demo{% endblock %}{% endblock %}</title>
|
||||||
|
{% block stylesheets %}
|
||||||
|
<link rel="stylesheet" href="/static/style.css" />
|
||||||
|
{% block extrastylesheet %}{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="content">{% block content %}{% endblock %}</div>
|
||||||
|
<div id="footer">
|
||||||
|
{% block footer %}
|
||||||
|
© Copyright 2024 by <a href="http://blog.jeffthecoder.xyz/">Jeff</a>.
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
6
templates/index.html
Normal file
6
templates/index.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{% extends "_layout.html" %}
|
||||||
|
{% block title %}A simple and silly address prober{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<div id="address">{{ address }}</div>
|
||||||
|
<form method="post" action="/reload"><button type="submit">reload</button></form>
|
||||||
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user