547 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			547 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| 
 | |
| //! Run 
 | |
| //! ```not_rust
 | |
| //! cargo run -p example-static-file-server
 | |
| //! ```
 | |
| 
 | |
| // use axum_auth::AuthBasic;
 | |
| 
 | |
| #![cfg_attr(docsrs, feature(doc_cfg))]
 | |
| #![doc(html_logo_url = "../images/docserver.svg")]
 | |
| #[doc = include_str!("../README.md")]
 | |
| // #![doc(html_no_source)]
 | |
| 
 | |
| // TODO tasks https://docs.rs/async-sqlx-session/latest/async_sqlx_session/struct.SqliteSessionStore.html
 | |
| 
 | |
| use rand_core::{SeedableRng,OsRng, RngCore};
 | |
| use rand_chacha::ChaCha8Rng;
 | |
| 
 | |
| use axum::{
 | |
|     extract::Host,
 | |
|     handler::HandlerWithoutStateExt,
 | |
|     routing::MethodRouter,
 | |
|     http::{
 | |
|         StatusCode,
 | |
|         Uri,
 | |
|         header::HeaderValue,
 | |
|         Method,
 | |
|     },
 | |
|     BoxError,
 | |
|     Extension,
 | |
|     response::Redirect,
 | |
| //    http::Request, handler::HandlerWithoutStateExt, http::StatusCode, routing::get, Router,    
 | |
|     Router,
 | |
| };
 | |
| use tower::ServiceBuilder;
 | |
| use axum_server::tls_rustls::RustlsConfig;
 | |
| use std::{
 | |
|     net::SocketAddr, 
 | |
|     path::PathBuf,
 | |
|     sync::{Arc, Mutex},
 | |
|     path::Path, 
 | |
| };
 | |
| // use std::net::SocketAddr;
 | |
| // use tower::ServiceExt;
 | |
| use tower_http::{
 | |
|     services::{ServeDir,ServeFile},
 | |
|     trace::TraceLayer,
 | |
|     cors::CorsLayer,
 | |
| };
 | |
| use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
 | |
| use once_cell::sync::Lazy;
 | |
| // use once_cell::sync::{Lazy,OnceCell};
 | |
| // use async_session::{Session, SessionStore, MemoryStore};
 | |
| 
 | |
| use tera::Context;
 | |
| use async_sqlx_session::SqliteSessionStore;
 | |
| use sqlx::AnyPool;
 | |
| 
 | |
| mod tera_tpls;
 | |
| mod defs;
 | |
| mod users;
 | |
| mod login_password;
 | |
| mod handlers;
 | |
| mod tools;
 | |
| 
 | |
| use defs::{
 | |
|     AppDBs,
 | |
|     SessionStoreDB,
 | |
|     FileStore,
 | |
|     Config,
 | |
|     load_from_file,
 | |
|     parse_args,
 | |
|     AppConnectInfo,
 | |
| };
 | |
| use users::UserStore;
 | |
| 
 | |
| use tera_tpls::init_tera;
 | |
| use tower_cookies::CookieManagerLayer;
 | |
| use handlers::{
 | |
|     handle_404,
 | |
|     rewrite_request_uri,
 | |
|     admin_router_handlers,
 | |
|     users_router_handlers,
 | |
|     pages_router_handlers,
 | |
| };
 | |
| use crate::tools::get_socket_addr;
 | |
| 
 | |
| pub const USER_AGENT: &str = "user-agent";
 | |
| pub const SESSION_COOKIE_NAME: &str = "doc_session";
 | |
| pub const CFG_FILE_EXTENSION: &str  = ".toml";
 | |
| pub const FILE_SCHEME: &str = "file:///"; 
 | |
| 
 | |
| pub const PKG_NAME: &str = env!("CARGO_PKG_NAME");
 | |
| // static WEBSERVER: AtomicUsize = AtomicUsize::new(0);
 | |
| pub const PKG_VERSION: &str = env!("CARGO_PKG_VERSION");
 | |
| // const PKG_VERSION: Option&<&'static str> = option_env!("CARGO_PKG_VERSION");
 | |
| // const PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
 | |
| // const COOKIE_NAME: &str = "lc_authz";
 | |
| // const COOKIE_SEP: &str = ":tk:";
 | |
| const GIT_VERSION: &str = ""; //git_version::git_version!();
 | |
| static GIT_VERSION_NAME: Lazy<String> = Lazy::new(|| {
 | |
|     format!("v{} [build: {}]",PKG_VERSION,GIT_VERSION)
 | |
| });
 | |
| static PKG_FULLNAME: Lazy<String> = Lazy::new(|| {
 | |
|     format!("{}: TII CL Rust",PKG_NAME)
 | |
| });
 | |
| 
 | |
| pub const USERS_TABLENAME: &str = "users";
 | |
| pub const USERS_FILESTORE: &str = "users";
 | |
| pub const DEFAULT_ROLES: &str = "user";
 | |
| 
 | |
| 
 | |
| #[derive(Clone, Copy)]
 | |
| struct Ports {
 | |
|     http: u16,
 | |
|     https: u16,
 | |
| }
 | |
| 
 | |
| pub fn route(path: &str, method_router: MethodRouter) -> Router {
 | |
|     Router::new().route(path, method_router)
 | |
| }
 | |
| 
 | |
| #[tokio::main]
 | |
| async fn main() {
 | |
|     let config_path = parse_args();
 | |
|     if config_path.is_empty() {
 | |
|         eprintln!("No config-file found");
 | |
|         std::process::exit(2)
 | |
|     }
 | |
|     //	pretty_env_logger::init();
 | |
|     let config = {
 | |
|         let mut server_cfg: Config = load_from_file(&config_path, "server-config").unwrap_or_else(|e|{
 | |
|             eprintln!("Settings error: {}",e);
 | |
|             std::process::exit(2)
 | |
|         });
 | |
|         server_cfg.load_items();
 | |
|         //config.fix_root_path::<ServerConfig>(String::new());
 | |
|         server_cfg
 | |
|     };
 | |
|     if config.verbose > 1 { dbg!("{:?}",&config); }
 | |
|     tracing_subscriber::registry()
 | |
|         .with(
 | |
|             tracing_subscriber::EnvFilter::try_from_default_env()
 | |
|                 .unwrap_or_else(|_| "example_static_file_server=debug,tower_http=debug".into()),
 | |
|         )
 | |
|         .with(tracing_subscriber::fmt::layer())
 | |
|         .init();
 | |
| 
 | |
|     // you can convert handler function to service
 | |
| 
 | |
|     // let store = FileStore {
 | |
|     //     sess_path: "store".to_owned(),
 | |
|     //     ses_file: "data".to_owned(),
 | |
|     //     // sess_path: sessions_config.session_store_uri.replace(FILE_SCHEME,"").to_owned(),
 | |
|     //     // ses_file: sessions_config.session_store_file.to_owned(),
 | |
|     // };
 | |
|     // let session_store = SessionStoreDB::connect_file_store(store);
 | |
|     // let session_store = SessionStoreDB::connect_memory_store();
 | |
|     let session_store = if config.session_store_uri.starts_with(FILE_SCHEME) {
 | |
|         let store = FileStore {
 | |
|             sess_path: config.session_store_uri.replace(FILE_SCHEME,"").to_owned(),
 | |
|             ses_file: config.session_store_file.to_owned(),
 | |
|         };
 | |
|         if let Err(e) = store.check_paths() {
 | |
|             eprintln!("Error creation File Store: {}",e);
 | |
|             std::process::exit(2)
 | |
|         }
 | |
|         SessionStoreDB::connect_file_store(store)
 | |
|     } else if config.session_store_uri.starts_with("sqlite:") {
 | |
|         let store = SqliteSessionStore::new(&config.session_store_uri).await.unwrap_or_else(|e|{
 | |
|             eprintln!("Error session database {}: {}",
 | |
|                 config.session_store_uri,e
 | |
|             );
 | |
|             std::process::exit(2)
 | |
|         });
 | |
|         let _ = store.migrate().await;
 | |
|         let _ = store.cleanup().await;
 | |
|         SessionStoreDB::connect_sqlite_store(store)
 | |
|     } else if config.session_store_uri.starts_with("memory") {
 | |
|         SessionStoreDB::connect_memory_store()
 | |
|     } else {
 | |
|         SessionStoreDB::None
 | |
|     }; 
 | |
|     let user_store = if config.users_store_uri.starts_with(FILE_SCHEME) {
 | |
|         let users_store_uri = config.users_store_uri.replace(FILE_SCHEME,"").to_owned();
 | |
|         if ! Path::new(&users_store_uri).exists() {
 | |
|             if let Err(e) = std::fs::File::create(Path::new(&users_store_uri)) {
 | |
|                 eprintln!("Error creation Users store {}: {}",
 | |
|                     &users_store_uri,e);
 | |
|                 std::process::exit(2)
 | |
|             }
 | |
|         }
 | |
|         UserStore::File(users_store_uri)
 | |
|     // } else if config.users_store_uri.starts_with("sqlite:") {
 | |
|     } else if config.users_store_uri.contains("sql") {
 | |
|         //let m = Migrator::new(Path::new("./migrations")).await?;
 | |
|         //}
 | |
|         let pool = AnyPool::connect(&config.users_store_uri).await.unwrap_or_else(|e|{
 | |
|             eprintln!("Error pool database {}: {}",
 | |
|                 config.users_store_uri,e
 | |
|             );
 | |
|             std::process::exit(2)
 | |
|         });
 | |
|         // let pool = SqlitePool::connect(&config.users_store_uri).await.unwrap_or_else(|e|{
 | |
|         //     eprintln!("Error pool database {}: {}",
 | |
|         //         config.users_store_uri,e
 | |
|         //     );
 | |
|         //     std::process::exit(2)
 | |
|         // });
 | |
|         UserStore::Sql(pool)
 | |
|     } else {
 | |
|         eprintln!("User store {}: Not defined",&config.users_store_uri);
 | |
|         std::process::exit(2)
 | |
|     };
 | |
|     #[cfg(feature = "casbin")]
 | |
|     let enforcer = if !config.authz_model_path.is_empty() && ! config.authz_policy_path.is_empty() {
 | |
|         AppDBs::create_enforcer(
 | |
|         Box::leak(config.authz_model_path.to_owned().into_boxed_str()),
 | |
|         Box::leak(config.authz_policy_path.to_owned().into_boxed_str())
 | |
|         ).await
 | |
|     } else {
 | |
|         eprintln!("Error auth enforcer {} + {}",
 | |
|             config.authz_model_path, config.authz_policy_path
 | |
|         );
 | |
|         std::process::exit(2);
 | |
|     };
 | |
| 
 | |
|     let ports = Ports {
 | |
|         http: 7878,
 | |
|         https: 8800,
 | |
|     };
 | |
|     // optional: spawn a second server to redirect http requests to this server
 | |
|     if config.protocol.contains("http") {
 | |
|         tokio::spawn(redirect_http_to_https(ports));
 | |
|     }
 | |
| 
 | |
|     let mut origins: Vec<HeaderValue> = Vec::new();
 | |
|     for itm in config.allow_origin.to_owned() {
 | |
|         match HeaderValue::from_str(itm.as_str()) {
 | |
|             Ok(val) => origins.push(val),
 | |
|             Err(e) =>  println!("error {} with {} header for allow_origin",e,itm),
 | |
|         }
 | |
|     }	
 | |
| 
 | |
|     let mut context = Context::new();
 | |
|     context.insert("server_name","DOC Server");
 | |
|     context.insert("pkg_name",&PKG_NAME);
 | |
|     context.insert("pkg_version",&PKG_VERSION);
 | |
|     context.insert("git_version",&GIT_VERSION);
 | |
|     context.insert("git_version_name",GIT_VERSION_NAME.as_str());
 | |
|     context.insert("pkg_fullname",PKG_FULLNAME.as_str());
 | |
| 
 | |
|     // let app = Router::new().route("/", get(handler));
 | |
|     #[cfg(feature = "authstore")]
 | |
|     let app_dbs = Arc::new(
 | |
|         AppDBs::new(&config, session_store, user_store,
 | |
|             init_tera(&config.templates_path), context
 | |
|         )
 | |
|     );
 | |
|     #[cfg(feature = "casbin")]
 | |
|     let app_dbs = Arc::new(
 | |
|         AppDBs::new(&config, session_store, user_store, enforcer,
 | |
|             init_tera(&config.templates_path), context
 | |
|         )
 | |
|     );
 | |
|     let middleware = 
 | |
|         axum::middleware::from_fn_with_state(app_dbs.clone(),rewrite_request_uri);
 | |
|     // apply the layer around the whole `Router`
 | |
|     // this way the middleware will run before `Router` receives the request
 | |
| 
 | |
|     let mut web_router = Router::new();
 | |
| 
 | |
|     // Parse serv_paths to add static paths as service 
 | |
|     for item in &config.serv_paths {
 | |
|         // Try to check src_path ...
 | |
|         let src_path: String;
 | |
|         if Path::new(&item.src_path).exists() {
 | |
|             src_path = format!("{}",item.src_path); 
 | |
|         } else {
 | |
|             src_path = if item.src_path.starts_with("/") {
 | |
|                 format!("{}",item.src_path)
 | |
|             } else {
 | |
|                 format!("{}/{}",&config.root_path,item.src_path)
 | |
|             };
 | |
|             if ! Path::new(&src_path).exists() {
 | |
|                 eprintln!("File path {}: not found", &src_path);
 | |
|                 continue;
 | |
|             } 
 | |
|         }    
 | |
|         // Add ServeDir with not_found page ...
 | |
|         if item.not_found.is_empty() {
 | |
|             web_router = web_router.nest_service(
 | |
|                 &item.url_path,
 | |
|                 ServeDir::new(&src_path).not_found_service(handle_404.into_service())
 | |
|             );
 | |
|             println!("Added path {} => {}", &src_path,&item.url_path);
 | |
|         } else {
 | |
|             web_router = web_router.nest_service(
 | |
|                 &item.url_path,
 | |
|                 ServeDir::new(&src_path).not_found_service(ServeFile::new(&item.not_found))
 | |
|             );
 | |
|             println!("Added path {} => {} ({})", &src_path,&item.url_path,&item.not_found);
 | |
|         }
 | |
|     }
 | |
|     let mut key = [0u8; 16];
 | |
|     let mut os_rng = OsRng{};
 | |
|     os_rng.fill_bytes(&mut key);
 | |
|     let random = ChaCha8Rng::seed_from_u64(OsRng.next_u64());
 | |
| 
 | |
|     web_router = web_router
 | |
|         .merge(users_router_handlers())
 | |
|         .merge(admin_router_handlers())
 | |
|         .merge(pages_router_handlers())
 | |
|         .layer(ServiceBuilder::new().layer(middleware))
 | |
|         .layer(CookieManagerLayer::new())
 | |
|         .layer(Extension(app_dbs))
 | |
|         .layer(Extension(Arc::new(Mutex::new(random))))
 | |
|         .fallback_service(handle_404.into_service())
 | |
|         ;
 | |
| 
 | |
|     // if !config.html_path.is_empty() && !config.html_url.is_empty() {
 | |
|     //     MAIN_URL.set(config.server.html_url.to_owned()).unwrap_or_default();
 | |
|     //     println!("SpaRoutert local path {} to {}",&config.server.html_path,&config.server.html_url);
 | |
|     //     web_router = web_router.merge(
 | |
|     //     )
 | |
|     //     .fallback(fallback);
 | |
|     // }
 | |
| 
 | |
|     if config.verbose > 2 { dbg!("{:?}",&origins); }
 | |
|     if config.allow_origin.len() > 0 {
 | |
|          web_router = web_router.layer(CorsLayer::new()
 | |
|             .allow_origin(origins)
 | |
|             .allow_methods(vec![Method::GET, Method::POST])
 | |
|             .allow_headers(tower_http::cors::Any)
 | |
|         );
 | |
|     }
 | |
|     let addr = get_socket_addr(&config.bind,config.port);
 | |
|     tracing::debug!("listening on {}", addr);
 | |
|     println!("listening on {}", addr);
 | |
|     if config.protocol.as_str() == "http" {
 | |
|     // let app_with_middleware = middleware.layer(app);
 | |
|     // run https server
 | |
|         //let addr = SocketAddr::from(([127, 0, 0, 1], ports.http));
 | |
|         //tracing::debug!("listening on {}", addr);
 | |
|         let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
 | |
|         axum::serve(listener, web_router.layer(TraceLayer::new_for_http())
 | |
|             .into_make_service_with_connect_info::<SocketAddr>()
 | |
|            )
 | |
|           .await
 | |
|           .unwrap();
 | |
|     } else {
 | |
|         // configure certificate and private key used by https
 | |
|         // let tls_config = RustlsConfig::from_pem_file(
 | |
|         //     PathBuf::from(env!("CARGO_MANIFEST_DIR"))
 | |
|         //         .join("self_signed_certs")
 | |
|         //         .join("cert.pem"),
 | |
|         //     PathBuf::from(env!("CARGO_MANIFEST_DIR"))
 | |
|         //         .join("self_signed_certs")
 | |
|         //         .join("key.pem"),
 | |
|         // )
 | |
|         let tls_config = RustlsConfig::from_pem_file(
 | |
|             PathBuf::from(&config.cert_file),
 | |
|             PathBuf::from(&config.key_file)
 | |
|         )
 | |
|         .await
 | |
|         .unwrap_or_else(|e|{
 | |
|             eprintln!("Error TLS config: {}",e);
 | |
|             std::process::exit(2)
 | |
|         });
 | |
|         //let addr = SocketAddr::from(([127, 0, 0, 1], ports.https));
 | |
|         //tracing::debug!("listening on {}", addr);
 | |
|         //let app_with_middleware = middleware.layer(app);
 | |
|         // apply the layer around the whole `Router`middleware.layer(app);
 | |
| //           .serve(web_router.into_make_service())
 | |
|         axum_server::bind_rustls(addr, tls_config)
 | |
|           .serve(
 | |
|             // web_router.layer(TraceLayer::new_for_http())
 | |
|             web_router
 | |
|             .into_make_service_with_connect_info::<AppConnectInfo>()
 | |
|         )
 | |
|         .await
 | |
|         .unwrap();
 | |
|     }
 | |
| /*
 | |
|     // let port = 3002;
 | |
| 
 | |
|     // tokio::join!(
 | |
|     //     serve(using_serve_dir(), 3001),
 | |
|     //     serve(using_serve_dir_with_assets_fallback(), 3002),
 | |
|     //     serve(using_serve_dir_only_from_root_via_fallback(), 3003),
 | |
|     //     serve(using_serve_dir_with_handler_as_service(), 3004),
 | |
|     //     serve(two_serve_dirs(), 3005),
 | |
|     //     serve(calling_serve_dir_from_a_handler(), 3006),
 | |
|     // );
 | |
| 
 | |
|     //let addr = SocketAddr::from(([127, 0, 0, 1], port));
 | |
|     let addr = SocketAddr::from(([192,168,1,4], port));
 | |
|     let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
 | |
|     tracing::debug!("listening on {}", listener.local_addr().unwrap());
 | |
| 
 | |
|         // let _ = axum::Server::bind(&addr)
 | |
|         //     .serve(
 | |
|         //         // router.layer(TraceLayer::new_for_http())
 | |
|         //         //router.into_make_service_with_connect_info::<SocketAddr>()
 | |
|         //         router.into_make_service()
 | |
|         //     )
 | |
|         //     .await
 | |
|         //     .expect("server failed");
 | |
|  
 | |
|     // axum_server::bind(addr)
 | |
|     //     .serve(router.into_make_service())
 | |
|     //     .await
 | |
|     //     .unwrap();
 | |
|      axum::serve(listener, router.layer(TraceLayer::new_for_http()))
 | |
|          .await
 | |
|         .unwrap();
 | |
|     // let _ = axum::Server::bind(&addr)
 | |
|     //     .serve(
 | |
|     //             app.layer(TraceLayer::new_for_http()).into_make_service()
 | |
|     //            //    web_router.into_make_service_with_connect_info::<SocketAddr>()
 | |
|     //             // web_router.into_make_service()
 | |
|     //     )
 | |
|     //     .await
 | |
|     //     .unwrap();
 | |
| */
 | |
| }
 | |
| 
 | |
| async fn redirect_http_to_https(ports: Ports) {
 | |
|     fn make_https(host: String, uri: Uri, ports: Ports) -> Result<Uri, BoxError> {
 | |
|         let mut parts = uri.into_parts();
 | |
| 
 | |
|         parts.scheme = Some(axum::http::uri::Scheme::HTTPS);
 | |
| 
 | |
|         if parts.path_and_query.is_none() {
 | |
|             parts.path_and_query = Some("/".parse().unwrap());
 | |
|         }
 | |
| 
 | |
|         let https_host = host.replace(&ports.http.to_string(), &ports.https.to_string());
 | |
|         parts.authority = Some(https_host.parse()?);
 | |
| 
 | |
|         Ok(Uri::from_parts(parts)?)
 | |
|     }
 | |
| 
 | |
|     let redirect = move |Host(host): Host, uri: Uri| async move {
 | |
|         match make_https(host, uri, ports) {
 | |
|             Ok(uri) => Ok(Redirect::permanent(&uri.to_string())),
 | |
|             Err(error) => {
 | |
|                 tracing::warn!(%error, "failed to convert URI to HTTPS");
 | |
|                 Err(StatusCode::BAD_REQUEST)
 | |
|             }
 | |
|         }
 | |
|     };
 | |
| 
 | |
|     let addr = SocketAddr::from(([127, 0, 0, 1], ports.http));
 | |
|     let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
 | |
|     tracing::debug!("listening on {}", listener.local_addr().unwrap());
 | |
|     axum::serve(listener, redirect.into_make_service_with_connect_info::<SocketAddr>())
 | |
|         .await
 | |
|         .unwrap();
 | |
| }
 | |
| 
 | |
| /*
 | |
| fn using_serve_dir() -> Router {
 | |
|     // serve the file in the "assets" directory under `/assets`
 | |
|     Router::new().nest_service("/assets", ServeDir::new("assets"))
 | |
| }
 | |
| 
 | |
| fn using_serve_dir_with_assets_fallback() -> Router {
 | |
|     // `ServeDir` allows setting a fallback if an asset is not found
 | |
|     // so with this `GET /assets/doesnt-exist.jpg` will return `index.html`
 | |
|     // rather than a 404
 | |
|     let serve_dir = ServeDir::new("assets").not_found_service(ServeFile::new("assets/index.html"));
 | |
| 
 | |
|     Router::new()
 | |
|         .route("/foo", get(|| async { "Hi from /foo" }))
 | |
|         .nest_service("/assets", serve_dir.clone())
 | |
|         .fallback_service(serve_dir)
 | |
| }
 | |
| 
 | |
| fn using_serve_dir_only_from_root_via_fallback() -> Router {
 | |
|     // you can also serve the assets directly from the root (not nested under `/assets`)
 | |
|     // by only setting a `ServeDir` as the fallback
 | |
|     let serve_dir = ServeDir::new("assets").not_found_service(ServeFile::new("assets/index.html"));
 | |
| 
 | |
|     Router::new()
 | |
|         .route("/foo", get(|| async { "Hi from /foo" }))
 | |
|         .fallback_service(serve_dir)
 | |
| }
 | |
| 
 | |
| fn using_serve_dir_with_handler_as_service() -> Router {
 | |
|     async fn handle_404() -> (StatusCode, &'static str) {
 | |
|         (StatusCode::NOT_FOUND, "Not found")
 | |
|     }
 | |
| 
 | |
|     // you can convert handler function to service
 | |
|     let service = handle_404.into_service();
 | |
| 
 | |
|     let serve_dir = ServeDir::new("assets").not_found_service(service);
 | |
| 
 | |
|     Router::new()
 | |
|         .route("/foo", get(|| async { "Hi from /foo" }))
 | |
|         .fallback_service(serve_dir)
 | |
| }
 | |
| 
 | |
| fn two_serve_dirs() -> Router {
 | |
|     // you can also have two `ServeDir`s nested at different paths
 | |
|     let serve_dir_from_assets = ServeDir::new("assets");
 | |
|     let serve_dir_from_dist = ServeDir::new("dist");
 | |
| 
 | |
|     Router::new()
 | |
|         .nest_service("/assets", serve_dir_from_assets)
 | |
|         .nest_service("/dist", serve_dir_from_dist)
 | |
| }
 | |
| 
 | |
| #[allow(clippy::let_and_return)]
 | |
| fn calling_serve_dir_from_a_handler() -> Router {
 | |
|     // via `tower::Service::call`, or more conveniently `tower::ServiceExt::oneshot` you can
 | |
|     // call `ServeDir` yourself from a handler
 | |
|     Router::new().nest_service(
 | |
|         "/foo",
 | |
|         get(|request: Request<_>| async {
 | |
|             let service = ServeDir::new("assets");
 | |
|             let result = service.oneshot(request).await;
 | |
|             result
 | |
|         }),
 | |
|     )
 | |
| }
 | |
| 
 | |
| async fn serve(app: Router, port: u16) {
 | |
|     let addr = SocketAddr::from(([127, 0, 0, 1], port));
 | |
|     let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
 | |
|     tracing::debug!("listening on {}", listener.local_addr().unwrap());
 | |
|     axum::serve(listener, app.layer(TraceLayer::new_for_http()))
 | |
|         .await
 | |
|         .unwrap();
 | |
|     // let _ = axum::Server::bind(&addr)
 | |
|     //     .serve(
 | |
|     //             app.layer(TraceLayer::new_for_http()).into_make_service()
 | |
|     //            //    web_router.into_make_service_with_connect_info::<SocketAddr>()
 | |
|     //             // web_router.into_make_service()
 | |
|     //     )
 | |
|     //     .await
 | |
|     //     .unwrap();
 | |
| }
 | |
| */
 | |
| 
 |