Middleware-плагины
target: middleware оборачивает существующие балансеры и фильтрует/мерджит их выдачу.Middleware-плагин не заменяет балансер — он садится поверх одного или нескольких балансеров и:
- получает уже собранные ответы upstream’ов,
- решает, что показать пользователю,
- может убирать дубли, фильтровать по качеству, тегу, голосу, языку,
- может объединять данные с нескольких источников в один список.
Когда использовать
| Задача | Решение |
|---|---|
| Скрыть всё ниже 720p | Middleware с фильтром quality.minHeight >= 720 |
| Слить collaps + filmix в один список | Middleware с upstream’ами ["collaps", "filmix"] |
| Убрать рекламные дубли в озвучках | Middleware дедуплицирует voice[] по нормализованному имени |
Принудительно проксировать всё через /proxy/ |
Middleware прогоняет каждый data[].url через host_proxy_url |
Manifest
{
"id": "quality_filter",
"name": "Quality filter",
"version": "0.1.0",
"target": "middleware",
"entry_server": "plugin.wasm",
"language": "tinygo",
"abi_version": 1,
"upstreams": ["collaps", "filmix"],
"permissions": ["log"],
"config_schema": [
{"key": "minQuality", "type": "enum", "options": ["480","720","1080","2160"], "default": "720"}
]
}upstreams — обязательное поле для middleware. Допустимы:
- любой ID, который зарегистрирован в
DynamicRouteRegistry(built-in балансеры, JS-модули, другие WASM-плагины), "*"— wildcard, используется только в тестах (резолвер не разворачивает его автоматически).
Как это работает
Lampa ──GET /lite/quality_filter──► lampac-go
│
┌────────────┴────────────┐
│ MiddlewareHandler │
└────────────┬────────────┘
│ resolveUpstream("collaps")
│ resolveUpstream("filmix")
▼
┌─────────────────────────┐
│ /lite/collaps │ → JSON
│ /lite/filmix │ → JSON
└────────────┬────────────┘
│ inject __upstreams в config
▼
┌─────────────────────────┐
│ wazero.handle(ptr, len) │
└────────────┬────────────┘
│
▼
ответ LampaUpstream’ы вызываются последовательно — это сделано осознанно: cookies / DDoS-Guard / CDN cooldown’ы плохо переносят параллельные запросы (см. заметки про Mirage CDN). 99% middleware кейсов — ≤3 upstream’а, так что заметной потери latency нет.
TinyGo пример
package main
import (
"encoding/json"
"strconv"
"lampac.cc/sdk/lampac"
)
func main() {}
type item struct {
Name string `json:"name"`
Quality map[string]interface{} `json:"quality,omitempty"`
}
type list struct {
Type string `json:"type"`
Data []item `json:"data"`
}
//export handle
func handle(ptr, length uint32) uint64 {
inv := lampac.ReadInvocation(ptr, length)
var cfg struct{ MinQuality string `json:"minQuality"` }
_ = json.Unmarshal(inv.Config, &cfg)
minH, _ := strconv.Atoi(cfg.MinQuality)
if minH == 0 { minH = 720 }
out := list{Type: "movie"}
for _, up := range lampac.Upstreams(inv) {
if up.Error != "" {
lampac.Warn("upstream " + up.Balancer + " failed: " + up.Error)
continue
}
var env list
if err := json.Unmarshal(up.Body, &env); err != nil { continue }
for _, it := range env.Data {
if hasHigherQuality(it, minH) {
out.Data = append(out.Data, it)
}
}
}
body, _ := json.Marshal(out)
return lampac.WriteResponse(body)
}
func hasHigherQuality(it item, min int) bool {
for label := range it.Quality {
n, _ := strconv.Atoi(strings.TrimRight(label, "p"))
if n >= min { return true }
}
return false
}Полный исходник: wasm_modules/quality_filter.
Как хост передаёт upstream’ы плагину
Чтобы не ломать ABI, upstream-данные кладутся в inv.Config под зарезервированным ключом __upstreams:
{
"minQuality": "720",
"__upstreams": [
{"balancer": "collaps", "status": 200, "body": {"type":"movie","data":[...]}},
{"balancer": "filmix", "status": 503, "error": "upstream timeout"}
]
}В TinyGo SDK:
ups := lampac.Upstreams(inv) // []UpstreamResultВ Rust SDK:
let ups: Vec<UpstreamResult> = lampac_sdk::upstreams(&inv);Цепочки middleware
Один middleware может звать другого через upstreams. Это работает, потому что middleware-плагин зарегистрирован в DynamicRouteRegistry ровно так же, как балансер:
quality_filter --upstreams: ["voice_dedup"]──► voice_dedup --upstreams: ["collaps","filmix"]──► collaps + filmixТолько не зацикливай.
Permissions
Middleware не нуждается в http:* (если только не делает свои HTTP-запросы помимо upstream’ов). Минимальный набор:
"permissions": ["log"]Если плагин нормализует URL’ы через host_proxy_url — добавь "proxy".
Production tips
- Cap upstream timeout — каждый вызов upstream’а ограничен 30 секундами. Если у вас балансер с долгим resolve (зетфлекс, мираж), ставьте таймаут пользователя в Lampa выше.
- Ошибки одного upstream’а не валят middleware — он получает
up.Errorи решает сам. - Кеширование — middleware-плагин может кешировать через
host_cache_*ровно так же, как server-плагин.