Plateformes
Rust
Runlight tourne dans un service Rust sur tokio, derrière axum, hyper ou tout serveur qui accepte un service tower, avec ses tables dans la base de données de l’application grâce à sqlx.
La crate Rust, c’est Runlight réécrit en Rust. Elle sert le même tableau de bord et la même API, et elle donne à chaque requête la même réponse que la bibliothèque TypeScript. Ses chiffres vont dans les mêmes tables, donc chacune peut lire une base de données écrite par l’autre. Il lui faut Rust 1.88 ou plus récent, et elle tourne sur tokio. Le stockage vient d’une deuxième crate, runlight-sqlx, qui demande Rust 1.94 pour sqlx 0.9.
Installation
Ajoutez les deux crates, avec la base de données que vous utilisez et l’adaptateur de framework que vous voulez.
toml
[dependencies]
runlight = { version = "0.1", features = ["axum"] }
runlight-sqlx = { version = "0.1", features = ["sqlite"] }La feature transport de la crate runlight est activée par défaut et lui fournit un client pour les services d’e-mail, les installations connectées, les icônes de sites et l’assistant IA. La feature tower fait des routes un service tower, et axum ajoute les routeurs décrits plus bas. Les features de runlight-sqlx sont sqlite, postgres et mysql.
Créer l’instance
Créez un Runlight au démarrage de votre service et gardez-le dans votre état. Le cloner ne coûte presque rien.
rust
use runlight::{Runlight, RunlightOptions, SiteOptions};
let store = runlight_sqlx::connect("sqlite:data/runlight.db").await?;
let mut options = RunlightOptions::new(store);
options.site = Some(SiteOptions {
name: Some("example.com".into()),
hostnames: Some(vec!["example.com".into()]),
timezone: Some("Europe/London".into()),
..SiteOptions::default()
});
let rl = Runlight::new(options)?;Les options portent les mêmes noms que dans Configuration, en snake case, donc sites, trust_proxy, rate_limit, mail et managed_sites fonctionnent tous comme là-bas. Les tables sont créées à la première requête.
Stockages
runlight_sqlx::connect ouvre son propre pool à partir d’une URL. Pour utiliser le pool que votre application a déjà, passez-le, et Runlight garde ses tables à côté des vôtres.
Les tables de Runlight commencent par rl_. sqlite_with_prefix, postgres_with_prefix, mysql_with_prefix et connect_with_prefix prennent un préfixe à utiliser à la place, comme runlight_sqlx::sqlite_with_prefix(pool, "wp_rl_")?, pour que deux applications qui partagent une base gardent chacune leurs propres tables. Un préfixe compte jusqu’à 32 lettres, chiffres et tirets bas, et ne commence pas par un chiffre. store.tables() liste les tables du stockage par leur nom, pour une application qui retire Runlight.
SQLite
rust
let store = runlight_sqlx::sqlite(pool); // a sqlx::SqlitePoolLe stockage réserve une connexion du pool à ses instructions, l’une après l’autre, en mode WAL avec un délai d’attente de cinq secondes en cas de verrou. La feature sqlite compile SQLite depuis les sources, donc votre build a besoin d’un compilateur C.
MySQL et MariaDB
rust
let store = runlight_sqlx::mysql(pool); // a sqlx::MySqlPoolCe stockage fonctionne avec MySQL 8.0.20 ou plus récent et MariaDB 11.4 ou plus récent, et le pool demande au moins deux connexions. L’authentification par défaut de MySQL 8 sur une connexion sans TLS demande la feature mysql-rsa de sqlx, donc activez-la dans votre propre Cargo.toml ou connectez-vous en TLS.
Postgres
rust
let store = runlight_sqlx::postgres(pool); // a sqlx::PgPoolLe pool demande au moins deux connexions. Chaque instruction s’exécute seule, donc les écritures de Runlight ne rejoignent jamais une transaction ouverte par votre application.
axum
Fusionnez les routeurs dans votre application. Le tableau de bord et l’API répondent sur /runlight, et les liens courts sur /go/{slug}.
rust
use runlight::{RoutesOptions, TokenOption};
let routes = rl.routes(RoutesOptions {
token: TokenOption::Given(std::env::var("RUNLIGHT_TOKEN")?),
..RoutesOptions::default()
})?;
let app = axum::Router::new()
.merge(routes.router())
.merge(rl.link_router())
.layer(axum::middleware::from_fn_with_state(rl.clone(), runlight::adapters::axum_support::middleware));Le middleware répond à une requête sur un domaine de liens avec son lien court avant que vos propres routes ne la voient, et il enregistre les requêtes des agents IA sur vos pages. Une requête pour tout autre hôte continue vers votre application sans qu’on y touche. Gardez cette couche, puisqu’un routeur axum ne voit que les chemins qu’on lui a donnés et que les liens courts d’un domaine de liens se trouvent à la racine de leur propre nom. Servez l’application avec into_make_service_with_connect_info::<SocketAddr>() pour que Runlight voie l’adresse de la connexion quand aucun en-tête de proxy ne nomme le visiteur. Ajoutez ensuite le script à chaque page, juste avant </head>.
html
<script defer src="/runlight/s.js"></script>Définissez ensuite RUNLIGHT_TOKEN avec une longue chaîne aléatoire et ouvrez une fois /runlight/?token= suivi de cette chaîne pour vous connecter, comme l’explique Pour commencer. RoutesOptions accepte les réglages que prend routes() dans Configuration, en snake case, donc base_path, accounts et origin fonctionnent comme là-bas.
link_router() répond à chaque requête qu’axum route vers /go/{slug}. Une requête sur un domaine de liens passe en premier, donc /go/<name> y est un 404 comme tout autre chemin qui n’est pas l’un des liens de ce domaine. Sur votre propre hôte, il redirige quand le lien existe, et un nom sans lien reçoit le simple 404 de Runlight, puisqu’axum a déjà reconnu la route et ne peut plus confier la requête au fallback de votre application. rl.link_service() répond de la même façon.
Autres serveurs
Routes est un tower::Service pour n’importe quel http::Request, donc hyper et tout ce qui repose sur tower peuvent le servir tel quel. Chaque requête qu’il reçoit sur un domaine de liens reçoit le lien court en réponse, et le service de liens courts de rl.link_service() fait de même. Sans la feature tower, construisez vous-même un runlight::http::Request et appelez routes.handle(request).await, qui répond avec un runlight::http::Response.
La vérification planifiée
La vérification planifiée renouvelle les sels quotidiens, envoie les rapports par e-mail arrivés à échéance, applique la durée de conservation des visites de chaque site et construit les agrégats qui gardent les longues périodes rapides. Appelez rl.check().await toutes les quelques minutes depuis une tâche à vous. Quand rien dans le service ne tourne à intervalles réguliers, définissez CRON_SECRET et faites appeler /runlight/api/check avec lui par un planificateur, comme le montre Vérification planifiée.
rust
let checker = rl.clone();
tokio::spawn(async move {
loop {
if let Err(error) = checker.check().await {
eprintln!("Runlight check failed: {error}");
}
tokio::time::sleep(std::time::Duration::from_secs(300)).await;
}
});Localisation
Runlight lit le pays, la région et la ville dans les en-têtes qu’ajoutent Vercel, Cloudflare et Netlify. Derrière autre chose, donnez-lui une base de villes MaxMind ou DB-IP par runlight::mmdb::Mmdb, qui lit le fichier page par page selon les besoins des recherches.
rust
options.geo = Some(std::sync::Arc::new(runlight::mmdb::Mmdb::open("data/dbip-city-lite.mmdb")?));Différences avec la bibliothèque TypeScript
La crate Rust passe les tests de conformité imposés à la bibliothèque TypeScript, sur SQLite, Postgres, MySQL et MariaDB. Les différences viennent de la façon dont Rust s’exécute.
- La crate fait ses requêtes sortantes, pour les services d’e-mail, les installations connectées, les icônes de sites et l’assistant IA, par un
Fetcherque vous pouvez remplacer, donc les tests et les réseaux verrouillés peuvent passer le leur. - Elle lit les fuseaux horaires dans la base du système, donc un fuseau dont les règles ont changé après la copie de la bibliothèque TypeScript suit les règles les plus récentes.
- Il n’y a pas de serveur autonome en Rust. Pour un seul tableau de bord sur plusieurs sites, faites tourner le serveur Node ou le serveur d’un autre langage, et connectez-y le site de ce service.