Go and C++ HTTP Frameworks
Two systems-language examples that govern the same GET /hello / POST /echo contract through a local Chio sidecar. Go uses chio.Protect as an http.Handler decorator; C++ uses chio::drogon::ChioMiddleware registered per route.
What it shows
chio-go-http: a singlechio.Protect(handler, opts...)call that wraps anyhttp.Handler. The example useschias the inner router; it works equally well withnet/http,gorilla/mux, or any handler.chio-drogon: a Drogon middleware namedchio::drogon::ChioMiddlewareattached to specific routes plus achio::drogon::receipt_id(request)accessor inside handlers.- The C++ smoke verifies that the receipt content hash is bound to the exact raw JSON bytes sent by the client.
- Both examples talk to a local
chio api protectsidecar.
Drogon is optional
hello-drogon is gated by CMake. If cmake is missing or Drogon::Drogon cannot be found, both run.sh and smoke.sh skip with a clear message. Install Drogon, or set CMAKE_PREFIX_PATH, before running the example.Prerequisites
- Go: Go 1.21+ (see
go.mod). - C++: a C++17 compiler, CMake 3.16+, and the Drogon framework. The example pulls
packages/sdk/chio-drogonviaadd_subdirectory. - The
chioCLI onPATH. The smokes start a local sidecar and trust service.
Run them
cd examples/hello-chi # or hello-drogon
./run.sh
# Full smoke (sidecar + trust + deny + allow)
./smoke.shDefault ports:
| Example | Env var | Default |
|---|---|---|
hello-chi | HELLO_CHI_PORT | 8013 |
hello-drogon | HELLO_DROGON_PORT | 8020 |
Go (chi)
chio.Protect wraps any http.Handler and returns a new http.Handler. The chi router goes inside that wrapper unchanged; you keep your normal routing, middleware, and handler signatures.
Module
module hello-chi
go 1.21
require (
github.com/backbay-labs/chio/sdks/go/chio-go-http v0.0.0
github.com/go-chi/chi/v5 v5.2.3
)
require github.com/google/uuid v1.6.0 // indirect
replace github.com/backbay-labs/chio/sdks/go/chio-go-http => ../../sdks/go/chio-go-httpServer
package main
import (
"encoding/json"
"log"
"net/http"
"os"
chio "github.com/backbay-labs/chio/sdks/go/chio-go-http"
"github.com/go-chi/chi/v5"
)
type echoRequest struct {
Message string `json:"message"`
Count int `json:"count"`
}
type echoResponse struct {
Message string `json:"message"`
Count int `json:"count"`
}
func main() {
router := chi.NewRouter()
router.Get("/healthz", func(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
})
router.Get("/hello", func(w http.ResponseWriter, _ *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{"message": "hello from chi"})
})
router.Post("/echo", func(w http.ResponseWriter, r *http.Request) {
var payload echoRequest
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
return
}
writeJSON(w, http.StatusOK, echoResponse{
Message: payload.Message,
Count: payload.Count,
})
})
handler := chio.Protect(
router,
chio.WithSidecarURL(envOrDefault("CHIO_SIDECAR_URL", "http://127.0.0.1:9090")),
)
addr := "127.0.0.1:" + envOrDefault("HELLO_CHI_PORT", "8013")
log.Printf("hello-chi listening on http://%s", addr)
if err := http.ListenAndServe(addr, handler); err != nil {
log.Fatal(err)
}
}
func envOrDefault(name, fallback string) string {
value := os.Getenv(name)
if value == "" {
return fallback
}
return value
}
func writeJSON(w http.ResponseWriter, status int, payload any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(payload)
}Notes: chio.Protect returns an http.Handler, so you can compose it with any other Go HTTP middleware. chio.WithSidecarURL is the only required option; everything else has sensible defaults. The receipt id is set on response headers; access it via context if you need it inside the handler.
Why not cgo? The Go SDK uses pure HTTP to the sidecar. That keeps cross-compilation, static linking, and Windows builds working without a cgo dependency.
C++ (Drogon)
Drogon registers handlers by name and accepts a list of named middlewares per handler. Add "chio::drogon::ChioMiddleware" to the list for any route you want governed.
CMake
cmake_minimum_required(VERSION 3.16)
project(HelloDrogon LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
find_package(Drogon CONFIG QUIET)
if(NOT TARGET Drogon::Drogon)
# ... writes a .skip file and returns; smoke.sh prints a clear message
endif()
add_subdirectory(
"${CMAKE_CURRENT_SOURCE_DIR}/../../packages/sdk/chio-drogon"
"${CMAKE_CURRENT_BINARY_DIR}/chio-drogon"
EXCLUDE_FROM_ALL
)
add_executable(hello_drogon main.cpp)
target_link_libraries(hello_drogon PRIVATE ChioDrogon::chio_drogon)
target_compile_features(hello_drogon PRIVATE cxx_std_17)Source
#include <drogon/drogon.h>
#include "chio/drogon.hpp"
int main() {
chio::drogon::Options options;
options.sidecar_url = std::getenv("CHIO_SIDECAR_URL")
? std::getenv("CHIO_SIDECAR_URL")
: "http://127.0.0.1:9090";
options.sidecar_failure_mode = chio::drogon::SidecarFailureMode::FailClosed;
chio::drogon::configure(options);
drogon::app().registerHandler(
"/healthz",
[](const drogon::HttpRequestPtr&,
std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
Json::Value body(Json::objectValue);
body["status"] = "ok";
auto response = drogon::HttpResponse::newHttpJsonResponse(body);
callback(response);
},
{drogon::Get});
drogon::app().registerHandler(
"/hello",
[](const drogon::HttpRequestPtr& request,
std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
Json::Value body(Json::objectValue);
body["message"] = "hello from drogon";
body["receipt_id"] = chio::drogon::receipt_id(request);
callback(drogon::HttpResponse::newHttpJsonResponse(body));
},
{drogon::Get, "chio::drogon::ChioMiddleware"});
drogon::app().registerHandler(
"/echo",
[](const drogon::HttpRequestPtr& request,
std::function<void(const drogon::HttpResponsePtr&)>&& callback) {
const auto payload = request->getJsonObject();
Json::Value body(Json::objectValue);
body["message"] = payload && payload->isMember("message")
? (*payload)["message"].asString() : "";
body["count"] = payload && payload->isMember("count")
? (*payload)["count"].asInt() : 1;
body["receipt_id"] = chio::drogon::receipt_id(request);
callback(drogon::HttpResponse::newHttpJsonResponse(body));
},
{drogon::Post, "chio::drogon::ChioMiddleware"});
drogon::app().addListener("127.0.0.1", 8020);
drogon::app().run();
}Notes: SidecarFailureMode::FailClosed denies a request if the sidecar is unreachable. The list passed as the third argument to registerHandler contains the HTTP method and the names of middlewares to apply, in order. chio::drogon::receipt_id(request) returns the receipt id assigned by the middleware on this request.
Critical wiring
One line each. The chi router is wrapped with chio.Protect; the Drogon handler list names the middleware.
handler := chio.Protect(
router,
chio.WithSidecarURL(envOrDefault("CHIO_SIDECAR_URL", "http://127.0.0.1:9090")),
)drogon::app().registerHandler(
"/hello",
[](const drogon::HttpRequestPtr& request, ...) { ... },
{drogon::Get, "chio::drogon::ChioMiddleware"}); // <- middleware nameSmoke assertions
# /hello allow
assert body["message"] == "hello from chi", body
# /echo deny (403)
assert body["error"] == "chio_access_denied", body
assert body["receipt_id"], body
# /echo allow with X-Chio-Capability
assert body["message"] == "hello", body
assert body["count"] == 2, body# Drogon adds handled_by to prove the upstream ran
assert body["message"] == "hello from drogon", body
assert body["receipt_id"], body
assert body["handled_by"] == "drogon", body
# Receipt id in body MUST match x-chio-receipt-id header
[[ "${HELLO_RECEIPT_ID}" == "${HELLO_HEADER_RECEIPT_ID}" ]]
# Content-hash check: receipt.content_hash binds to raw POST bytes
body_hash = sha256(raw_payload)
content_hash = sha256(canonical({body_hash, method, path, query, route_pattern}))
assert receipt["content_hash"] == content_hashThe Drogon smoke is the only one that re-derives the content hash and asserts byte-equality against the persisted receipt. That is the check that proves the receipt is bound to the exact request bytes.
Inspect after
cd .artifacts/$(ls -t .artifacts | head -1)
# Headers carry x-chio-receipt-id
grep -i x-chio-receipt-id allow.headers
# expect: x-chio-receipt-id: 01J...
# 3 persisted receipts
wc -l receipts.ndjson
jq -r '.id, .verdict.outcome, .content_hash' receipts.ndjson
# Drogon: receipt id should appear in the body too
jq -r '.receipt_id' allow.json
# SQLite peek
sqlite3 state/sidecar-receipts.sqlite3 \
"select id, content_hash, verdict_outcome from receipts;"When this fits
add_subdirectory). Don't use this if you would rather sidecar-front the service: see OpenAPI Sidecar. For managed runtimes see JVM and .NET.Binding shape
| Aspect | Go (chi) | C++ (Drogon) |
|---|---|---|
| SDK package | github.com/backbay-labs/chio/sdks/go/chio-go-http | packages/sdk/chio-drogon |
| Integration shape | http.Handler wrapper | Drogon named middleware per route |
| Linking model | Pure Go; no cgo | Static link; add_subdirectory |
| Sidecar URL | chio.WithSidecarURL(...) option | chio::drogon::Options::sidecar_url |
| Receipt access | Response header / context | chio::drogon::receipt_id(request) |
| Failure mode | Configurable on the option set | SidecarFailureMode::FailClosed in this example |
Next
- Go SDK reference
- HTTP Framework Middleware
- Protect an API: the zero-code reverse-proxy alternative.
- JVM and .NET, Node HTTP Frameworks, Python HTTP Frameworks
- Examples Overview