![Latest Version] ![Docs badge]
Shors - a library for creating transport layer for distributed systems built with tarantool-module. Shors contains four components: - http router - rpc router - rpc client - builtin components like: - middlewares (opentelemetry, metrics, etc) - logger
Use a route::Builder for create http routes. After route created just register it with http Server.
Example:
```rust use shors::transport::http::route::Builder; use shors::transport::http::{server, Request}; use shors::transport::Context; use std::error::Error;
fn makehttpendpoint() {
let endpoint = Builder::new()
.withmethod("GET")
.withpath("/concat/a/:a/b/:b")
.build(
|ctx: &mut Context, request: Request| -> Result<_, Box
Ok(a.to_string() + b)
},
);
let s = server::Server::new(); s.register(Box::new(endpoint)); } ```
A more complex example (with groups, error handling, custom and builtin middlewares): ```rust use oncecell::sync::Lazy; use opentelemetry::sdk::export::trace::stdout; use opentelemetry::sdk::trace::Tracer; use shors::transport::http::route::Builder; use shors::transport::http::{server, Request, Response}; use shors::transport::Context; use shors::{middleware, shorserror}; use std::error::Error;
static OPENTELEMETRYTRACER: Lazy
pub fn makehttpendpoints() { let routegroup = Builder::new() .withpath("/v1") .witherrorhandler(|ctx, err| { shorserror!(ctx: ctx, "http error {}", err); Response { status: 501, body: "request fail".tostring(), } }) .withmiddleware(|route| { println!("got new http request!"); route }) .withmiddleware(middleware::http::otel(&OPENTELEMETRY_TRACER)) .group();
#[derive(serde::Deserialize)] struct EchoRequest { text: String, }
let echo = routegroup
.builder()
.withmethod("POST")
.withpath("/echo")
.build(
|ctx: &mut Context, request: Request| -> Result<_, Box
let ping = routegroup
.builder()
.withmethod("GET")
.withpath("/ping")
.build(|ctx: &mut Context, _request: Request| -> Result<_, Box
let s = server::Server::new(); s.register(Box::new(echo)); s.register(Box::new(ping)); } ```
Rpc transport required exported stored procedure - rpc_handler.
Create stored procedure. Example (where RPC_SERVER is the Server instance): ```rust
pub extern "C" fn rpchandler(ctx: FunctionCtx, args: FunctionArgs) -> cint { RPC_SERVER.with(|srv| srv.handle(ctx, args)) } ```
And export it from cartridge role. Example:
lua
box.schema.func.create('mylib.rpc_handler', { language = 'C', if_not_exists = true })
rawset(_G, 'rpc_handler', function(path, ctx, ...)
return box.func['mylib.rpc_handler']:call({ path, ctx, { ... } })
end)
Working with rpc routes same as http: use route::Builder for creating rpc routes. After route created register it with rpc Server.
Complex example: ```rust use oncecell::unsync::Lazy; use shors::log::RequestIdOwner; use shors::transport::rpc::server::Server; use shors::transport::{rpc, Context}; use std::error::Error; use shors::{middleware, shorserror};
threadlocal! {
pub static RPCSERVER: Lazy
fn initrpc() -> tarantool::Result<()> { let routes = rpc::route::Builder::new() .witherrorhandler(|ctx, err| { shorserror!(ctx: ctx, "rpc error {}", err); }) .withmiddleware(|route| { println!("got new rpc request!"); route }) .withmiddleware(middleware::rpc::otel(&OPENTELEMETRY_TRACER)) .group();
let sumroute = routes.builder().withpath("/sum").build(
|ctx: &mut Context, req: rpc::Request| -> Result<_, Box
RPCSERVER.with(|srv| { srv.register(Box::new(sumroute)); });
Ok(()) } ```
There is a special component for interaction with remote rpc endpoints. Currently, client can call rpc endpoint in four modes: - by bucketid (vshard) - by bucketid (vshard) async (without waiting for an response) - by replicaset id (call current master) - by cartridge role (call current master)
Rpc client require register some lua code in luaopen_ function (or in any init function).
Example: ```rust
pub unsafe extern "C" fn luaopenlibstub(l: *mut ffilua::luaState) -> cint { let lua = tlua::StaticLua::fromstatic(l); shors::initlua_functions(&lua).unwrap(); 1 } ```
```rust let lua = tarantool::lua_state();
let params = vec![2, 3, 4];
let resp = rpc::client::Builder::new(&lua)
.shard_endpoint("/add")
.call(&mut Context::background(), bucket_id, ¶ms)?;
```
```rust let lua = tarantool::lua_state();
rpc::client::Builder::new(&lua)
.async_shard_endpoint("/ping")
.call(&mut Context::background(), bucket_id, &())?;
```
```rust let lua = tarantool::lua_state();
let params = vec![2, 3, 4];
let resp = rpc::client::Builder::new(&lua)
.replicaset_endpoint("/add")
.prefer_replica()
.call(&mut Context::background(), rs_uuid, ¶ms)?;
```
NOTE: calling rpc endpoint by role require register exported rpc_handler stored procedure as exported role method. For example:
lua
return {
role_name = 'app.roles.stub',
...
rpc_handler = function(path, ctx, ...)
return box.func['libstub.rpc_handle']:call({ path, ctx, { ... } })
end,
}
Call example: ```rust let lua = tarantool::lua_state();
let resp = rpc::client::Builder::new(&lua)
.role_endpoint("app.roles.stub", "/ping")
.call(&mut Context::background(), &())?;
```
with-trace
not set bash
make unit-test
bash
(cd tests/testapplication/ && cartridge build)
make int-test