Chio/Docs

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 single chio.Protect(handler, opts...) call that wraps any http.Handler. The example uses chi as the inner router; it works equally well with net/http, gorilla/mux, or any handler.
  • chio-drogon: a Drogon middleware named chio::drogon::ChioMiddleware attached to specific routes plus a chio::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 protect sidecar.

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-drogon via add_subdirectory.
  • The chio CLI on PATH. The smokes start a local sidecar and trust service.

Run them

bash
cd examples/hello-chi   # or hello-drogon
./run.sh

# Full smoke (sidecar + trust + deny + allow)
./smoke.sh

Default ports:

ExampleEnv varDefault
hello-chiHELLO_CHI_PORT8013
hello-drogonHELLO_DROGON_PORT8020

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

examples/hello-chi/go.mod
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-http

Server

examples/hello-chi/main.go
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

examples/hello-drogon/CMakeLists.txt
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

examples/hello-drogon/main.cpp
#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.

examples/hello-chi/main.go:189
handler := chio.Protect(
    router,
    chio.WithSidecarURL(envOrDefault("CHIO_SIDECAR_URL", "http://127.0.0.1:9090")),
)
examples/hello-drogon/main.cpp:294
drogon::app().registerHandler(
    "/hello",
    [](const drogon::HttpRequestPtr& request, ...) { ... },
    {drogon::Get, "chio::drogon::ChioMiddleware"});  // <- middleware name

Smoke assertions

examples/hello-chi/smoke.sh
# /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
examples/hello-drogon/smoke.sh
# 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_hash

The 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

bash
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

Use this when: your service is Go (chi, gin, gorilla, plain net/http) or Drogon, and you want cross-compile-safe binaries (Go, no cgo) or static-link (Drogon, via 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

AspectGo (chi)C++ (Drogon)
SDK packagegithub.com/backbay-labs/chio/sdks/go/chio-go-httppackages/sdk/chio-drogon
Integration shapehttp.Handler wrapperDrogon named middleware per route
Linking modelPure Go; no cgoStatic link; add_subdirectory
Sidecar URLchio.WithSidecarURL(...) optionchio::drogon::Options::sidecar_url
Receipt accessResponse header / contextchio::drogon::receipt_id(request)
Failure modeConfigurable on the option setSidecarFailureMode::FailClosed in this example

Next