Skip to content

Commit 6834597

Browse files
committed
v0.1.0
1 parent da0c4d0 commit 6834597

37 files changed

+2884
-11
lines changed

.gitignore

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
# Generated by Cargo
2-
# will have compiled files and executables
31
debug/
42
target/
53

6-
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7-
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
84
Cargo.lock
5+
.DS_Store
96

107
# These are backup files generated by rustfmt
118
**/*.rs.bk
129

1310
# MSVC Windows builds of rustc generate these, which store debugging information
1411
*.pdb
1512

16-
# RustRover
17-
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
18-
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
19-
# and can be added to the global gitignore or merged into this file. For a more nuclear
20-
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
21-
#.idea/
13+
# Configuration directory generated by CLion
14+
.idea
15+
16+
# Configuration directory generated by VSCode
17+
.vscode

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[workspace]
2+
members = ["actix-cloud", "actix-cloud-codegen"]
3+
resolver = "2"
4+
exclude = ["examples"]

Makefile

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
SHELL = /bin/bash
2+
3+
.ONESHELL:
4+
.PHONY: check help
5+
6+
all: help
7+
8+
## check: Check code and style.
9+
check:
10+
@cargo clippy -- -D clippy::all
11+
@cargo fmt --all -- --check
12+
13+
## help: Show this help.
14+
help: Makefile
15+
@echo Usage: make [command]
16+
@sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'

README.md

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,148 @@
1-
# actix-cloud
1+
# actix-cloud
2+
3+
Actix Cloud is an all-in-one web framework based on [Actix Web](https://github.com/actix/actix-web).
4+
5+
## Features
6+
Actix Cloud is highly configurable. You can only enable needed features, implement your own feature backend or even use other libraries.
7+
8+
- [logger](#logger) (Default: Enable)
9+
- [i18n](#i18n) (Default: Disable)
10+
- [security](#security) (Embedded)
11+
- memorydb backend
12+
- [default](#memorydb-default) (Embedded)
13+
- [redis](#memorydb-redis) (Default: Disable)
14+
- [auth](#auth) (Embedded)
15+
- [session](#session) (Default: Disable)
16+
17+
## Guide
18+
19+
### Quick Start
20+
You can refer to [Hello world](examples/hello_world/) example for basic usage.
21+
22+
### Application
23+
Since application configuration can be quite dynamic, you need to build on your own. Here are some useful middlewares:
24+
25+
```
26+
App::new()
27+
.wrap(middleware::Compress::default()) // compress page
28+
.wrap(SecurityHeader::default().build()) // default security header
29+
.wrap(SessionMiddleware::builder(memorydb.clone(), Key::generate()).build()) // session
30+
...
31+
.app_data(state_cloned.clone())
32+
```
33+
34+
### logger
35+
We use [tracing](https://github.com/tokio-rs/tracing) as our logger library. It is thread safe. You can use it everywhere.
36+
37+
Start logger:
38+
```
39+
LoggerBuilder::new().level(Level::DEBUG).start() // colorful output
40+
LoggerBuilder::new().json().start() // json output
41+
```
42+
You can also customize the logger with `filter`, `transformer`, etc.
43+
44+
Reinit logger (e.g., in plugins), or manually send logs:
45+
```
46+
logger.init(LoggerBuilder::new());
47+
logger.sender().send(...);
48+
```
49+
50+
### i18n
51+
We use `rust-i18n-support` from [rust-i18n](https://github.com/longbridgeapp/rust-i18n) as our i18n core.
52+
53+
Load locale:
54+
```
55+
let mut locale = Locale::new(String::from("en-US"));
56+
locale.add_locale(i18n!("locale"));
57+
```
58+
59+
Translate:
60+
```
61+
t!(locale, "hello.world")
62+
t!(locale, "hello.name", name = "MEME")
63+
```
64+
65+
See [examples](examples/i18n) for more usage.
66+
67+
### security
68+
Middleware to add security headers:
69+
```
70+
app.wrap(SecurityHeader::default().build())
71+
```
72+
73+
Default header:
74+
```
75+
X-Content-Type-Options: nosniff
76+
Referrer-Policy: strict-origin-when-cross-origin
77+
X-Frame-Options: DENY
78+
X-XSS-Protection: 1; mode=block
79+
Cross-Origin-Opener-Policy: same-origin
80+
Content-Security-Policy: default-src 'none'; script-src 'none'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'
81+
```
82+
83+
Enable HSTS when using HTTPS:
84+
```
85+
security_header.set_default_hsts();
86+
```
87+
```
88+
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
89+
```
90+
91+
### memorydb-default
92+
Actix Cloud has a default memory database backend used for sessions. You can also use your own backend if you implement `actix_cloud::memorydb::MemoryDB`.
93+
94+
**Note: the default backend does not have memory limitation, DDoS is possible if gateway rate limiting is not implemented**
95+
96+
```
97+
DefaultBackend::new().await.unwrap()
98+
```
99+
100+
### memorydb-redis
101+
Redis can be used as another backend for memory database.
102+
103+
```
104+
RedisBackend::new("redis://user:[email protected]:6379/0").await.unwrap(),
105+
```
106+
107+
### auth
108+
Authentication is quite simple, you only need to implement an extractor and a checker.
109+
110+
Extractor is used to extract your own authentication type from request. For example, assume we use 0 for guest and 1 for admin. Our authentication type is just `Vec<u32>`:
111+
```
112+
fn perm_extractor(req: &mut ServiceRequest) -> Vec<u32> {
113+
let mut ret = Vec::new();
114+
ret.push(0); // guest permission is assigned by default.
115+
116+
// test if query string has `admin=1`.
117+
let qs = QString::from(req.query_string());
118+
if qs.get("admin").is_some_and(|x| x == "1") {
119+
ret.push(1);
120+
}
121+
ret
122+
}
123+
```
124+
125+
Checker is used to check the permission, the server will return 403 if the return value is false:
126+
```
127+
fn is_guest(p: Vec<u32>) -> bool {
128+
p.into_iter().find(|x| *x == 0).is_some()
129+
}
130+
```
131+
132+
Then build the `Router` and configure in the App using `build_router`:
133+
```
134+
app.service(scope("/api").configure(build_router(...)))
135+
```
136+
137+
### session
138+
Most features and usages are based on [actix-session](https://crates.io/crates/actix-session). Except for these:
139+
- MemoryDB is the only supported storage.
140+
- Error uses `actix-cloud::error::Error`.
141+
- You can set `_ttl` in the session to override the TTL of the session.
142+
143+
```
144+
app.wrap(SessionMiddleware::builder(memorydb.clone(), Key::generate()).build())
145+
```
146+
147+
## License
148+
This project is licensed under the [MIT license](LICENSE).

actix-cloud-codegen/Cargo.toml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[package]
2+
name = "actix-cloud-codegen"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[features]
7+
default = []
8+
i18n = ["dep:rust-i18n-support"]
9+
10+
[dependencies]
11+
quote = "1.0.36"
12+
syn = "2.0.72"
13+
proc-macro2 = "1.0.86"
14+
15+
rust-i18n-support = { version = "3.1.1", optional = true }
16+
17+
[lib]
18+
proc-macro = true

actix-cloud-codegen/src/i18n.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use quote::quote;
2+
3+
use std::collections::HashMap;
4+
use syn::parse;
5+
6+
#[derive(Debug)]
7+
pub(crate) struct Option {
8+
pub(crate) locales_path: String,
9+
}
10+
11+
impl parse::Parse for Option {
12+
fn parse(input: parse::ParseStream) -> parse::Result<Self> {
13+
let locales_path = input.parse::<syn::LitStr>()?.value();
14+
15+
Ok(Self { locales_path })
16+
}
17+
}
18+
19+
pub(crate) fn generate_code(data: &HashMap<String, String>) -> proc_macro2::TokenStream {
20+
let mut locales = Vec::<proc_macro2::TokenStream>::new();
21+
22+
for (k, v) in data {
23+
let k = k.to_owned();
24+
let v = v.to_owned();
25+
26+
locales.push(quote! {
27+
#k => #v,
28+
});
29+
}
30+
31+
// result
32+
quote! {
33+
actix_cloud::map! [
34+
#(#locales)*
35+
"" => "" // eat last comma
36+
]
37+
}
38+
}

actix-cloud-codegen/src/lib.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use proc_macro::TokenStream;
2+
use quote::quote;
3+
#[cfg(feature = "i18n")]
4+
use rust_i18n_support::{is_debug, load_locales};
5+
#[cfg(feature = "i18n")]
6+
use std::{collections::HashMap, env, path};
7+
8+
#[cfg(feature = "i18n")]
9+
mod i18n;
10+
11+
#[proc_macro_attribute]
12+
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
13+
let mut output: TokenStream = (quote! {
14+
#[::actix_cloud::actix_web::rt::main(system = "::actix_cloud::actix_web::rt::System")]
15+
})
16+
.into();
17+
18+
output.extend(item);
19+
output
20+
}
21+
22+
#[cfg(feature = "i18n")]
23+
/// Init I18n translations.
24+
///
25+
/// This will load all translations by glob `**/*.yml` from the given path.
26+
///
27+
/// ```ignore
28+
/// i18n!("locales");
29+
/// ```
30+
///
31+
/// # Panics
32+
///
33+
/// Panics is variable `CARGO_MANIFEST_DIR` is empty.
34+
#[proc_macro]
35+
pub fn i18n(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
36+
let option = match syn::parse::<i18n::Option>(input) {
37+
Ok(input) => input,
38+
Err(err) => return err.to_compile_error().into(),
39+
};
40+
41+
// CARGO_MANIFEST_DIR is current build directory
42+
let cargo_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is empty");
43+
let current_dir = path::PathBuf::from(cargo_dir);
44+
let locales_path = current_dir.join(option.locales_path);
45+
46+
let data = load_locales(&locales_path.display().to_string(), |_| false);
47+
let mut translation = HashMap::new();
48+
for (lang, mp) in data {
49+
for (k, v) in mp {
50+
translation.insert(format!("{lang}.{k}"), v);
51+
}
52+
}
53+
let code = i18n::generate_code(&translation);
54+
55+
if is_debug() {
56+
println!("{code}");
57+
}
58+
59+
code.into()
60+
}

actix-cloud/Cargo.toml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
[package]
2+
name = "actix-cloud"
3+
version = "0.1.0"
4+
edition = "2021"
5+
authors = ["MXWXZ <[email protected]>"]
6+
description = "Actix Cloud is an all-in-one web framework based on Actix Web."
7+
8+
[features]
9+
default = ["logger"]
10+
serde = ["dep:serde", "dep:serde_json", "dep:serde_with"]
11+
redis = ["dep:redis"]
12+
rustls = ["actix-web/rustls-0_23", "dep:rustls", "dep:rustls-pemfile"]
13+
logger = ["dep:tracing", "dep:tracing-subscriber", "dep:colored", "serde"]
14+
i18n = ["actix-cloud-codegen/i18n"]
15+
session = ["dep:actix-utils", "serde", "dep:rand"]
16+
17+
[dependencies]
18+
actix-web = { version = "4.8.0", features = ["secure-cookies"] }
19+
parking_lot = "0.12.3"
20+
thiserror = "1.0.63"
21+
tokio = { version = "1.39.2", features = ["full"] }
22+
async-trait = "0.1.81"
23+
chrono = "0.4.38"
24+
futures = "0.3.30"
25+
26+
# serde
27+
serde = { version = "1.0.205", features = ["derive"], optional = true }
28+
serde_json = { version = "1.0.122", optional = true }
29+
serde_with = { version = "3.9.0", optional = true }
30+
31+
# logger
32+
tracing = { version = "0.1.40", optional = true }
33+
tracing-subscriber = { version = "0.3.18", features = [
34+
"json",
35+
"parking_lot",
36+
], optional = true }
37+
colored = { version = "2.1.0", optional = true }
38+
39+
# redis
40+
redis = { version = "0.26.1", features = [
41+
"tokio-rustls-comp",
42+
"connection-manager",
43+
], optional = true }
44+
45+
# rustls
46+
rustls = { version = "0.23.12", optional = true }
47+
rustls-pemfile = { version = "2.1.3", optional = true }
48+
49+
# session
50+
actix-utils = { version = "3.0.1", optional = true }
51+
rand = { version = "0.8.5", optional = true }
52+
53+
actix-cloud-codegen = { version = "0.1.0", path = "../actix-cloud-codegen" }

0 commit comments

Comments
 (0)