Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(volo-http): support merging routers #277

Merged
merged 5 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ same-file = "1"
serde = "1"
serde_json = "1"
serde_yaml = "0.9"
simdutf8 = "0.1"
socket2 = "0.5"
syn = "1"
tempfile = "3"
Expand Down
1 change: 1 addition & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ path = "src/http/simple.rs"
anyhow.workspace = true
async-stream.workspace = true
bytes.workspace = true
faststr.workspace = true
lazy_static.workspace = true
metainfo.workspace = true
motore.workspace = true
Expand Down
111 changes: 74 additions & 37 deletions examples/src/http/simple.rs
Original file line number Diff line number Diff line change
@@ -1,61 +1,106 @@
use std::{net::SocketAddr, time::Duration};

use serde::Deserialize;
use faststr::FastStr;
use serde::{Deserialize, Serialize};
use volo_http::{
layer::TimeoutLayer,
route::{get, post, MethodRouter, Router},
Address, Bytes, Json, Method, Params, Server, StatusCode, Uri,
Address, Bytes, Json, MaybeInvalid, Method, Params, Server, StatusCode, Uri,
};

async fn hello() -> &'static str {
"hello, world\n"
}

async fn echo(params: Params) -> Result<Bytes, StatusCode> {
if let Some(echo) = params.get("echo") {
return Ok(echo.clone());
}
Err(StatusCode::BAD_REQUEST)
}

#[derive(Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug)]
struct Person {
name: String,
age: u8,
phones: Vec<String>,
}

async fn json(Json(request): Json<Person>) {
async fn json_get() -> Json<Person> {
Json(Person {
name: "Foo".to_string(),
age: 25,
phones: vec!["Bar".to_string(), "114514".to_string()],
})
}

async fn json_post(Json(request): Json<Person>) -> String {
let first_phone = request
.phones
.get(0)

Check warning on line 33 in examples/src/http/simple.rs

View workflow job for this annotation

GitHub Actions / clippy

accessing first element with `request .phones.get(0)`

warning: accessing first element with `request .phones.get(0)` --> examples/src/http/simple.rs:31:23 | 31 | let first_phone = request | _______________________^ 32 | | .phones 33 | | .get(0) | |_______________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#get_first = note: `#[warn(clippy::get_first)]` on by default help: try | 31 ~ let first_phone = request 32 + .phones.first() |
.map(|p| p.as_str())
.unwrap_or("no number");
println!(
"{} is {} years old, {}'s first phone number is {}",
format!(
"{} is {} years old, {}'s first phone number is `{}`\n",
request.name, request.age, request.name, first_phone
);
)
}

async fn test(u: Uri, m: Method) -> Result<&'static str, (StatusCode, &'static str)> {
println!("uri: {u:?}");
println!("method: {m:?}");
if u.to_string().ends_with("a") {
Ok("a") // http://localhost:3000/test?a=a
} else {
Err((StatusCode::BAD_REQUEST, "b")) // http://localhost:3000/test?a=bb
async fn test(
u: Uri,
m: Method,
data: MaybeInvalid<FastStr>,
) -> Result<String, (StatusCode, &'static str)> {
let msg = unsafe { data.assume_valid() };
match m {
Method::GET => Err((StatusCode::BAD_REQUEST, "Try POST something\n")),
Method::POST => Ok(format!("{m} {u}\n\n{msg}\n")),
_ => unreachable!(),
}
}

async fn timeout_test() {
tokio::time::sleep(Duration::from_secs(5)).await
tokio::time::sleep(Duration::from_secs(10)).await
}

async fn echo(params: Params) -> Result<Bytes, StatusCode> {
if let Some(echo) = params.get("echo") {
return Ok(echo.clone());
}
Err(StatusCode::BAD_REQUEST)
}

fn timeout_handler(uri: Uri, peer: Address) -> StatusCode {
tracing::info!("Timeout on `{}`, peer: {}", uri, peer);
StatusCode::INTERNAL_SERVER_ERROR
}

fn index_router() -> Router {
// curl http://127.0.0.1:8080/
Router::new().route("/", get(hello))
}

fn user_router() -> Router {
Router::new()
// curl http://localhost:8080/user/json_get
.route("/user/json_get", get(json_get))
// curl http://localhost:8080/user/json_post \
// -X POST \
// -H "Content-Type: application/json" \
// -d '{"name":"Foo", "age": 25, "phones":["Bar", "114514"]}'
.route("/user/json_post", post(json_post))
}

fn test_router() -> Router {
Router::new()
// curl http://127.0.0.1:8080/test/extract
// curl http://127.0.0.1:8080/test/extract -X POST -d "114514"
.route(
"/test/extract",
MethodRouter::builder().get(test).post(test).build(),
)
// curl http://127.0.0.1:8080/test/timeout
.route(
"/test/timeout",
get(timeout_test).layer(TimeoutLayer::new(Duration::from_secs(1), timeout_handler)),
)
// curl -v http://127.0.0.1:8080/test/param/114514
.route("/test/param/:echo", get(echo))
}

#[tokio::main(flavor = "multi_thread")]
async fn main() {
let subscriber = tracing_subscriber::FmtSubscriber::builder()
Expand All @@ -65,22 +110,14 @@
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");

let app = Router::new()
.route(
"/",
get(hello).layer(TimeoutLayer::new(Duration::from_secs(1), || {
StatusCode::INTERNAL_SERVER_ERROR
})),
)
.route("/:echo", get(echo))
.route("/user", post(json))
.route(
"/test",
MethodRouter::builder().get(test).post(test).build(),
)
.route("/timeout", get(timeout_test))
.layer(TimeoutLayer::new(Duration::from_secs(1), timeout_handler));
.merge(index_router())
.merge(user_router())
.merge(test_router())
.layer(TimeoutLayer::new(Duration::from_secs(5), || {
StatusCode::INTERNAL_SERVER_ERROR
}));

let addr: SocketAddr = "[::]:9091".parse().unwrap();
let addr: SocketAddr = "[::]:8080".parse().unwrap();
let addr = volo::net::Address::from(addr);

println!("Listening on {addr}");
Expand Down
2 changes: 2 additions & 0 deletions volo-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ hyper = { version = "1", features = ["server", "http1", "http2"] }
hyper-util = { version = "0.1", features = ["tokio"] }

bytes.workspace = true
faststr.workspace = true
futures-util.workspace = true
matchit.workspace = true
mime.workspace = true
Expand All @@ -33,6 +34,7 @@ parking_lot.workspace = true
pin-project.workspace = true
serde.workspace = true
serde_json.workspace = true
simdutf8.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = [
"time",
Expand Down
Loading
Loading