StyloBot Release Series: L'architettura della Sidecar (Italiano (Italian))

StyloBot Release Series: L'architettura della Sidecar

Monday, 01 June 2026

//

20 minute read

Il motore di rilevamento del StyloBot' è ASP.NET CoreM SK2 Questo post spiega come quel motore si connette ai portali Go , NodeM Sk4js applicationsM sk5 e ad ogni altro stack tramite un'autostrana gRPC M Sk6 un Go SDK tipolato M sk7 e un plugin Caddy , senza che nessuno di quegli utenti abbia bisogno di sapere nulla sulle interfacce M k9NET M k10

StyloBot

L'informazione figura nella parte dispositiva. github.com/scottgal/stylobot-go SDK, la github.com/scottgal/caddy-stylobot plugin, e il Mostlylucid.BotDetection.Sidecar Il contenitore sarà pubblicato tra poco. Tutto qui descrive la superficie che esponeranno.

StyloBot Release Series

  1. Comportamento, Non identità: perché StyloBot modella i clienti comportamentalmente
  2. Comportamento-Aware ASP.NET UI: il server- la superficie restituita per le applicazioni .NET
  3. Trovare e riparare la crescita senza limiti nei servizi NET Long-Running .: la disciplina di affidabilità che mantiene il motore noioso nella produzione.
  4. Comportamento-Conoscere l'interfaccia grafica del tipoScript: ExpressM SK1 Fastify, e componenti del browser
  5. L'architettura della Sidecar: questo articolo
  6. Imparare a diventare più veloce: il sistema di apprendimento adattativo, quattroM SK2 memoria a livello di livello+, e la cassa del verdetto
  7. Provare a fare ciò che non si ferma.: la disciplina di verificazioneM SK1 un file BDF guida la regressione , carico, e calibrazione
  8. StyloExtract - un convertitore locale di HTML per Markdown: l'HTML→Layere Markdown che si combina con il rilevatore , il walkeder bug lucidVIEW catturatoM SK3 e la catena di cibo per cani che lo rende onesto

Perché una carrozza laterale?

Un proxy inversa è il posto ovvio per far funzionare la rilevazione dei bot: si siede davanti a tuttoM SK1 vede ogni richiesta, e può bloccare prima che ogni codice dell'applicazione funzioniMSC3 Questa logica si mantiene fino a quando non si chiede cosa dovrebbe fare l'applicazione. fare in modo diverso. basandosi su chi fa la richiesta. Una passerella che blocca completamente è una porta; quello di cui la maggior parte delle applicazioni ha veramente bisogno è un verdetto su cui poter agire in molti modi simultaniM SK2 far girare l'API , personalizzare l'interfaccia utenteMSC4 escludere il traffico dall'analisiMST5 aggiungere un passo di frizione al checkoutM ST6 Una porta non può fare niente di questoM st7 Un tubo di verdetto puòMst8

Il modello della carrozza laterale separa le due preoccupazioni. La passerella rimane veloce e senza stato. La carrozza posteriore mantiene lo stato di sessioneM SK2 gli ancrami archetipi , lo stato d'immozioneMSC4 e il cache del verdetto che rende la rilevazione accurata MST5 l'archetipo MST6 il modello dell'ancrameMst7 il per-MST8 il cache di verdetto delle impronte digitaliM ST9 e i quattro sistemi di apprendimento a quattro livelliM st10 sono ricoperti Imparare a diventare più veloce). Le due comunicano attraverso la rete locale ( lo stesso host o lo stesso Pod) così il percorso rotondatoM SK3 è di microsecondi a un singoloMSC4 millisecondi di cifreMST5 non è il mSSK6ms di una chiamata API remotaMst7 Il budget della latenza è ciò che rende la rilevazione di una richiesta per ogni -pragenza pratica

Questo non è un modello nuovo. Proxy dell'ambasciatore Lo fa esattamente per il servizio-problemi di mesh M SK1mTLS, ripetizioniMSC3 rompere circuitiMNK4 Dapr Lo fa per lo stato e la pub/sub;. OpenTelemetry Collector Lo fa per la telemetria. Linkerd , Consul Connect, e AWS App Mesh tutti segueno lo stesso modelloM SK3 Il modello continua a comparire perché risolve un vero problemaMSC4 volete un comportamento di stato complesso che attraversa i confini linguistici senza riimplementarlo in ogni linguaMST5

Perché non inserirlo come una biblioteca?

L'alternativa è compilare la rilevazione direttamente nel gateway. Per Go significa una pura reimplementazione di -Go o un collegamento CGo a una biblioteca C. Per Node significa l'esecuzione della rilevazione nel processo in parallelo all'applicazioneM SK4

né è realistico per un motore di questa complessità. StyloBot ha 49 rilevatori organizzati in quattro esposizioni. le onde. (Le onde più tardi sparano solo quando i segnali precedenti lo garantisconoM SK1 un credenzale-il tentativo di sopravvivere attiva dei rilevatori diversi da quelli di una ricerca su Googlebot ). Mantiene ogni sessione I vettori della catena di Markov in uno spazio dimensionale. Una catena Markov è solo un modello di probabilità su ", dato l'ultima cosa che questa sessione ha fatto. Detezione della comunità di Leiden su quei vettori: un grafico-l'algoritmo che crea gruppi di sessioni che si comportano allo stesso modoM SK2 questo è il modo in cui StyloBot individua una rete di bot anche quando le sessioni individuali sembrano tranquille . E persiste tutto ciò a SQLite tra le richiesteMSC4 Quello stato ha bisogno di un ciclo di vita indipendenteMNK5 Non può ricominciare con il processo dei nodi o essere strappato quando la passerella ri scarica la propria configurazioneMسک6

Una carrozza laterale permette a ogni componente di fare quello che è buono.

graph TD
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px
    classDef store fill:none,stroke:#f59e0b,stroke-width:2px

    GW["Gateway<br/>Caddy / YARP / nginx"]:::input
    SD["StyloBot Sidecar · ASP.NET Core<br/>gRPC :5090 · REST :5091<br/>≤50ms per Detect RPC"]:::async
    APP["Upstream Application<br/>Node / Go / ASP.NET<br/>reads req.stylobot.verdict"]:::good
    DB[("SQLite<br/>sessions · signatures · reputation")]:::store

    GW --> SD
    SD <--> DB
    GW --> APP

Il gateway chiama la sidecar, inietta il risultato come header HTTP, e opzionalmente bloccaM SK2 L'applicazione in arrivo legge i headers e agisce sul verdetto . La sidecar persists state between requestsMSC4 La filiera di rilevamento va all'interno del singolo call gRPC e il suo risultato si propaga come nove headerHTTPMNK5

La carrozza laterale.

Mostlylucid.BotDetection.Sidecar è un processo minimo ASP.NET Core . Non ha UIM SK2 nessun servizio di file statici , e nessun routing al di là del gRPC e dei punti finali RESTMNK4 Inizia il motore di rilevamento completo e espone due portiMSC5

  • :5090: HTTPM SK1 per i clienti gRPC ( passerelle, Proxy di accessoMSC4 il Node cliente gR PCMST5
  • :5091: HTTP /1.1 per i clienti RESTM SK2 re-exporting /api/v1/* i punti finali.

gRPC

gRPC è un sistema di call procedure remoto ad alta performance sviluppato da Google. Bufferi del protocollo (protobufM SK1 come formato di cavo: un encodamento binario compacto che è più veloce da serializzare e più piccolo sul cavo rispetto a JSON . gRPC funziona su HTTPMSC4 il che significa che ottiene moltiplicazioni SSK5multiple requests over one TCP connectionMNK6 gratisMMK7

L'interfaccia è definita in un .proto file. Da quel file, i generatori di codici, producono battute di cliente e server in qualsiasi lingua supportata. .proto file in modo che qualsiasi lingua con una implementazione gRPC possa chiamare la sidecar: Go, NodeM SK2 Python , RustMSC4 JavaMST5 e molti altriMSV6

L'interfaccia gRPC

Il servizio ha tre RPC:

service DetectionService {
  rpc Detect(DetectRequest)             returns (DetectResponse);
  rpc DetectBatch(DetectBatchRequest)   returns (DetectBatchResponse);
  rpc RenderWidget(RenderWidgetRequest) returns (RenderWidgetResponse);
}

Detect è il per-il percorso caldo della richiesta. il metodo di passazioneM SK2 il percorsoMNK3 i headerMK4 l'IP remotoMRK5 e i dati opzionali delle impronte digitali TLSMMK6 Funziona la conduttura delle onde MEK7 solo i rilevatori che la richiesta MEK8 i segnali garantisconoMKK9 aggiorna il vettore di sessioneMZK10 fa dei punteggi contro il repertorio di reputazione MGK11 e restituisce un verdettoMDK12

DetectBatch conduce diverse richieste sequenzialmente. Usata per la riproduzione del log e l'analisi offline, non perM SK2uso della passerella per la richiesta .

RenderWidget accetta una sequenza di schemi liquidi, un verdetto opzionale, e una chiave - mappa di valori delle variabili aggiuntiveM SK3 poi riproduce il server di schemettiMSC4 sul lato e restituisce HTMLMST5 Ecco come i caller non--.NET producono botMSP7aware HTML senza creare un processo di rendering separatoMSV8 i dettagli nella sezione RenderWidget sotto

Cosa succede all'interno di una chiamata Detect

Un breve glossario prima del diagramma, visto che questi nomi appariranno:

  • Scatola nera: un pezzo per -la chiave di richiestaM SK2la borsa di valori (e.gMSC5 request.ip.is_datacenter, detection.useragent.confidence). I rilevatori gli scriveno dei segnali ; i rilevatori più tardi nella stessa onda li leggonoM SK2 Live solo per la durata di una richiesta. Raw PII PSK4IPMSC5 String UAMNK6 rimane nel contesto della richiesta e non atterra mai sulla lavagna
  • Contexto sintetico di Http: uno scaffaleM SK1in HttpContext il servizio gRPC viene costruito dai campi di richiesta proto. Il motore di rilevamento è stato progettato per ASP.NET middleware e si aspetta di leggere da HttpContext; se ne sintetizza uno, lo stesso motore può funzionare inalterato all'interno di un call gRPC.
  • Contribuzioni alla Detezione / Evidenza Aggregata: e output del rilevatore ( il segnale scrive i deltas di fiducia +) e il risultato combinatoM SK4
sequenceDiagram
    participant GW as Gateway (Caddy)
    participant SD as gRPC Service
    participant ORC as BlackboardOrchestrator
    participant DET as Detectors (up to 49, 4 waves)
    participant DB as SQLite

    GW->>SD: Detect RPC { method, path, headers, remoteIp }
    SD->>ORC: DetectAsync(syntheticHttpContext)
    ORC->>DET: Wave 0 - Identity + ContentSequence
    ORC->>DET: Wave 1 - Fast path <1ms: UA, Header, IP, Heuristic ...
    ORC->>DET: Wave 2 - Session vectors, Behavioural waveform
    ORC->>DET: Wave 3 - Slow path: DNS, advanced fingerprinting
    DET-->>ORC: DetectionContributions (signals, confidence deltas)
    ORC->>DB: update session vector and reputation score
    DB-->>ORC: ok
    ORC-->>SD: AggregatedEvidence { botProbability, riskBand, ... }
    SD-->>GW: DetectResponse { isBot, riskBand, recommendedAction, ... }

L'intera catena si muove all'interno del singolo call gRPC. Non c'è lavoro asynco dopo il ritorno della risposta.

Il Go SDK

Il codice di Gateway in Go non può importare l' ASP.NET sidecar. Quello che può fare è chiamarlo via gRPCM SK2 Il Go SDK (github.com/scottgal/stylobot-go) fornisce un'interfaccia tipificata che nasconde completamente i tipi di protobuf generati dai chiamatori.

Perché il SDK nasconde i tipi di protobuf

Protobuf-il codice generato è verboso e ha un API insolito. I enumi sono rappresentati come integeriM SK2 Le stringhe vengono come nomi di enumi proto.RISK_BAND_HIGH, non "High"). I nomi dei campi sono cammelCase in alcuni generatori e serpente_case in altriM SK2 Esibire i tipi di proto nella vostra API pubblica significa che i vostri chiamatori devono capire tutto questo .

Il SDK traduce una volta al confine (enumi proto alle stringhe canoniche,struzioni proto alle semplici Structure GoM SK2 e i chiamatori non la vedono mai

// the only interface you depend on: no proto imports required
type Client interface {
    Detect(ctx context.Context, req DetectRequest) (*Verdict, error)
    DetectBatch(ctx context.Context, reqs []DetectRequest) ([]*Verdict, error)
    RenderWidget(ctx context.Context, req RenderRequest) (*RenderResponse, error)
    Close() error
}

DetectRequest e Verdict sono semplici strutture Go:

type DetectRequest struct {
    Method   string
    Path     string
    Headers  map[string]string
    RemoteIP string
    Protocol string  // "http" or "https"; defaults to "https" if empty
    TLS      *TLSInfo
}

type Verdict struct {
    IsBot             bool
    BotProbability    float32
    Confidence        float32
    BotType           string   // "AiBot", "Scraper", "GoodBot", ...
    BotName           string
    RiskBand          string   // "VeryLow", "Low", "Elevated", "Medium", "High", "VeryHigh"
    RecommendedAction string   // "Allow", "Throttle", "Challenge", "Block"
    ThreatScore       float32
    ThreatBand        string
    ProcessingTimeMs  float32
    DetectorsRun      int32
    Reasons           []Reason
}

Creare un cliente e attivare la rilevazione:

import (
    stylobot "github.com/scottgal/stylobot-go"
    "context"
    "time"
)

client, err := stylobot.NewClient(
    "localhost:5090",
    stylobot.WithTimeout(50 * time.Millisecond),
    stylobot.WithAPIKey(os.Getenv("SB_API_KEY")),
)
if err != nil {
    log.Fatal(err)
}
defer client.Close()

verdict, err := client.Detect(ctx, stylobot.DetectRequest{
    Method:   r.Method,
    Path:     r.URL.RequestURI(),
    RemoteIP: r.RemoteAddr,
    Headers:  extractHeaders(r),
    Protocol: "https",
})
if err != nil {
    // fail open: log and continue
    log.Printf("stylobot detect failed: %v", err)
    return next(w, r)
}

if verdict.RecommendedAction == "Block" {
    http.Error(w, "Forbidden", http.StatusForbidden)
    return
}

Lassa connessione e sicurezza di partenza

grpc.NewClient crea un canale cliente ma non crea immediatamente una connessione TCP. La connessione avviene durante la prima chiamata RPC. Questo significa che il vostro processo di gateway inizia con successo anche se l'autostrana non è ancora iniziata . La prima richiesta dopo startup può fallire M SK3e dovrebbe essere gestita con fallimentoMSC4openMNK5 ma ogni richiesta successiva funziona normalmente una volta che l' autostrana è in funzioneMST6

Questo è diverso dai clienti HTTP, dove di solito si connette durante la creazione. gRPC Si va alla documentation copre il ciclo di vita in dettaglio.

Interazione di ritardo

WithTimeout su NewClient stabilisce un termine di chiamata predefinito per ogni -messa applicata all'interno di ognuno. Detect chiamare. Se il vostro codice di chiamata M SK1o il middleware come il plugin Caddy) deriva già un termine di termineMSC3 un contesto limitato dalla richiesta incomingente , il SDK applichiamo qualunque termine finisca primaMST5 Quando il plugin caddy è in usoMst6 il plugin possiede il termine mst7msM st8 potete omitare WithTimeout a partire da NewClient e lasciate che il plugin lo controlli. Per l'uso stand-alone (un handler che chiama direttamente il SDK NewClient come mostrato prima.

Il plugin Caddy

Caddy è un server web basato su Go- e proxy inverso con HTTPS automatico. Il suo sistema di plugin è compilatoM SK2time : che usate xcaddy per costruire un binario Caddy personalizzato che includa i vostri plugin, producendo un singolo auto-contenante il binario senza dipendenza del tempo di runtime dalle biblioteche condiviseM SK2 Questo è diverso da nginx 's dynamic module system MSC4.so file loaded at runtime). Il plugin StyloBot (github.com/scottgal/caddy-stylobot) registra un middleware handler che chiama il Go SDK su ogni richiesta.

La configurazione del Caddyfile:

{
    order stylobot before respond
}

:80 {
    stylobot {
        endpoint localhost:5090   # gRPC host:port of the sidecar
        timeout   50ms            # per-request deadline; fails open on expiry
        # on_block 503            # optional: change the block status code (default: 403)
    }
    reverse_proxy upstream:3000
}

Il plugin inserisce nove titoli di verdetto su ogni richiesta inviata:

Insieme campo di sorgente SSK2
X-StyloBot-IsBot isBot (boolM SK1
X-StyloBot-Probability botProbability (0.0-1.0)
X-StyloBot-Confidence confidence (0.0-1.0)
X-StyloBot-BotType eM SK1g. AiBot, Scraper, GoodBot
X-StyloBot-BotName eM SK1g. GPTBot, Googlebot
X-StyloBot-RiskBand VeryLow ... VeryHigh
X-StyloBot-Action Allow / Throttle / Challenge / Block
X-StyloBot-ThreatScore numerico
X-StyloBot-ThreatBand None ... Critical

Si chiede dove isBot=true e Action=Block Sono fermati alla porta con un 403 e non raggiungono mai l'altopiano . Tutto il resto (incluendo i robot con un Throttle o Challenge Ricommandazione) viene inviato con tutti i nove titoli intatti. Questa è la divisione previstaM SK2 il gateway gestisce i blocchi rigidi ; l'influente gestisce le nuanceMSC4

on_block cambia il codice di stato usato quando il gateway blocca (defaultM SK1 403). Set on_block 503 per sopprimere la logica di ritestazione nei scraper che trattano 403 come ritestabile.

Cosa fa il middleware su ogni richiesta?

flowchart TD
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px

    A["1. Strip inbound X-StyloBot-* headers"]:::input
    B["2. context.WithTimeout(r.Context(), 50ms)"]:::input
    C["3. sbClient.Detect(ctx, DetectRequest)"]:::async
    D{error?}
    E["log warn - fail open<br/>forward unchanged"]:::good
    F["4. injectHeaders<br/>X-StyloBot-IsBot, Probability, Confidence,<br/>BotType, BotName, RiskBand, Action,<br/>ThreatScore, ThreatBand"]:::input
    I["next.ServeHTTP - forward to upstream<br/>with all verdict headers injected"]:::good

    A --> B --> C --> D
    D -->|yes| E --> I
    D -->|no| F --> I

Step 1, strisciate i titoli entranti. Un cliente che conosce il X-StyloBot-* i nomi del titolo potrebbero auto-injectare un giudizio favorevole e farvi sopravvivere al fallimento-pace apertoM SK2Ne togliere prima significa che il giudizio che vedete in arrivo viene sempre dall'automobile laterale .

passo 2, termine di termine del contesto. L'intervallo di tempo è derivato da r.Context() usando context.WithTimeout (che richiede una durata relativaM SK1 context.WithDeadline prendono un tempo assoluto; sono equivalenti). derivano da r.Context() Invece di context.Background() è il punto chiave: se il cliente si disconnette prima che la chiamata del gRPC sia completata, l'annunciazione si propaga e la macchina laterale smette di processare prestoM SK2

Passi 3 e 4, rilevare e iniettareM SK2 I nove campi di giudizio diventano nove. X-StyloBot-* i titoli. I titoli sono stabiliti prima del blocco di controllo, così che l'alto flusso li legge attraverso styloBotMiddleware({ mode: 'headers' }) per tutte le richieste non bloccate-. Requests where isBot=true e recommendedAction=Block Vengono restituite come 403 all'entrata ; tutto il resto va avanti con i testi del verdetto completo attaccati.

Implementazione:

// from sdk/caddy/stylobot.go
func (s *StyloBot) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
    for _, name := range stylobotHeaders {
        r.Header.Del(name)
    }

    ctx, cancel := context.WithTimeout(r.Context(), s.timeout)
    defer cancel()

    verdict, err := s.sbClient.Detect(ctx, sb.DetectRequest{
        Method:   r.Method,
        Path:     r.URL.RequestURI(),
        RemoteIP: ExtractIP(r),
        Protocol: r.Proto,
        Headers:  ExtractHeaders(r),
    })
    if err != nil {
        s.logger.Warn("stylobot detect failed, failing open", zap.Error(err))
        return next.ServeHTTP(w, r)
    }

    injectHeaders(r, verdict)

    if verdict.IsBot && s.OnBlock > 0 && verdict.RecommendedAction == "Block" {
        http.Error(w, "Forbidden", s.OnBlock)
        return nil
    }
    return next.ServeHTTP(w, r)
}

L'edificio con xcaddy

I plugin Caddy devono essere compilati nel binario usando xcaddy. Il Dockerfile nei test di integrazione mostra il modello:

# from tests/integration/caddy-sidecar/Dockerfile
FROM caddy:2-builder AS builder

WORKDIR /build
COPY sdk/caddy/ caddy-plugin/
COPY sdk/go/    go/

WORKDIR /build/caddy-plugin

RUN xcaddy build \
    --with github.com/scottgal/caddy-stylobot=/build/caddy-plugin \
    --with github.com/scottgal/stylobot-go=/build/go

FROM caddy:2
COPY --from=builder /build/caddy-plugin/caddy /usr/bin/caddy
COPY tests/integration/caddy-sidecar/Caddyfile /etc/caddy/Caddyfile

La xcaddy rimpiazza la directiva.

Il plugin's go.mod contiene:

replace github.com/scottgal/stylobot-go => ../go

Questo dice alla catena di strumenti Go " quando vedete. stylobot-go, usa il directorio locale invece di prelevare dal proxy del module." Funziona per go build e go test nel directorio di plugin.

xcaddy crea un nuovo modulo Go temporaneo per la sua costruzione. Quel modulo non eredita. replace istruzioni dal plugin's go.mod. Senza il secondo --with argument, xcaddy proverebbe a scaricare stylobot-go a partire da pkg.go.dev ( dove non è ancora stato pubblicato) e fallisceM SK2

L'informazione figura nella parte dispositiva. --with module=path l'argomentazione è il equivalente nativo di xcaddy' replace directiva: mappe un percorso di moduli verso un directorio locale al momento della costruzione. Entrambi i moduli locali devono essere chiamati esplicitamenteM SK2

RenderWidget: Template liquidi su gRPC

RenderWidget è un gRPC RPC sul sidecar che accetta una sequenza di schemi liquidi, la riproduce con il contesto di rilevamento, e restituisce HTMLM SK2 Questo permette ad ogni chiamatore ( andare in proxyMSC4 Node Layer SSRMST5 Pipeline di lottoMSP6 produrre botMSSK7 essere consapevoli del HTML senza fare un processo di rendering separatoMSL8

Immagini di liquidi

Liquido è una lingua di templaggio creata da Shopify, usata dai temi di shopify , Jekyll, Pages of GitHubM SK3 e molti altri sistemi+. Le sue proprietà chiaveMSC5 sicure da usare con l'utenteMST6Template fornete MSSK7 nessun'esecuzione arbitraria del codice+MST8 abbastanza semplice da essere scritta dagli sviluppatori non-MST9+Mst10+Mt11+ StyloBot usa Fluid.Core, una alta -performance .implementazione NET di Liquid, per rendere server templatesMSC4sideM SK5

L'implementazione della carrozza laterale:

// from src/Mostlylucid.BotDetection.Sidecar/Services/DetectionGrpcService.cs
private static readonly FluidParser Parser = new();  // static, shared, compiled templates cached

public override async Task<Proto.RenderWidgetResponse> RenderWidget(
    Proto.RenderWidgetRequest request, ServerCallContext context)
{
    if (!Parser.TryParse(request.Template, out var template, out var error))
        return new Proto.RenderWidgetResponse { Success = false, Error = error };

    var ctx = new TemplateContext();
    if (request.Verdict is { } v)
    {
        ctx.SetValue("isBot",             v.IsBot);
        ctx.SetValue("botProbability",    (double)v.BotProbability);
        ctx.SetValue("botType",           v.BotType);
        ctx.SetValue("botName",           v.BotName);
        ctx.SetValue("riskBand",          v.RiskBand.ToString());
        ctx.SetValue("recommendedAction", v.RecommendedAction.ToString());
        ctx.SetValue("threatScore",       (double)v.ThreatScore);
        ctx.SetValue("threatBand",        v.ThreatBand.ToString());
    }
    foreach (var kv in request.Vars)
        ctx.SetValue(kv.Key, kv.Value);

    var html = await template.RenderAsync(ctx);
    return new Proto.RenderWidgetResponse { Html = html, Success = true };
}

Fluid.Core mantiene un archivio interno di schemi compilati. Rendimenti ripetuti della stessa sequenza di scheme skip reM SK2parsing FluidParser è statico e condiviso su tutte le chiamate del gRPC.

Il Nodo StyloBotGrpcClient.renderWidget() l'esempio e la completa riferimento alla variabile template sono nella Article di TypeScript SDK.

Lo chiamo da Go:

rendered, err := client.RenderWidget(ctx, stylobot.RenderRequest{
    Template: `{% if isBot %}<p class="warning">Bot: {{ botType }}</p>{% endif %}`,
    Verdict:  verdict,
    Vars:     map[string]string{"locale": "en-GB"},
})
if err == nil && rendered.Success {
    fmt.Fprint(w, rendered.HTML)
}

La sintax del template è identica anche se chiamate. RenderWidget da Go, Node, o usa <sb-widget> nel browser: lo stesso motore liquido, gli stessi nomi di variabiliM SK2 la stessa strada di renderingMSC3

Lato di produzione

graph LR
    classDef input fill:none,stroke:#3b82f6,stroke-width:2px
    classDef async fill:none,stroke:#a855f7,stroke-width:2px
    classDef good fill:none,stroke:#22c55e,stroke-width:2px
    classDef store fill:none,stroke:#f59e0b,stroke-width:2px

    INT([Internet])
    CF["Cloudflare<br/>Tunnel / CDN"]
    CA["Caddy<br/>+ caddy-stylobot"]:::input
    SD["StyloBot Sidecar<br/>:5090 gRPC  ·  :5091 REST"]:::async
    WEB["Upstream App<br/>Node / Go / ASP.NET"]:::good
    DB[("SQLite<br/>sessions · reputation")]:::store

    INT --> CF --> CA
    CA -->|"gRPC Detect<br/>≤50ms"| SD
    SD <-->|"persist"| DB
    CA -->|"X-StyloBot-* headers"| WEB
    WEB -->|"/_stylobot/partials/render<br/>(widget rendering)"| SD

L'applicazione in arrivo chiama il sidecar direttamente per rendere gli widget, bypassing the gateway. Il rendere degli widget ha bisogno del contesto del verdicto completo e succede dopo che la richiesta ha già passato la detezione delle porteM SK2 quindi non ci sono duplicazioni di rilevamentoMSC3

Succede-apertura a ogni strato

Caddy plugin, Node middleware, and Go SDK all'error openM SK2 a sidecar timeout or error becomes an warning log and a permissive empty verdictMNK3 not a MRK4xxMMK5 The 50ms Caddy deadline is a coldMSC7start safety marginMST8 the steadyMSSK9state cost on a warm connection is \1–5msMSL11

Il modo in cui il traffico è bloccato, perché la rilevazione non è disponibile, è peggio dell'assenza di traffic bot durante un'interruzione della macchina laterale.


La serie di esibizioni continua. Più post su interni di rilevamento, schemi di deploiamentoM SK2 osservabilità , e la topologia commerciale sono ancora in arrivoMSC4 L'arco finoraMST5

Source per la implementazione: github.comM SK1scottgal/stylobot. Motore viventeM SK1 Armatura dashboard, e controlli commerciali a stylobot.net.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.