# StyloBot Release Series - Die Sidecar-Architektur

*StyloBot-'-Detektions-Engine ist ASP..-NET Core-MSC2-Dieser Beitrag erklärt, wie dieser Motor mit Go Gateways verbunden ist.,-Node-.-js-Anwendungen. ,- und jeder andere Stapel über einen gRPC Sidecar.MSC6- ein typisiertes Go SDKK7- und ein Caddy-Plugin, ohne dass die Verbraucher etwas über die internen Systeme von .-NET wissen müssen.*

[<img src="/articleimages/stylobot-logo.svg" alt="StyloBot" width="120" />](https://www.stylobot.net)

> **Das `github.com/scottgal/stylobot-go` SDK-,- das `github.com/scottgal/caddy-stylobot` plugin-,- und die `Mostlylucid.BotDetection.Sidecar` Container wird in Kürze veröffentlicht** Alles unten beschreibt die Oberfläche, die sie aussetzen werden

> **StyloBot Release Series**
> 
> 1. [**Verhalten nicht Identität**](/blog/stylobot-fingerprint)Warum StyloBot die Kunden verhaltensmäßig modelliert
> 2. [**Verhalten Bewusst WERDEN**](/blog/behaviour-aware-ux)Die Server-Oberfläche von -, die für Anwendungen von . oder NET bestimmt ist
> 3. [**Ungebundenes Wachstum in langen --Diensten finden und beheben**](/blog/stylobot-release-reliability)Die Zuverlässigkeitsdisziplin, die den Motor in der Produktion langweilig hält
> 4. [**Verhalten--Bewusstsein TypScript UI**](/blog/typescript-sdk)Komponenten von , und Browsern
> 5. **Die Sidecar-Architektur**Dieser Artikel
> 6. [**Schneller lernen**](/blog/stylobot-release-learning)Das adaptive Lernsystem (MSK) und der Urteilscache
> 7. [**Testen der Sache, die Won-' sitt Still**](/blog/stylobot-release-nondeterministic-testing)Eine BDF-Datei treibt Regression an.
> 8. [**StyloExtrahieren Sie einen lokalen HTML zu Markdown Konverter**](/blog/stylobot-release-styloextract)Die HTML-Markdown-Schicht, die sich mit dem Detektor verbindet, hat den Walker Bug lucidVIEW gefangen und die Hundefutterschleife, die es ehrlich machte.

<!--category-- Architecture, Go, ASP.NET Core, StyloBot, gRPC -->
<datetime class="hidden">2026-06-01T10:30</datetime>

# Warum ein Beiwagen?

Ein umgekehrter Proxy ist der offensichtliche Ort, um Bot-Detektion zu starten.:. Er sitzt vor allem und sieht jede Anfrage und kann blocken bevor jeder Anwendungscode läuft.MSC3. Diese Logik hält richtig, bis Sie fragen, was Ihre Anwendung sollte *anders machen* based on who is make the request- - ein Gateway, das direkt blockiert ist ein gate- -;-, was die meisten Anwendungen tatsächlich brauchen, ist ein Urteil können sie auf mehrere Weisen sofort handeln - : drosseln die API-,-Personalisierung der UI-MSC4-exclude Traffic aus analytics - ,- fügen Sie einen Reibungsschritt bei Checkout-.-Ein Gate kann nichts von dem tun - . - Ein Urteil Pipeline can -MSC8-

Das Sidecar-Muster trennt die beiden Belange.Das Gateway bleibt schnell und staatenlos.Das Sidecar hält den Sitzungszustand aufrecht., archetype anker, , drift state,, quitting cache, das die Detektion präzise macht. [Schneller lernen](/blog/stylobot-release-learning)Die beiden kommunizieren über das lokale Netzwerk .. . .(samer Host oder gleiches Pod... so ist der runde -" Trip Mikrosekunden zu einzelnen -"stelligen Millisekunden " . ," nicht die . 50-200"ms eines entfernten API-Aufrufs " .. Das Latenzbudget ist, was per... -"Erkennung praktisch macht .

Dies ist kein neuartiges Muster. [Gesandter Proxy](https://www.envoyproxy.io/) tut genau dies für den Dienst ,- ,mesh betrifft , ( , mTLS , , , Retries , ., , Stromkreisbruch , [Dapr](https://dapr.io/) tut es für Zustand und pub-/-sub [OpenTelemetrie Sammler](https://opentelemetry.io/docs/collector/) does it for telemetry- - .-Linkerd-,-Consul Connect- und AWS App Mesh folgen alle dem gleichen Modell. Das Muster erscheint immer, weil es ein echtes Problem löst.: - Sie wollen komplexe stateful Verhalten, das Sprachgrenzen überschreitet, ohne es in jeder Sprache umzuimplementieren.

[TOC]

## Warum nicht einfach als Bibliothek einbetten?

Die Alternative ist das Kompilieren von Detektion direkt in die Gateway-Bibliothek, d.h. eine reine Pressemitteilung oder eine CGo-Bindung an eine C-Bibliothek.

Ebenso wenig ist realistisch für einen Motor dieser Komplexität... StyloBot hat über vier Ausführungs-Detektoren organisiert. **Wellen** (-spätere Wellen feuern nur dann, wenn frühere Signale es rechtfertigen. ;-skalierender Versuch löst andere Detektoren aus als ein Googlebot crawling. **Markov-Kettenvektoren** Eine Markov-Kette ist nur ein Wahrscheinlichkeitsmodell über ", gegeben das letzte, was diese Sitzung getan hat,, was als nächstes kommt?", und 129, Dimensionen erfasst genug Seite-, transition Form Menschen zu sagen und Bots auseinander ., Es läuft **Nachweis der Leiden-Gemeinschaft** über die Vektoren ,: , ein Graph , - , Clustering-Algorithmus , dass Gruppen Sitzungen gleichen Verhaltens , , , das ist wie StyloBot ein Bot-Netzwerk auch wenn einzelne Sessions fein aussehen , und es bleibt all dies auf SQLite zwischen Anfragen ,. , Dieser Zustand braucht einen unabhängigen Lebenszyklus .. , Es kann nicht mit dem Knoten-Prozess neu starten oder abgerissen werden, wenn das Gateway reloads seine Konfiguration , . ,

Ein Sidecar lässt jede Komponente tun, was es gut ist

```mermaid
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
```

Das Gateway ruft den Sidecar auf und injiziert das Ergebnis als HTTP-Header.,. Optional blockiert die Upstream-Anwendung die Header und wirkt nach dem Urteil.MS K3. Das Sidecar hält an Zustand zwischen Anfragen... Die Detektionspipeline läuft innerhalb des einzelnen gRPC-Aufrufs und sein Ergebnis propagiert sich als neun HTTP-Kopfzeilen.

# Der Beiwagen

`Mostlylucid.BotDetection.Sidecar` ist ein minimaler ASP-.-NET-Kernprozess, der keine UI-MSC2 ist, keine statische Datei dient und kein Routing über die Endpunkte gRPC und REST hinaus verfügt. Es startet den Volldetektionsmotor und setzt zwei Ports aus.

- **:5090**HTTP®/2 für gRPC-Clients ( gateways, , gehen Proxies, , der Knoten gRPc-Client ) ,
- **:5091**HTTP-Ausfuhr für REST-Clients `/api/v1/*` Endpunkte

## gRPC

[gRPC](https://grpc.io/) ist ein hochleistungsfähiges Remote-Procedure-Call Framework, das bei Google entwickelt wurde. [Protokollpuffer](https://protobuf.dev/) Eine kompakte binäre Kodierung, die schneller zu Serialisierung und kleiner auf dem Draht ist als JSON-.. gRPC läuft über HTTP./2,, was bedeutet, dass es Multiplexing Mehrfache Anfragen über eine TCP-Verbindung erhält.

Die Schnittstelle ist in einem `.proto` aus dieser Datei erzeugen typisierte Client- und Server-Stubs in jeder unterstützten Sprache. `.proto` Dateien, so dass jede Sprache mit einer gRPC-Implementierung das Sidecar nennen kann.

## Die gRPC-Schnittstelle

Der Dienst hat drei RPCs.

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

**`Detect`** ist der per---Request-Hot Path?.-Pass es Methode ,,-Pfad , ,-Header , ,-Remote IP , und optional TLS Fingerabdruckdaten , .-Es läuft die Welle Pipeline nur die Detektoren, dass die Anfrage ,'-Signale Warrant , die Sitzung vector , ,-Scores gegen den Reputation Store .,- und gibt ein Urteil ,

**`DetectBatch`** läuft mehrere Anfragen sequentiell ,. , verwendet für Protokoll-Wiedergabe und Offline-Analyse , , , nicht per , - , anfordern Gateway verwenden .. ,

**`RenderWidget`** akzeptiert eine Liquid-Schablone-String-Strings-,, ein optionales Urteilsurteil und eine Schlüsselkarte von zusätzlichen Variablen.,, macht dann den Vorlagenserver wieder und gibt HTML.MSC5 zurück.Dies ist die Art und Weise, wie Nicht-MSC6-NET-Anrufer Bot?-aware-HTML produzieren, ohne einen separaten Renderprozess aufzustellen.;, Details im Abschnitt RenderWidget weiter unten.

## Was in einem Detect-Anruf passiert

Ein schnelles Glossar vor dem Diagramm, da diese Namen erscheinen werden.

- **Tafel**:, ein Per--, Schlüssel anfordern -, Wertbeutel, ., g. `request.ip.is_datacenter`, `detection.useragent.confidence`Detektoren schreiben Signale zu ihr;. Spätere Detektor in der gleichen Welle lesen sie.. Lebt nur für die Dauer einer Anfrage und landet nie auf der Tafel...
- **Synthetisch HttpKontext**:" ein Ständer ,-"in `HttpContext` der gRPC-Service baut aus den proto request fields. Die Detektionsmaschine wurde für ASP ,.'NET Middleware entwickelt und erwartet von `HttpContext`Die Synthese lässt den gleichen Motor in einem gRPC-Ruf unverändert laufen.
- **NachweisBeiträge** / **Aggregierte Evidenz**Detektorausgänge ( signal schreibt Vertrauen Deltas ) und ihr zusammengeführtes Ergebnis .

```mermaid
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, ... }
```

Die gesamte Pipeline läuft innerhalb des einzelnen gRPC-Aufrufs...Es gibt keine Async-Arbeit, nachdem die Antwort zurückgegeben wurde.

# Das Go SDK

Gateway-Code in Go kann nicht importieren, die ASP .. .NET Sidecar ?. . Was es tun kann, ist es über gRPC nennen .`github.com/scottgal/stylobot-go`-) , bietet eine getippte Schnittstelle, die die erzeugten Protobuf Typen von Anrufern vollständig versteckt

### Warum das SDK Protobuf Typen versteckt

Protobuf---generierter Code ist verbose und hat eine ungewöhnliche API..-Enums werden als Integer dargestellt.MSC2-Strings kommen als rohe proto enum Namen.`RISK_BAND_HIGH`Nicht `"High"`Feldnamen sind Kamelfall in einigen Generatoren und Schlangenfall in anderen Ausstellen von Prototypen in Ihrer öffentlichen API bedeutet, dass Ihre Anrufer all dies verstehen müssen

Das SDK übersetzt einmal an der Grenze .( enums to canonical strings . , . proto structs to plain Go stucts ,) . und Anrufer nie sehen es .

```go
// 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` und `Verdict` sind einfache Go-Strukturen

```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
}
```

Erstellen eines Clients und laufende Erkennung

```go
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
}
```

### Lazy Verbindung und Startsicherheit

`grpc.NewClient` erstellt einen Client-Kanal, baut aber nicht sofort eine TCP-Verbindung auf Die Verbindung erfolgt beim ersten RPC-Anruf .. . Dies bedeutet, dass Ihr Gateway-Prozess erfolgreich startet, auch wenn das Sidecar noch nicht gestartet ist . . . Die erste Anfrage nach dem Start kann fehlschlagen .( . und sollte mit failant behandelt werden .

Dies unterscheidet sich von HTTP-Clients, in denen Sie typischerweise bei der Erstellung verbinden. [gRPC Go Dokumentation](https://pkg.go.dev/google.golang.org/grpc#NewClient) deckt den Lebenszyklus im Detail ab

### Timeout-Interaktion

`WithTimeout` am `NewClient` setzt eine Standard-Per---Call-Deadline, die in jedem `Detect` call-.-Wenn Ihr aufrufender Code ( oder Middleware wie das Caddy-Plugin )- bereits eine Frist ableitet, die von der eingehenden Anfrage , eingeblendet wird, gilt das SDK, welche Frist zuerst abläuft..-Wenn das Cadody-Plugin verwendet wird, kann das Plugin die Frist 50-ms nicht mehr verwenden. `WithTimeout` von `NewClient` und lassen Sie das Plugin es steuern...... Für den Standalone-Einsatz....... ein Handler, der das SDK direkt anruft.... `NewClient` wie oben gezeigt

# Das Caddy-Modul

[Caddy](https://caddyserver.com/) ist ein Go®--basierter Webserver und Reverse Proxy mit automatischer HTTPS®MS K1. Sein Plugin-System wird kompiliert.-?time?:? [xcaddy](https://github.com/caddyserver/xcaddy) um eine benutzerdefinierte Caddy-Binärdatei zu erstellen, die Ihre Plugins enthält, die ein einziges selbst produzierendes Binärmodul ohne Laufzeitabhängigkeit von geteilten Bibliotheken erzeugen, das sich von dem dynamischen Modulsystem nginx-' unterscheidet.`.so` Dateien, die zur Laufzeit geladen werden... das StyloBot-Plugin...`github.com/scottgal/caddy-stylobot`) registriert einen Middleware-Handler, der das Go SDK auf jede Anfrage ruft

Die Konfiguration der Caddyfiles

```caddy
{
    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
}
```

Das Plugin injiziert neun Urteil-Header auf jede übermittelte Anfrage.

Quelle Feld
|---|---|
| `X-StyloBot-IsBot` | `isBot` (-Bool-)-|-
| `X-StyloBot-Probability` | `botProbability` (0.0-1.0) |
| `X-StyloBot-Confidence` | `confidence` (0.0-1.0) |
| `X-StyloBot-BotType` |" e-, ." g, ,." `AiBot`, `Scraper`, `GoodBot` |
| `X-StyloBot-BotName` |" e-, ." g, ,." `GPTBot`, `Googlebot` |
| `X-StyloBot-RiskBand` | `VeryLow` ... `VeryHigh` |
| `X-StyloBot-Action` | `Allow` / `Throttle` / `Challenge` / `Block` |
| `X-StyloBot-ThreatScore` ,| , numerisch , |
| `X-StyloBot-ThreatBand` | `None` ... `Critical` |

Anträge auf `isBot=true` und `Action=Block` werden am Gateway mit einem 403 stoppt und erreichen nie die vorgelagerten ., alles andere, einschließlich Bots mit `Throttle` oder `Challenge` Empfehlung: Das Gateway wird mit allen neun Headern intakt weitergeleitet.Das ist der beabsichtigte Split. Das Gateway verarbeitet harte Blöcke.

`on_block` ändert den Statuscode, der verwendet wird, wenn das Gateway blockiert. `on_block 503` zu unterdrücken retry Logik in Schabern, die als wiederverwendbar behandeln

## Was die Middleware auf jede Anfrage macht

```mermaid
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
```

**Schritt - Streifen inbound Kopfzeilen** Ein Kunde, der die `X-StyloBot-*` Header-Namen konnten sich selbst aussenden ein günstiges Urteil und haben es überleben die fail-?open path .? Stripping sie zuerst bedeutet das Urteil der Upstream sieht immer aus dem Sidecar?.?

**Schritt: Kontext-Deadline** Der Timeout ist abgeleitet von `r.Context()` Verwendung `context.WithTimeout` .( , die eine relative Dauer dauert . `context.WithDeadline` nimmt eine absolute Zeit... sie sind äquivalent... `r.Context()` eher als `context.Background()` ist der Schlüsselpunkt, wenn sich der Client vor dem gRPC-Aufruf trennt und die Absage propagiert und das Sidecar die Verarbeitung vorzeitig beendet.

**Schritte 3 und 4, erkennen und injizieren** Die neun Urteilsfelder werden zu neun `X-StyloBot-*` headers-.-Header werden vor dem Blockcheck gesetzt, also liest der Upstream sie über `styloBotMiddleware({ mode: 'headers' })` für alle nicht geblockten Anfragen `isBot=true` und `recommendedAction=Block` werden am Gateway als 403 zurückgegeben, alles andere vorwärts mit den vollständigen Urteil-Headern angehängt.

Die Implementierung

```go
// 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)
}
```

## Gebäude mit xcaddy

Caddy-Plugins müssen in die binäre kompiliert werden [xcaddy](https://github.com/caddyserver/xcaddy)Das Dockerfile in den Integrationstests zeigt das Muster.

```dockerfile
# 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
```

### Die xcaddy-Richtlinie wird ersetzt

Das Plugin„'" `go.mod` enthält:

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

Dies sagt die Go-Toolchain, wenn Sie sehen `stylobot-go`Verwenden Sie das lokale Verzeichnis, anstatt aus dem Modul Proxy zu holen. Es funktioniert für `go build` und `go test` im Plugin-Verzeichnis

xcaddy erstellt ein frisches temporäres Go-Modul für sein build-.-Das Modul erbt nicht `replace` Direktiven aus dem Plugin `go.mod`Ohne die zweite `--with` Argument: xcaddy würde versuchen, herunterladen `stylobot-go` von `pkg.go.dev` (, wo es noch nicht veröffentlicht wird, ) und fail-.

Das `--with module=path` argument ist xcaddy.'.s natives Äquivalent einer `replace` Direktive: : , es zeigt einen Modulpfad zu einem lokalen Verzeichnis zum Zeitpunkt des Erstellens an.. , Beide lokale Module müssen explizit benannt werden.

# RenderWidget-:® Flüssige Vorlagen über gRPC

`RenderWidget` ist ein gRPC RPC auf dem Sidecar, der eine Liquid-Schablone-String akzeptiert, das es mit dem Erkennungskontext rendert,, und HTML-Street zurückgibt.Dies erlaubt jedem Anrufer einen Proxy ,, Node SSR Layer,, Batch Pipeline ), Bot--aware-HTML zu produzieren ohne einen separaten Renderprozess auszuführen..

### Flüssige Vorlagen

[flüssig](https://shopify.github.io/liquid/) ist eine templating Sprache, die von Shopify erstellt wurde., used by Shopify themes............... und viele andere Systeme...... seine wichtigsten Eigenschaften...... sicher mit Benutzer zu laufen...... ...-... mitgelieferte Vorlagen...... keine willkürliche Code-Ausführung...... ..),... einfach genug für Nicht---Entwickler zum Schreiben...... [Fluid-.-Körner](https://github.com/sebastienros/fluid)Eine hohe Leistung von - ist die Implementierung von Liquid-, zum Rendern von Vorlagen.

Die Sidecar-Implementierung

```csharp
// 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 pflegt einen internen kompilierten Vorlagencache... Wiederholte Renders des gleichen Template-Strings überspringen re...-.parsing ?.. `FluidParser` ist statisch und über alle gRPC-Aufrufe verteilt

Der Knoten `StyloBotGrpcClient.renderWidget()` Beispiel und die vollständige Vorlage Variable Referenz sind in der [TypeScript SDK Artikel](/blog/typescript-sdk).

Ich rufe es von Go-:.

```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)
}
```

Die Syntax der Vorlage ist identisch, ob Sie aufrufen `RenderWidget` von Go-,-Knoten oder verwenden `<sb-widget>` im Browser die gleiche Flüssige Engine die gleichen variablen Namen die gleiche Rendering-Pfad

# Produktionsplan

```mermaid
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
```

Die vorgelagerte Anwendung ruft das Sidecar direkt für Widget-Rendering auf, um das Gateway zu umgehen... Widget Rendering benötigt den vollständigen Urteilskontext und passiert, nachdem die Anfrage bereits eine Gateway-Erkennung bestanden hat.

## Fehler beim Öffnen auf jeder Ebene

Caddy plugin-,-Node Middleware - ,- und Go SDK alle ausfallen offen :-ein Sidecar-Timeout oder Fehler wird zu einem Warnprotokoll und eine permissive leere Urteil , nicht ein , 5 xx , die ,50-ms Caddy Deadline ist ein kalt - Start Sicherheitsmarge -;- die steady , --state Kosten auf einer warmen Verbindung ist , ist .

Der trade---off dieses codiert encodes-:-Blockierung legitimen Verkehr, weil die Erkennung nicht verfügbar ist schlimmer als fehlenden Bot-Verkehr während eines Sidecar-Ausfälles

---


Die Release-Serie geht weiter... mehr Beiträge auf Detektion Internals... ,... Bereitstellungsmuster...,... Beobachtbarkeit... und die kommerzielle Topologie sind noch nicht da.

- [Verhalten nicht Identität](/blog/stylobot-fingerprint)Warum die Detektion verhaltensbedingt sein muss, ist nicht identitätsbezogen.
- [Verhalten Bewusst WERDEN](/blog/behaviour-aware-ux)Verwandeln Sie das Urteil in Razor-Steuerungspolitik
- [Ungebundenes Wachstum in langen --Diensten finden und beheben](/blog/stylobot-release-reliability)Die Zuverlässigkeitsdisziplin, die den Motor unter anhaltendem Verkehr hält
- [Verhalten--Bewusstsein TypScript UI](/blog/typescript-sdk)Das gleiche Urteil tauchte in Express-,. Fastify und dem Browser auf.
- **Die Sidecar-Architektur** Dieser Artikel zeigt, wie der Motor über das Go SDK-, und das Caddy Plugin in jeden Stack steckt.

Quelle für die Implementierung [github-.-com./-scottgal./-stylobot](https://github.com/scottgal/stylobot)Live-Engine, Dashboard und kommerzielle Steuerungen am [Stylobot-.-net](https://www.stylobot.net).