From da93d0f5e52ea25c73f90922760379f384f7ea94 Mon Sep 17 00:00:00 2001 From: StellarisW Date: Tue, 10 Sep 2024 15:29:34 +0800 Subject: [PATCH] docs: add docs for volo-http and refactor volo directories (#1125) --- content/en/docs/volo/cli/_index.md | 8 + .../docs/volo/cli/getting-started/_index.md | 126 ++++++ content/en/docs/volo/cli/migrate/_index.md | 23 + content/en/docs/volo/faq/_index.md | 2 +- content/en/docs/volo/guide/_index.md | 2 +- content/en/docs/volo/motore/_index.md | 2 +- content/en/docs/volo/overview/_index.md | 12 +- content/en/docs/volo/pilota/_index.md | 2 +- content/en/docs/volo/volo-grpc/_index.md | 2 +- .../volo/volo-grpc/getting-started/_index.md | 48 +- content/en/docs/volo/volo-http/_index.md | 7 + .../volo/volo-http/getting-started/_index.md | 96 ++++ .../en/docs/volo/volo-http/overview/_index.md | 59 +++ .../docs/volo/volo-http/tutorials/_index.md | 12 + .../volo/volo-http/tutorials/middleware.md | 195 +++++++++ .../docs/volo/volo-http/tutorials/request.md | 165 +++++++ .../docs/volo/volo-http/tutorials/response.md | 175 ++++++++ .../en/docs/volo/volo-http/tutorials/route.md | 410 ++++++++++++++++++ .../volo/volo-http/tutorials/static-fs.md | 29 ++ .../volo/volo-http/tutorials/websocket.md | 93 ++++ content/en/docs/volo/volo-thrift/_index.md | 2 +- .../volo-thrift/getting-started/_index.md | 48 +- content/zh/docs/volo/cli/_index.md | 8 + .../docs/volo/cli/getting-started/_index.md | 125 ++++++ content/zh/docs/volo/cli/migrate/_index.md | 23 + content/zh/docs/volo/faq/_index.md | 2 +- content/zh/docs/volo/guide/_index.md | 2 +- content/zh/docs/volo/motore/_index.md | 2 +- content/zh/docs/volo/pilota/_index.md | 2 +- content/zh/docs/volo/volo-grpc/_index.md | 2 +- .../volo/volo-grpc/getting-started/_index.md | 46 +- content/zh/docs/volo/volo-http/_index.md | 7 + .../volo/volo-http/getting-started/_index.md | 96 ++++ .../zh/docs/volo/volo-http/overview/_index.md | 56 +++ .../docs/volo/volo-http/tutorials/_index.md | 12 + .../volo/volo-http/tutorials/middleware.md | 193 +++++++++ .../docs/volo/volo-http/tutorials/request.md | 165 +++++++ .../docs/volo/volo-http/tutorials/response.md | 176 ++++++++ .../zh/docs/volo/volo-http/tutorials/route.md | 406 +++++++++++++++++ .../volo/volo-http/tutorials/static-fs.md | 29 ++ .../volo/volo-http/tutorials/websocket.md | 92 ++++ content/zh/docs/volo/volo-thrift/_index.md | 2 +- .../volo-thrift/getting-started/_index.md | 46 +- 43 files changed, 2832 insertions(+), 178 deletions(-) create mode 100644 content/en/docs/volo/cli/_index.md create mode 100644 content/en/docs/volo/cli/getting-started/_index.md create mode 100644 content/en/docs/volo/cli/migrate/_index.md create mode 100644 content/en/docs/volo/volo-http/_index.md create mode 100644 content/en/docs/volo/volo-http/getting-started/_index.md create mode 100644 content/en/docs/volo/volo-http/overview/_index.md create mode 100644 content/en/docs/volo/volo-http/tutorials/_index.md create mode 100644 content/en/docs/volo/volo-http/tutorials/middleware.md create mode 100644 content/en/docs/volo/volo-http/tutorials/request.md create mode 100644 content/en/docs/volo/volo-http/tutorials/response.md create mode 100644 content/en/docs/volo/volo-http/tutorials/route.md create mode 100644 content/en/docs/volo/volo-http/tutorials/static-fs.md create mode 100644 content/en/docs/volo/volo-http/tutorials/websocket.md create mode 100644 content/zh/docs/volo/cli/_index.md create mode 100644 content/zh/docs/volo/cli/getting-started/_index.md create mode 100644 content/zh/docs/volo/cli/migrate/_index.md create mode 100644 content/zh/docs/volo/volo-http/_index.md create mode 100644 content/zh/docs/volo/volo-http/getting-started/_index.md create mode 100644 content/zh/docs/volo/volo-http/overview/_index.md create mode 100644 content/zh/docs/volo/volo-http/tutorials/_index.md create mode 100644 content/zh/docs/volo/volo-http/tutorials/middleware.md create mode 100644 content/zh/docs/volo/volo-http/tutorials/request.md create mode 100644 content/zh/docs/volo/volo-http/tutorials/response.md create mode 100644 content/zh/docs/volo/volo-http/tutorials/route.md create mode 100644 content/zh/docs/volo/volo-http/tutorials/static-fs.md create mode 100644 content/zh/docs/volo/volo-http/tutorials/websocket.md diff --git a/content/en/docs/volo/cli/_index.md b/content/en/docs/volo/cli/_index.md new file mode 100644 index 0000000000..1a739ef172 --- /dev/null +++ b/content/en/docs/volo/cli/_index.md @@ -0,0 +1,8 @@ +--- +title: "Cli" +linkTitle: "Cli" +weight: 2 +keywords: ["Rust", "Volo", "cli"] +description: "Volo-cli Usage" +--- + diff --git a/content/en/docs/volo/cli/getting-started/_index.md b/content/en/docs/volo/cli/getting-started/_index.md new file mode 100644 index 0000000000..23907ff3f6 --- /dev/null +++ b/content/en/docs/volo/cli/getting-started/_index.md @@ -0,0 +1,126 @@ +--- +title: "Getting Started" +linkTitle: "Getting Started" +weight: 2 +keywords: ["Volo", "cli", "Tutorial", "Install"] +description: "This document covers the preparation of the development environment, quick start and basic tutorials of Volo-HTTP." +--- + +Volo provides CLI tools of the same name, and it provide functions as follows: + +1. Server-side scaffold generation + + support generate HTTP or RPC server-side scaffold by IDL like Thrift, Protobuf + +2. Stub management + +3. Old version migration + +## Part 1. Install Cli tool + +```bash +cargo install volo-cli +``` + +Need rustc version >= 1.80.0 + +Then, we type: + +```bash +volo help +``` + +and we can see the output as follows: + +```plain +Usage: volo [OPTIONS] + +Commands: + init init your thrift or grpc project + http manage your http project + repo manage your repo + idl manage your idl + migrate migrate your config from old version + help Print this message or the help of the given subcommand(s) + +Options: + -v, --verbose... Turn on the verbose mode. + -n, --entry-name The entry name, defaults to 'default'. [default: default] + -h, --help Print help + -V, --version Print version +``` + +## Part 2. Generate RPC code + +To create a RPC project, we need to write an IDL first. Let's take Thrift for example. + + +Create a new Thrift IDL in the project directory: + +`vim idl/rpc_example.thrift` + +```thrift +namespace rs volo.rpc.example + +struct Item { + 1: required i64 id, + 2: required string title, + 3: required string content, + + 10: optional map extra, +} + +struct GetItemRequest { + 1: required i64 id, +} + +struct GetItemResponse { + 1: required Item item, +} + +service ItemService { + GetItemResponse GetItem (1: GetItemRequest req), +} +``` + +Execute the following command: + +`volo init volo-rpc-example idl/rpc_example.thrift` + +At this point, our entire catalog is structured as follows: + +```plain +. +├── Cargo.toml +├── idl +│ └── rpc_example.thrift +├── rust-toolchain.toml +├── src +│ ├── bin +│ │ └── server.rs +│ └── lib.rs +└── volo-gen +├── Cargo.toml +├── build.rs +├── src +│ └── lib.rs +└── volo.yml +``` + +## Part 2. Generate HTTP code + +Execute the following command: + +`volo http init volo-http-example` + +At this point, our entire catalog is structured as follows: + +```bash +$ tree +. +├── Cargo.toml +└── src + ├── bin + │   └── server.rs + └── lib.rs +``` diff --git a/content/en/docs/volo/cli/migrate/_index.md b/content/en/docs/volo/cli/migrate/_index.md new file mode 100644 index 0000000000..24ce6d66fa --- /dev/null +++ b/content/en/docs/volo/cli/migrate/_index.md @@ -0,0 +1,23 @@ +--- +title: "Migrate" +linkTitle: "Migrate" +weight: 4 +keywords: ["Volo", "cli", "Migrate"] +description: "Volo-Cli Migrate" +--- + +Migrating from an older version of `volo.yml` to the current version of `volo.yml` + +```bash +Usage: volo migrate [OPTIONS] + +Options: + -v, --verbose... Turn on the verbose mode. + -h, --help Print help + -V, --version Print version +``` + +## How to migrate? + +1. Switch to the root directory of the project (where `volo.yml` is located). +2. Run the command `volo migrate` \ No newline at end of file diff --git a/content/en/docs/volo/faq/_index.md b/content/en/docs/volo/faq/_index.md index 9a39c63bce..9fb232ab8f 100644 --- a/content/en/docs/volo/faq/_index.md +++ b/content/en/docs/volo/faq/_index.md @@ -1,7 +1,7 @@ --- title: "FAQ" linkTitle: "FAQ" -weight: 8 +weight: 9 keywords: ["Volo", "FAQ", "volo-cli", "poll_ready"] description: Answers to frequently asked questions. --- diff --git a/content/en/docs/volo/guide/_index.md b/content/en/docs/volo/guide/_index.md index 88b6b18342..ad64ec2d65 100644 --- a/content/en/docs/volo/guide/_index.md +++ b/content/en/docs/volo/guide/_index.md @@ -1,6 +1,6 @@ --- title: "Guide" linkTitle: "Guide" -weight: 4 +weight: 6 description: Project usage guide. --- diff --git a/content/en/docs/volo/motore/_index.md b/content/en/docs/volo/motore/_index.md index d1901947f5..0d02b31265 100644 --- a/content/en/docs/volo/motore/_index.md +++ b/content/en/docs/volo/motore/_index.md @@ -1,7 +1,7 @@ --- title: "Motore" linkTitle: "Motore" -weight: 6 +weight: 7 keywords: ["Motore", "AFIT", "RPITIT"] Description: Motore is an async middleware abstraction powered by AFIT and RPITIT. --- diff --git a/content/en/docs/volo/overview/_index.md b/content/en/docs/volo/overview/_index.md index 59071c961e..0c4fac0b96 100644 --- a/content/en/docs/volo/overview/_index.md +++ b/content/en/docs/volo/overview/_index.md @@ -53,7 +53,11 @@ We have also created an organization [Volo-rs](http://github.com/volo-rs), any c ## Related Projects -1. [Volo-rs](http://github.com/volo-rs):The volo ecosystem which contains a lot of useful components. -2. [Pilota](https://github.com/cloudwego/pilota):A thrift and protobuf implementation in pure rust with high performance and extensibility. -3. [Motore](https://github.com/cloudwego/motore):Middleware abstraction layer powered by AFIT and RPITIT. -4. [Metainfo](https://github.com/cloudwego/metainfo):Transmissing metainfo across components. +- [Volo-rs](http://github.com/volo-rs):The volo ecosystem which contains a lot of useful components. +- [Pilota](https://github.com/cloudwego/pilota):A thrift and protobuf implementation in pure rust with high performance and extensibility. +- [Motore](https://github.com/cloudwego/motore):Middleware abstraction layer powered by AFIT and RPITIT. +- [Metainfo](https://github.com/cloudwego/metainfo):Transmissing metainfo across components. + +## Related Articles + +- [China's First Rust-based RPC Framework - Volo is Officially Open Source!](https://www.cloudwego.io/blog/2022/08/30/chinas-first-rust-based-rpc-framework-volo-is-officially-open-source/) \ No newline at end of file diff --git a/content/en/docs/volo/pilota/_index.md b/content/en/docs/volo/pilota/_index.md index bff0f4bfcf..e0c1490c75 100644 --- a/content/en/docs/volo/pilota/_index.md +++ b/content/en/docs/volo/pilota/_index.md @@ -1,7 +1,7 @@ --- title: "Pilota" linkTitle: "Pilota" -weight: 7 +weight: 8 keywords: ["Pilota", "Thrift", "Protobuf", "Plugin"] Description: Pilota is a Thrift and Protobuf implementation in pure rust with high performance and extensibility. --- diff --git a/content/en/docs/volo/volo-grpc/_index.md b/content/en/docs/volo/volo-grpc/_index.md index f5bf52b5ee..1c27e54d63 100644 --- a/content/en/docs/volo/volo-grpc/_index.md +++ b/content/en/docs/volo/volo-grpc/_index.md @@ -1,7 +1,7 @@ --- title: "Volo-gRPC" linkTitle: "Volo-gRPC" -weight: 3 +weight: 5 keywords: ["Volo", "gRPC", "Getting Started", "Guidelines"] description: "CLI tool installation, quick start and basic tutorials for Volo-gRPC." --- diff --git a/content/en/docs/volo/volo-grpc/getting-started/_index.md b/content/en/docs/volo/volo-grpc/getting-started/_index.md index 92d75b9d2d..0b60931085 100644 --- a/content/en/docs/volo/volo-grpc/getting-started/_index.md +++ b/content/en/docs/volo/volo-grpc/getting-started/_index.md @@ -6,47 +6,13 @@ keywords: ["Volo", "gRPC", "Tutorial", "Install"] description: "This document covers the preparation of the development environment, quick start and basic tutorials of Volo-gRPC." --- -## Part 1. Install the CLI Tool - -Volo provides CLI tools of the same name for initializing projects, managing IDLs, and more. - -```bash -cargo install volo-cli -``` - -> Make sure that the Rust version is >= 1.75.0 - -Then run: - -```bash -volo help -``` - -You should see something like the following: - -```bash -USAGE: - volo [OPTIONS] - -OPTIONS: - -h, --help Print help information - -n, --entry-name The entry name, defaults to 'default'. [default: default] - -v, --verbose Turn on the verbose mode. - -V, --version Print version information - -SUBCOMMANDS: - help Print this message or the help of the given subcommand(s) - idl manage your idl - init init your project -``` - -## Part 2. Create a gRPC Server +## Part 1. Create a gRPC Server Volo-gRPC is an RPC framework so that the bottom layer requires two major functions: Serialization and Transport. IDL is short for `Interface Definition Language`. -### 2.1 Why IDL +### 1.1 Why IDL If we want to do RPC, we need to know what interface is for the server, what parameters to pass, and what the return value is, just like two people talking to each other, we need to make sure we are speaking the same language and doing the same thing. @@ -55,7 +21,7 @@ At this time, we need to use IDL to specify the protocol for both sides, just li Protobuf IDL is a full-stack RPC solution for cross-language, the specific syntax can be seen in [protocol-buffers/docs/proto3](https://developers.google.com/protocol-buffers/docs/proto3). -### 2.2 Write IDL +### 1.2 Write IDL To create a gRPC project, we need to write a protobuf IDL first. @@ -171,7 +137,7 @@ cargo run --bin server At this point, we have our server running! -## Part 3. Create a Client +## Part 2. Create a Client In the previous section, we wrote a server, now let's write a client and call the server. @@ -255,7 +221,7 @@ Finally, we go back to the current directory and execute the following command, cargo run --bin client ``` -## Part 4. Add a Middleware +## Part 3. Add a Middleware Next, let's look at how to add middleware to Volo. @@ -320,7 +286,7 @@ Server::new() At this point, it prints out how long the request took at the INFO log level. -## Part 5. What's Next? +## Part 4. What's Next? Congratulations, you've read this far! At this point, we've basically learned how to use Volo, and we're ready to use Volo to kick off our Rust journey @@ -333,7 +299,7 @@ If there is a dire lack of components, you are welcomed to raise an issue in: ht In the meantime, welcome to join our Lark user group and share your experience with us about Volo.
-Volo_feishu +Volo_feishu


diff --git a/content/en/docs/volo/volo-http/_index.md b/content/en/docs/volo/volo-http/_index.md new file mode 100644 index 0000000000..20abdd718c --- /dev/null +++ b/content/en/docs/volo/volo-http/_index.md @@ -0,0 +1,7 @@ +--- +title: "Volo-HTTP" +linkTitle: "Volo-HTTP" +weight: 3 +keywords: ["Volo", "HTTP", "Getting started", "Tutorials"] +description: "Volo-HTTP Tutorials。" +--- diff --git a/content/en/docs/volo/volo-http/getting-started/_index.md b/content/en/docs/volo/volo-http/getting-started/_index.md new file mode 100644 index 0000000000..eed66cc8b4 --- /dev/null +++ b/content/en/docs/volo/volo-http/getting-started/_index.md @@ -0,0 +1,96 @@ +--- +title: "Getting Started" +linkTitle: "Getting Started" +weight: 2 +keywords: ["Volo", "Http", "Tutorial", "Install"] +description: "This document covers the preparation of the development environment, quick start and basic tutorials of Volo-HTTP." +--- + +## Preparing the Development Environment + +1. If you have not set up a Rust development environment before, you can refer to [Install Rust](https://www.rust-lang.org/tools/install). +2. It is recommended that you use the latest version of Rust, or ensure that Rustc >= 1.80.0. +3. If you have not installed `volo-cli`, please refer to [Quick Start](https://www.cloudwego.io/zh/docs/volo/volo-li/getting-started/). + +## Create Server + +> The following example `volo-cli` version is **0.10.3**, volo-http version is **0.2.14**. + +1. Create http project scaffolding using Volo-Cli. + + ```bash + mkdir -p volo-http-example + cd volo-http-example + volo http init volo-http-example + ``` + + The directory structure after the scaffold is created is as follows. + + ```bash + $ tree + . + ├── Cargo.toml + └── src + ├── bin + │   └── server.rs + └── lib.rs + ``` + + `src/lib.rs` content is: + + ```rust + use volo_http::server::route::{get, Router}; + + async fn index_handler() -> &'static str { + "It Works!\n" + } + + pub fn example_router() -> Router { + Router::new().route("/", get(index_handler)) + } + ``` + + As we can see, when the server is started, requesting the `/` path using the `GET` method expects an `It Works!` response + +2. Run `cargo run` to start the server, after you see `Listening on [::]:8080` in the terminal, it means the Server is running successfully. + + We can use `curl` to verify + + ```bash + $ curl -v http://localhost:8080/ + * Host localhost:8080 was resolved. + * IPv6: ::1 + * IPv4: 127.0.0.1 + * Trying [::1]:8080... + * Connected to localhost (::1) port 8080 + > GET / HTTP/1.1 + > Host: localhost:8080 + > User-Agent: curl/8.6.0 + > Accept: */* + > + < HTTP/1.1 200 OK + < content-length: 10 + < date: Sun, 01 Sep 2024 16:52:55 GMT + < + It Works! + * Connection #0 to host localhost left intact + ``` + +## What's Next? + +Congratulations, you've read this far! At this point, we've basically learned how to use Volo, and we're ready to use Volo to kick off our Rust journey + +Next, you may need to select the right components, put them together, and interface with your system. + +The related ecosystem maintained by Volo will be located in: https://github.com/volo-rs, we are working to build our ecosystem, and welcome everyone to join us ~ + +If there is a dire lack of components, you are welcomed to raise an issue in: https://github.com/cloudwego/volo, we will support it as soon as possible. + +In the meantime, welcome to join our Lark user group and share your experience with us about Volo. + +
+Volo_feishu +
+

+ +Looking forward to your unique work created with Volo. diff --git a/content/en/docs/volo/volo-http/overview/_index.md b/content/en/docs/volo/volo-http/overview/_index.md new file mode 100644 index 0000000000..92b0a5b2d1 --- /dev/null +++ b/content/en/docs/volo/volo-http/overview/_index.md @@ -0,0 +1,59 @@ +--- +title: "Overview" +linkTitle: "Overview" +weight: 1 +keywords: ["HTTP", "volo", "Features", "Performance"] +description: "Volo-HTTP Features、Performance。" +--- + +## CloudWeGo-Volo + +Volo-HTTP is a Rust language HTTP microservice framework , using [Motore](https://github.com/cloudwego/motore) implemented based on AFIT and RPITIT as the middleware abstraction layer , and combined with the internal needs of ByteDance . +It is characterized by high ease of use, high performance and strong scalability. Using Volo-HTTP, you can quickly develop a microservice based on the HTTP protocol. + +### Features + +#### High Performance + +Rust is known for its high performance and safety. We always **take high performance as our goal** in the design and implementation process, +reduce the overhead of each place as much as possible, and improve the performance of each implementation. + +#### Easy to Use + + Rust is known for being hard to learn and hard to use, + and we want to make it as easy as possible for users to use the Volo framework and write microservices in the Rust language, + providing the most ergonomic and intuitive coding experience possible. + Therefore, we make ease of use one of our most important goals. + + For example, we provide the volo command line tool for bootstrapping HTTP projects. + + You can add any type that implements the Extractor trait to the request parameters in the handler to use it as needed, + and you can also return any type that implements `IntoResponse` as a handler. + + Volo-HTTP already implements these traits for most of the built-in types, so you can just focus on writing the business logic inside the handler. + + We also provide a middleware mechanism based on the layer model, so you can easily use the middleware by calling the `layer` method of `route`. + +#### Strong Scalability + + Volo-HTTP uses `Motore` as its middleware abstraction, which is powered by AFIT and RPITIT. + + Through RPITIT, we can avoid many unnecessary Box memory allocations, improve ease of use, + and provide users with a more friendly programming interface and a more ergonomic programming paradigm. + + Benefiting from Rust's powerful expression and abstraction capabilities, through the flexible middleware Service abstraction, + developers can **process HTTP requests and responses** in a very unified form. + + For example, service governance functions such as service discovery and load balancing can be implemented + in the form of services without the need to implement Trait independently. + + We have also created an organization [volo-rs](https://github.com/volo-rs), any contributions are welcome. + +## Related Projects + +- [`motore`](https://github.com/cloudwego/motore) + +## Related Articles + +- [China's First Rust-based RPC Framework - Volo is Officially Open Source!](https://www.cloudwego.io/blog/2022/08/30/chinas-first-rust-based-rpc-framework-volo-is-officially-open-source/) +- [Introducing Monoio: a high-performance Rust Runtime based on io-uring](https://www.cloudwego.io/blog/2023/04/17/introducing-monoio-a-high-performance-rust-runtime-based-on-io-uring/) diff --git a/content/en/docs/volo/volo-http/tutorials/_index.md b/content/en/docs/volo/volo-http/tutorials/_index.md new file mode 100644 index 0000000000..d9e2d1fef3 --- /dev/null +++ b/content/en/docs/volo/volo-http/tutorials/_index.md @@ -0,0 +1,12 @@ +--- +title: "Tutorials" +linkTitle: "Tutorials" +weight: 3 +keywords: + [ + "Tutorials", + "Examples", + "Basic features", + ] +description: "Volo-HTTP tutorials including examples and basic features" +--- diff --git a/content/en/docs/volo/volo-http/tutorials/middleware.md b/content/en/docs/volo/volo-http/tutorials/middleware.md new file mode 100644 index 0000000000..59b95e9aac --- /dev/null +++ b/content/en/docs/volo/volo-http/tutorials/middleware.md @@ -0,0 +1,195 @@ +--- +title: "Middleware" +date: 2024-09-02 +weight: 5 +keywords: + [ + "Middleware", + ] +description: "Volo-HTTP middleware" +--- + +## Using Middleware + +In Volo-HTTP, middleware is usually implemented as a `Layer`, but there are some built-in middleware in Volo-HTTP, too. + +For example, we use the built-in `TimeoutLayer`. + +```rust +use std::net::SocketAddr; +use std::time::Duration; +use volo::net::Address; +use volo_http::{ + context::ServerContext, + http::StatusCode, + server::{layer::TimeoutLayer, route::get}, + Router, Server, +}; + +fn index_handler() -> &'static str { + "Hello, World!" +} + +fn timeout_handler(_: &ServerContext) -> (StatusCode, &'static str) { + (StatusCode::INTERNAL_SERVER_ERROR, "Timeout!\n") +} + +#[volo::main] +async fn main() { + let app = Router::new() + .route("/", get(index_handler)) + .layer(TimeoutLayer::new(Duration::from_secs(1), timeout_handler)); + + let addr = "[::]:8080".parse::().unwrap(); + let addr = Address::from(addr); + + Server::new(app).run(addr).await.unwrap(); +} +``` + +## Writing a Middleware + +In Volo-HTTP, there are also functions provided that facilitate middleware implementation, such as `from_fn` and `map_response`. + +Both can receive a function to be used as middleware, but the difference is that the +- `from_fn` receives a Request and returns a Response, in which it can either call an inner service or return a Response directly. +- `map_response` acts on Response, receives Response and returns the processed Response. + +### `from_fn` + +Functions used by `from_fn` can extract parameters of a specific type via extractor. + +But in the end, the `cx`, `req` and `next` parameters must be appended and the inner service is invoked via `next.run(cx, req).await`. + +Here we take `from_fn` as an example of a middleware implementation for logging the time spent on a single request: + +```rust +use std::net::SocketAddr; +use std::time::{Duration, Instant}; + +use volo_http::{ + context::ServerContext, + http::Uri, + request::ServerRequest, + response::ServerResponse, + server::{ + middleware::{self, Next}, + route::get, + IntoResponse, + }, + Address, Router, Server, +}; + +fn index_handler() -> &'static str { + "Hello, World!" +} + +pub async fn trace_request( + peer: Address, + uri: Uri, + cx: &mut ServerContext, + req: ServerRequest, + next: Next, +) -> ServerResponse { + let start = Instant::now(); + let ret = next.run(cx, req).await.into_response(); + let status = ret.status(); + let cost = Instant::now().duration_since(start); + tracing::info!("`{peer}` request `{uri}`, response {status}, cost {cost:?}"); + ret +} + +#[volo::main] +async fn main() { + let app = Router::new() + .route("/", get(index_handler)) + .layer(middleware::from_fn(trace_request)); + + let addr = "[::]:8080".parse::().unwrap(); + let addr = Address::from(addr); + + Server::new(app).run(addr).await.unwrap(); +} + +``` + +Or you can return early for a specific request, e.g. if we implement a ~~deficient~~ middleware that has a 50% chance of rejecting the current request. + +```rust +// You should add `rand = "0.8"` in `Cargo.toml` for using `rand::random` + +pub async fn random_reject( + cx: &mut ServerContext, + req: ServerRequest, + next: Next, +) -> ServerResponse { + if rand::random() { + return StatusCode::FORBIDDEN.into_response(); + } + next.run(cx, req).await.into_response() +} +``` + +This form can be used in scenarios such as authentication, if the request is not allowed to be accessed by the service, +you can directly return a specific Response, without the need to execute the subsequent Service. + +### `map_response` + +`map_response` works on Response, receives Response and returns the processed Response. + +This way you can do some general logic with the Response, such as appending cross-domain headers or setting cookies. + +Since we implement the `IntoResponse` trait for the following types. +- `((HeaderName, HeaderValue), Response)` +- `([(HeaderName, HeaderValue); N], Response`) + +We can be easily accomplished in `map_response` to appending headers to a Response, etc. with the following code: + +```rust +use std::net::SocketAddr; +use volo::net::Address; +use volo_http::{ + response::ServerResponse, + server::{middleware, IntoResponse, Router}, + Server, +}; + +pub async fn append_header(resp: ServerResponse) -> impl IntoResponse { + (("Header", "Value"), resp) +} + +pub async fn append_headers(resp: ServerResponse) -> impl IntoResponse { + ( + [ + ("Header1", "Value1"), + ("Header2", "Value2"), + ("Header3", "Value3"), + ], + resp, + ) +} + +#[volo::main] +async fn main() { + let app = Router::new() + /* ...... */ + .layer(middleware::map_response(append_header)) + .layer(middleware::map_response(append_headers)); + + let addr = "[::]:8080".parse::().unwrap(); + let addr = Address::from(addr); + + Server::new(app).run(addr).await.unwrap(); +} +``` + +We can notice that the return value type of `append_header(s)` is `impl IntoResponse`. +but actually, the return value types for each of these two functions are: +- `((&'static str, &'static str), ServerResponse)` +- `([(&'static str, &'static str); 3], ServerResponse)` + +But these two types are more troublesome to write, so you can directly use the `impl IntoResponse` way to achieve, +as long as the return value type to ensure that the implementation of the `IntoResponse` that can be + +Note that even if the return value type is `impl IntoResponse`, you still need to make sure that the return value in the function is of the same type. +Because using this approach also requires a type-specific return value, we just leave it up to the compiler to derive it. diff --git a/content/en/docs/volo/volo-http/tutorials/request.md b/content/en/docs/volo/volo-http/tutorials/request.md new file mode 100644 index 0000000000..dcc3585e8f --- /dev/null +++ b/content/en/docs/volo/volo-http/tutorials/request.md @@ -0,0 +1,165 @@ +--- +title: "Request" +date: 2024-09-02 +weight: 3 +keywords: + [ + "Route", + "Request", + "Parameter", + "WebSocket" + ] +description: "Volo-HTTP Request parameters extraction" +--- + +## Extraction of Routing Parameters + +Volo-HTTP's handler can take multiple extractors as arguments, e.g., + +```rust +use volo_http::Address; + +async fn who_am_i(peer: Address) -> String { + format!("You are `{peer}`") +} + +async fn post_something(data: String) -> string { + format!("data: `{data}`") +} +``` + +In addition to this, handlers can take deserializable objects such as `json`, `form`, `query`, etc. as arguments. + +The Rust pattern matching feature is used here to receive parameters: `json`, `form`, `query`, and so on. + +```rust +use volo_http::{ + http::StatusCode, + server::{ + extract::{Form, Query}, + route::{get, Router}, + }, +}; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +struct Login { + username: String, + password: String, +} + +fn process_login(info: Login) -> Result { + if info.username == "admin" && info.password == "password" { + Ok("Login Success!".to_string()) + } else { + Err(StatusCode::IM_A_TEAPOT) + } +} + +// test with: +// curl "http://localhost:8080/user/login?username=admin&password=admin" +// curl "http://localhost:8080/user/login?username=admin&password=password" +async fn get_with_query(Query(info): Query) -> Result { + process_login(info) +} + +// test with: +// curl http://localhost:8080/user/login -X POST -d 'username=admin&password=admin' +// curl http://localhost:8080/user/login -X POST -d 'username=admin&password=password' +async fn post_with_form(Form(info): Form) -> Result { + process_login(info) +} + +pub fn user_login_router() -> Router { + Router::new().route("/user/login", get(get_with_query).post(post_with_form)) +} +``` + +## What is `extractor`? + +The types that can be used as handler arguments implement `FromContext` or `FromRequest`, which are often referred to as `extractor`. + +Where `FromContext` doesn't consume the body of the request, i.e. the data passed in by methods such as POST, + +Whereas `FromRequest` consumes the body of the request, so the handler can only have at most one parameter of a type that implements `FromRequest`. + +## Types that Implement `extractor` By Default + +**`FromContext`** +- `Address` +- `Uri` +- `Method` +- `Option` +- `Result` +- `Query` +- `WebSocketUpgrade` + +**`FromRequest`** +- `Option` +- `Result` +- `ServerRequest` +- `Vec` +- `Bytes` +- `String` +- `FastStr` +- `MaybeInvalid` +- `Form` + +## Implementing Extractors for Your Own Types + +We can receive our own types directly as arguments to a handler, which requires implementing `FromContext` or `FromRequest` for our own types. + +For example, we might get the LogID from the header of a request, in which case we can define a type and implement `FromContext` for it: + +```rust +use std::convert::Infallible; + +use volo_http::{ + context::ServerContext, + http::request::Parts, + server::extract::FromContext, +}; + +const LOGID_KEY: &str = "x-logid"; + +pub enum LogID { + ID(String), + None, +} + +impl FromContext for LogID { + type Rejection = Infallible; + + async fn from_context( + _: &mut ServerContext, + parts: &mut Parts, + ) -> Result { + let id = match parts.headers.get(LOGID_KEY) { + Some(log_id) => LogID::ID(log_id.to_str().unwrap().to_owned()), + None => LogID::None, + }; + + Ok(id) + } +} +``` + +Once you have implemented the LogID type, you can use it as an extractor and receive it directly using a handler: + +```rust +async fn show_logid(id: LogID) -> String { + match id { + LogID::ID(s) => format!("{s}"), + LogID::None => "LogID not found".to_owned(), + } +} + +pub fn logid_router() -> Router { + Router::new().route("/extract/logid", get(print_logid)) +} +``` + +One thing to note is that when implementing a handler, the types `Uri`, `Method`, `Address`, etc. are extracted via `FromContext`. +Types such as Body are not consumed, and can be listed in any of the handler's parameters. +However, since Body can only be consumed once, types such as `String`, `Bytes`, `From`, `Json`, etc. extracted by `FromRequest` +**So it can only be placed in the last parameter of the handler**. diff --git a/content/en/docs/volo/volo-http/tutorials/response.md b/content/en/docs/volo/volo-http/tutorials/response.md new file mode 100644 index 0000000000..4d404e7b31 --- /dev/null +++ b/content/en/docs/volo/volo-http/tutorials/response.md @@ -0,0 +1,175 @@ +--- +title: "Response" +date: 2024-09-02 +weight: 4 +keywords: + [ + "Response", + ] +description: "Volo-HTTP Response" +--- + +## Routing Responses + +The `Volo-HTTP` handler can also return any `impl IntoResponse` type, for example: + +```rust +use volo_http::{ + http::StatusCode +}; + +// 默认返回 `StatusCode::OK` +async fn ping() {} + +// ref: [RFC2324](https://datatracker.ietf.org/doc/html/rfc2324) +async fn who_are_you() -> StatusCode { + StatusCode::IM_A_TEAPOT +} +``` + +## Handler Response Tips + +handler In addition to customizable parameters, the return value type can also be customized, such as: + +```rust +async fn ping() {} +async fn hello_world() -> &'static str { "Hello, World" } +async fn teapot() -> StatusCode { StatusCode::IM_A_TEAPOT } +``` + +All three of these functions are legitimate handlers, since all of these return value types implement the `IntoResponse` trait. + +In the framework, types such as `Form`, `Json`, etc. also implement `IntoResponse` by default and can add a Content-Type to the response. + +### Use Json for Response + +```rust +use volo_http::{ + json::Json, + server::{ + route::{get, post, Router}, + IntoResponse, + }, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +struct Person { + name: String, + age: u8, + phones: Vec, +} + +// test with: +// curl http://localhost:8080/json/get +async fn json_get() -> Json { + Json(Person { + name: "Foo".to_string(), + age: 25, + phones: vec!["Bar".to_string(), "114514".to_string()], + }) +} + +// test with: +// curl http://localhost:8080/json/post \ +// -X POST \ +// -H "Content-Type: application/json" \ +// -d '{"name":"Foo", "age": 25, "phones":["Bar", "114514"]}' +async fn json_post(Json(request): Json) -> String { + let first_phone = request + .phones + .first() + .map(|p| p.as_str()) + .unwrap_or("no number"); + format!( + "{} is {} years old, {}'s first phone number is `{}`\n", + request.name, request.age, request.name, first_phone + ) +} + +pub fn json_test_router() -> Router { + Router::new() + .route("/json/get", get(json_get)) + .route("/json/post", post(json_post)) +} +``` + +### Compound Response Types + +The `IntoResponse` trait not only supports String, Json, etc., but there are other ways to use it, such as: + +```rust +async fn return_result() -> Result<&'static str, StatusCode> { + if rand::random() { + Ok("It Works!\n") + } else { + Err(StatusCode::IM_A_TEAPOT) + } +} + +async fn return_with_status_code() -> (StatusCode, String) { + (StatusCode::NOT_FOUND, "Not Found!\n".to_owned()) +} + +pub fn response_router() -> Router { + Router::new() + .route("/response/result", get(return_result)) + .route("/response/status", get(return_with_status_code)) +} +``` + +Using `Result<&'static str, StatusCode>` as the return value type. +You can use the contents of str as the `Response` when returning Ok, `StatusCode` when returning Err, and return the status code of the `Response`. +When returning Err, use `StatusCode` as the status code for `Response` and return an empty implementation. + +With `(StatusCode, String)` you can use the `String` as the Body of the `Response` and set the status code of the `Response` to the value of `StatusCode`. + +## Implementing IntoResponse for Your Own Types + +For custom types, you can implement `IntoResponse` as the return value of the handler, here is an example. + +In business logic, we often define our own internal error types or error codes. +Since the framework implements `IntoResponse` for `Result`, we can implement `IntoResponse` for errors. +We can easily write handlers by implementing `IntoResponse` for errors: + +```rust +use volo_http::{ + server::{IntoResponse}, + http::{StatusCode}, + response::ServerResponse, + PathParams, +}; + +#[derive(PartialEq, Eq)] +pub struct ErrorCode(usize); + +impl ErrorCode { + pub const INVALID_USER_ID: Self = Self(1); + pub const INVALID_VIDEO_ID: Self = Self(2); +} + +impl IntoResponse for ErrorCode { + fn into_response(self) -> ServerResponse { + match self { + Self::INVALID_USER_ID => "Invalid User ID".into_response(), + Self::INVALID_VIDEO_ID => StatusCode::BAD_REQUEST.into_response(), + Self(code) => format!("Unknown error code {code}").into_response() + } + } +} + +async fn handler( + PathParams((uid, vid)): PathParams<(String, String)>, +) -> Result +{ + if uid == "admin" { + return Err(ErrorCode::INVALID_USER_ID); + } + if vid == "-1" { + return Err(ErrorCode::INVALID_VIDEO_ID); + } + Ok(format!("uid: {uid}, vid: {vid}")) +} +``` + +The response format of the above code is very irregular, **Functional demonstration use only**, in the actual business, please use a unified response format according to the needs of the diff --git a/content/en/docs/volo/volo-http/tutorials/route.md b/content/en/docs/volo/volo-http/tutorials/route.md new file mode 100644 index 0000000000..54fcd405aa --- /dev/null +++ b/content/en/docs/volo/volo-http/tutorials/route.md @@ -0,0 +1,410 @@ +--- +title: "Route" +date: 2024-09-02 +weight: 2 +keywords: + [ + "Route", + "Route group", + "Static route", + "parameter route", + "route priority", + ] +description: "Volo-HTTP route function" +--- + +## Route Registration + +Route handlers can be created using `get`, `post` and other functions. + +The corresponding functions can be imported in the `volo_http::route` package, e.g, `get` via `volo_http::route::get`. + +First, we need a simple handler: `volo_http::route::get`. + +```rust +async fn foo_handler() -> &'static str { + "Hello, World!\n" +} +``` + +Each route requires a relative path and a method. +Let's take the GET method for routing to the `“/foo”` path as an example, + +which can be created by using the `route` method after creating a `Router` in the following way. + +```rust +use volo_http::server::route::{get, Router}; + +pub fn test_router() -> Router { + Router::new().route("/foo", get(foo_handler)) +} +``` + +You can chain calls to `MethodRouter` from `get` to set handlers for other methods. + +```rust +use volo_http::server::route::{get, Router}; + +pub fn test_router() -> Router { + Router::new() + .route("/foo", get(foo_handoer).post(foo_handler)) +} +``` + +Routing rules can also be created by chaining calls to `Router`: + +```rust +use volo_http::server::route::{get, Router}; + +pub fn test_router() -> Router { + Router::new() + .route("/foo", get(foo_handler)) + .route("/bar", post(bar_handler)) +} +``` + +Once created, the route can be `merged` into the main route: + +```rust +use std::net::SocketAddr; +use std::time::Duration; +use volo::net::Address; +use volo_http::{Router, Server}; + +#[volo::main] +async fn main() { + let app = Router::new() + .merge(example_router()) + .merge(test_router()) + .layer(TimeoutLayer::new(Duration::from_secs(1), timeout_handler)); + + let addr = "[::]:8080".parse::().unwarp(); + let addr = Address::from(addr); + + Server::new(app).run(addr).await.unwarp(); +} +``` + +## Routing methods + +Use a method such as `use volo_http::server::route::get` to import a route as a GET. + +Currently supported routing methods are: + +- `options` +- `get` +- `post` +- `put` +- `delete` +- `head` +- `trace` +- `connect` +- `trace` + +## Route Types + +`Volo-HTTP` supports rich route types for complex route matching, including static routes, dynamic routes (named parameter, wildcard). + +Priority of routes: static routes > named parameter routes > wildcard routes. + +### Static route + +```rust +use volo_http::server::route::{get, Router}; + +async fn index_handler() -> &'static str { + "Hello, World" +} + +async fn index_router() -> Router { + Router::new().route("/", get(index)); +} +``` + +### Named Parameter Route + +Volo-HTTP supports routing with named parameters like `{id}`, and the named parameters only match a single path segment. + +If you set up a `/user/{id}` route, the match will be as follows. + +| **Route** | **Matched** | **Param Value** | +|:--------------------|:-----------:|:---------------:| +| `/user/100` | Yes | 100 | +| `/user/101/profile` | No | - | +| `/user/` | No | - | + +Volo-HTTP also supports multiple named parameters, e.g. `/{platform}/user/{id}`. + +**Example**: + +```rust +use volo::FastStr; +use volo_http::server::{ + param::PathParamsMap, + route::{get, Router}, +}; + +async fn param_handler(map: PathParamsMap) -> FastStr { + // Note: The `unwarp` method is not recommended for production environments. + // It is used here for tutorial purposes only. + map.get("id").unwarp().clone() +} + +async fn param_router() -> Router { + Router::new().route("/user/{id}", get(param_handler)) +} +``` + +### Wildcard Route + +Volo-HTTP supports the use of `*path` as a wildcard parameter for routes, and the wildcard parameter matches everything. + +**Note: The wildcard parameter must be placed at the end of the route. **Note: Wildcards should be placed at the end of the route. + +If we set up a `/src/{*path}` route, it will match as follows: + +| **Route** | **Matched** | **Param Value** | +|:--------------------------|:-----------:|:------------------:| +| `/src/` | Yes | - | +| `/src/somefile.rs` | No | somefile.rs | +| `/src/subdir/somefile.rs` | No | subdir/somefile.rs | + +**Example**: + +```rust +use volo::FastStr; +use volo_http::server::{ + param::PathParamsMap, + route::{get, Router}, +}; + +async fn param_handler(map: PathParamsMap) -> FastStr { + // Note: The `unwarp` method is not recommended for production environments. + // It is used here for tutorial purposes only. + map.get("path").unwarp().clone() +} + +async fn param_router() -> Router { + Router::new().route("/src/{*path}", get(param_handler)) +} +``` + +### Routing parameter values extraction + +- `PathParamsMap` + + Provide a routing parameter `AHashmap` + + ```rust + use volo_http::param::PathParamsMap; + + async fn param_handler(map: PathParamsMap) -> FastStr { + // Note: The `unwarp` method is not recommended for production environments. + // It is used here for tutorial purposes only. + map.get("id").unwarp().clone() + } + ``` + +- `PathParams` + + User-definable routing parameter values using pattern matching features + + ```rust + use volo_http::param::PathParams; + + async fn param_handler(PathParams(id): PathParams) -> String { + id + } + ``` + +## Route Group + +Volo-HTTP provides the ability to route `nests`, which are used to support **route grouping**. + +```rust +impl Router { + /*...*/ + pub fn nest(self, uri: U, router: Router) -> Self + where + U: AsRef, + { + self.nest_route(uri.as_ref().to_owned(), Route::new(router)) + } +} +``` + +We can use the given url prefix path as the route prefix for a given route, and then merge it into the current route + +```rust +use volo_http::server::{ + param::PathParams, + route::{get, Router}, +}; + +async fn hello_world() -> &'static str { + "Hello, World" +} + +async fn get_tid(PathParams(tid): PathParams) -> String { + tid +} + +async fn get_uid_and_tid(PathParams((uid, tid)): PathParams<(String, String)>) -> String { + format!("uid: {uid}, tid: {tid}") +} + +async fn user_router() -> Router { + Router::new() + .route("/name", get(hello_world)) + .route("/post/{tid}", get(get_uid_and_tid)); +} + +async fn post_router() -> Router { + Router::new() + .route("/name", get(hello_world)) + .route("/{tid}", get(get_tid)); +} + +async fn router() -> Router { + Router::new() + .nest("/user/{uid}", user_router()) + .nest("/post", post_router()) + // The routing paths here are as follows. + // /user/{uid}/name + // /user/{uid}/post/{tid} + // /post/name + // /post/{tid} +} +``` + +## Fallback + +Volo-HTTP provides a fallback function to handle request url or method mismatches. + +### url + +> Note: Only one **router fallback** can be set globally, otherwise it will generate a **panic** when the `merge` method is called. + +**Example**: + +```rust +async fn index_handler() -> &'static str { + "Hello, World" +} + +async fn fallback_handler() -> (http::StatusCode, &'static str) { + (http::StatusCode::NOT_FOUND, "404 Not Found") +} + +async fn router() -> Router { + Router::new() + .route("/", get(index_handler)) + .fallback(fallback_handler) +} +``` + +### method + +```rust +async fn index_handler() -> &'static str { + "Hello, World" +} + +async fn fallback_handler() -> (http::StatusCode, &'static str) { + (http::StatusCode::METHOD_NOT_ALLOWED, "method not matched") + +} + +async fn router() -> Router { + Router::new() + .route("/", get(index_handler).fallback(fallback_handler)) +} +``` + +## Using `Service` as a Route + +Routes can be implemented using a traditional `Service`, and the handler approach mentioned above will also work as a `Service`, +but you need to create a route for the GET method via `get_service`: + +```rust +use std::convert::Infallible; + +use volo_http::{ + context::ServerContext, + request::ServerRequest, + response::ServerResponse, + server::{ + route::{get_service, Router}, + IntoResponse, + }, +}; +use motore::Service; + +#[derive(Clone)] +pub struct JsonGetService; + +impl Service for JsonGetService { + type Response = ServerResponse; + type Error = Infallible; + + async fn call( + &self, + _cx: &mut ServerContext, + _req: ServerRequest, + ) -> Result { + Ok(json_get().await.into_response()) + } +} + +pub fn json_test_router() -> Router { + Router::new() + /* ...... */ + .route( + "/json/get_srv", + get_service(JsonGetService), + ) +} +``` + +For this simpler `Service`, it is also possible to implement it directly using a function via `service_fn` without defining a structure: + +```rust +use std::convert::Infallible; + +use volo_http::{ + context::ServerContext, + json::Json, + request::ServerRequest, + response::ServerResponse, + server::{ + route::{post_service, Router}, + IntoResponse, + }, +}; +use motore::{service::service_fn, Service}; + +async fn json_post_srv( + cx: &mut ServerContext, + req: ServerRequest, +) -> Result { + let (parts, body) = req.into_parts(); + let data = match Json::::from_request(cx, parts, body).await { + Ok(data) => data, + Err(_) => { + return Ok("Invalid data!".into_response()); + } + }; + Ok(json_post(Some(data)).await.into_response()) +} + +pub fn json_test_router() -> Router { + Router::new() + /* ...... */ + .route( + "/json/post_srv", + post_service(service_fn(json_post_srv)), + ) +} +``` + +The final run is expected to be consistent with `json_get` and `json_post`. \ No newline at end of file diff --git a/content/en/docs/volo/volo-http/tutorials/static-fs.md b/content/en/docs/volo/volo-http/tutorials/static-fs.md new file mode 100644 index 0000000000..2e948b70ca --- /dev/null +++ b/content/en/docs/volo/volo-http/tutorials/static-fs.md @@ -0,0 +1,29 @@ +--- +title: Static-FS +date: 2024-09-02 +weight: 5 +keywords: + [ + "File Transfer" + ] +description: “Volo-HTTP Static-FS” +--- + +Volo-HTTP provides the `ServeDir` function for registering static files. + +```rust +use volo_http::server::{ + route::{get, Router}, + utils::ServeDir, +}; + +async fn file_router() -> Router { + Router::new() + .route("/", get(|| async {"Hello, World!"})) + .nest_service("/static/", ServeDir::new(".")) +} +``` + +where the `ServeDir::new` parameter specifies the path to be transmitted + +`”.” ` means transferring all files in the current command execution path. \ No newline at end of file diff --git a/content/en/docs/volo/volo-http/tutorials/websocket.md b/content/en/docs/volo/volo-http/tutorials/websocket.md new file mode 100644 index 0000000000..2d5dbf343d --- /dev/null +++ b/content/en/docs/volo/volo-http/tutorials/websocket.md @@ -0,0 +1,93 @@ +--- +title: "WebSocket" +date: 2024-09-02 +weight: 6 +keywords: + [ + "WebSocket", + ] +description: "Volo-HTTP WebSocket" +--- + +Volo-HTTP supports the WebSocket protocol + +## Write handler + +Since Volo-HTTP's built-in `WebSocket` type implements `FromContext`, we can add a WebSocket extractor directly to the handler. + +The following is a simple example of a WebSocket handler that displays the received text message: + +```rust +use volo_http::{ + response::ServerResponse, + server::{ + route::get, + utils::{Message, WebSocket, WebSocketUpgrade}, + }, + Router, +}; + +async fn handle_socket(mut socket: WebSocket) { + while let Some(Ok(msg)) = socket.next().await { + match msg { + Message::Text(_) => { + socket.send(msg).await.unwrap(); + } + _ => {} + } + } +} + +async fn ws_handler(ws: WebSocketUpgrade) -> ServerResponse { + ws.on_upgrade(handle_socket) +} + +async fn ws_router() -> Router { + Router::new().route("/ws", get(ws_handler)); +} +``` + +## WebSocket Settings + +Options for WebSocket such as transport configuration and protocol support can be configured via the `Config` structure. + +### Transport settings + +See `tokio_tungstenite::tungstenite::protocol::WebSocketConfig` for details. + +```rust +use tokio_tungstenite::tungstenite::protocol::WebSocketConfig as WebSocketTransConfig; +use volo_http::server::utils::WebSocketConfig; + +let config = WebSocketConfig::new().set_transport(WebSocketTransConfig { + write_buffer_size: 128 * 1024, + ..<_>::default() +}); +``` + +### Set supported protocols + +```rust +use volo_http::server::utils::WebSocketConfig; + +let config = WebSocketConfig::new().set_protocols(["graphql-ws", "graphql-transport-ws"]); +``` + +## Error Handling + +When processing a WebSocket connection, various errors may be encountered, such as a connection upgrade failure. +These errors can be handled by **providing custom error handling callbacks**. + +```rust +use std::collections::HashMap; + +use volo_http::{ + response::ServerResponse, + server::utils::{WebSocket, WebSocketConfig, WebSocketUpgrade}, +}; + +async fn ws_handler(ws: WebSocketUpgrade) -> ServerResponse { + ws.on_failed_upgrade(|error| unimplemented!()) + .on_upgrade(|socket| async {}) +} +``` \ No newline at end of file diff --git a/content/en/docs/volo/volo-thrift/_index.md b/content/en/docs/volo/volo-thrift/_index.md index cfe560baf2..b8521f4ce2 100644 --- a/content/en/docs/volo/volo-thrift/_index.md +++ b/content/en/docs/volo/volo-thrift/_index.md @@ -1,7 +1,7 @@ --- title: "Volo-Thrift" linkTitle: "Volo-Thrift" -weight: 2 +weight: 4 keywords: ["Volo", "Thrift", "Getting Started", "Guidelines"] description: "CLI tool installation, quick start and basic tutorials for Volo-Thrift." --- diff --git a/content/en/docs/volo/volo-thrift/getting-started/_index.md b/content/en/docs/volo/volo-thrift/getting-started/_index.md index 2dd57e6a17..d94f6808d9 100644 --- a/content/en/docs/volo/volo-thrift/getting-started/_index.md +++ b/content/en/docs/volo/volo-thrift/getting-started/_index.md @@ -6,47 +6,13 @@ keywords: ["Volo", "Thrift", "Tutorial", "Install"] description: "This document covers the preparation of the development environment, quick start and basic tutorials of Volo-Thrift." --- -## Part 1. Install the CLI Tool - -Volo provides CLI tools of the same name for initializing projects, managing IDLs, and more. - -```bash -cargo install volo-cli -``` - -> Make sure that the Rust version is >= 1.75.0 - -Then run: - -```bash -volo help -``` - -You should see something like the following: - -```bash -USAGE: - volo [OPTIONS] - -OPTIONS: - -h, --help Print help information - -n, --entry-name The entry name, defaults to 'default'. [default: default] - -v, --verbose Turn on the verbose mode. - -V, --version Print version information - -SUBCOMMANDS: - help Print this message or the help of the given subcommand(s) - idl manage your idl - init init your project -``` - -## Part 2. Create a Thrift Server +## Part 1. Create a Thrift Server Volo-Thrift is an RPC framework so that the bottom layer requires two major functions: Serialization and Transport. IDL is short for `Interface Definition Language`. -### 2.1 Why IDL +### 1.1 Why IDL If we want to do RPC, we need to know what interface is for the server, what parameters to pass, and what the return value is, just like two people talking to each other, we need to make sure we are speaking the same language and doing the same thing. @@ -55,7 +21,7 @@ At this time, we need to use IDL to specify the protocol for both sides, just li Thrift IDL is a full-stack RPC solution for cross-language. You can see the syntax of Thrift IDL in [thrift-missing-guide](https://diwakergupta.github.io/thrift-missing-guide/) or [Thrift interface description language](http://thrift.apache.org/docs/idl). -### 2.2 Write IDL +### 1.2 Write IDL To create a Thrift project, we need to write a Thrift IDL first. @@ -170,7 +136,7 @@ cargo run --bin server At this point, we have our server running! -## Part 3. Create a Client +## Part 2. Create a Client In the previous section, we wrote a server, now let's write a client and call the server. @@ -253,7 +219,7 @@ Finally, we go back to the current directory and execute the following command, cargo run --bin client ``` -## Part 4. Add a Middleware +## Part 3. Add a Middleware Next, let's look at how to add middleware to Volo. @@ -322,7 +288,7 @@ volo_gen::volo::example::ItemServiceServer::new(S) At this point, at the INFO log level, it prints out how long the request took; At the DEBUG logging level, it also types the details of the request and response. -## Part 5. What's Next? +## Part 4. What's Next? Congratulations, you've read this far! At this point, we've basically learned how to use Volo, and we're ready to use Volo to kick off our Rust journey @@ -335,7 +301,7 @@ If there is a dire lack of components, you are welcomed to raise an issue in: ht In the meantime, welcome to join our Lark user group and share your experience with us about Volo.
-Volo_feishu +Volo_feishu


diff --git a/content/zh/docs/volo/cli/_index.md b/content/zh/docs/volo/cli/_index.md new file mode 100644 index 0000000000..48008a6871 --- /dev/null +++ b/content/zh/docs/volo/cli/_index.md @@ -0,0 +1,8 @@ +--- +title: "命令行工具" +linkTitle: "命令行工具" +weight: 2 +keywords: ["Rust", "Volo", "cli"] +description: "Volo-cli 使用说明" +--- + diff --git a/content/zh/docs/volo/cli/getting-started/_index.md b/content/zh/docs/volo/cli/getting-started/_index.md new file mode 100644 index 0000000000..51e6bef01e --- /dev/null +++ b/content/zh/docs/volo/cli/getting-started/_index.md @@ -0,0 +1,125 @@ +--- +title: "快速开始" +linkTitle: "快速开始" +weight: 2 +keywords: ["Volo", "cli", "快速开始", "安装"] +description: "Volo-Cli 开发环境准备、快速上手与基础教程。" +--- + +Volo 框架提供了同名的命令行工具,提供以下功能: + +1. 服务端骨架生成 + + 支持通过 Thrift, Protobuf 的 IDL 生成 HTTP 或 RPC 服务端项目的骨架 + +2. 桩代码管理 + +3. 旧版本迁移 + +## Part 1. 安装命令行工具 + +```bash +cargo install volo-cli +``` + +需要 rust 版本 >= 1.80.0 + +随后,我们输入: + +```bash +volo help +``` + +就能看到类似以下输出啦: + +```plain +Usage: volo [OPTIONS] + +Commands: + init init your thrift or grpc project + http manage your http project + repo manage your repo + idl manage your idl + migrate migrate your config from old version + help Print this message or the help of the given subcommand(s) + +Options: + -v, --verbose... Turn on the verbose mode. + -n, --entry-name The entry name, defaults to 'default'. [default: default] + -h, --help Print help + -V, --version Print version +``` + +## Part 2. 生成 rpc 代码 + +为了创建一个 RPC 项目, 我们需要先编写一个 IDL, 这里以 Thrift 为例 + +在项目目录下新建一个 Thrift IDL + +`vim idl/rpc_example.thrift` + +```thrift +namespace rs volo.rpc.example + +struct Item { + 1: required i64 id, + 2: required string title, + 3: required string content, + + 10: optional map extra, +} + +struct GetItemRequest { + 1: required i64 id, +} + +struct GetItemResponse { + 1: required Item item, +} + +service ItemService { + GetItemResponse GetItem (1: GetItemRequest req), +} +``` + +执行以下命令: + +`volo init volo-rpc-example idl/rpc_example.thrift` + +这时候,我们整个目录的结构如下: + +```plain +. +├── Cargo.toml +├── idl +│ └── rpc_example.thrift +├── rust-toolchain.toml +├── src +│ ├── bin +│ │ └── server.rs +│ └── lib.rs +└── volo-gen +├── Cargo.toml +├── build.rs +├── src +│ └── lib.rs +└── volo.yml +``` + +## Part 2. 生成 http 代码 + +执行以下命令: + +`volo http init volo-http-example` + +这时候, 我们整个目录的结构如下: + +```bash +$ tree +. +├── Cargo.toml +└── src + ├── bin + │   └── server.rs + └── lib.rs +``` diff --git a/content/zh/docs/volo/cli/migrate/_index.md b/content/zh/docs/volo/cli/migrate/_index.md new file mode 100644 index 0000000000..634c46995c --- /dev/null +++ b/content/zh/docs/volo/cli/migrate/_index.md @@ -0,0 +1,23 @@ +--- +title: "旧版本迁移" +linkTitle: "旧版本迁移" +weight: 4 +keywords: ["Volo", "cli", "版本迁移"] +description: "Volo-Cli 旧版本迁移" +--- + +从旧版本 `volo.yml` 迁移到现版本 `volo.yml` + +```bash +Usage: volo migrate [OPTIONS] + +Options: + -v, --verbose... Turn on the verbose mode. + -h, --help Print help + -V, --version Print version +``` + +## 如何迁移? + +1. 切换到项目根目录(`volo.yml`所在目录) +2. 执行命令 `volo migrate` \ No newline at end of file diff --git a/content/zh/docs/volo/faq/_index.md b/content/zh/docs/volo/faq/_index.md index 102bf77b12..d0ad7033fc 100644 --- a/content/zh/docs/volo/faq/_index.md +++ b/content/zh/docs/volo/faq/_index.md @@ -1,7 +1,7 @@ --- title: "FAQ" linkTitle: "FAQ" -weight: 8 +weight: 9 keywords: ["Volo", "FAQ", "volo-cli", "poll_ready"] description: 常见问题回答汇总。 --- diff --git a/content/zh/docs/volo/guide/_index.md b/content/zh/docs/volo/guide/_index.md index 9f83d97078..c6e78fa391 100644 --- a/content/zh/docs/volo/guide/_index.md +++ b/content/zh/docs/volo/guide/_index.md @@ -1,6 +1,6 @@ --- title: "Guide" linkTitle: "Guide" -weight: 4 +weight: 6 description: 项目使用指南。 --- diff --git a/content/zh/docs/volo/motore/_index.md b/content/zh/docs/volo/motore/_index.md index 4af75cd534..3999b4fcc4 100644 --- a/content/zh/docs/volo/motore/_index.md +++ b/content/zh/docs/volo/motore/_index.md @@ -1,7 +1,7 @@ --- title: "Motore" linkTitle: "Motore" -weight: 6 +weight: 7 keywords: ["Motore", "AFIT", "RPITIT"] Description: Motore 是一个使用了 AFIT 和 RPITIT 特性的中间件抽象层。 --- diff --git a/content/zh/docs/volo/pilota/_index.md b/content/zh/docs/volo/pilota/_index.md index 9f4bbf2d6c..7fee659e83 100644 --- a/content/zh/docs/volo/pilota/_index.md +++ b/content/zh/docs/volo/pilota/_index.md @@ -1,7 +1,7 @@ --- title: "Pilota" linkTitle: "Pilota" -weight: 7 +weight: 8 keywords: ["Pilota", "Thrift", "Protobuf", "Plugin"] Description: Pilota 是一个使用纯 Rust 编写的 Thrift 和 Protobuf 实现,具有高性能和高扩展性。 --- diff --git a/content/zh/docs/volo/volo-grpc/_index.md b/content/zh/docs/volo/volo-grpc/_index.md index fabc638c0a..b1b65d4f67 100644 --- a/content/zh/docs/volo/volo-grpc/_index.md +++ b/content/zh/docs/volo/volo-grpc/_index.md @@ -1,7 +1,7 @@ --- title: "Volo-gRPC" linkTitle: "Volo-gRPC" -weight: 3 +weight: 5 keywords: ["Volo", "gRPC", "快速上手", "基础教程"] description: "Volo-gRPC 命令行工具安装、快速上手与基础教程。" --- diff --git a/content/zh/docs/volo/volo-grpc/getting-started/_index.md b/content/zh/docs/volo/volo-grpc/getting-started/_index.md index 2de1715700..4619943588 100644 --- a/content/zh/docs/volo/volo-grpc/getting-started/_index.md +++ b/content/zh/docs/volo/volo-grpc/getting-started/_index.md @@ -6,41 +6,7 @@ keywords: ["Volo", "gRPC", "快速开始", "安装"] description: "Volo-gRPC 开发环境准备、快速上手与基础教程。" --- -## Part 1. 安装命令行工具 - -Volo 提供了同名的命令行工具,用来初始化项目、管理 IDL 等。 - -```bash -cargo install volo-cli -``` - -> 需要 rust 版本>= 1.75.0 - -随后,我们输入: - -```bash -volo help -``` - -就能看到类似以下输出啦: - -```bash -USAGE: - volo [OPTIONS] - -OPTIONS: - -h, --help Print help information - -n, --entry-name The entry name, defaults to 'default'. [default: default] - -v, --verbose Turn on the verbose mode. - -V, --version Print version information - -SUBCOMMANDS: - help Print this message or the help of the given subcommand(s) - idl manage your idl - init init your project -``` - -## Part 2. 创建一个 gRPC Server +## Part 1. 创建一个 gRPC Server Volo-gRPC 是一个 RPC 框架,既然是 RPC,底层就需要两大功能: @@ -49,14 +15,14 @@ Volo-gRPC 是一个 RPC 框架,既然是 RPC,底层就需要两大功能: IDL 全称是 `Interface Definition Language`,接口定义语言。 -### 2.1 Why IDL +### 1.1 Why IDL 如果我们要进行 RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的,就好比两个人之间交流,需要保证在说的是同一个语言、同一件事。 这时候,就需要通过 IDL 来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道函数签名一样。 Protobuf IDL 是一套跨语言的全栈式 RPC 解决方案,具体的语法可以看参考 [protocol-buffers/docs/proto3](https://developers.google.com/protocol-buffers/docs/proto3)。 -### 2.2 编写 IDL +### 1.2 编写 IDL 为了创建一个 gRPC 项目,我们需要先编写一个 protobuf IDL。 @@ -172,7 +138,7 @@ cargo run --bin server 至此,我们已经能把我们的 server 跑起来啦! -## Part 3. 编写 Client 端 +## Part 2. 编写 Client 端 上一节中,我们编写完成了 server 端,现在让我们来编写我们的 client 端并调用我们的 server 端。 @@ -258,7 +224,7 @@ cargo run --bin client 大功告成! -## Part 4. 添加一个中间件 +## Part 3. 添加一个中间件 接下来,让我们来看下如何给 Volo 添加一个中间件。 @@ -323,7 +289,7 @@ Server::new() 这时候,在 info 日志级别下,我们会打印出请求的耗时。 -## Part 5. What's Next? +## Part 4. What's Next? 恭喜你,阅读到了这里! 至此,我们已经基本学会了 Volo 的大部分使用了,可以使用 Volo 来开启我们愉快的 Rust 之旅啦~ diff --git a/content/zh/docs/volo/volo-http/_index.md b/content/zh/docs/volo/volo-http/_index.md new file mode 100644 index 0000000000..eaa3d84866 --- /dev/null +++ b/content/zh/docs/volo/volo-http/_index.md @@ -0,0 +1,7 @@ +--- +title: "Volo-HTTP" +linkTitle: "Volo-HTTP" +weight: 3 +keywords: ["Volo", "HTTP", "快速上手", "基础教程"] +description: "Volo-HTTP 命令行工具安装、快速上手与基础教程。" +--- diff --git a/content/zh/docs/volo/volo-http/getting-started/_index.md b/content/zh/docs/volo/volo-http/getting-started/_index.md new file mode 100644 index 0000000000..177dbad827 --- /dev/null +++ b/content/zh/docs/volo/volo-http/getting-started/_index.md @@ -0,0 +1,96 @@ +--- +title: "快速开始" +linkTitle: "快速开始" +weight: 2 +keywords: ["Volo", "Http", "快速开始", "安装"] +description: "Volo-HTTP 快速上手与基础教程。" +--- + +## 准备开发环境 + +1. 如果您之前未搭建过 Rust 开发环境,可以参考[Install Rust](https://www.rust-lang.org/tools/install) +2. 推荐使用最新版本的 Rust,或保证 Rustc >= 1.80.0 +3. 如果您未安装过 `volo-cli`,请参考[快速开始](https://www.cloudwego.io/zh/docs/volo/volo-li/getting-started/) + +## 创建一个 Server + +> 以下示例 `volo-cli` 版本为 **0.10.3**, volo-http 版本为 **0.2.14** + +1. 使用 **volo-cli** 创建 http 项目脚手架 + + ```bash + mkdir -p volo-http-example + cd volo-http-example + volo http init volo-http-example + ``` + + 脚手架创建完成后的目录结构如下: + + ```bash + $ tree + . + ├── Cargo.toml + └── src + ├── bin + │   └── server.rs + └── lib.rs + ``` + + `src/lib.rs` 文件内容如下: + + ```rust + use volo_http::server::route::{get, Router}; + + async fn index_handler() -> &'static str { + "It Works!\n" + } + + pub fn example_router() -> Router { + Router::new().route("/", get(index_handler)) + } + ``` + + 可以看出,当 server 启动后,使用 `GET` 方法请求 `/` 路径期望得到 `It Works!` 的响应 + +2. 运行 `cargo run` 启动服务端,在终端看到 `Listening on [::]:8080` 后, 表示 Server 就成功跑起来了。 + + 我们通过 `curl` 进行验证 + + ```bash + $ curl -v http://localhost:8080/ + * Host localhost:8080 was resolved. + * IPv6: ::1 + * IPv4: 127.0.0.1 + * Trying [::1]:8080... + * Connected to localhost (::1) port 8080 + > GET / HTTP/1.1 + > Host: localhost:8080 + > User-Agent: curl/8.6.0 + > Accept: */* + > + < HTTP/1.1 200 OK + < content-length: 10 + < date: Sun, 01 Sep 2024 16:52:55 GMT + < + It Works! + * Connection #0 to host localhost left intact + ``` + +## What's Next? + +恭喜你,阅读到了这里! 至此,我们已经基本学会了 Volo-HTTP 的使用了,可以使用 Volo-HTTP 来开启我们愉快的 Rust 之旅啦~ + +接下来,你可能需要选择合适的组件,组装在一起,和你的系统进行对接。 + +Volo 维护的相关生态会集中在: 中,我们正在努力打造我们的生态,也非常欢迎大家一起参与~ + +如果有比较急缺的组件,也欢迎在官方仓库: 的 issue 中提出,我们也会优先支持社区最急缺的组件。 + +同时,欢迎加入我们的飞书用户群,交流 Volo 的使用心得~ + +
+Volo_feishu +
+

+ +期待你使用 Volo 创造出属于你的独一无二的作品~ diff --git a/content/zh/docs/volo/volo-http/overview/_index.md b/content/zh/docs/volo/volo-http/overview/_index.md new file mode 100644 index 0000000000..7901c22ed1 --- /dev/null +++ b/content/zh/docs/volo/volo-http/overview/_index.md @@ -0,0 +1,56 @@ +--- +title: "概览" +linkTitle: "概览" +weight: 1 +keywords: ["HTTP", "volo", "架构设计", "框架特点", "框架性能"] +description: "Volo-HTTP 架构设计、框架特点、框架性能。" +--- + +## CloudWeGo-Volo + +Volo-HTTP 是 Rust 语言的 HTTP 微服务框架, 使用了基于 AFIT 和 RPITIT 实现的 [Motore](https://github.com/cloudwego/motore) 作为中间件抽象层,并结合字节跳动内部的需求, +使其具有高易用性、高性能、高扩展性的特点,使用 Volo-HTTP,可以快速开发一个基于 HTTP 协议的微服务。 + +### 框架特点 + +#### 高性能 + + Rust 以高性能和安全著称,我们在设计和实现过程中也时刻**以高性能作为我们的目标**,尽可能降低每一处的开销,提升每一处实现的性能。 + +#### 易用性好 + + ~~Rust 以难学难用而闻名~~,我们希望尽可能降低用户使用 Volo-HTTP 框架以及使用 Rust 语言编写微服务的难度, + 提供最符合人体工程学和直觉的编码体验。因此,我们把易用性作为我们最重要的目标之一。 + + 比如,我们提供了 volo 命令行工具,用于初始化 HTTP 项目脚手架; + + 用户可在 handler 中请求参数任意添加 实现了 `Extractor trait` 的类型来按需使用,同时用户也可以将任意实现了 `IntoResponse` 的类型作为 handler 返回 + + Volo-HTTP 已经为绝大部分内置类型实现了以上 trait,因此用户只需专注 handler 内部的业务逻辑编写即可。 + + 我们还提供了基于 layer 模型下的中间件机制,用户可调用 `route` 的 `layer` 方法轻松使用中间件。 + + +#### 扩展性强 + + Volo-HTTP 使用 `Motore` 作为其中间件抽象层, `Motore` 基于 AFIT 和 RPITIT 设计。 + + 通过 RPITIT,我们可以避免很多不必要的 Box 内存分配,以及提升易用性,给用户提供更友好的编程接口和更符合人体工程学的编程范式。 + + 收益于 Rust 强大的表达和抽象能力,通过灵活的中间件 Service 抽象,开发者可以以非常统一的形式,**对 HTTP 请求和响应做处理**。 + + 比如,服务发现、负载均衡等服务治理功能,都可以以 Service 形式进行实现,而不需要独立实现 Trait。 + + 相关的扩展,我们会放在 [volo-rs](https://github.com/volo-rs) 组织下,也欢迎大家贡献自己的扩展到 `volo-rs`。 + +## 相关项目 + +- [Volo-rs](http://github.com/volo-rs):Volo 生态下的组件集合 +- [Pilota](https://github.com/cloudwego/pilota):使用纯血 rust 实现的 Thrift 和 Protobuf 高性能序列化组件 +- [Motore](https://github.com/cloudwego/motore):基于 AFIT 和 RPITIT 特性实现的中间件抽象层 +- [Metainfo](https://github.com/cloudwego/metainfo):组件间的元数据透传 + +## 相关文章 + +- [选择 Go 还是 Rust?CloudWeGo-Volo 基于 Rust 语言的探索实践](https://www.cloudwego.io/zh/blog/2022/09/06/%E9%80%89%E6%8B%A9-go-%E8%BF%98%E6%98%AF-rustcloudwego-volo-%E5%9F%BA%E4%BA%8E-rust-%E8%AF%AD%E8%A8%80%E7%9A%84%E6%8E%A2%E7%B4%A2%E5%AE%9E%E8%B7%B5/) +- [国内首个基于 Rust 语言的 RPC 框架 — Volo 正式开源!](https://www.cloudwego.io/zh/blog/2022/08/30/%E5%9B%BD%E5%86%85%E9%A6%96%E4%B8%AA%E5%9F%BA%E4%BA%8E-rust-%E8%AF%AD%E8%A8%80%E7%9A%84-rpc-%E6%A1%86%E6%9E%B6-volo-%E6%AD%A3%E5%BC%8F%E5%BC%80%E6%BA%90/) diff --git a/content/zh/docs/volo/volo-http/tutorials/_index.md b/content/zh/docs/volo/volo-http/tutorials/_index.md new file mode 100644 index 0000000000..848dc0b60d --- /dev/null +++ b/content/zh/docs/volo/volo-http/tutorials/_index.md @@ -0,0 +1,12 @@ +--- +title: "开发指南" +linkTitle: "开发指南" +weight: 3 +keywords: + [ + "开发指南", + "代码示例", + "基本特性", + ] +description: "Volo-HTTP 开发指南,包括代码示例、基本特性。" +--- diff --git a/content/zh/docs/volo/volo-http/tutorials/middleware.md b/content/zh/docs/volo/volo-http/tutorials/middleware.md new file mode 100644 index 0000000000..0b6fc9e688 --- /dev/null +++ b/content/zh/docs/volo/volo-http/tutorials/middleware.md @@ -0,0 +1,193 @@ +--- +title: "中间件" +date: 2024-09-02 +weight: 5 +keywords: + [ + "中间件", + ] +description: "Volo-HTTP 中间件" +--- + +## 使用中间件 + +在 Volo-HTTP 中, 中间件一般是作为 `Layer` 实现的, Volo-HTTP 中也有一些内置的中间件, + +比如我们使用内置的 `TimeoutLayer`: + +```rust +use std::net::SocketAddr; +use std::time::Duration; +use volo::net::Address; +use volo_http::{ + context::ServerContext, + http::StatusCode, + server::{layer::TimeoutLayer, route::get}, + Router, Server, +}; + +fn index_handler() -> &'static str { + "Hello, World!" +} + +fn timeout_handler(_: &ServerContext) -> (StatusCode, &'static str) { + (StatusCode::INTERNAL_SERVER_ERROR, "Timeout!\n") +} + +#[volo::main] +async fn main() { + let app = Router::new() + .route("/", get(index_handler)) + .layer(TimeoutLayer::new(Duration::from_secs(1), timeout_handler)); + + let addr = "[::]:8080".parse::().unwrap(); + let addr = Address::from(addr); + + Server::new(app).run(addr).await.unwrap(); +} +``` + +## 编写一个中间件 + +在 Volo-HTTP 中, 也提供了一些便于实现中间件的功能, 如 `from_fn` 和 `map_response`。 + +两者都可以接收一个函数来作为中间件,不过区别是, +- `from_fn` 接收 Request 并返回 Response, 在其函数中可以调用内层服务, 也可以直接返回 Response +- `map_response` 作用于 Response, 接收 Response 并返回处理过的 Response + +### `from_fn` + +`from_fn` 使用的函数可以通过 extractor 提取特定类型的参数, + +但最后一定要附加 `cx`, `req` 和 `next` 这三个参数,并通过 `next.run(cx, req).await` 来调用内层的服务。 + +这里我们以 `from_fn` 为例,实现一个用于记录单个请求耗时的中间件: + +```rust +use std::net::SocketAddr; +use std::time::{Duration, Instant}; + +use volo_http::{ + context::ServerContext, + http::Uri, + request::ServerRequest, + response::ServerResponse, + server::{ + middleware::{self, Next}, + route::get, + IntoResponse, + }, + Address, Router, Server, +}; + +fn index_handler() -> &'static str { + "Hello, World!" +} + +pub async fn trace_request( + peer: Address, + uri: Uri, + cx: &mut ServerContext, + req: ServerRequest, + next: Next, +) -> ServerResponse { + let start = Instant::now(); + let ret = next.run(cx, req).await.into_response(); + let status = ret.status(); + let cost = Instant::now().duration_since(start); + tracing::info!("`{peer}` request `{uri}`, response {status}, cost {cost:?}"); + ret +} + +#[volo::main] +async fn main() { + let app = Router::new() + .route("/", get(index_handler)) + .layer(middleware::from_fn(trace_request)); + + let addr = "[::]:8080".parse::().unwrap(); + let addr = Address::from(addr); + + Server::new(app).run(addr).await.unwrap(); +} + +``` + +或者也可以对特定请求提前返回, 比如我们实现一个~~缺德~~的中间件,有 50% 的几率会拒绝当前的请求: + +```rust +// You should add `rand = "0.8"` in `Cargo.toml` for using `rand::random` + +pub async fn random_reject( + cx: &mut ServerContext, + req: ServerRequest, + next: Next, +) -> ServerResponse { + if rand::random() { + return StatusCode::FORBIDDEN.into_response(); + } + next.run(cx, req).await.into_response() +} +``` + +这种形式可以用于鉴权等场景, 如果请求不允许被访问该服务, 可以直接返回一个特定的 Response, 而无需执行后续的 Service。 + +### `map_response` + +`map_response` 作用于 Response, 接收 Response 并返回处理过的 Response + +这种方式可以对 Response 进行一些通用逻辑的处理,比如追加跨域相关的 headers 或者设置 Cookies 等 + +由于我们为以下类型实现了 `IntoResponse` 这个 trait: +- `((HeaderName, HeaderValue), Response)` +- `([(HeaderName, HeaderValue); N], Response`) + +可以在 `map_response` 中借助以下形式方便地实现为 Response 追加 headers 等功能: + +```rust +use std::net::SocketAddr; +use volo::net::Address; +use volo_http::{ + response::ServerResponse, + server::{middleware, IntoResponse, Router}, + Server, +}; + +pub async fn append_header(resp: ServerResponse) -> impl IntoResponse { + (("Header", "Value"), resp) +} + +pub async fn append_headers(resp: ServerResponse) -> impl IntoResponse { + ( + [ + ("Header1", "Value1"), + ("Header2", "Value2"), + ("Header3", "Value3"), + ], + resp, + ) +} + +#[volo::main] +async fn main() { + let app = Router::new() + /* ...... */ + .layer(middleware::map_response(append_header)) + .layer(middleware::map_response(append_headers)); + + let addr = "[::]:8080".parse::().unwrap(); + let addr = Address::from(addr); + + Server::new(app).run(addr).await.unwrap(); +} +``` + +注意到 `append_header(s)` 的返回值类型是 `impl IntoResponse` +其实这两个函数的返回值类型分别是: +- `((&'static str, &'static str), ServerResponse)` +- `([(&'static str, &'static str); 3], ServerResponse)` + +但是这两个类型写起来比较麻烦, 所以可以直接使用 `impl IntoResponse` 的方式实现, 只要保证返回值类型实现了 `IntoResponse` 即可 + +需要注意的是,即使返回值类型直接写了 `impl IntoResponse`, 但也需要保证函数中的返回值是同一个类型, +因为使用这种方式也需要一个特定类型的返回值, 只是我们将这个工作交给编译器来推导了。 diff --git a/content/zh/docs/volo/volo-http/tutorials/request.md b/content/zh/docs/volo/volo-http/tutorials/request.md new file mode 100644 index 0000000000..b9cfdc6144 --- /dev/null +++ b/content/zh/docs/volo/volo-http/tutorials/request.md @@ -0,0 +1,165 @@ +--- +title: "路由请求" +date: 2024-09-02 +weight: 3 +keywords: + [ + "路由", + "路由请求", + "参数", + "WebSocket" + ] +description: "Volo-HTTP 路由请求参数提取" +--- + +## 路由参数的提取 + +Volo-HTTP 的 handler 可以接受多个 extractor 作为参数, 例如: + +```rust +use volo_http::Address; + +async fn who_am_i(peer: Address) -> String { + format!("You are `{peer}`") +} + +async fn post_something(data: String) -> string { + format!("data: `{data}`") +} +``` + +除此之外,handler 可以使用 `json`, `form`, `query` 等可以被反序列化的对象作为参数 + +这里使用了 Rust 模式匹配的特性来接收参数: + +```rust +use volo_http::{ + http::StatusCode, + server::{ + extract::{Form, Query}, + route::{get, Router}, + }, +}; +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +struct Login { + username: String, + password: String, +} + +fn process_login(info: Login) -> Result { + if info.username == "admin" && info.password == "password" { + Ok("Login Success!".to_string()) + } else { + Err(StatusCode::IM_A_TEAPOT) + } +} + +// test with: +// curl "http://localhost:8080/user/login?username=admin&password=admin" +// curl "http://localhost:8080/user/login?username=admin&password=password" +async fn get_with_query(Query(info): Query) -> Result { + process_login(info) +} + +// test with: +// curl http://localhost:8080/user/login -X POST -d 'username=admin&password=admin' +// curl http://localhost:8080/user/login -X POST -d 'username=admin&password=password' +async fn post_with_form(Form(info): Form) -> Result { + process_login(info) +} + +pub fn user_login_router() -> Router { + Router::new().route("/user/login", get(get_with_query).post(post_with_form)) +} +``` + +## 什么是 `extractor`? + +可以作为 handler 参数的类型都实现了 `FromContext` 或 `FromRequest`, 这种类型我们通常称为 `extractor`。 + +其中,`FromContext` 不会消费请求的 body,即 POST 等方法传入的数据, + +而 `FromRequest` 会消费请求的 body,所以 handler 的参数中最多只能有一个实现了 `FromRequest` 的类型。 + +## 默认实现了 `extractor` 的类型 + +**`FromContext`** +- `Address` +- `Uri` +- `Method` +- `Option` +- `Result` +- `Query` +- `WebSocketUpgrade` + +**`FromRequest`** +- `Option` +- `Result` +- `ServerRequest` +- `Vec` +- `Bytes` +- `String` +- `FastStr` +- `MaybeInvalid` +- `Form` + +## 为自己的类型实现 extractor + +我们可以将自己的类型作为 handler 的参数直接接收,这需要为自己的类型实现 `FromContext` 或 `FromRequest`。 + +例如,我们可能会从请求的 header 中获取 LogID,这种情况下可以定义一个类型,然后为其实现 `FromContext`: + +```rust +use std::convert::Infallible; + +use volo_http::{ + context::ServerContext, + http::request::Parts, + server::extract::FromContext, +}; + +const LOGID_KEY: &str = "x-logid"; + +pub enum LogID { + ID(String), + None, +} + +impl FromContext for LogID { + type Rejection = Infallible; + + async fn from_context( + _: &mut ServerContext, + parts: &mut Parts, + ) -> Result { + let id = match parts.headers.get(LOGID_KEY) { + Some(log_id) => LogID::ID(log_id.to_str().unwrap().to_owned()), + None => LogID::None, + }; + + Ok(id) + } +} +``` + +实现了 LogID 这个类型后,就可以将其作为 extractor,直接使用 handler 接收了: + +```rust +async fn show_logid(id: LogID) -> String { + match id { + LogID::ID(s) => format!("{s}"), + LogID::None => "LogID not found".to_owned(), + } +} + +pub fn logid_router() -> Router { + Router::new().route("/extract/logid", get(print_logid)) +} +``` + +需要注意的一点是,在实现一个 handler 时,对于 `Uri`, `Method`, `Address` 等这一类通过 `FromContext` 提取, +不会消费 Body 等类型可以在 handler 的参数中任意排列, +但由于 Body 只能被消费一次,所以通过 `FromRequest` 提取的如 `String`, `Bytes`, `From`, `Json` 等类型, +**只能放在 handler 最后一个参数的位置**。 diff --git a/content/zh/docs/volo/volo-http/tutorials/response.md b/content/zh/docs/volo/volo-http/tutorials/response.md new file mode 100644 index 0000000000..f517f05eff --- /dev/null +++ b/content/zh/docs/volo/volo-http/tutorials/response.md @@ -0,0 +1,176 @@ +--- +title: "路由响应" +date: 2024-09-02 +weight: 4 +keywords: + [ + "路由", + "响应", + ] +description: "Volo-HTTP 路由响应" +--- + +## 路由响应 + +`Volo-HTTP` 的 handler 也可以返回任意 `impl IntoResponse` 的类型, 例如: + +```rust +use volo_http::{ + http::StatusCode +}; + +// 默认返回 `StatusCode::OK` +async fn ping() {} + +// ref: [RFC2324](https://datatracker.ietf.org/doc/html/rfc2324) +async fn who_are_you() -> StatusCode { + StatusCode::IM_A_TEAPOT +} +``` + +## handler response 使用技巧 + +handler 除了传入的参数可以自定义以外, 返回值类型也是可以自定义的, 如: + +```rust +async fn ping() {} +async fn hello_world() -> &'static str { "Hello, World" } +async fn teapot() -> StatusCode { StatusCode::IM_A_TEAPOT } +``` + +以上三个函数都是合法的 handler,因为这些返回值类型都实现了 `IntoResponse` 这一 trait。 + +在框架中,`Form`, `Json` 等类型也默认实现了 `IntoResponse`,并且可以在响应时添加 Content-Type。 + +### 使用 Json 作为应答 + +```rust +use volo_http::{ + json::Json, + server::{ + route::{get, post, Router}, + IntoResponse, + }, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +struct Person { + name: String, + age: u8, + phones: Vec, +} + +// test with: +// curl http://localhost:8080/json/get +async fn json_get() -> Json { + Json(Person { + name: "Foo".to_string(), + age: 25, + phones: vec!["Bar".to_string(), "114514".to_string()], + }) +} + +// test with: +// curl http://localhost:8080/json/post \ +// -X POST \ +// -H "Content-Type: application/json" \ +// -d '{"name":"Foo", "age": 25, "phones":["Bar", "114514"]}' +async fn json_post(Json(request): Json) -> String { + let first_phone = request + .phones + .first() + .map(|p| p.as_str()) + .unwrap_or("no number"); + format!( + "{} is {} years old, {}'s first phone number is `{}`\n", + request.name, request.age, request.name, first_phone + ) +} + +pub fn json_test_router() -> Router { + Router::new() + .route("/json/get", get(json_get)) + .route("/json/post", post(json_post)) +} +``` + +### 复合 Response 类型 + +`IntoResponse` 这一 trait 不仅支持 String, Json 等类型,还有一些其他的方式可以使用, 如: + +```rust +async fn return_result() -> Result<&'static str, StatusCode> { + if rand::random() { + Ok("It Works!\n") + } else { + Err(StatusCode::IM_A_TEAPOT) + } +} + +async fn return_with_status_code() -> (StatusCode, String) { + (StatusCode::NOT_FOUND, "Not Found!\n".to_owned()) +} + +pub fn response_router() -> Router { + Router::new() + .route("/response/result", get(return_result)) + .route("/response/status", get(return_with_status_code)) +} +``` + +使用 `Result<&'static str, StatusCode>` 作为返回值类型, +可以在返回 Ok 时使用 str 的内容作为 `Response`, +在返回 Err 时使用 `StatusCode` 作为 `Response` 的状态码,并返回一个空的实现。 + +而使用 `(StatusCode, String)` 可以将该 `String` 作为 `Response` 的 Body,并将 `Response` 的状态码设为 `StatusCode` 的值。 + +## 为自己的类型实现 IntoResponse + +对于自定义的类型,可以通过实现 `IntoResponse` 的方式作为 handler 的返回值,下面给一个例子。 + +在业务逻辑中,我们经常会定义一些自己内部的错误类型或者错误码, +由于框架对 `Result` 也实现了 `IntoResponse`, +所以我们可以通过对错误实现 `IntoResponse` 的方式方便地编写 handler: + +```rust +use volo_http::{ + server::{IntoResponse}, + http::{StatusCode}, + response::ServerResponse, + PathParams, +}; + +#[derive(PartialEq, Eq)] +pub struct ErrorCode(usize); + +impl ErrorCode { + pub const INVALID_USER_ID: Self = Self(1); + pub const INVALID_VIDEO_ID: Self = Self(2); +} + +impl IntoResponse for ErrorCode { + fn into_response(self) -> ServerResponse { + match self { + Self::INVALID_USER_ID => "Invalid User ID".into_response(), + Self::INVALID_VIDEO_ID => StatusCode::BAD_REQUEST.into_response(), + Self(code) => format!("Unknown error code {code}").into_response() + } + } +} + +async fn handler( + PathParams((uid, vid)): PathParams<(String, String)>, +) -> Result +{ + if uid == "admin" { + return Err(ErrorCode::INVALID_USER_ID); + } + if vid == "-1" { + return Err(ErrorCode::INVALID_VIDEO_ID); + } + Ok(format!("uid: {uid}, vid: {vid}")) +} +``` + +以上代码的响应格式非常不规范,**仅供功能展示使用**,在实际的业务中请根据需求使用统一的响应格式 diff --git a/content/zh/docs/volo/volo-http/tutorials/route.md b/content/zh/docs/volo/volo-http/tutorials/route.md new file mode 100644 index 0000000000..10e5a09020 --- /dev/null +++ b/content/zh/docs/volo/volo-http/tutorials/route.md @@ -0,0 +1,406 @@ +--- +title: "路由" +date: 2024-09-02 +weight: 2 +keywords: + [ + "路由", + "路由组", + "静态路由", + "参数路由", + "路由优先级", + ] +description: "Volo-HTTP 提供的路由功能" +--- + +## 路由注册 + +路由的 handler 可以使用 `get`, `post` 等函数创建。 + +在 `volo_http::route` 包中可导入对应函数, 如 `get` 则通过 `volo_http::route::get` 导入 + +首先, 我们需要一个简单的 handler: + +```rust +async fn foo_handler() -> &'static str { + "Hello, World!\n" +} +``` + +每条路由都需要对应一个相对路径 (path) 和一个方法 (method) 。这里我们以路由到 `"/foo"` 路径的 GET 方法为例, + +可以通过以下的方式,在创建一个 `Router` 后使用 `route` 方法创建: + +```rust +use volo_http::server::route::{get, Router}; + +pub fn test_router() -> Router { + Router::new().route("/foo", get(foo_handler)) +} +``` + +可以对 `get` 得到的 `MethodRouter` 进行链式调用,为其设置其他 method 的 handler: + +```rust +use volo_http::server::route::{get, Router}; + +pub fn test_router() -> Router { + Router::new() + .route("/foo", get(foo_handoer).post(foo_handler)) +} +``` + +也可以通过对 `Router` 的链式调用来创建更多的路由规则: + +```rust +use volo_http::server::route::{get, Router}; + +pub fn test_router() -> Router { + Router::new() + .route("/foo", get(foo_handler)) + .route("/bar", post(bar_handler)) +} +``` + +创建完成后, 可以将该路由 `merge` 到主路由中: + +```rust +use std::net::SocketAddr; +use std::time::Duration; +use volo::net::Address; +use volo_http::{Router, Server}; + +#[volo::main] +async fn main() { + let app = Router::new() + .merge(example_router()) + .merge(test_router()) + .layer(TimeoutLayer::new(Duration::from_secs(1), timeout_handler)); + + let addr = "[::]:8080".parse::().unwarp(); + let addr = Address::from(addr); + + Server::new(app).run(addr).await.unwarp(); +} +``` + +## 路由方法 + +使用如 `use volo_http::server::route::get` 导入路由为 GET 的方法 + +目前支持的路由方法有: + +- `options` +- `get` +- `post` +- `put` +- `delete` +- `head` +- `trace` +- `connect` +- `trace` + +## 路由类型 + +`Volo-HTTP` 支持丰富的路由类型用于实现复杂的路由匹配功能,包括静态路由、动态路由 (命名参数、通配参数) 。 + +路由的优先级: 静态路由 > 命名参数路由 > 通配参数路由 + +### 静态路由 + +```rust +use volo_http::server::route::{get, Router}; + +async fn index_handler() -> &'static str { + "Hello, World" +} + +async fn index_router() -> Router { + Router::new().route("/", get(index)); +} +``` + +### 命名参数路由 + +Volo-HTTP 支持使用 `{id}` 这样的命名参数设置路由, 并且命名参数只匹配单个路径段 + +如果设置 `/user/{id}` 路由,则匹配情况如下: + +| **路径** | **是否匹配** | **参数值** | +|:--------------------|:--------:|:-------:| +| `/user/100` | 匹配 | 100 | +| `/user/101/profile` | 不匹配 | - | +| `/user/` | 不匹配 | - | + +当然 Volo-HTTP 也是支持多个命名参数的,如 `/{platform}/user/{id}` + +**代码示例**: + +```rust +use volo::FastStr; +use volo_http::server::{ + param::PathParamsMap, + route::{get, Router}, +}; + +async fn param_handler(map: PathParamsMap) -> FastStr { + // 注意: 生产环境下不推荐使用 `unwarp` 方法, 这里仅供作为教程使用 + map.get("id").unwarp().clone() +} + +async fn param_router() -> Router { + Router::new().route("/user/{id}", get(param_handler)) +} +``` + +### 通配参数路由 + +Volo-HTTP 支持使用 `*path` 这样的通配参数设置路由, 并且通配参数回匹配所有内容。 + +**注意: 通配参数需放在路由的末尾。** + +如果我们设置 `/src/{*path}` 路由,匹配情况如下: + +| **路径** | **是否匹配** | **参数值** | +|:--------------------------|:--------:|:------------------:| +| `/src/` | 不匹配 | - | +| `/src/somefile.rs` | 匹配 | somefile.rs | +| `/src/subdir/somefile.rs` | 匹配 | subdir/somefile.rs | + +**代码示例**: + +```rust +use volo::FastStr; +use volo_http::server::{ + param::PathParamsMap, + route::{get, Router}, +}; + +async fn param_handler(map: PathParamsMap) -> FastStr { + // 注意: 生产环境下不推荐使用 `unwarp` 方法, 这里仅供作为教程使用 + map.get("path").unwarp().clone() +} + +async fn param_router() -> Router { + Router::new().route("/src/{*path}", get(param_handler)) +} +``` + +### 路由参数取值 + +- `PathParamsMap` + + 提供一个路由参数的 `AHashmap` + + ```rust + use volo_http::param::PathParamsMap; + + async fn param_handler(map: PathParamsMap) -> FastStr { + // 注意: 生产环境下不推荐使用 `unwarp` 方法, 这里仅供作为教程使用 + map.get("id").unwarp().clone() + } + ``` + +- `PathParams` + + 用户可使用模式匹配特性自定义路由参数取值 + + ```rust + use volo_http::param::PathParams; + + async fn param_handler(PathParams(id): PathParams) -> String { + id + } + ``` + +## 路由组 + +Volo-HTTP 提供了路由 `nest` 的能力, 用于支持**路由分组**的功能。 + +```rust +impl Router { + /*...*/ + pub fn nest(self, uri: U, router: Router) -> Self + where + U: AsRef, + { + self.nest_route(uri.as_ref().to_owned(), Route::new(router)) + } +} +``` + +我们可使用给定的 url 前缀路径来作为给定路由的路由前缀, 然后并合并到当前的路由中 + +```rust +use volo_http::server::{ + param::PathParams, + route::{get, Router}, +}; + +async fn hello_world() -> &'static str { + "Hello, World" +} + +async fn get_tid(PathParams(tid): PathParams) -> String { + tid +} + +async fn get_uid_and_tid(PathParams((uid, tid)): PathParams<(String, String)>) -> String { + format!("uid: {uid}, tid: {tid}") +} + +async fn user_router() -> Router { + Router::new() + .route("/name", get(hello_world)) + .route("/post/{tid}", get(get_uid_and_tid)); +} + +async fn post_router() -> Router { + Router::new() + .route("/name", get(hello_world)) + .route("/{tid}", get(get_tid)); +} + +async fn router() -> Router { + Router::new() + .nest("/user/{uid}", user_router()) + .nest("/post", post_router()) + // 这里的路由路径如下: + // /user/{uid}/name + // /user/{uid}/post/{tid} + // /post/name + // /post/{tid} +} +``` + +## fallback + +Volo-HTTP 提供了 fallback 功能用于处理请求 url 或 method 不匹配的情况 + +### url + +> 注意:全局只能设置一个 **router fallback**,否则会在调用 `merge` 方法时产生 **panic** + +**代码示例**: + +```rust +async fn index_handler() -> &'static str { + "Hello, World" +} + +async fn fallback_handler() -> (http::StatusCode, &'static str) { + (http::StatusCode::NOT_FOUND, "404 Not Found") +} + +async fn router() -> Router { + Router::new() + .route("/", get(index_handler)) + .fallback(fallback_handler) +} +``` + +### method + +```rust +async fn index_handler() -> &'static str { + "Hello, World" +} + +async fn fallback_handler() -> (http::StatusCode, &'static str) { + (http::StatusCode::METHOD_NOT_ALLOWED, "method not matched") + +} + +async fn router() -> Router { + Router::new() + .route("/", get(index_handler).fallback(fallback_handler)) +} +``` + +## 使用 `Service` 作为路由 + +路由可以使用传统的 `Service` 实现, 上文中提到的使用 handler 的方式也会转化为 `Service` 来运行, +但需要通过 `get_service` 来为 GET 方法创建路由: + +```rust +use std::convert::Infallible; + +use volo_http::{ + context::ServerContext, + request::ServerRequest, + response::ServerResponse, + server::{ + route::{get_service, Router}, + IntoResponse, + }, +}; +use motore::Service; + +#[derive(Clone)] +pub struct JsonGetService; + +impl Service for JsonGetService { + type Response = ServerResponse; + type Error = Infallible; + + async fn call( + &self, + _cx: &mut ServerContext, + _req: ServerRequest, + ) -> Result { + Ok(json_get().await.into_response()) + } +} + +pub fn json_test_router() -> Router { + Router::new() + /* ...... */ + .route( + "/json/get_srv", + get_service(JsonGetService), + ) +} +``` + +对于这种比较简单的 `Service`,也可以不定义结构体,通过 `service_fn` 直接使用函数实现: + +```rust +use std::convert::Infallible; + +use volo_http::{ + context::ServerContext, + json::Json, + request::ServerRequest, + response::ServerResponse, + server::{ + route::{post_service, Router}, + IntoResponse, + }, +}; +use motore::{service::service_fn, Service}; + +async fn json_post_srv( + cx: &mut ServerContext, + req: ServerRequest, +) -> Result { + let (parts, body) = req.into_parts(); + let data = match Json::::from_request(cx, parts, body).await { + Ok(data) => data, + Err(_) => { + return Ok("Invalid data!".into_response()); + } + }; + Ok(json_post(Some(data)).await.into_response()) +} + +pub fn json_test_router() -> Router { + Router::new() + /* ...... */ + .route( + "/json/post_srv", + post_service(service_fn(json_post_srv)), + ) +} +``` + +最终运行的效果预期与 `json_get` 和 `json_post` 一致 \ No newline at end of file diff --git a/content/zh/docs/volo/volo-http/tutorials/static-fs.md b/content/zh/docs/volo/volo-http/tutorials/static-fs.md new file mode 100644 index 0000000000..4f97624a78 --- /dev/null +++ b/content/zh/docs/volo/volo-http/tutorials/static-fs.md @@ -0,0 +1,29 @@ +--- +title: "Static-FS" +date: 2024-09-02 +weight: 5 +keywords: + [ + "文件传输" + ] +description: "Volo-HTTP Static-FS" +--- + +Volo-HTTP 提供了 `ServeDir` 函数用于注册静态文件 + +```rust +use volo_http::server::{ + route::{get, Router}, + utils::ServeDir, +}; + +async fn file_router() -> Router { + Router::new() + .route("/", get(|| async {"Hello, World!"})) + .nest_service("/static/", ServeDir::new(".")) +} +``` + +其中 `ServeDir::new` 的参数可指定路径进行传输 + +`"."` 表示传输当前命令执行路径的所有文件 diff --git a/content/zh/docs/volo/volo-http/tutorials/websocket.md b/content/zh/docs/volo/volo-http/tutorials/websocket.md new file mode 100644 index 0000000000..fc9b091f6f --- /dev/null +++ b/content/zh/docs/volo/volo-http/tutorials/websocket.md @@ -0,0 +1,92 @@ +--- +title: "WebSocket" +date: 2024-09-02 +weight: 6 +keywords: + [ + "WebSocket", + ] +description: "Volo-HTTP WebSocket" +--- + +Volo-HTTP 支持 WebSocket 协议 + +## 编写 handler + +由于 Volo-HTTP 内置的 `WebSocket` 类型实现了 `FromContext`, 所以我们可以直接在 handler 里面添加 WebSocket extractor + +下面是一个简单的 WebSocket handler 示例,它会回显收到的文本消息: + +```rust +use volo_http::{ + response::ServerResponse, + server::{ + route::get, + utils::{Message, WebSocket, WebSocketUpgrade}, + }, + Router, +}; + +async fn handle_socket(mut socket: WebSocket) { + while let Some(Ok(msg)) = socket.next().await { + match msg { + Message::Text(_) => { + socket.send(msg).await.unwrap(); + } + _ => {} + } + } +} + +async fn ws_handler(ws: WebSocketUpgrade) -> ServerResponse { + ws.on_upgrade(handle_socket) +} + +async fn ws_router() -> Router { + Router::new().route("/ws", get(ws_handler)); +} +``` + +## WebSocket 设置 + +可以通过 `Config` 结构体来配置 WebSocket 的选项,例如传输配置和协议支持。 + +### 传输设置 + +具体配置请参考 `tokio_tungstenite::tungstenite::protocol::WebSocketConfig` + +```rust +use tokio_tungstenite::tungstenite::protocol::WebSocketConfig as WebSocketTransConfig; +use volo_http::server::utils::WebSocketConfig; + +let config = WebSocketConfig::new().set_transport(WebSocketTransConfig { + write_buffer_size: 128 * 1024, + ..<_>::default() +}); +``` + +### 设置支持的协议 + +```rust +use volo_http::server::utils::WebSocketConfig; + +let config = WebSocketConfig::new().set_protocols(["graphql-ws", "graphql-transport-ws"]); +``` + +## 错误处理 + +在处理 WebSocket 连接时,可能会遇到各种错误,例如连接升级失败。可以通过**提供自定义的错误处理回调**来处理这些错误。 + +```rust +use std::collections::HashMap; + +use volo_http::{ + response::ServerResponse, + server::utils::{WebSocket, WebSocketConfig, WebSocketUpgrade}, +}; + +async fn ws_handler(ws: WebSocketUpgrade) -> ServerResponse { + ws.on_failed_upgrade(|error| unimplemented!()) + .on_upgrade(|socket| async {}) +} +``` \ No newline at end of file diff --git a/content/zh/docs/volo/volo-thrift/_index.md b/content/zh/docs/volo/volo-thrift/_index.md index 8374c8c47e..7cf979ab2e 100644 --- a/content/zh/docs/volo/volo-thrift/_index.md +++ b/content/zh/docs/volo/volo-thrift/_index.md @@ -1,7 +1,7 @@ --- title: "Volo-Thrift" linkTitle: "Volo-Thrift" -weight: 2 +weight: 4 keywords: ["Volo", "Thrift", "快速上手", "基础教程"] description: "Volo-Thrift 命令行工具安装、快速上手与基础教程。" --- diff --git a/content/zh/docs/volo/volo-thrift/getting-started/_index.md b/content/zh/docs/volo/volo-thrift/getting-started/_index.md index 11477589ac..082cb2d4e6 100644 --- a/content/zh/docs/volo/volo-thrift/getting-started/_index.md +++ b/content/zh/docs/volo/volo-thrift/getting-started/_index.md @@ -6,41 +6,7 @@ keywords: ["Volo", "Thrift", "快速开始", "安装"] description: "Volo-Thrift 开发环境准备、快速上手与基础教程。" --- -## Part 1. 安装命令行工具 - -Volo 提供了同名的命令行工具,用来初始化项目、管理 IDL 等。 - -```bash -cargo install volo-cli -``` - -> 需要 rust 版本>= 1.75.0 - -随后,我们输入: - -```bash -volo help -``` - -就能看到类似以下输出啦: - -```bash -USAGE: - volo [OPTIONS] - -OPTIONS: - -h, --help Print help information - -n, --entry-name The entry name, defaults to 'default'. [default: default] - -v, --verbose Turn on the verbose mode. - -V, --version Print version information - -SUBCOMMANDS: - help Print this message or the help of the given subcommand(s) - idl manage your idl - init init your project -``` - -## Part 2. 创建一个 Thrift Server +## Part 1. 创建一个 Thrift Server Volo-Thrift 是一个 RPC 框架,既然是 RPC,底层就需要两大功能: @@ -49,14 +15,14 @@ Volo-Thrift 是一个 RPC 框架,既然是 RPC,底层就需要两大功能 IDL 全称是 `Interface Definition Language`,接口定义语言。 -### 2.1 Why IDL +### 1.1 Why IDL 如果我们要进行 RPC,就需要知道对方的接口是什么,需要传什么参数,同时也需要知道返回值是什么样的,就好比两个人之间交流,需要保证在说的是同一个语言、同一件事。 这时候,就需要通过 IDL 来约定双方的协议,就像在写代码的时候需要调用某个函数,我们需要知道函数签名一样。 Thrift IDL 是一套跨语言的全栈式 RPC 解决方案,具体的语法可以看参考 [thrift-missing-guide](https://diwakergupta.github.io/thrift-missing-guide/) 或官方 [Thrift interface description language](http://thrift.apache.org/docs/idl)。 -### 2.2 编写 IDL +### 1.2 编写 IDL 为了创建一个 Thrift 项目,我们需要先编写一个 Thrift IDL。 @@ -172,7 +138,7 @@ cargo run --bin server 至此,我们已经能把我们的 server 跑起来啦! -## Part 3. 编写 Client 端 +## Part 2. 编写 Client 端 上一节中,我们编写完成了 server 端,现在让我们来编写我们的 client 端并调用我们的 server 端。 @@ -257,7 +223,7 @@ cargo run --bin client 大功告成! -## Part 4. 添加一个中间件 +## Part 3. 添加一个中间件 接下来,让我们来看下如何给 Volo 添加一个中间件。 @@ -325,7 +291,7 @@ volo_gen::volo::example::ItemServiceServer::new(S) 这时候,在 info 日志级别下,我们会打印出请求的耗时;在 debug 日志级别下,我们还会打出请求和响应的详细数据。 -## Part 5. What's Next? +## Part 4. What's Next? 恭喜你,阅读到了这里! 至此,我们已经基本学会了 Volo 的大部分使用了,可以使用 Volo 来开启我们愉快的 Rust 之旅啦~