1 unstable release

Uses new Rust 2024

0.12.0-alpha.3 Jul 21, 2025

#1000 in HTTP server

Download history 93/week @ 2025-07-16 40/week @ 2025-07-23 14/week @ 2025-08-20 18/week @ 2025-08-27 28/week @ 2025-09-03

60 downloads per month
Used in 5 crates (4 directly)

MIT and GPL-3.0-or-later

73KB
1K SLoC

Impulse Server Kit

State-of-art simple and powerful web server based on Salvo. Provides extended tracing, configuration-over-YAML, HTTP3, TLS v1.3, MessagePack + SIMD JSON ser/de support, ACME, OpenAPI and OpenTelemetry features by default.

Table of contents:

How's it work

  1. You load configuration from the file on the startup via load_generic_config function.
  2. You start logging, check config for misconfigurations and load the state - all just via load_generic_state function.
  3. You create your own salvo::Router and then generate server's Future and handle by start function.
  4. You manually start awaiting server.

Using Server Kit

To use Server Kit, include this line into your Cargo.toml:

[dependencies]
impulse-server-kit = { git = "https://github.com/impulse-sw/impulse-kit.git", tag = "0.11" }

And create empty {app-name}.yaml to fill later (see Configuration Overview below).

Extended utilities

Server Kit uses impulse-utils to improve functionality by:

  • providing describeful ServerError and associated MResult
  • providing SIMD JSON and MsgPack support
  • easy response macros

Read more: impulse-utils.

4 Quick start steps

  1. Create Setup struct with your setup data fields and GenericValues inside.
  2. Create simple endpoints - your HTTP requests' handlers.
  3. Create server-example.yaml file in crate root.
  4. Just setup your application in 5 lines in main.

YAML configuration example:

startup_type: http_localhost
server_port: 8801
allow_oapi_access: true
oapi_frontend_type: Scalar
oapi_name: Server Test OAPI
oapi_ver: 0.0.1
oapi_api_addr: /api
enable_io_logs: true
io_log_level: debug

Cargo.toml:

[package]
name = "impulse-server-example"
version = "0.1.0"
edition = "2021"

[dependencies]
impulse-server-kit = { git = "https://github.com/impulse-sw/impulse-kit.git", tag = "0.11" }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["macros"] }
tracing = "1"

The code itself:

use impulse_server_kit::prelude::*;
use serde::Deserialize;

#[derive(Deserialize, Default, Clone)]
struct Setup {
  #[serde(flatten)]
  generic_values: GenericValues,
  // this could be your global variables, such as the database URLs
}

impl GenericSetup for Setup {
  fn generic_values(&self) -> &GenericValues { &self.generic_values }
  fn generic_values_mut(&mut self) -> &mut GenericValues { &mut self.generic_values }
}

#[derive(Deserialize, Serialize, Debug, salvo::oapi::ToSchema)]
/// Some hello
struct HelloData {
  /// Hello's text
  text: String,
}

#[endpoint(
  tags("test"),
  request_body(content = HelloData, content_type = "application/json", description = "Some JSON hello to MsgPack"),
  responses((status_code = 200, description = "Some MsgPack hello", body = HelloData, content_type = ["application/msgpack"]))
)]
#[instrument(skip_all, fields(http.uri = req.uri().path(), http.method = req.method().as_str()))]
/// Convert hello from JSON to MsgPack
///
/// Simple endpoint which translates any given `HelloData` from JSON into MsgPack format.
async fn json_to_msgpack(req: &mut Request, depot: &mut Depot) -> MResult<MsgPack<HelloData>> {
  let hello = req.parse_json::<HelloData>().await?;
  let app_name = depot.obtain::<Setup>()?.generic_values().app_name.as_str();
  msgpack!(HelloData { text: format!("From `{}` application: {}", app_name, hello.text) })
}

#[endpoint(
  tags("test"),
  request_body(content = HelloData, content_type = "application/msgpack", description = "Some MsgPack hello to JSON"),
  responses((status_code = 200, description = "Some JSON hello", body = HelloData, content_type = ["application/json"]))
)]
#[instrument(skip_all, fields(http.uri = req.uri().path(), http.method = req.method().as_str()))]
/// Convert hello from MsgPack to JSON
///
/// Simple endpoint which translates any given `HelloData` from MsgPack into Json format.
async fn msgpack_to_json(req: &mut Request, depot: &mut Depot) -> MResult<Json<HelloData>> {
  let hello = req.parse_msgpack::<HelloData>().await?;
  let app_name = depot.obtain::<Setup>()?.generic_values().app_name.as_str();
  json!(HelloData { text: format!("From `{}` application: {}", app_name, hello.text) })
}

fn tests_router() -> Router {
  Router::new()
    .push(Router::with_path("msgpack-to-json").post(msgpack_to_json))
    .push(Router::with_path("json-to-msgpack").post(json_to_msgpack))
}

#[tokio::main]
async fn main() {
  let setup = load_generic_config::<Setup>("server-example").await.unwrap();
  let state = load_generic_state(&setup, true).await.unwrap();
  
  // any setup, like DB or auth client
  
  let router = get_root_router_autoinject(&state, setup.clone())
    // .hoop(salvo::affix_state::inject(my_db_client).inject(my_auth_client))
    .push(tests_router());
  let (server, _handler) = start(state, &setup, router).await.unwrap();
  server.await
}

Here we go! You can now start the server with cargo run!

Common Salvo documentation

Server Kit is just a layer on top of Salvo framework. Use its documentation and examples to know how to develop web servers in Server Kit.

Code API Overview

[!NOTE] To setup these features, you have to write them in your code.

Logging

To install log collector application-wide, make sure that you've loaded generic state with true second option:

let state = load_generic_state(&setup, true).await.unwrap();

And, for logs, use either provided or included by yours tracing crate:

use tracing;  // or `use impulse_server_kit::prelude::*;

// inside any function
tracing::info!("There are {} available chickens!", chickens.len());

OpenTelemetry

Spans example:

// Import `tracing` module
use impulse_server_kit::prelude::*;

// Use `tracing::instrument` attribute macro to instrument your handler and automatically create `my_handler` span
#[handler]
#[tracing::instrument(skip_all, fields(http.uri = req.uri().path(), http.method = req.method().as_str()))]
async fn my_handler() -> MResult<OK> {
  // Use `tracing` instead of `log`
  tracing::debug!("This is the DEBUG level log!");
  
  // Use `.instrument(...)` method over async functions to define spans
  any_async_func
    .instrument(tracing::info_span!("Executed any async function"))
    .await;
  
  ok!()
}

Metrics example:

// Import `otel` module
use impulse_server_kit::prelude::*;

// Get a meter
let meter = otel::api::global::meter("my_meter");

// Create a metric
let counter = meter.u64_counter("my_counter").build();
counter.add(1, &[KeyValue::new("key", "value")]);

Force HTTPS

To enforce HTTPS, you should start another server via start_force_https_redirect function:

let (server, handler) = start_force_https_redirect(80, 443).await.unwrap();

Configuration Overview

[!NOTE] To setup these features, you have no need to edit code, just {your-app}.yaml.

Startup types

There are several startup types:

  1. http_localhost - will listen http://127.0.0.1:{port} only
  2. unsafe_http - will listen http://0.0.0.0:{port}
  3. https_acme (requires acme feature) - will listen https://{host}:{port} with ACME support
  4. quinn_acme (requires both acme and http3 features) - will listen https:// and quic:// with ACME
  5. https_only - will listen https://{host}:{port}
  6. quinn (requires http3 feature) - will listen https:// and quic://
  7. quinn_only (requires http3 feature) - will listen quic://{host}:{port}

Any HTTPS connection will use TLS v1.3 only.

Example:

startup_type: quinn

ACME domain

Specify acme_domain to use ACME (TLS ALPN-01).

Example:

startup_type: quinn_acme
server_host: 0.0.0.0
server_port: 443
acme_domain: tls-alpn-01.domain.com

Server host & server port

Specify server_host as IP address to listen with server (except http_localhost and unsafe_http startup types).

Specify server_port to listen with server. If you use your app with Server Kit as internal service, specify any port; if you want to expose your ports to the Internet, use 80 to HTTP and 443 for HTTPS or QUIC.

Also, if you want to specify your listening port after application start, you can use server_port_achiever field (see below).

Example:

startup_type: quinn
server_host: 0.0.0.0
server_port: 443

SSL key & certs

Example:

startup_type: quinn
server_host: 0.0.0.0
server_port: 443
ssl_crt_path: certs/fullchain.pem
ssl_key_path: certs/privkey.pem

Server port achieveing

You can specify server_port_achiever field to any filepath to make server wait for file creation and writing actual server port to listen to it.

Example:

startup_type: quinn
server_host: 0.0.0.0
server_port_achiever: write/port/to/me.txt

Auto-migrate binary

Specify auto_migrate_bin field to automatically execute any binary (for example, DB migrations) before actual server start.

Allow CORS

Specify allow_cors_domain field to automatically manage CORS policy to given domain or domains.

Example:

# ...
allow_cors_domain: "https://my-domain.com"

Allow OAPI

[!NOTE] Any OAPI config option requires oapi feature to be enabled:

[dependencies]
impulse-server-kit = { .., features = ["oapi"] }

Specify allow_oapi_access field to automatically generate OpenAPI specifications and provide to users.

Example:

# ...
allow_oapi_access: true
oapi_frontend_type: Scalar # or `SwaggerUI`
oapi_name: My API
oapi_ver: 0.1.0

Logging

Server Kit uses tracing for logging inside routes' logic. You can choose any of these log types:

  • I/O logs (terminal)
  • file logs
  • RFC 5424 (syslog) logs
  • ECS (Elastic Common Schema with disabled normalization) structured JSON logs

See how to use logging inside your code

Log levels

There are 5 log level types available:

  • trace
  • debug
  • info
  • warn
  • error
File rotation types

There are 4 log file rotation types available:

  • never
  • daily
  • hourly
  • minutely

I/O logs

Configuration example:

enable_io_logs: true
io_log_level: info    # error | warn | info | debug | trace

File logs

Logs will be written in file(-s) inside logs folder.

Configuration example:

enable_file_logs: true
file_log_level: info           # error | warn  | info   | debug    | trace
file_log_rotation: daily       # never | daily | hourly | minutely
file_log_max_rolling_files: 5  # by default

Syslog

Logs produced by this connector will send by one of 4 transports:

  • TCP
  • UDP
  • Unix Socket (Datagram)
  • Unix Socket Stream

You should configure syslog_addr. Configuration example:

enable_syslog_logs: true
syslog_addr: "udp://127.0.0.1:514"  # schemas: `tcp://` | `udp://` | `unix://` | `ustream://`
syslog_log_level: info

ECS

ECS logs will be also written in file(-s) (folder ecs-logs). Configuration example:

enable_ecs_logs: true
ecs_log_level: info       # error | warn  | info   | debug    | trace
ecs_rotation: daily       # never | daily | hourly | minutely
ecs_max_rolling_files: 5  # by default

OpenTelemetry

[!NOTE] Any OpenTelemetry config option requires otel feature to be enabled:

[dependencies]
impulse-server-kit = { .., features = ["otel"] }

Server Kit supports gRPC span exporter and HTTP binary metrics exporter.

Span tracing

To activate span tracing, enable otel feature (enabled by default) and specify otel_grpc_endpoint field:

otel_grpc_endpoint: http://localhost:4317  # Jaeger default gRPC write API endpoint

Also, you can specify log level (if none specified, goes back to log_level field):

otel_log_level: info  # error | warn | info | debug | trace

See how can you use spans.

Read more about tracing: tracing docs.

Metrics

To activate metrics collector, enable otel feature (enabled by default) and specify otel_http_endpoint field:

otel_http_endpoint: http://localhost:9090/api/v1/otlp/v1/metrics  # Prometheus default write API endpoint

See how can you use metrics.

Read more about Meter: opentelemetry docs.

Server Kit also provides these default metrics:

  • sk_requests - total number of requests
  • sk_request_duration - HTTP request duration in seconds
  • sk_active_connections - number of active HTTP connections

These metrics are implied automatically by using get_root_router_autoinject function. You also can use it by hands:

Router::new()
  .hoop(impulse_server_kit::startup::sk_default_metrics)

Dependencies

~47–70MB
~1.5M SLoC