salvo-rs / salvo Goto Github PK
View Code? Open in Web Editor NEWA powerful web framework built with a simplified design.
Home Page: https://salvo.rs
License: Apache License 2.0
A powerful web framework built with a simplified design.
Home Page: https://salvo.rs
License: Apache License 2.0
Hello,
I am very new to both Rust and Salvo, and i was hoping someone could point me into the right direction.
i have build a client that sends some data from BORG Backup in json to a web api. This is whats beeing send:
{"token": "some-authentication-token", "date": "20220801", "hostname": "\"hostname\"", "name": "\"videoserver-2022-08-01T04:00:01-vod\"", "original_size": "6629973649001", "repository_id": "\"8b7c8f3f50a639069ec9d19f3a675382\"", "compressed_size": "6472889824365", "files": "2421814", "duration": "487.036629"}
now i need to post this to a web api and put it in a database. Can anyone point me in the right direction how i can use Salvo for this?
Hi.
Congrats for this slick and intuitive framework!
So, I took a look at the CORS page, but it is blank, however, I'm not sure if it still undocumented, or really a bug in the website:
Link: https://salvo.rs/book/middlewares/cors
cheers
$ cargo build --release
Compiling salvo_core v0.13.3 (/media/data1/rust/salvo/core)
error[E0433]: failed to resolve: use of undeclared type `StatusCode`
--> core/src/error.rs:57:29
|
57 | res.set_status_code(StatusCode::INTERNAL_SERVER_ERROR);
| ^^^^^^^^^^ use of undeclared type `StatusCode`
https://salvo.rs/book/quick-start/hello_world/
quick-start中的hello_world 編譯出現問題
error[E0599]: no method named unwrap
found for unit type ()
in the current scope
--> src/main.rs:10:74
|
10 | Server::new(TcpListener::bind("127.0.0.1:7878")).serve(router).await.unwrap();
| ^^^^^^ method not found in ()
之後我看github中的其他例子時, 才發現並不需要unwrap()
Cloned salvo locally and noticed it took a while to download (abt 30s). I think there might be a binary in the git history.
Cloning into 'salvo'...
remote: Enumerating objects: 7643, done.
remote: Counting objects: 100% (7643/7643), done.
remote: Compressing objects: 100% (1520/1520), done.
remote: Total 24612 (delta 6356), reused 7232 (delta 5984), pack-reused 16969
Receiving objects: 100% (24612/24612), 100.63 MiB | 2.94 MiB/s, done.
Resolving deltas: 100% (19187/19187), done.
```rust
It appears that the only way to manage application state is static variables which might not be safe to be accessed in multi-threaded environment. Is there any plan to have a feature like app_data in Actix?
Is it possible to add support for ructe templates? This is generic enough that it can probably be in the core without any feature flags.
Currently I need to create a helper function and set status code and content-type.
fn render<F>(res: &mut Response, do_render: F) -> Result<&mut Response>
where
F: FnOnce(&mut Vec<u8>) -> std::io::Result<()>,
{
let mut buf = Vec::new();
do_render(&mut buf)?;
res.render(Text::Html(String::from_utf8(buf)?));
Ok(res)
}
#[handler]
pub async fn hello_world(res: &mut Response) -> Result<()> {
render(res, |o| templates::hello_world(o))?
.with_status_code(StatusCode::OK)
.with_header("content-type", "text/html", true)?;
}
I would like salvo to support callback (F: FnOnce(&mut Vec<u8>) -> std::io::Result<()>
) so I can write the following code.
#[handler]
pub async fn hello_world(res: &mut Response) -> Result<()> {
res.render(Text::Html(|o| templates::hello_world(o)?));
Ok(())
}
Since internally it creates Vec
probably need to support capacity too which could use tuples.
#[handler]
pub async fn get_posts(res: &mut Response) -> Result<()> {
res.render(Text::Html((|o| templates::hello_world(o)?), 1000));
Ok(())
}
Here is a reference for trillium-ructe. https://github.com/prabirshrestha/trillium-ructe
Describe the bug
could not find extra
in salvo
Rss/Atom are quite popular for use in websites. Would be good if we can have following apis.
res.render(Text::Rss(....))
res.render(Text::Atom(....))
extract_body, extract_json do not need the target struct is Extractible.
So, I decide to rename to parse_body, parse_json.
redirect temporary seems to doing redirect permanent internally.
salvo/core/src/http/response.rs
Lines 358 to 359 in 0fb3099
Might be better to expose apis similar to axum instead? i.e. redirect_temporary
, redirect_permanent
, redirect_found
, redirect_to
.
https://docs.rs/axum/0.2.3/axum/response/struct.Redirect.html
Found a code on the book
#[derive(Serialize, Debug)] struct User { name: String, } let user = User{name: "jobs"}; res.render(Text::Json(user));
Seems like it is no longer working as with implementation of Piece, the Text::Json no longer accepts Serializable but a Piece. Probably there is missing implementation of Piece from Serializable.
Hey there!
I belong to an open source security research community, and a member (@w-henderson) has found an issue, but doesn’t know the best way to disclose it.
If not a hassle, might you kindly add a SECURITY.md
file with an email, or another contact method? GitHub recommends this best practice to ensure security issues are responsibly disclosed, and it would serve as a simple instruction for security researchers in the future.
Thank you for your consideration, and I look forward to hearing from you!
(cc @huntr-helper)
Is your feature request related to a problem? Please describe.
In order to correlate logs we need to have some sort of ids.
Describe the solution you'd like
Add support for request id similar to the following.
Describe alternatives you've considered
Roll my own handler. This is common pattern that may be worth having as extras.
Additional context
While there are multiple types of id that could be used for logging I usually look for 2 ids one that is generated by the client and the other that is generated by the server.
For client request id.
// default
.hoop(RequestIdHandler::new())
// customized
.hoop(RequestIdHandler::new()
.with_request_header("x-request-id")
.with_response_header("x-request-id")
.with_id_generator(some_fn)) // if the id is absent probably want to auto generate.
For server request id. Since client request can't be trusted as they can replay the requests using curl/fiddler tools. We can also have an option to always force generate the id.
// default
.hoop(ResponseIdHandler::new())
// customized
.hoop(ResponseIdHandler::new()
.with_response_header("x-server-request-id")
.with_id_generator(some_fn))
Should also have ext methods so the id for RequestIdHandler
and ResponseIdHandler
can be retrieved.
I would like to set appropriate caching headers for any responses include html and apis besides file. This improves performance on slow network when fetching it again.
I would like to have similar feature to trillium's caching-headers.
(In case you are wondering why I'm filing issue, I'm trying to port by blog from trillium to salvo https://github.com/prabirshrestha/rblog with the main reason being trillium not support first class error handling compared to salvo)
main
in tokio
and main
function is not allowed to be async
error[E0433]: failed to resolve: could not find main
in tokio
--> examples\hello-world\src\main.rs:20:10
|
20 | #[tokio::main]
| ^^^^ could not find main
in tokio
error[E0752]: main
function is not allowed to be async
--> examples\hello-world\src\main.rs:21:1
|
21 | async fn main() {
| ^^^^^^^^^^^^^^^ main
function is not allowed to be async
Some errors have detailed explanations: E0433, E0752.
For more information about an error, try rustc --explain E0433
.
error: could not compile example-hello-world
due to 2 previous errors
Add techempower web framework benchmark when more people know about salvo
当前路由不匹配的,会返回一个默认的html页面,这对api服务是比较奇怪的, 根路由配置一个after来抓404貌似不行?
像actix-web的话,允许用户分别配置一个处理404,json表单解析错误之类的Fn
Describe the bug
i am trying to read json thats posted to my handler. the documentation on https://salvo.rs/book/basics/request/ says i should do it like so: req.read::<User>().await;
but when i do this, the program wont compile due to the above error
dependencies
rustc 1.51.0 (2fd73fabe 2021-03-23)
Deepin GNU/Linux 20.2
[dependencies]
salvo = { version = "0.11", features = ["full"] }
tokio = { version = "1", features = ["full"] }
code
use salvo::prelude::*;
#[tokio::main]
async fn main() {
let debug_mode = true;
let admin_mode = true;
let router = Router::new()
.get(index)
.push(
Router::new()
.path("users")
.before(auth)
.post(create_user)
.push(Router::new().path(r"<id:num>").post(update_user).delete(delete_user)),
)
.push(
Router::new()
.path("users")
.get(list_users)
.push(Router::new().path(r"<id:num>").get(show_user)),
)
.then(|router| {
if debug_mode {
router.push(Router::new().path("debug").get(debug))
} else {
router
}
})
.then(|router| {
if admin_mode {
router.push(Router::new().path("admin").get(admin))
} else {
router
}
})
;
Server::new(router).bind(([0, 0, 0, 0], 7878)).await;
}
#[fn_handler]
async fn admin(res: &mut Response) {
res.render_plain_text("Admin page");
}
#[fn_handler]
async fn debug(res: &mut Response) {
res.render_plain_text("Debug page");
}
#[fn_handler]
async fn index(res: &mut Response) {
res.render_plain_text("Hello world!");
}
#[fn_handler]
async fn auth(res: &mut Response) {
res.render_plain_text("user has authed\n\n");
}
#[fn_handler]
async fn list_users(res: &mut Response) {
res.render_plain_text("list users");
}
#[fn_handler]
async fn show_user(res: &mut Response) {
res.render_plain_text("show user");
}
#[fn_handler]
async fn create_user(res: &mut Response) {
res.render_plain_text("user created");
}
#[fn_handler]
async fn update_user(res: &mut Response) {
res.render_plain_text("user updated");
}
#[fn_handler]
async fn delete_user(res: &mut Response) {
res.render_plain_text("user deleted");
}
run
github@github:~/Workspace/demo1$ RUST_BACKTRACE=1 cargo run
warning: unused variable: `debug_mode`
--> src/router.rs:7:9
|
7 | let debug_mode = true;
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_debug_mode`
|
= note: `#[warn(unused_variables)]` on by default
warning: unused variable: `admin_mode`
--> src/router.rs:8:9
|
8 | let admin_mode = true;
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_admin_mode`
warning: 2 warnings emitted
Finished dev [unoptimized + debuginfo] target(s) in 0.10s
Running `target/debug/demo1`
thread 'main' panicked at 'index out of bounds: the len is 0 but the index is 0', /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/salvo_core-0.11.1/src/routing/filter/impls/path.rs:60:18
stack backtrace:
0: rust_begin_unwind
at /rustc/2fd73fabe469357a12c2c974c140f67e7cdd76d0/library/std/src/panicking.rs:493:5
1: core::panicking::panic_fmt
at /rustc/2fd73fabe469357a12c2c974c140f67e7cdd76d0/library/core/src/panicking.rs:92:14
2: core::panicking::panic_bounds_check
at /rustc/2fd73fabe469357a12c2c974c140f67e7cdd76d0/library/core/src/panicking.rs:69:5
3: <usize as core::slice::index::SliceIndex<[T]>>::index
at /home/github/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/index.rs:182:10
4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
at /home/github/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/index.rs:15:9
5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
at /home/github/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/vec/mod.rs:2176:9
6: <salvo_core::routing::filter::impls::path::CharPartBuilder<C> as salvo_core::routing::filter::impls::path::PartBuilder>::build
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/salvo_core-0.11.1/src/routing/filter/impls/path.rs:60:18
7: salvo_core::routing::filter::impls::path::PathParser::scan_parts
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/salvo_core-0.11.1/src/routing/filter/impls/path.rs:486:40
8: salvo_core::routing::filter::impls::path::PathParser::parse
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/salvo_core-0.11.1/src/routing/filter/impls/path.rs:546:29
9: salvo_core::routing::filter::impls::path::PathFilter::new
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/salvo_core-0.11.1/src/routing/filter/impls/path.rs:589:32
10: salvo_core::routing::router::Router::path
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/salvo_core-0.11.1/src/routing/router.rs:152:21
11: demo1::main::{{closure}}
at ./src/main.rs:14:23
12: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
at /home/github/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/future/mod.rs:80:19
13: tokio::park::thread::CachedParkThread::block_on::{{closure}}
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.5.0/src/park/thread.rs:263:54
14: tokio::coop::with_budget::{{closure}}
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.5.0/src/coop.rs:106:9
15: std::thread::local::LocalKey<T>::try_with
at /home/github/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:272:16
16: std::thread::local::LocalKey<T>::with
at /home/github/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/thread/local.rs:248:9
17: tokio::coop::with_budget
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.5.0/src/coop.rs:99:5
18: tokio::coop::budget
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.5.0/src/coop.rs:76:5
19: tokio::park::thread::CachedParkThread::block_on
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.5.0/src/park/thread.rs:263:31
20: tokio::runtime::enter::Enter::block_on
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.5.0/src/runtime/enter.rs:151:13
21: tokio::runtime::thread_pool::ThreadPool::block_on
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.5.0/src/runtime/thread_pool/mod.rs:71:9
22: tokio::runtime::Runtime::block_on
at /home/github/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.5.0/src/runtime/mod.rs:452:43
23: demo1::main
at ./src/main.rs:3:1
24: core::ops::function::FnOnce::call_once
at /home/github/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
分别在windows, linux上,
IIS, APACHE, NGINX 如何部署.
查询了很多地方并没有如何部署rust web server的详细教程. 万分感谢.
I want to use Extractible to extract data from cookie.
But I found cookie is not a valid source for extract.
Is that possible to add it?
Thanks!
Currently need to import from hyper.
use hyper::header::HeaderValue;
res.with_header(header::CONTENT_TYPE, HeaderValue::from_static("blah")?, true)?;
Describe the bug
I recently updated salvo from 1.16 to 1.29. Sadly my webSocket server would no longer compile.
So I went grab the example at the bottom of salvo homepage, that one ain't compiling as well.
Cargo.toml:
[package]
name = "noname-server"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
salvo = { version = "0.*", features = ["full"] }
tokio = { version = "1", features = ["full"] }
tokio-stream = "*"
futures = "*"
futures-util = "0.3"
once_cell = "1"
tracing = "0.1"
tracing-subscriber = "0.2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
rand = "0.8"
thiserror = "1"
Error:
error[E0412]: cannot find type `HttpError` in this scope
--> src/main.rs:6:71
|
6 | async fn connect(req: &mut Request, res: &mut Response) -> Result<(), HttpError> {
| ^^^^^^^^^ not found in this scope
error[E0698]: type inside `async` block must be known in this context
--> src/main.rs:5:1
|
5 | #[handler]
| ^^^^^^^^^^ cannot infer type
|
note: the type is part of the `async` block because of this `await`
--> src/main.rs:5:1
|
5 | #[handler]
| ^^^^^^^^^^
= note: this error originates in the attribute macro `handler` (in Nightly builds, run with -Z macro-backtrace for more info)
Some errors have detailed explanations: E0412, E0698.
For more information about an error, try `rustc --explain E0412`.
error: could not compile `noname-server` due to 2 previous errors
To Reproduce
Steps to reproduce the behavior:
Expected behavior
The example should compile(hopefully).
Desktop (please complete the following information):
It is very easy to make spelling mistake when using header names. It will be good to have a enum of KnownHeaderName that can be easily referenced. Reference: https://github.com/trillium-rs/trillium/blob/4ece63c91f6394efef9f48cb060fe7b9a964ce3a/http/src/header_name.rs#L199-L348
考虑添加一下贡献指南吗?还有Issue模板之类的文档
Consider adding a contribution guide? And some documents such as Issue templates
Examples include using static DirHandler but it would be good to have an example for NamedFile. I had to dig in the source code to see how it was used.
#[handler]
async fn get_file(req: &mut Request, res: &mut Response) -> anyhow::Result<()> {
let path = "./file.txt";
let file = NamedFile::open(path).await?;
file.send(req.headers(), res).await;
Ok(())
}
Also might be worth adding docs so can see an example without looking at the source code at https://docs.rs/axum/0.2.3/axum/response/struct.Redirect.html#.
This is useful for production apps where we can gradually rollout the secret.
let session_handler = SessionHandler::new(
MemoryStore::new(),
b"primary_secret",
vec![b"fallback_secret1", b"fallback_secret2"]
);
You always sign with the primary secret but when decoding you try primary first and if it fails to decode use fallback secrets.
Similar to https://echo.labstack.com/middleware/trailing-slash/.
Some blog sites forces trailing slash and some doesn't. In order to maintain SEO as I move moved from an old blog framework I would like to force trailing slash or remove it.
Forcing trailing slash also makes markdown just work for blogs. For those navigating to http://localhost:8080/posts/welcome/
with the welcome/article.md
one could write ![image](image.png)
which is in welcome/image.png
folder and it would correctly render the image http://localhost:8080/posts/welcome/image.png
.
Handler后面一章应该是Request吧?
现在是“世界,你好”
Is your feature request related to a problem? Please describe.
Not possible to use on existing std::net::TcpListener
.
Describe the solution you'd like
I prefer to use systemfd when working on websites so it can reuse the tcp connection.
cargo install systemfd # install systemfd binary
systemfd --no-pid -s http::8080 -- cargo watch -x 'run'
use listenfd::ListenFd; // cargo add listenfd
let mut listenfd = ListenFd::from_env();
let listener = if let Some(listener) = listenfd.take_tcp_listener(0).unwrap() {
listener // this is std::net::TcpListener
} else {
TcpListener::bind("127.0.0.1:8080") // this is salvo TcpListener
};
Server::new(listener).serve(router).await;
But this doesn't work since TpcListener
from salvo and std::net
are not compatible.
Would it be possible to support this use case?
I use a similar pattern to the fn_handler
proc macro in a crate that I'm working on and didn't want to require the user to annotate the function with a proc macro. Instead, I use this pattern:
struct Handler<F: Future<Output = ()>> {
caller: fn(&mut Request, &mut Response, &mut Depot) -> F
}
impl<F: Future<Output = ()>> Into<Handler<F>> for fn(&mut Request, &mut Response, &mut Depot) -> F {
fn into(self) -> Handler {
Handler {
caller: self
}
}
}
impl<F: Future<Output = ()>> Into<Handler<F>> for fn(&mut Request, &mut Response) -> F {
fn into(self) -> Handler {
Handler {
caller: |req, resp, _dep| self(req, resp)
}
}
}
impl<F: Future<Output = ()>> Into<Handler<F>> for fn(&mut Request) -> F {
fn into(self) -> Handler {
Handler {
caller: |req, _resp, _dep| self(req)
}
}
}
//...etc for all combinations
You might need to add a lifetime annotation into the future generic, but this might let you skip the need for impl SalvoHandler
. SalvoHandler
would be directly impl for the Handler struct, which you can still treat as Box<dyn SalvoHandler>
. To pass the handlers into the router, you would just accept impl into<Handler>
.
However, I would suggest just sticking with a single type generic over the future, forcing req, resp, and depot to be part of every handler. It might be a bit ceremonious in boiler plate, but would be easy to copy-paste and forces consistency within a given codebase.
Not sure if this is the expected or not but does seem weird that it doesn't behave as I thought it would. There are two bugs I found.
#[handler]
async fn hello_world(res: &mut Response) {
res.render("hello");
}
let router = Router::new()
.hoop(extra::compression::brotli())
.get(hello_world);
I see compression when sending file called hello.txt
with contents hello
.
let router = Router::new()
.hoop(extra::compression::brotli())
.get(extra::serve_static::FileHandler::new("./hello.txt"));
extra::compression::brotli
. Do note that browsers can send accept-encoding which is comma separated (accept-encoding: gzip, deflate, br
) as is usually the preferred encoding algorithm they expect but server can decide to return any.curl -vv http://localhost:8080 -H 'Accept-Encoding: gzip'
* Trying 127.0.0.1:8080...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.84.0
> Accept: */*
> Accept-Encoding: gzip
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-disposition: inline
< content-type: text/plain; charset=utf-8
< last-modified: Mon, 05 Sep 2022 07:55:20 GMT
< etag: "6fbaf-6-6315ab68-1c107dc6"
< accept-ranges: bytes
< content-encoding: br
< transfer-encoding: chunked
< date: Mon, 05 Sep 2022 08:05:36 GMT
<
hello
I would had thought I would specify compress as just compression()
instead of algorithm. By default it should register all 3 algorithms and based on the Accept-Encoding
header it should compress accordingly.
I was expecting it to work similar to trillium's implementation of compression handler which most of the other frameworks do too. https://github.com/trillium-rs/trillium/blob/4ece63c91f6394efef9f48cb060fe7b9a964ce3a/compression/src/lib.rs
Is your feature request related to a problem? Please describe.
Adding a database to a salvo project requires unsafe rust as in the case of the https://github.com/driftluo/myblog/blob/master/src/db_wrapper.rs#L416 website featured in the readme
Describe the solution you'd like
A way to easily connect a database to salvo maybe like how actix does it would make things alot easier.
Since router is configured in different files, it is hoped that there is a way to display all configurations.
Now, Response Body is use BytesMut, this may cause copy unnecessary.
I'm proxing to create-react-app and I'm finding it very slow as it is taking several seconds.
When directly hitting the site it is fast so definitely something with salovo's proxy implementation.
I didn't have the issue when using trillium-proxy.
fn spa() -> Proxy {
Proxy::new(vec!["http://localhost:3000".into()])
}
async fn make_service(config: AppConfig) -> AppResult<Service> {
let router = Router::new()
.hoop(extra::affix::inject(config))
.hoop(extra::logging::Logger::default())
.hoop(extra::compression::Compression::default().with_force_priority(true)) // Compression must be before CachingHeader.
.hoop(extra::caching_headers::CachingHeaders::default())
.push(Router::with_path("<**rest>").handle(spa()));
Ok(Service::new(router))
}
Middleware can be deployed for authentication. However having route guard type of mechanism can help greatly in organizing authorization code.
Implement additional guard on routes
Similar functionality is already available in Actix which helps a lot in properly organizing the authorization logic. At the point of route guard processing, the user might already have authenticated and the the guard only decides if the user can access the route based on the data in the request.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.