Page not found :(
The page you are looking for doesn't exist or has been moved.
diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..b1d4e79 --- /dev/null +++ b/404.html @@ -0,0 +1,34 @@ +
The page you are looking for doesn't exist or has been moved.
Posted 2024-09-04 05:19:42 ‐ 2 min read
The size of the release binary built with spring-rs is half of the SpringBoot jar package.
The size of the Docker image built with spring-rs is 1/4 of the SpringBoot image.
The runtime memory usage of using spring-rs is 1/10 of that of SpringBoot.
The QPS of the simplest web application using spring-rs is twice that of SpringBoot.
The QPS of a web application with database queries using spring-rs is basically the same as SpringBoot.
The data query tool currently used is sqlx
. sqlx's performance support for MySQL is very poor, and the stress test results are only half of SpringBoot, so it is recommended to use PostgreSQL as the backend of sqlx.
Next, I will connect to rust-postgres to see if the performance will be improved compared to sqlx.
Detailed stress test code and related data can be found in this link
The Benchmark of the spring-rs
Posted 2024-09-04 05:19:42 ‐ 2 min read
The spring-stream plugin is a programming tool for real-time streaming message processing, which can greatly simplify message processing based on files, redis stream, and kafka.
Posted 2024-08-25 05:19:42 ‐ 1 min read
After a month of precipitation, I wrote a microservice framework similar to spring-boot in rust. The following is an example of the simplest web application
Posted 2024-08-04 05:19:42 ‐ 1 min read
Click here to be redirected. \ No newline at end of file diff --git a/blog/spring-rs-initial-version/index.html b/blog/spring-rs-initial-version/index.html new file mode 100644 index 0000000..87775ec --- /dev/null +++ b/blog/spring-rs-initial-version/index.html @@ -0,0 +1,113 @@ +
Posted 2024-08-04 05:19:42 ‐ 1 min read
After a month of precipitation, I wrote a microservice framework similar to spring-boot in rust. The following is an example of the simplest web application
use spring::{route, get, App};
+use spring_web::{
+ extractor::Path, handler::TypeRouter, response::IntoResponse,
+ Router, WebConfigurator, WebPlugin,
+};
+
+#[auto_config(WebConfigurator)]
+#[tokio::main]
+async fn main() {
+ App::new()
+ .add_plugin(WebPlugin)
+ .run()
+ .await
+}
+
+#[get("/")]
+async fn hello_world() -> impl IntoResponse {
+ "hello world"
+}
+
+#[route("/hello/:name", method = "GET", method = "POST")]
+async fn hello(Path(name): Path<String>) -> impl IntoResponse {
+ format!("hello {name}")
+}
+
spring-rs
uses plugins to integrate several popular frameworks in the rust ecosystem and provides procedural macros to simplify development.
If you are interested in spring-rs
, you can click here to get started quickly.
Posted 2024-08-25 05:19:42 ‐ 1 min read
The spring-stream plugin is a programming tool for real-time streaming message processing, which can greatly simplify message processing based on files, redis stream, and kafka.
Here is a simple producer:
#[auto_config(WebConfigurator)]
+#[tokio::main]
+async fn main() {
+ App::new()
+ .add_plugin(StreamPlugin)
+ .add_plugin(WebPlugin)
+ .run()
+ .await
+}
+
+#[get("/")]
+async fn send_msg(Component(producer): Component<Producer>) -> Result<impl IntoResponse> {
+ let now = SystemTime::now();
+ let json = json!({
+ "success": true,
+ "msg": format!("This message was sent at {:?}", now),
+ });
+ let resp = producer
+ .send_json("topic", json)
+ .await
+ .context("send msg failed")?;
+
+ let seq = resp.sequence();
+ Ok(Json(json!({"seq":seq})))
+}
+
Producer is used to send messages to the message store. Spring-stream is implemented using sea-streamer at the bottom layer, which abstracts file, stdio, redis, and kafka The message storage layer allows developers to send and process messages using a unified interface.
Here is a simple consumer code:
#[tokio::main]
+async fn main() {
+ App::new()
+ .add_plugin(StreamPlugin)
+ .add_consumer(consumers())
+ .run()
+ .await
+}
+
+fn consumers() -> Consumers {
+ Consumers::new().typed_consumer(listen_topic_do_something)
+}
+
+#[stream_listener("topic")]
+async fn listen_topic_do_something(Json(payload): Json<Payload>) {
+ tracing::info!("{:#?}", payload);
+ // do something
+}
+
Click here to view the relevant documentation.
spring-rs is a application framework written in Rust, similar to SpringBoot in java ecosystem. spring-rs provides an easily extensible plug-in system for integrating excellent projects in Rust community, such as axum, sqlx, sea-orm, etc.
The premise of using spring-rs is that you are familiar with the basic syntax of Rust and the usage of cargo dependency package management tool.
If you already know these prerequisites, click this Quick Start →, which introduces how to quickly get started with spring-rs.
spring-web
(Based on axum
)spring-sqlx
(Integrated with sqlx
)spring-postgres
(Integrated with rust-postgres
)spring-sea-orm
(Integrated with sea-orm
)spring-redis
(Integrated with redis
)spring-mail
(integrated with lettre
)spring-job
(integrated with tokio-cron-scheduler
)spring-stream
(Integrate sea-streamer
to implement message processing such as redis-stream and kafka)spring-opentelemetry
(integrate with opentelemetry
to implement full observability of logging, metrics, tracing)spring-tarpc
(Integratetarpc
to implement RPC calls)We also welcome community experts to contribute their own plugins. Contributing →
Click here to view common problems encountered when using spring-rs
Help →
On this page, I will introduce how to quickly get started with spring-rs
Add the following dependencies to your Cargo.toml
file
[dependencies]
+# Spring provides the core plugin system and useful Procedural Macros
+spring = "0.1.1"
+# If you are going to write a web application, add spring-web
+spring-web = "0.1.1"
+# If the application needs to interact with the database, add spring-sqlx
+spring-sqlx = { version="0.1.1", features = ["mysql"] }
+# The spring-rs project uses the tokio asynchronous runtime by default
+tokio = "1"
+
use anyhow::Context;
+use spring::{auto_config, App};
+use spring_sqlx::{
+ sqlx::{self, Row},
+ ConnectPool, SqlxPlugin,
+};
+use spring_web::{
+ axum::response::IntoResponse,
+ error::Result,
+ extractor::{Component, Path},
+ WebConfigurator, WebPlugin,
+};
+use spring_web::{get, route};
+
+// Main function entry
+#[auto_config(WebConfigurator)] // auto config web router
+#[tokio::main]
+async fn main() {
+ App::new()
+ .add_plugin(SqlxPlugin) // Add plug-in
+ .add_plugin(WebPlugin)
+ .run()
+ .await
+}
+
+// The get macro specifies the Http Method and request path.
+// spring-rs also provides other standard http method macros such as post, delete, patch, etc.
+#[get("/")]
+async fn hello_world() -> impl IntoResponse {
+ "hello world"
+}
+
+// You can also use the route macro to specify the Http Method and request path.
+// Path extracts parameters from the HTTP request path
+#[route("/hello/:name", method = "GET", method = "POST")]
+async fn hello(Path(name): Path<String>) -> impl IntoResponse {
+ format!("hello {name}")
+}
+
+// Component can extract the connection pool registered by the SqlxPlugin in AppState
+#[get("/version")]
+async fn sqlx_request_handler(Component(pool): Component<ConnectPool>) -> Result<String> {
+ let version = sqlx::query("select version() as version")
+ .fetch_one(&pool)
+ .await
+ .context("sqlx query failed")?
+ .get("version");
+ Ok(version)
+}
+
Create a config
directory in the root path of the project, where the spring-rs
configuration files will be stored.
You can first create an app.toml
file in this directory with the following content:
[web]
+port = 8000 # Configure the web service port. If not configured, the default port is 8080
+
+[sqlx] # Configure the database connection information of sqlx
+uri = "mysql://user:password@127.0.0.1:3306"
+
spring-rs
supports multiple environment configurations: dev (development), test (testing), and prod (production), corresponding to the three configuration files app-dev.toml
, app-dev.toml
, and app-prod.toml
. The configuration in the environment configuration file will override the configuration items of the app.toml
main configuration file.
spring-rs
will activate the configuration file of the corresponding environment according to the SPRING_ENV
environment variable.
Coding is complete, please make sure your database can be connected normally, then let's start running.
cargo run
+
During development it can be very handy to have cargo automatically recompile the code on changes.
This can be accomplished very easily by using cargo-watch.
cargo watch -x run
+
Answers to frequently asked questions.
TODO:
spring-opendal integrates Apache OpenDAL™ into spring-rs, providing native support for all types of storage systems, including object storage services, file storage services, and many more.
For specific examples, please refer to the with-spring-web project.
cargo run --color=always --package spring-opendal --example with-spring-web --features=services-fs
+
cargo test --test blocking --features="services-memory layers-blocking test-layers" -- --nocapture
+
spring
is the core module of this project, which includes: configuration management, plugin management, and component management.
Plugin
trait.Configurable
trait.Clone
trait.Note: To avoid deep copying of large struct in Component, it is recommended to use the newtype pattern to reference them via
Arc<T>
.
Add dependencies
spring = { version = "0.1.1" } # This crate contains the definition of plugin traits
+serde = { workspace = true, features = ["derive"] } # Used to parse plugin configuration items
+
use serde::Deserialize;
+use spring::async_trait;
+use spring::config::Configurable;
+use spring::{app::AppBuilder, plugin::Plugin};
+
+struct MyPlugin;
+
+#[async_trait]
+impl Plugin for MyPlugin {
+ async fn build(&self, app: &mut AppBuilder) {
+ // Call app.get_config::<Config>() method to get configuration items
+ match app.get_config::<Config>() {
+ Ok(config) => {
+ println!("{:#?}", config);
+ assert_eq!(config.a, 1);
+ assert_eq!(config.b, true);
+
+ // Get the configuration items to build the corresponding components
+
+ }
+ Err(e) => println!("{:?}", e),
+ }
+ }
+}
+
+/// Plugin configuration
+#[derive(Debug, Configurable, Deserialize)]
+#[config_prefix = "my-plugin"]
+struct Config {
+ a: u32,
+ b: bool,
+}
+
For the complete code, refer to plugin-example
, or refer to other built-in plugin codes.
spring-job is based on tokio-cron-scheduler
spring-job = { version = "0.1.1" }
+
App implements the JobConfigurator feature, which can be used to configure the scheduling task:
1 #[tokio::main]
+ 2 async fn main() {
+ 3 App::new()
+ 4 .add_plugin(JobPlugin)
+ 5 .add_plugin(SqlxPlugin)
+ 6 .add_jobs(jobs())
+ 7 .run()
+ 8 .await
+ 9 }
+ 10
+ 11 fn jobs() -> Jobs {
+ 12 Jobs::new().typed_job(cron_job)
+ 13 }
+ 14
+ 15 #[cron("1/10 * * * * *")]
+ 16 async fn cron_job() {
+ 17 println!("cron scheduled: {:?}", SystemTime::now())
+ 18 }
+
You can also use the auto_config
macro to implement automatic configuration. This process macro will automatically register the scheduled tasks marked by the Procedural Macro into the app:
+#[auto_config(JobConfigurator)]
+ #[tokio::main]
+ async fn main() {
+ App::new()
+ .add_plugin(JobPlugin)
+ .add_plugin(SqlxPlugin)
+- .add_jobs(jobs())
+ .run()
+ .await
+}
+
The SqlxPlugin
plugin above automatically registers a Sqlx connection pool component for us. We can use Component
to extract this connection pool from App. It should be noted that although the implementation principles of spring-job
's Component
and spring-web
's Component
are similar, these two extractors belong to different crates.
use spring_sqlx::{
+ sqlx::{self, Row}, ConnectPool
+};
+use spring_job::cron;
+use spring_job::extractor::Component;
+
+#[cron("1/10 * * * * *")]
+async fn cron_job(Component(db): Component<ConnectPool>) {
+ let time: String = sqlx::query("select DATE_FORMAT(now(),'%Y-%m-%d %H:%i:%s') as time")
+ .fetch_one(&db)
+ .await
+ .context("query failed")
+ .unwrap()
+ .get("time");
+ println!("cron scheduled: {:?}", time)
+}
+
logger is based on tracing
The log plugin is a plugin built into the spring
core module and is also the first plugin loaded by spring. This plugin does not need to be added manually by the developer.
This plugin integrates the most popular log library tracing
and provides the following configuration items
[logger]
+enable = true # Whether to enable the log function, which is enabled by default
+pretty_backtrace = false # Whether to print stack information, which is disabled by default. It is recommended to enable it only during application development
+level = "info" # The default log level is info
+format = "compact" # The log format supports compact, pretty, and json, and the default is compact
+override_filter = "info,axum=debug" # Override the default log filter level, and specify the log level for the crate library
+file = { enabled = true } # Whether to write logs to files, which is not enabled by default
+
You can also configure the log file in more detail
[logger.file]
+enabled = true # Whether to write logs to files
+non_blocking = true # Whether to enable non-blocking writing, which is enabled by default
+format = "compact" # Log format supports compact, pretty, json, the default is compact
+rotation = "daily" # Log rotation mode minutely, hourly, daily, never, the default is daily rotation
+dir = "./logs" # Log file directory
+filename_prefix = "app" # Log file prefix name
+filename_suffix = "log" # Log file suffix name
+max_log_files = 365 # Maximum number of logs to retain
+
spring-mail is an automatic assembly for lettre
Lettre is the most popular mail client in Rust and supports asynchronous API. spring-mail mainly uses its tokio asynchronous API.
spring-mail = { version = "0.1.1" }
+
[mail]
+host = "smtp.gmail.com" # SMTP mail server address,
+port = 465 # SMTP server port number
+secure = true # Response timeout, in milliseconds
+auth = { user = "user@gmail.com", password = "passwd" } # Authentication information
+
After configuring the above configuration items, the plugin will automatically register a Mailer
STMP asynchronous client. This object is an alias of lettre::AsyncSmtpTransport<Tokio1Executor>
.
pub type Mailer = lettre::AsyncSmtpTransport<Tokio1Executor>;
+
The MailPlugin
plugin automatically registers an SMTP client for us. We can use Component
to extract this connection pool from AppState. Component
is an axum extractor.
async fn send_mail(Component(mailer): Component<Mailer>) -> Result<impl IntoResponse> {
+ let email = Message::builder()
+ .from("NoBody <nobody@domain.tld>".parse().unwrap())
+ .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
+ .to("hff1996723@163.com".parse().unwrap())
+ .subject("Happy new year")
+ .header(ContentType::TEXT_PLAIN)
+ .body(String::from("Be happy!"))
+ .unwrap();
+ let resp = mailer.send(email).await.context("send mail failed")?;
+ Ok(Json(resp))
+}
+
For the complete code, please refer to mail-example
spring-postgres is an autowire for rust-postgres
tokio-postgres is a database connection tool similar to sqlx. Unlike sqlx, it only focuses on implementing postgresql database connections.
spring-postgres = { version = "0.1.1" }
+
[postgres]
+connect = "postgres://root:12341234@localhost:5432/myapp_development" # Database address to connect to
+
After configuring the above configuration items, the plugin will automatically register a Postgres
object. This object wraps tokio_postgres::Client
.
pub struct Postgres(Arc<tokio_postgres::Client>);
+
The PgPlugin
plugin automatically registers a Postgres
object for us. We can use Component
to extract this connection pool from AppState. Component
is an axum extractor.
#[get("/postgres")]
+async fn hello_postgres(Component(pg): Component<Postgres>) -> Result<impl IntoResponse> {
+ let rows = pg
+ .query("select version() as version", &[])
+ .await
+ .context("query postgresql failed")?;
+
+ let version: String = rows[0].get("version");
+
+ Ok(Json(version))
+}
+
Complete code reference postgres-example
spring-redis is an automatic assembly for redis-rs
spring-redis = { version = "0.1.1" }
+
[redis]
+uri = "redis://127.0.0.1/" # redis database address
+
+# The following are all optional configurations
+connection_timeout = 10000 # Connection timeout, in milliseconds
+response_timeout = 1000 # Response timeout, in milliseconds
+number_of_retries = 6 # Retry times, interval time increases exponentially
+exponent_base = 2 # Interval time exponential base, unit milliseconds
+factor = 100 # Interval time growth factor, default 100 times growth
+max_delay = 60000 # Maximum interval time
+
After configuring the above configuration items, the plugin will automatically register a Redis
connection management object. This object is an alias of redis::aio::ConnectionManager
.
pub type Redis = redis::aio::ConnectionManager;
+
The RedisPlugin
plugin automatically registers a connection management object for us. We can use Component
to extract this connection pool from AppState. Component
is an axum extractor.
async fn list_all_redis_key(Component(mut redis): Component<Redis>) -> Result<impl IntoResponse> {
+ let keys: Vec<String> = redis.keys("*").await.context("redis request failed")?;
+ Ok(Json(keys))
+}
+
Complete code reference redis-example
spring-sea-orm is an automatic assembly for sea-orm
spring-sea-orm = { version = "0.1.1", features = ["postgres"] }
+sea-orm = { version = "1.0" } # Mainly to adapt to the entity code generated by sea-orm-cli
+
You can replace postgres
, mysql
, sqlite
feature to select the appropriate database driver.
[sea-orm]
+uri = "postgres://root:123456@localhost:5432/pg_db" # Database address
+min_connections = 1 # Minimum number of connections in the connection pool, the default value is 1
+max_connections = 10 # Maximum number of connections in the connection pool, the default value is 10
+acquire_timeout = 30000 # Connection timeout, in milliseconds, default 30s
+idle_timeout = 600000 # Connection idle time, in milliseconds, default 10min
+connect_timeout = 1800000 # Maximum connection survival time, in milliseconds, default 30min
+enable_logging = true # Print sql log
+
After configuring the above configuration items, the plugin will automatically register a DbConn
connection pool object. This object is an alias of sea_orm::DbConn
.
pub type DbConn = sea_orm::DbConn;
+
sea-orm-cli provides a great model code generation function. You only need to define the table structure in the database, and after a simple configuration, the model code corresponding to the database structure can be generated, which can save a lot of code writing work.
The SeaOrmPlugin
plugin automatically registers a connection pool component for us. We can use Component
to extract this connection pool from AppState. Component
is an axum extractor.
use spring_sqlx::{sqlx::{self, Row}, ConnectPool};
+use spring_web::get;
+use spring_web::extractor::Component;
+use spring_web::error::Result;
+use anyhow::Context;
+
+#[get("/:id")]
+async fn get_todo_list(
+ Component(db): Component<DbConn>,
+ Path(id): Path<i32>
+) -> Result<String> {
+ let rows = TodoItem::find()
+ .filter(todo_item::Column::ListId.eq(id))
+ .all(&db)
+ .await
+ .context("query todo list failed")?;
+ Ok(Json(rows))
+}
+
spring-sea-orm
extends SeaOrm's Select with the PaginationExt feature.
In addition, web pagination parameter parsing is also provided. Just add the with-web
function to the dependency.
spring-sea-orm = { version = "<version>", features = ["postgres", "with-web"] }
+
The configuration is as follows:
# sea-orm-web configuration
+[sea-orm-web]
+one_indexed = false # 1-based index, closed by default
+max_page_size = 2000 # Maximum supported page size, to avoid OOM caused by server attacks, default value 2000
+default_page_size = 20 # Default page size, 20
+
Use as follows:
#[get("/")]
+async fn get_todo_list(
+ Component(db): Component<DbConn>,
+ Query(query): Query<TodoListQuery>,
+ pagination: Pagination,
+) -> Result<impl IntoResponse> {
+ let rows = TodoList::find()
+ .filter(query)
+ .page(&db, pagination)
+ .await
+ .context("query todo list failed")?;
+ Ok(Json(rows))
+}
+
For the complete code, please refer to sea-orm-example
spring-sqlx is an automatic assembly for sqlx
spring-sqlx = { version = "0.1.1", features = ["mysql"] }
+
You can replace postgres
, mysql
, sqlite
feature to select the appropriate database driver.
[sqlx]
+uri = "postgres://root:123456@localhost:5432/pg_db" # Database address
+min_connections = 1 # Minimum number of connections in the connection pool, the default value is 1
+max_connections = 10 # Maximum number of connections in the connection pool, the default value is 10
+acquire_timeout = 30000 # Connection timeout, in milliseconds, default 30s
+idle_timeout = 600000 # Connection idle time, in milliseconds, default 10min
+connect_timeout = 1800000 # Maximum connection survival time, in milliseconds, default 30min
+
After configuring the above configuration items, the plugin will automatically register a ConnectPool
connection pool object. This object is an alias for sqlx::AnyPool
.
pub type ConnectPool = sqlx::AnyPool;
+
The SqlxPlugin
plugin automatically registers a Sqlx connection pool component for us. We can use Component
to extract this connection pool from AppState. Component
is an axum extractor.
use spring_sqlx::{sqlx::{self, Row}, ConnectPool};
+use spring_web::get;
+use spring_web::extractor::Component;
+use spring_web::error::Result;
+use anyhow::Context;
+
+#[get("/version")]
+async fn mysql_version(Component(pool): Component<ConnectPool>) -> Result<String> {
+ let version = sqlx::query("select version() as version")
+ .fetch_one(&pool)
+ .await
+ .context("sqlx query failed")?
+ .get("version");
+ Ok(version)
+}
+
Complete code reference sqlx-example
spring-stream is based on sea-streamer
spring-stream = { version = "0.1.1", features=["file"] }
+
spring-stream supports four message storages: file
, stdio
, redis
, and kafka
.
[stream]
+uri = "file://./stream" # StreamerUri data stream address
+
StreamUri supports file, stdio, redis, and kafka. For the format of uri, please refer to StreamerUri.
# File stream configuration
+[stream.file]
+connect = { create_file = "CreateIfNotExists" }
+
+# Standard stream configuration
+[stream.stdio]
+connect = { loopback = false }
+
+# Redis stream configuration
+[stream.redis]
+connect = { db=0,username="user",password="passwd" }
+
+# Kafka stream configuration
+[stream.kafka]
+connect = { sasl_options={mechanism="Plain",username="user",password="passwd"}}
+
StreamPlugin
registers a Producer
for sending messages. If you need to send messages in json format, you need to add the json
feature in the dependencies:
spring-stream = { version = "0.1.1", features=["file","json"] }
+
1 use anyhow::Context;
+ 2 use serde_json::json;
+ 3 use spring::{auto_config, App};
+ 4 use spring_stream::{Producer, StreamPlugin};
+ 5 use spring_web::error::Result;
+ 6 use spring_web::get;
+ 7 use spring_web::{
+ 8 axum::response::{IntoResponse, Json},
+ 9 extractor::Component,
+ 10 WebConfigurator, WebPlugin,
+ 11 };
+ 12 use std::time::SystemTime;
+ 13
+ 14 #[auto_config(WebConfigurator)]
+ 15 #[tokio::main]
+ 16 async fn main() {
+ 17 App::new()
+ 18 .add_plugin(StreamPlugin)
+ 19 .add_plugin(WebPlugin)
+ 20 .run()
+ 21 .await
+ 22 }
+ 23
+ 24 #[get("/")]
+ 25 async fn send_msg(Component(producer): Component<Producer>) -> Result<impl IntoResponse> {
+ 26 let now = SystemTime::now();
+ 27 let json = json!({
+ 28 "success": true,
+ 29 "msg": format!("This message was sent at {:?}", now),
+ 30 });
+ 31 let resp = producer
+ 32 .send_json("topic", json)
+ 33 .await
+ 34 .context("send msg failed")?;
+ 35
+ 36 let seq = resp.sequence();
+ 37 Ok(Json(json!({"seq":seq})))
+ 38 }
+ 39
+
spring-stream
provides a process macro called stream_listener
to subscribe to messages from a specified topic. The code is as follows:
1 use spring::tracing;
+ 2 use spring::App;
+ 3 use spring_stream::consumer::Consumers;
+ 4 use spring_stream::extractor::Json;
+ 5 use spring_stream::file::AutoStreamReset;
+ 6 use spring_stream::handler::TypedConsumer;
+ 7 use spring_stream::stream_listener;
+ 8 use spring_stream::{file::FileConsumerOptions, StreamConfigurator, StreamPlugin};
+ 9 use stream_file_example::Payload;
+ 10
+ 11 #[tokio::main]
+ 12 async fn main() {
+ 13 App::new()
+ 14 .add_plugin(StreamPlugin)
+ 15 .add_consumer(consumers())
+ 16 .run()
+ 17 .await
+ 18 }
+ 19
+ 20 fn consumers() -> Consumers {
+ 21 Consumers::new().typed_consumer(listen_topic_do_something)
+ 22 }
+ 23
+ 24 #[stream_listener(
+ 25 "topic",
+ 26 "topic2",
+ 27 file_consumer_options = fill_file_consumer_options
+ 28 )]
+ 29 async fn listen_topic_do_something(Json(payload): Json<Payload>) {
+ 30 tracing::info!("{:#?}", payload);
+ 31 }
+ 32
+ 33 fn fill_file_consumer_options(opts: &mut FileConsumerOptions) {
+ 34 opts.set_auto_stream_reset(AutoStreamReset::Earliest);
+ 35 }
+ 36
+
View the complete example code stream-file-example, stream-redis-example, stream-kafka-example
spring-web is based on axum
Axum is one of the best web frameworks in the Rust community. It is a sub-project based on hyper maintained by Tokio. Axum provides web routing, declarative HTTP request parsing, HTTP response serialization, and can be combined with the middleware in the tower ecosystem.
spring-web = { version = "0.1.1" }
+
[web]
+binding = "172.20.10.4" # IP address of the network card to bind, default 127.0.0.1
+port = 8000 # Port number to bind, default 8080
+
+# Web middleware configuration
+[web.middlewares]
+compression = { enable = true } # Enable compression middleware
+logger = { enable = true } # Enable log middleware
+catch_panic = { enable = true } # Capture panic generated by handler
+limit_payload = { enable = true, body_limit = "5MB" } # Limit request body size
+timeout_request = { enable = true, timeout = 60000 } # Request timeout 60s
+
+# Cross-domain configuration
+cors = { enable = true, allow_origins = [
+ "*.github.io",
+], allow_headers = [
+ "Authentication",
+], allow_methods = [
+ "GET",
+ "POST",
+], max_age = 60 }
+
+# Static resource configuration
+static = { enable = true, uri = "/static", path = "static", precompressed = true, fallback = "index.html" }
+
App implements the WebConfigurator feature, which can be used to specify routing configuration:
1 use spring::App;
+ 2 use spring_web::get;
+ 3 use spring_web::{WebPlugin, WebConfigurator, Router, axum::response::IntoResponse, handler::TypeRouter};
+ 4 use spring_sqlx::SqlxPlugin;
+ 5
+ 6 #[tokio::main]
+ 7 async fn main() {
+ 8 App::new()
+ 9 .add_plugin(SqlxPlugin)
+ 10 .add_plugin(WebPlugin)
+ 11 .add_router(router())
+ 12 .run()
+ 13 .await
+ 14 }
+ 15
+ 16 fn router() -> Router {
+ 17 Router::new().typed_route(hello_word)
+ 18 }
+ 19
+ 20 #[get("/")]
+ 21 async fn hello_word() -> impl IntoResponse {
+ 22 "hello word"
+ 23 }
+
You can also use the auto_config
macro to implement automatic configuration. This process macro will automatically register the routes marked by the Procedural Macro into the app:
+#[auto_config(WebConfigurator)]
+ #[tokio::main]
+ async fn main() {
+ App::new()
+ .add_plugin(SqlxPlugin)
+ .add_plugin(WebPlugin)
+- .add_router(router())
+ .run()
+ .await
+}
+
+-fn router() -> Router {
+- Router::new().typed_route(hello_word)
+-}
+
get
in the above example is an attribute macro. spring-web
provides eight standard HTTP METHOD process macros: get
, post
, patch
, put
, delete
, head
, trace
, options
.
You can also use the route
macro to bind multiple methods at the same time:
use spring_web::route;
+use spring_web::axum::response::IntoResponse;
+
+#[route("/test", method = "GET", method = "HEAD")]
+async fn example() -> impl IntoResponse {
+ "hello world"
+}
+
In addition, spring also supports binding multiple routes to a handler, which requires the routes
attribute macro:
use spring_web::{routes, get, delete};
+use spring_web::axum::response::IntoResponse;
+
+#[routes]
+#[get("/test")]
+#[get("/test2")]
+#[delete("/test")]
+async fn example() -> impl IntoResponse {
+ "hello world"
+}
+
In the above example, the SqlxPlugin
plugin automatically registers a Sqlx connection pool component for us. We can use Component
to extract this connection pool from State. Component
is an axum extractor.
use anyhow::Context;
+use spring_web::get;
+use spring_web::{axum::response::IntoResponse, extractor::Component, error::Result};
+use spring_sqlx::{ConnectPool, sqlx::{self, Row}};
+
+#[get("/version")]
+async fn mysql_version(Component(pool): Component<ConnectPool>) -> Result<String> {
+ let version = sqlx::query("select version() as version")
+ .fetch_one(&pool)
+ .await
+ .context("sqlx query failed")?
+ .get("version");
+ Ok(version)
+}
+
Axum also provides other extractors, which are reexported under spring_web::extractor
.
You can use Config
to extract the configuration in the configuration toml.
use spring_web::get;
+use spring_web::{extractor::Config, axum::response::IntoResponse};
+use spring::config::Configurable;
+use serde::Deserialize;
+
+#[derive(Debug, Configurable, Deserialize)]
+#[config_prefix = "custom"]
+struct CustomConfig {
+ a: u32,
+ b: bool,
+}
+
+#[get("/config")]
+async fn use_toml_config(Config(conf): Config<CustomConfig>) -> impl IntoResponse {
+ format!("a={}, b={}", conf.a, conf.b)
+}
+
Add the corresponding configuration to your configuration file:
[custom]
+a = 1
+b = true
+
Complete code reference web-example
spring-rs is a application framework written in rust inspired by java's spring-boot
Get startedBenefiting from the awesome rust language, spring-rs has the ultimate performance comparable to C/C++
Compared to C/C++, the Rust language used by spring-rs provides memory safety and thread safety.
The core code of spring-rs does not exceed 5,000 lines, and the binary size of the release version packaged in rust is also small.
spring-rs provides a clear and concise API and optional Procedural Macros to simplify development.
spring-rs uses a highly extensible plug-in model, and users can customize plug-ins to extend program capabilities.
spring-rs uses toml to configure applications and plug-ins to improve application flexibility.
Posted 2024-09-04 05:19:42 ‐ 2 min read
使用spring-rs构建的release版二进制文件大小是SpringBoot jar包的一半。
使用spring-rs构建的Docker镜像大小是SpringBoot镜像的1/4。
使用spring-rs的运行时内存占用是SpringBoot运行时占用的1/10。
使用spring-rs的最简单的Web应用程序QPS是SpringBoot的2倍。
使用spring-rs的包含数据库查询的Web应用程序QPS和SpringBoot基本相当。
目前用的数据查询工具是sqlx
,sqlx对mysql性能支持很差,压测结果只有SpringBoot的一半,所以推荐使用PostgreSQL作为sqlx的后端。
接下来我会对接一下rust-postgres,看看性能会不会比sqlx有所提升。
详细压测代码和相关数据可以点击这个链接
rust的spring-rs和java的springboot相关压测报告
Posted 2024-09-04 05:19:42 ‐ 2 min read
spring-stream插件是实时流式处理消息的编程工具,可以大大简化基于文件、redis stream、kafka的消息处理
Posted 2024-08-25 05:19:42 ‐ 1 min read
经过一个月的沉淀,我用Rust编写了一个类似于spring-boot的微服务框架。下面是一个最简单的web应用的例子
Posted 2024-08-04 05:19:42 ‐ 1 min read
Click here to be redirected. \ No newline at end of file diff --git a/zh/blog/spring-rs-initial-version/index.html b/zh/blog/spring-rs-initial-version/index.html new file mode 100644 index 0000000..d4e9c9d --- /dev/null +++ b/zh/blog/spring-rs-initial-version/index.html @@ -0,0 +1,131 @@ +
Posted 2024-08-04 05:19:42 ‐ 1 min read
经过一个月的沉淀,我用Rust编写了一个类似于spring-boot的微服务框架。下面是一个最简单的web应用的例子
use spring::{route, get, App};
+use spring_web::{
+ extractor::Path, handler::TypeRouter, response::IntoResponse,
+ Router, WebConfigurator, WebPlugin,
+};
+
+#[auto_config(WebConfigurator)]
+#[tokio::main]
+async fn main() {
+ App::new()
+ .add_plugin(WebPlugin)
+ .run()
+ .await
+}
+
+#[get("/")]
+async fn hello_world() -> impl IntoResponse {
+ "hello world"
+}
+
+#[route("/hello/:name", method = "GET", method = "POST")]
+async fn hello(Path(name): Path<String>) -> impl IntoResponse {
+ format!("hello {name}")
+}
+
spring-rs
使用插件的方式整合了Rust生态中流行的几个框架,并提供了过程宏来简化开发。
对spring-rs
感兴趣的可以点击这里快速上手。
Posted 2024-08-25 05:19:42 ‐ 1 min read
spring-stream插件是实时流式处理消息的编程工具,可以大大简化基于文件、redis stream、kafka的消息处理
下面是一个简单的生产者:
#[auto_config(WebConfigurator)]
+#[tokio::main]
+async fn main() {
+ App::new()
+ .add_plugin(StreamPlugin)
+ .add_plugin(WebPlugin)
+ .run()
+ .await
+}
+
+#[get("/")]
+async fn send_msg(Component(producer): Component<Producer>) -> Result<impl IntoResponse> {
+ let now = SystemTime::now();
+ let json = json!({
+ "success": true,
+ "msg": format!("This message was sent at {:?}", now),
+ });
+ let resp = producer
+ .send_json("topic", json)
+ .await
+ .context("send msg failed")?;
+
+ let seq = resp.sequence();
+ Ok(Json(json!({"seq":seq})))
+}
+
Producer用于向消息存储发送消息。spring-stream底层使用sea-streamer实现,它抽象了file、stdio、redis、kafka消息存储层,使开发者可以用统一的接口来发送和处理消息。
下面是一个简单的消费者的代码:
#[tokio::main]
+async fn main() {
+ App::new()
+ .add_plugin(StreamPlugin)
+ .add_consumer(consumers())
+ .run()
+ .await
+}
+
+fn consumers() -> Consumers {
+ Consumers::new().typed_consumer(listen_topic_do_something)
+}
+
+#[stream_listener("topic")]
+async fn listen_topic_do_something(Json(payload): Json<Payload>) {
+ tracing::info!("{:#?}", payload);
+ // do something
+}
+
点击这里可以查看相关文档。
spring-rs是一个Rust编写的应用框架,类似于java生态的springboot。spring-rs提供了易于扩展的插件系统,用于整合Rust社区的优秀项目,例如axum、sqlx、sea-orm等。
使用spring-rs的前提是,您已熟悉Rust基本语法和Cargo依赖包管理工具的使用。
如果这些你都已了解,点击这个Quick Start →,它介绍了如何快速上手spring-rs。
spring-web
(基于axum
实现)spring-sqlx
(整合了sqlx
)spring-postgres
(整合了rust-postgres
)spring-sea-orm
(整合了sea-orm
)spring-redis
(整合了redis
)spring-mail
(整合了lettre
)spring-job
(整合了tokio-cron-scheduler
)spring-stream
(整合了sea-streamer
实现redis-stream、kafka等消息处理)spring-opentelemetry
(整合了opentelemetry
实现logging、metrics、tracing全套可观测性)spring-tarpc
(整合了tarpc
实现RPC调用)也欢迎社区的大牛贡献自己的插件。 Contributing →
点击这里可以查看spring-rs
使用过程中遇到的常见问题 Help →
在这个页面,我会介绍spring-rs如何快速上手spring-rs
在你的Cargo.toml
文件中添加下面的依赖
[dependencies]
+# spring提供了核心的插件系统和有用的过程宏
+spring = "0.1.1"
+# 如果你准备写web应用就添加spring-web
+spring-web = "0.1.1"
+# 如果应用需要和数据库交互就添加spring-sqlx
+spring-sqlx = { version="0.1.1", features = ["mysql"] }
+# spring-rs项目默认使用tokio异步运行时
+tokio = "1"
+
use anyhow::Context;
+use spring::{auto_config, App};
+use spring_sqlx::{
+ sqlx::{self, Row},
+ ConnectPool, SqlxPlugin,
+};
+use spring_web::{
+ axum::response::IntoResponse,
+ error::Result,
+ extractor::{Component, Path},
+ WebConfigurator, WebPlugin,
+};
+use spring_web::{get, route};
+
+// 主函数入口
+#[auto_config(WebConfigurator)] // 自动扫描web router
+#[tokio::main]
+async fn main() {
+ App::new()
+ .add_plugin(SqlxPlugin) // 添加插件
+ .add_plugin(WebPlugin)
+ .run()
+ .await
+}
+
+// get宏指定Http Method和请求路径。spring-rs还提供了post、delete、patch等其他标准http method宏
+#[get("/")]
+async fn hello_world() -> impl IntoResponse {
+ "hello world"
+}
+
+// 也可以使用route宏指定Http Method和请求路径。Path从HTTP请求中提取请求路径中的参数
+#[route("/hello/:name", method = "GET", method = "POST")]
+async fn hello(Path(name): Path<String>) -> impl IntoResponse {
+ format!("hello {name}")
+}
+
+// Component可以抽取由Sqlx插件在AppState中的注册的连接池
+#[get("/version")]
+async fn sqlx_request_handler(Component(pool): Component<ConnectPool>) -> Result<String> {
+ let version = sqlx::query("select version() as version")
+ .fetch_one(&pool)
+ .await
+ .context("sqlx query failed")?
+ .get("version");
+ Ok(version)
+}
+
在项目的根路径下创建一个config
目录,这里会存储spring-rs
的配置文件。
你可以在该目录下先创建一个app.toml
文件,内容如下:
[web]
+port = 8000 # 配置web服务端口,如果不配置默认就是8080端口
+
+[sqlx] # 配置sqlx的数据库连接信息
+uri = "mysql://user:password@127.0.0.1:3306"
+
spring-rs
支持多环境配置:dev(开发)、test(测试)、prod(生产),分别对应着app-dev.toml
、app-dev.toml
、app-prod.toml
三个配置文件。环境配置文件中的配置会覆盖app.toml
主配置文件的配置项。
spring-rs
会根据SPRING_ENV
环境变量激活对应环境的配置文件。
编码完成,请确保你的数据库能正常连接,然后就让我们开始运行起来吧。
cargo run
+
代码变更后自动编译并重启服务,这在开发过程中会非常方便。
使用cargo-watch
可以轻松实现这个功能。
cargo watch -x run
+
在这里回答一些常见问题
TODO
spring-opendal 集成 Apache OpenDAL™ 到 spring-rs 中, 可以为所有类型的存储系统提供了本地支持, 包括对象存储服务, 文件存储服务, 以及许多
具体的例子可以参考 with-spring-web 项目.
cargo run --color=always --package spring-opendal --example with-spring-web --features=services-fs
+
cargo test --test blocking --features="services-memory layers-blocking test-layers" -- --nocapture
+
插件是spring-rs的核心概念之一,spring-rs正是通过插件机制扩展应用的功能
spring
是该项目的核心模块,包含了:配置管理、插件管理、组件管理。
Plugin
特征。Configurable
特征。Clone
特征。注意:为了避免对Component内大结构的进行深拷贝,推荐使用newtype模式通过
Arc<T>
进行引用。
添加依赖
spring = { version = "0.1.1" } # 该crate中包含了插件trait的定义
+serde = { workspace = true, features = ["derive"] } # 用于解析插件的配置项
+
struct MyPlugin;
+
+#[async_trait]
+impl Plugin for MyPlugin {
+ async fn build(&self, app: &mut AppBuilder) {
+ // 调用app.get_config::<Config>(self)方法即可获取配置项
+ match app.get_config::<Config>(self) {
+ Ok(config) => {
+ println!("{:#?}", config);
+ assert_eq!(config.a, 1);
+ assert_eq!(config.b, true);
+
+ // 拿到配置项即可构建相应的组件
+
+ }
+ Err(e) => println!("{:?}", e),
+ }
+ }
+}
+
+/// 插件的配置
+#[derive(Debug, Configurable, Deserialize)]
+#[config_prefix = "my-plugin"]
+struct Config {
+ a: u32,
+ b: bool,
+}
+
完整代码参考plugin-example
,也可以参考自带的其他插件代码。
spring-job是基于tokio-cron-scheduler实现的
spring-job = { version = "0.1.1" }
+
App实现了JobConfigurator特征,可以通过该特征配置调度任务:
1 #[tokio::main]
+ 2 async fn main() {
+ 3 App::new()
+ 4 .add_plugin(JobPlugin)
+ 5 .add_plugin(SqlxPlugin)
+ 6 .add_jobs(jobs())
+ 7 .run()
+ 8 .await
+ 9 }
+ 10
+ 11 fn jobs() -> Jobs {
+ 12 Jobs::new().typed_job(cron_job)
+ 13 }
+ 14
+ 15 #[cron("1/10 * * * * *")]
+ 16 async fn cron_job() {
+ 17 println!("cron scheduled: {:?}", SystemTime::now())
+ 18 }
+
你也可以使用auto_config
宏来实现自动配置,这个过程宏会自动将被过程宏标记的调度任务注册进app中:
+#[auto_config(JobConfigurator)]
+ #[tokio::main]
+ async fn main() {
+ App::new()
+ .add_plugin(JobPlugin)
+ .add_plugin(SqlxPlugin)
+- .add_jobs(jobs())
+ .run()
+ .await
+}
+
上面的SqlxPlugin
插件为我们自动注册了一个Sqlx连接池组件,我们可以使用Component
从App中提取这个连接池。需要注意spring-job
的Component
和spring-web
的Component
虽然实现原理类似,但这两个extractor归属不同的crate下。
use spring_sqlx::{
+ sqlx::{self, Row}, ConnectPool
+};
+use spring_job::cron;
+use spring_job::extractor::Component;
+
+#[cron("1/10 * * * * *")]
+async fn cron_job(Component(db): Component<ConnectPool>) {
+ let time: String = sqlx::query("select DATE_FORMAT(now(),'%Y-%m-%d %H:%i:%s') as time")
+ .fetch_one(&db)
+ .await
+ .context("query failed")
+ .unwrap()
+ .get("time");
+ println!("cron scheduled: {:?}", time)
+}
+
logger插件是基于tracing实现的
日志插件是内置在spring
核心模块的插件,也是spring加载的第一个插件。这个插件不需要开发者手动添加。
该插件是集成了最流行的日志库tracing
,并提供了以下配置项
[logger]
+enable = true # 是否启用日志功能,默认是开启的
+pretty_backtrace = false # 是否打印堆栈信息,默认是关闭的,建议只在应用开发阶段开启
+level = "info" # 默认日志级别是info
+format = "compact" # 日志格式支持compact、pretty、json,默认是compact
+override_filter = "info,axum=debug" # 重写默认的日志过滤级别,可以针对crate库指定日志级别
+file = { enabled = true } # 是否将日志写入文件中,默认没有开启
+
你也可以对日志文件进行更详细的配置
[logger.file]
+enabled = true # 是否将日志写入文件
+non_blocking = true # 是否启用非阻塞方式写入,默认开启
+format = "compact" # 日志格式支持compact、pretty、json,默认是compact
+rotation = "daily" # 日志滚动方式minutely、hourly、daily、never,默认按天滚动
+dir = "./logs" # 日志文件目录
+filename_prefix = "app" # 日志文件前缀名
+filename_suffix = "log" # 日志文件后缀名
+max_log_files = 365 # 保留的最大日志数量
+
spring-mail是针对lettre的自动装配
lettre是Rust最流行的邮件客户端,并且支持异步API。spring-mail主要使用它的tokio异步API。
spring-mail = { version = "0.1.1" }
+
[mail]
+host = "smtp.gmail.com" # SMTP邮件服务器地址,
+port = 465 # SMTP服务器端口号
+secure = true # 响应超时时间,单位毫秒
+auth = { user = "user@gmail.com", password = "passwd" } # 认证信息
+
配置完上述配置项后,插件会自动注册一个Mailer
STMP异步客户端。该对象是lettre::AsyncSmtpTransport<Tokio1Executor>
的别名。
pub type Mailer = lettre::AsyncSmtpTransport<Tokio1Executor>;
+
MailPlugin
插件为我们自动注册了一个SMTP客户端,我们可以使用Component
从AppState中提取这个连接池,Component
是一个axum的extractor。
async fn send_mail(Component(mailer): Component<Mailer>) -> Result<impl IntoResponse> {
+ let email = Message::builder()
+ .from("NoBody <nobody@domain.tld>".parse().unwrap())
+ .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
+ .to("hff1996723@163.com".parse().unwrap())
+ .subject("Happy new year")
+ .header(ContentType::TEXT_PLAIN)
+ .body(String::from("Be happy!"))
+ .unwrap();
+ let resp = mailer.send(email).await.context("send mail failed")?;
+ Ok(Json(resp))
+}
+
完整代码参考mail-example
spring-postgres是针对rust-postgres的自动装配
tokio-postgres是和sqlx类似的数据库连接工具,和sqlx不同的是它只专注于实现postgresql的数据库连接。
spring-postgres = { version = "0.1.1" }
+
[postgres]
+connect = "postgres://root:12341234@localhost:5432/myapp_development" # 要连接的数据库地址
+
配置完上述配置项后,插件会自动注册一个Postgres
对象。该对象包装了tokio_postgres::Client
。
pub struct Postgres(Arc<tokio_postgres::Client>);
+
PgPlugin
插件为我们自动注册了一个Postgres
对象,我们可以使用Component
从AppState中提取这个连接池,Component
是一个axum的extractor。
#[get("/postgres")]
+async fn hello_postgres(Component(pg): Component<Postgres>) -> Result<impl IntoResponse> {
+ let rows = pg
+ .query("select version() as version", &[])
+ .await
+ .context("query postgresql failed")?;
+
+ let version: String = rows[0].get("version");
+
+ Ok(Json(version))
+}
+
完整代码参考postgres-example
spring-redis是针对redis-rs的自动装配
spring-redis = { version = "0.1.1" }
+
[redis]
+uri = "redis://127.0.0.1/" # redis 数据库地址
+
+# 下面都是可选配置
+connection_timeout = 10000 # 连接超时时间,单位毫秒
+response_timeout = 1000 # 响应超时时间,单位毫秒
+number_of_retries = 6 # 重试次数,间隔时间按指数增长
+exponent_base = 2 # 间隔时间指数基数,单位毫秒
+factor = 100 # 间隔时间增长因子,默认100倍增长
+max_delay = 60000 # 最大间隔时间
+
配置完上述配置项后,插件会自动注册一个Redis
连接管理对象。该对象是redis::aio::ConnectionManager
的别名。
pub type Redis = redis::aio::ConnectionManager;
+
RedisPlugin
插件为我们自动注册了一个连接管理对象,我们可以使用Component
从AppState中提取这个连接池,Component
是一个axum的extractor。
async fn list_all_redis_key(Component(mut redis): Component<Redis>) -> Result<impl IntoResponse> {
+ let keys: Vec<String> = redis.keys("*").await.context("redis request failed")?;
+ Ok(Json(keys))
+}
+
完整代码参考redis-example
spring-sea-orm是针对sea-orm的自动装配
spring-sea-orm = { version = "0.1.1", features = ["postgres"] }
+sea-orm = { version = "1.0" } # 主要为了适配sea-orm-cli生成的entity代码
+
可以替换postgres
、mysql
、sqlite
feature来选择合适的数据库驱动。
[sea-orm]
+uri = "postgres://root:123456@localhost:5432/pg_db" # 数据库地址
+min_connections = 1 # 连接池的最小连接数,默认值为1
+max_connections = 10 # 连接池的最大连接数,默认值为10
+acquire_timeout = 30000 # 占用连接超时时间,单位毫秒,默认30s
+idle_timeout = 600000 # 连接空闲时间,单位毫秒,默认10min
+connect_timeout = 1800000 # 连接的最大存活时间,单位毫秒,默认30min
+enable_logging = true # 打印sql日志
+
配置完上述配置项后,插件会自动注册一个DbConn
连接池对象。该对象是sea_orm::DbConn
的别名。
pub type DbConn = sea_orm::DbConn;
+
sea-orm-cli提供了非常棒的模型代码生成的功能。你只需在数据库内定义好表结构,经过简单的配置就能生成与数据库结构对应的模型代码,这可以节省大量的代码编写工作。
SeaOrmPlugin
插件为我们自动注册了一个连接池组件,我们可以使用Component
从AppState中提取这个连接池,Component
是一个axum的extractor。
use spring_sqlx::{sqlx::{self, Row}, ConnectPool};
+use spring_web::get;
+use spring_web::extractor::Component;
+use spring_web::error::Result;
+use anyhow::Context;
+
+#[get("/:id")]
+async fn get_todo_list(
+ Component(db): Component<DbConn>,
+ Path(id): Path<i32>
+) -> Result<String> {
+ let rows = TodoItem::find()
+ .filter(todo_item::Column::ListId.eq(id))
+ .all(&db)
+ .await
+ .context("query todo list failed")?;
+ Ok(Json(rows))
+}
+
spring-sea-orm
为SeaOrm的Select扩展了PaginationExt特征。
另外还提供了web翻页参数的解析,只需在依赖中添加with-web
功能即可。
spring-sea-orm = { version = "<version>", features = ["postgres", "with-web"] }
+
配置方式如下:
# sea-orm-web配置
+[sea-orm-web]
+one_indexed = false # 基于1的索引,默认关闭
+max_page_size = 2000 # 支持的最大页大小,避免服务器攻击出现OOM,默认值2000
+default_page_size = 20 # 默认页大小,20
+
使用方式如下:
#[get("/")]
+async fn get_todo_list(
+ Component(db): Component<DbConn>,
+ Query(query): Query<TodoListQuery>,
+ pagination: Pagination,
+) -> Result<impl IntoResponse> {
+ let rows = TodoList::find()
+ .filter(query)
+ .page(&db, pagination)
+ .await
+ .context("query todo list failed")?;
+ Ok(Json(rows))
+}
+
完整代码参考sea-orm-example
spring-sqlx是针对sqlx的自动装配
spring-sqlx = { version = "0.1.1", features = ["mysql"] }
+
可以替换postgres
、mysql
、sqlite
feature来选择合适的数据库驱动。
[sqlx]
+uri = "postgres://root:123456@localhost:5432/pg_db" # 数据库地址
+min_connections = 1 # 连接池的最小连接数,默认值为1
+max_connections = 10 # 连接池的最大连接数,默认值为10
+acquire_timeout = 30000 # 占用连接超时时间,单位毫秒,默认30s
+idle_timeout = 600000 # 连接空闲时间,单位毫秒,默认10min
+connect_timeout = 1800000 # 连接的最大存活时间,单位毫秒,默认30min
+
配置完上述配置项后,插件会自动注册一个ConnectPool
连接池对象。该对象是sqlx::AnyPool
的别名。
pub type ConnectPool = sqlx::AnyPool;
+
SqlxPlugin
插件为我们自动注册了一个Sqlx连接池组件,我们可以使用Component
从AppState中提取这个连接池,Component
是一个axum的extractor。
use spring_sqlx::{sqlx::{self, Row}, ConnectPool};
+use spring_web::get;
+use spring_web::extractor::Component;
+use spring_web::error::Result;
+use anyhow::Context;
+
+#[get("/version")]
+async fn mysql_version(Component(pool): Component<ConnectPool>) -> Result<String> {
+ let version = sqlx::query("select version() as version")
+ .fetch_one(&pool)
+ .await
+ .context("sqlx query failed")?
+ .get("version");
+ Ok(version)
+}
+
完整代码参考sqlx-example
spring-stream是基于sea-streamer实现的
spring-stream = { version = "0.1.1",features=["file"] }
+
spring-stream支持file
、stdio
、redis
、kafka
四种消息存储。
[stream]
+uri = "file://./stream" # StreamerUri 数据流地址
+
StreamUri支持file、stdio、redis、kafka。uri的格式具体参考StreamerUri。
# 文件流配置
+[stream.file]
+connect = { create_file = "CreateIfNotExists" }
+
+# 标准流配置
+[stream.stdio]
+connect = { loopback = false }
+
+# redis流配置
+[stream.redis]
+connect = { db=0,username="user",password="passwd" }
+
+# kafka流配置
+[stream.kafka]
+connect = { sasl_options={mechanism="Plain",username="user",password="passwd"}}
+
StreamPlugin
注册了一个Producer
用于发送消息。如果需要发送json格式的消息,需要在依赖项中添加json
的feature:
spring-stream = { version = "0.1.1", features=["file","json"] }
+
1 #[auto_config(WebConfigurator)]
+ 2 #[tokio::main]
+ 3 async fn main() {
+ 4 App::new()
+ 5 .add_plugin(StreamPlugin)
+ 6 .add_plugin(WebPlugin)
+ 7 .run()
+ 8 .await
+ 9 }
+ 10
+ 11 #[get("/")]
+ 12 async fn send_msg(Component(producer): Component<Producer>) -> Result<impl IntoResponse> {
+ 13 let now = SystemTime::now();
+ 14 let json = json!({
+ 15 "success": true,
+ 16 "msg": format!("This message was sent at {:?}", now),
+ 17 });
+ 18 let resp = producer
+ 19 .send_json("topic", json)
+ 20 .await
+ 21 .context("send msg failed")?;
+ 22
+ 23 let seq = resp.sequence();
+ 24 Ok(Json(json!({"seq":seq})))
+ 25 }
+
spring-stream
提供了stream_listener
的过程宏来订阅指定topic的消息,代码如下:
1 #[tokio::main]
+ 2 async fn main() {
+ 3 App::new()
+ 4 .add_plugin(StreamPlugin)
+ 5 .add_consumer(consumers())
+ 6 .run()
+ 7 .await
+ 8 }
+ 9
+ 10 fn consumers() -> Consumers {
+ 11 Consumers::new().typed_consumer(listen_topic_do_something)
+ 12 }
+ 13
+ 14 #[stream_listener("topic", file_consumer_options = fill_file_consumer_options)]
+ 15 async fn listen_topic_do_something(Json(payload): Json<Payload>) {
+ 16 tracing::info!("{:#?}", payload);
+ 17 }
+ 18
+ 19 fn fill_file_consumer_options(opts: &mut FileConsumerOptions) {
+ 20 opts.set_auto_stream_reset(AutoStreamReset::Earliest);
+ 21 }
+
完整示例代码查看stream-file-example、stream-redis-example、stream-kafka-example
spring-web是基于axum实现的
Axum是rust社区最优秀的Web框架之一,它是由tokio官方维护的一个基于hyper的子项目。Axum提供了web路由,声明式的HTTP请求解析,HTTP响应的序列化等功能,而且能够与tower生态中的中间件结合。
spring-web = { version = "0.1.1" }
+
[web]
+binding = "172.20.10.4" # 要绑定的网卡IP地址,默认127.0.0.1
+port = 8000 # 要绑定的端口号,默认8080
+
+# web中间件配置
+[web.middlewares]
+compression = { enable = true } # 开启压缩中间件
+logger = { enable = true } # 开启日志中间件
+catch_panic = { enable = true } # 捕获handler产生的panic
+limit_payload = { enable = true, body_limit = "5MB" } # 限制请求体大小
+timeout_request = { enable = true, timeout = 60000 } # 请求超时时间60s
+
+# 跨域配置
+cors = { enable = true, allow_origins = [
+ "*.github.io",
+], allow_headers = [
+ "Authentication",
+], allow_methods = [
+ "GET",
+ "POST",
+], max_age = 60 }
+
+# 静态资源配置
+static = { enable = true, uri = "/static", path = "static", precompressed = true, fallback = "index.html" }
+
App实现了WebConfigurator特征,可以通过该特征指定路由配置:
1 #[tokio::main]
+ 2 async fn main() {
+ 3 App::new()
+ 4 .add_plugin(SqlxPlugin)
+ 5 .add_plugin(WebPlugin)
+ 6 .add_router(router())
+ 7 .run()
+ 8 .await
+ 9 }
+ 10
+ 11 fn router() -> Router {
+ 12 Router::new().typed_route(hello_word)
+ 13 }
+ 14
+ 15 #[get("/")]
+ 16 async fn hello_word() -> impl IntoResponse {
+ 17 "hello word"
+ 18 }
+
你也可以使用auto_config
宏来实现自动配置,这个过程宏会自动将被过程宏标记的路由注册进app中:
+#[auto_config(WebConfigurator)]
+ #[tokio::main]
+ async fn main() {
+ App::new()
+ .add_plugin(SqlxPlugin)
+ .add_plugin(WebPlugin)
+- .add_router(router())
+ .run()
+ .await
+}
+
上面例子中的get
是一个属性宏,spring-web
提供了八个标准HTTP METHOD的过程宏:get
、post
、patch
、put
、delete
、head
、trace
、options
。
也可以使用route
宏同时绑定多个method:
use spring_web::route;
+use spring_web::axum::response::IntoResponse;
+
+#[route("/test", method = "GET", method = "HEAD")]
+async fn example() -> impl IntoResponse {
+ "hello world"
+}
+
除此之外,spring还支持一个handler绑定多个路由,这需要用到routes
属性宏:
use spring_web::{routes, get, delete};
+use spring_web::axum::response::IntoResponse;
+
+#[routes]
+#[get("/test")]
+#[get("/test2")]
+#[delete("/test")]
+async fn example() -> impl IntoResponse {
+ "hello world"
+}
+
上面的例子中SqlxPlugin
插件为我们自动注册了一个Sqlx连接池组件,我们可以使用Component
从State中提取这个连接池,Component
是一个axum的extractor。
#[get("/version")]
+async fn mysql_version(Component(pool): Component<ConnectPool>) -> Result<String> {
+ let version = sqlx::query("select version() as version")
+ .fetch_one(&pool)
+ .await
+ .context("sqlx query failed")?
+ .get("version");
+ Ok(version)
+}
+
axum也提供了其他的extractor,这些都被reexport到了spring_web::extractor
下。
你可以用Config
抽取配置toml中的配置。
#[derive(Debug, Configurable, Deserialize)]
+#[config_prefix = "custom"]
+struct CustomConfig {
+ a: u32,
+ b: bool,
+}
+
+#[get("/config")]
+async fn use_toml_config(Config(conf): Config<CustomConfig>) -> impl IntoResponse {
+ format!("a={}, b={}", conf.a, conf.b)
+}
+
在你的配置文件中添加相应配置:
[custom]
+a = 1
+b = true
+
完整代码参考web-example
spring-rs是Rust编写的应用框架,与java生态的spring-boot类似
快速上手得益于出色的Rust语言,spring-rs拥有与c/c++媲美的极致性能
相比C/C++,spring-rs使用的Rust语言提供了内存安全和线程安全的能力
spring-rs的核心代码不超过5000行,打包的release版二进制文件也非常小巧
spring-rs提供了清晰明了的API和可选的过程宏来简化开发
spring-rs采用高扩展性的插件模式,用户可以自定义插件扩展程序功能
spring-rs用toml配置应用和插件,提升应用灵活性