Go with net/http — bRRAIn Docs
The server shape of a Platform SDK extension using only Go's standard library: one client at start-up, handlers on 127.0.0.1, request-scoped deadlines and graceful shutdown.
Go with net/http
A long-running extension is an HTTP server. The supervisor starts your binary, sets the port variable your manifest names in listen.port_env, and reverse-proxies the routes your manifest declares (for example /api/apps/hello-notes/) to that port. The standard library is all you need.
package main
import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"
platformsdk "github.com/Qosil/bRRAIn/pkg/platform-sdk"
)
func mustEnv(name string) string {
v := strings.TrimSpace(os.Getenv(name))
if v == "" {
log.Fatalf("hello-notes: %s is required (the supervisor sets it)", name)
}
return v
}
func main() {
port := mustEnv("HELLO_NOTES_PORT") // the variable named in listen.port_env
client, err := platformsdk.New(platformsdk.Options{
BaseURL: mustEnv("BRRAIN_INTERNAL_URL"),
Token: mustEnv("BRRAIN_INTERNAL_TOKEN"),
UserAgent: "hello-notes/0.1.0",
HTTPClient: &http.Client{Timeout: 150 * time.Second}, // room for model calls
})
if err != nil {
log.Fatalf("hello-notes: build SDK client: %v", err)
}
mux := http.NewServeMux()
mux.HandleFunc("GET /api/apps/hello-notes/notes", listNotes(client))
srv := &http.Server{Addr: "127.0.0.1:" + port, Handler: mux}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_ = srv.Shutdown(shutdownCtx)
}()
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}
// listNotes shares the one client built at boot; it never builds a client per request.
func listNotes(c *platformsdk.Client) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
defer cancel()
entries, err := c.Vault().List(ctx, "extensions/hello-notes/notes")
var apiErr *platformsdk.APIError
switch {
case errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound:
entries = nil // nothing written yet
case err != nil:
log.Printf("list notes: %v", err)
http.Error(w, "notes are unavailable right now", http.StatusServiceUnavailable)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(entries)
}
}
What this shows:
- One client, built at start-up, shared by every handler through a closure. Never build a client per request.
- Listen on
127.0.0.1. The supervisor's proxy reaches you over loopback; binding0.0.0.0exposes the process for no benefit. - Request-scoped deadlines. Each handler derives its SDK context from
r.Context(), so a user who closes the page cancels the platform call too. - A first-run branch. 404 on a folder your extension has not created yet is not an error.
- Graceful shutdown on SIGTERM, so in-flight requests finish when the pod restarts or updates the extension.
The matching manifest declares the route prefix, the port variable and the sub-clients the code calls:
{
"slug": "hello-notes",
"name": "Hello Notes",
"version": "0.1.0",
"binary": "bin/hello-notes",
"listen": { "protocol": "http", "port_env": "HELLO_NOTES_PORT" },
"routes": [
{ "prefix": "/api/apps/hello-notes/", "method": "*", "proxy_to": "internal" }
],
"platform_sdk": {
"min_version": "1.2.0",
"consumes": ["vault.list"]
},
"env_required": ["BRRAIN_INTERNAL_URL", "BRRAIN_INTERNAL_TOKEN", "HELLO_NOTES_PORT"]
}
consumes uses the identifiers from platformsdk.OfferedSubClients(). The supervisor refuses to start an extension that declares an identifier the pod does not offer or a min_version newer than the pod's SDK, so check the manifest in a test with the SDK's manifest package (manifest.Load then ContractCheck).
Using a router framework
The SDK has no framework dependency. With Gin, Echo, chi or any other router the pattern is identical: build the client once in main, hand it to your handlers (closure, struct field or dependency container), and derive each call's context from the incoming request. See Gin and Echo.
Next
- Error handling
- bRRAIn Certified SDK Developer course: Module 8 covers the manifest, packaging, install, update and marketplace submission.