use std::{
collections::HashMap,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use miko::{
endpoint::layer::WithState,
ext::static_svc::StaticSvcBuilder,
extractor::{Form, Json, Path, Query, State, multipart::MultipartResult},
handler::{Req, Resp},
http::response::sse::{SseSender, spawn_sse_event},
macros::*,
openapi::AutoPaths,
router::Router,
ws::server::{IntoMessage, spawn_ws_event},
*,
};
use serde::Deserialize;
use tokio::sync::Mutex;
#[derive(OpenApi)]
#[openapi(
info(title = "Miko Basic Example API", version = "1.0.0"),
modifiers(&AutoPaths)
)]
struct ApiDoc;
#[derive(Deserialize)]
struct MyQuery {
name: String,
age: u8,
}
#[get("/")]
async fn hello_world() -> &'static str {
"Hello, World! (macro defined route)"
}
#[get("/with_query")]
async fn hello_with_query(#[query] name: String, #[query] age: u8) -> String {
format!(
r"You can also use Query extractor!
But #[query] is more convenient if you don't want to define a Query struct.
Hello, {}! You are {} years old. (macro defined route)",
name, age
)
}
#[get("/with_path/{a}/{b}")]
async fn hello_with_path(#[path] a: String, Path(b): Path<i32>) -> String {
format!(
r"Hello from path parameters!
#[path] and Path<T> has the same behavior.
They will extract the value from the path and convert it to the specified type.
But they are not named parameters, so the order matters.
a: {}, b: {} (macro defined route)",
a, b
)
}
#[post("/echo", method = "get,put")] async fn echo(
body: String, ) -> String {
format!("Echo: {}", body)
}
#[get("/json_resp")]
async fn json_resp() {
let mut map = HashMap::new();
map.insert("value1", 42);
map.insert("value2", 100);
Json(map) }
#[post("/json_req")]
async fn json_req(
#[body] data: HashMap<String, i32>, ) -> String {
format!("Received JSON data: {:?}", data)
}
#[prefix("/sub")] mod sub_routes {
use super::*;
#[get("/hello")]
async fn sub_hello() -> &'static str {
"Hello from sub route!"
}
}
struct ServiceComponent {
pub name: String,
pub version: String,
pub data: Mutex<String>,
}
#[component] impl ServiceComponent {
async fn new() -> Self {
ServiceComponent {
name: "demo".into(),
version: "1.0.0".into(),
data: Mutex::new("Initial service data".into()), }
}
async fn operation(&self) {
println!("ServiceComponent operation called.");
println!("Name: {}, Version: {}", self.name, self.version);
println!("Data: {}", self.data.lock().await);
}
async fn changed(&self) {
let mut data = self.data.lock().await;
*data = "Service data has been changed.".into();
println!("ServiceComponent has been changed.");
}
}
#[get("/use_dep")]
async fn use_dep(
#[dep] service: Arc<ServiceComponent>, ) {
service.operation().await;
service.changed().await;
service.operation().await;
service.data.lock().await.clone()
}
#[get("/data")]
async fn get_data(#[dep] service: Arc<ServiceComponent>) -> String {
service.data.lock().await.clone() }
#[get("/error")]
async fn error() {
AppError::from(tokio::io::Error::other("HAHA"))
}
#[get("/custom_error")]
async fn custom_error() {
AppError::BadGateway("Custom Bad Gateway".into())
}
#[get("/panic")]
async fn panic() -> &'static str {
panic!("This handler panics!");
}
#[get("/sse")]
async fn sse() {
spawn_sse_event(|sender| async move {
tokio::spawn(async move {
for i in 0..5 {
sender
.send(format!("data: SSE event number {}\n\n", i))
.await
.or_break();
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
});
})
}
#[get("/sse2")]
async fn sse2() {
|sender: SseSender| async move {
tokio::spawn(async move {
for i in 0..5 {
sender
.send(format!("data: SSE2 event number {}\n\n", i))
.await
.or_break();
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
});
}
}
#[get("/ws")]
async fn ws(mut req: Req) {
spawn_ws_event(
|mut io| async move {
io.send("hello world").await.expect("websocket send error");
let (mut w, mut r, _) = io.split();
{
let mut w = w.clone();
tokio::spawn(async move {
w.send("START --".into_message())
.await
.expect("websocket send error");
loop {
tokio::time::sleep(Duration::from_secs(5)).await;
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let msg = format!("server time: {}", now);
let _ = w.send(msg.into_message()).await;
}
});
}
tokio::spawn(async move {
while let Some(msg) = r.next().await {
let msg = msg.expect("websocket recv error");
if msg.is_text() {
let txt = msg.into_text().expect("websocket into text error");
let _ = w.send(txt.into_message()).await;
println!("recv text: {}", txt);
} else if msg.is_binary() {
let bin = msg.into_data();
println!("recv binary: {:?}", bin);
} else if msg.is_close() {
println!("websocket closed");
break;
}
}
});
},
&mut req,
None,
)
.expect("failed to spawn websocket handler")
}
#[get("/layer")]
#[layer(AddHeaderLayer::new("X-Route-Layer", "Applied"))]
async fn layer_test() -> String {
"Test route layer - check response headers for X-Route-Layer".to_string()
}
#[prefix("/layered")]
#[layer(AddHeaderLayer::new("X-Module-Layer", "Applied"))]
mod layered_module {
use super::*;
#[get("/test1")]
#[layer(AddHeaderLayer::new("X-Custom-Header", "Layer-Applied"))]
async fn test_single_layer() -> String {
"Test single layer - check response headers for X-Custom-Header".to_string()
}
#[prefix("/inner")]
#[layer(AddHeaderLayer::new("X-Inner-Layer", "Inner-Applied"))]
mod inner {
use super::*;
#[get("/test_inner")]
#[layer(AddHeaderLayer::new("X-Route-INNER-Layer", "Inner-Applied"))]
async fn test_inner_layer() -> String {
"Test inner module layer - check response headers for X-Inner-Layer".to_string()
}
}
}
#[post("/multipart")]
async fn multipart(multipart: MultipartResult) {
format!(
"Received multipart data: {:?}\n Files: {:?}",
multipart.fields, multipart.files
)
}
#[derive(Deserialize, Debug, ToSchema)]
#[allow(unused)]
struct FormStruct {
field1: String,
field2: i32,
}
#[post("/form")]
async fn form(Form(form_data): Form<FormStruct>) {
format!("Received form data: {:?}", form_data)
}
struct NewResp();
impl IntoResponse for NewResp {
fn into_response(self) -> Resp {
"Custom Response, or you can also use Response::builder".into_response()
}
}
#[get("/new_resp")]
async fn new_resp() -> NewResp {
NewResp()
}
struct AppState {
pub app_name: String,
pub app_version: String,
}
struct AnotherAppState {
pub description: String,
}
#[miko(sse, catch, build)]
pub async fn create_app() {
let mut no_macro_router = Router::new();
no_macro_router.get("/", async move || "Hello, World! (manually defined router)");
no_macro_router.get(
"/with_query",
async move |Query(queries): Query<MyQuery>| {
format!(
"Hello, {}! You are {} years old. (manually defined router)",
queries.name, queries.age
)
},
);
let mut router = router.with_state(AppState {
app_name: "Miko Demo App".into(),
app_version: "1.0.0".into(),
});
router.get("/with_state", async move |State(state): State<AppState>| {
format!(
"App Name: {}, App Version: {} (macro defined route with state)",
state.app_name, state.app_version
)
});
router.get_service(
"/single_state",
(async move |State(state): State<AnotherAppState>| {
format!(
"Description: {} (macro defined route with state)",
state.description
)
})
.with_state(AnotherAppState {
description: "Another".into(),
}),
);
router.nest("/no_macro", no_macro_router);
router.get("/api-docs/openapi.json", || async {
Json(ApiDoc::openapi())
});
router.static_svc(
"/static",
"./static",
Some(|options: StaticSvcBuilder| {
options
.cors_any()
.with_spa_fallback(true)
.with_fallback_files(["index.html", "index.htm", "index.php"])
}),
);
router.cors_any(); }
#[tokio::main]
#[allow(dead_code)]
async fn main() {
tracing_subscriber::fmt::init(); let app = create_app().await;
app.run().await.unwrap();
}
#[derive(Clone)]
struct AddHeaderLayer {
header_name: &'static str,
header_value: &'static str,
}
impl AddHeaderLayer {
fn new(header_name: &'static str, header_value: &'static str) -> Self {
Self {
header_name,
header_value,
}
}
}
impl<S> tower::Layer<S> for AddHeaderLayer {
type Service = AddHeaderService<S>;
fn layer(&self, inner: S) -> Self::Service {
AddHeaderService {
inner,
header_name: self.header_name,
header_value: self.header_value,
}
}
}
#[derive(Clone)]
struct AddHeaderService<S> {
inner: S,
header_name: &'static str,
header_value: &'static str,
}
impl<S> tower::Service<miko_core::Req> for AddHeaderService<S>
where
S: tower::Service<miko_core::Req, Response = miko_core::Resp> + Clone + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = std::pin::Pin<
Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>> + Send>,
>;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: miko_core::Req) -> Self::Future {
let mut inner = self.inner.clone();
let header_name = self.header_name;
let header_value = self.header_value;
Box::pin(async move {
let mut resp = inner.call(req).await?;
resp.headers_mut()
.insert(header_name, header_value.parse().unwrap());
Ok(resp)
})
}
}