Chio/Docs
LOGIN · JOIN

Go and C++ HTTP

Run Larkfield's pricing service in Go with chi and in C++ with Drogon, with every quote admitted by the kernel before the handler runs.

Guide chio@5f63e6e

On this page

A pricing service written in Go or C++ keeps its routes, validation, and storage. Chio wraps the handler, decides whether a quote request may reach it, and signs the decision, in the same contract the Node and Python services follow.

Starts from chio new pricing-go-cpp --example pricing-go-cpp. Both implementations serve POST /v1/quote and GET /v1/quotes on Larkfield's price table. The middleware architecture shows where the service and the sidecar meet.

Start

Keep the service and add one authority boundary

The kernel does not take over the pricing logic. The Go and C++ handlers compute the price in exact cents and record the quote; Chio decides, before either runs, whether the caller's grant covers the request.

1. Start the Go service and its sidecar

You need Go 1.21 or newer. The launcher fetches chio-go-http, builds the service, starts it on port 8101 beside a sidecar on 9101, and issues a five-minute grant to Basalt's buying agent. You can also download the project.

Terminal 1bash
chio new pricing-go-cpp --example pricing-go-cpp && cd pricing-go-cpp
python3 run.py

2. Quote and list

Terminal 2 · pricing-go-cppbash
python3 run.py quote sku-204 25
python3 run.py list
Output
{
  "status": 201,
  "body": {
    "currency": "USD", "id": 1, "percent": 25, "price": "43.50",
    "receipt_id": "4b1e…9c07", "sku": "sku-204"
  },
  "headers": { "x-chio-receipt-id": "4b1e…9c07" }
}
{
  "status": 200,
  "body": { "quotes": [{ "id": 1, "sku": "sku-204", "percent": 25, "price": "43.50" }] },
  "headers": {}
}

You should see a 201 with USD 43.50 for sku-204 at 25 percent and the admission receipt's ID, then the one recorded quote. python3 run.py revoke withdraws the grant; the same quote then returns 403 and records nothing.

Go: wrap the HTTP handler

chio.Protect takes an ordinary http.Handler, so the chi router stays inside it. The service answers /healthz before the wrapper and caps the body before the adapter buffers it.

main.go · in maingo
	protected := chio.Protect(router, chio.WithSidecarURL(sidecar))
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/healthz" {
			send(w, http.StatusOK, map[string]string{"status": "ready", "framework": "go"})
			return
		}
		// Cap bytes before the adapter collects and hashes the body.
		r.Body = http.MaxBytesReader(w, r.Body, 8192)
		protected.ServeHTTP(w, r)
	})

Depend on the published adapter

Read go.mod · complete 8 lines · complete file
go.mod · completego
module pricing-go

go 1.21

require (
	github.com/backbay-labs/chio/sdks/go/chio-go-http v0.2.0
	github.com/go-chi/chi/v5 v5.2.3
)

The complete Go service

Read main.go · complete 81 lines · complete file
main.go · completego
package main

import (
	"encoding/json"
	"errors"
	"io"
	"log"
	"net/http"
	"os"
	"time"

	chio "github.com/backbay-labs/chio/sdks/go/chio-go-http"
	"github.com/go-chi/chi/v5"
)

func send(w http.ResponseWriter, status int, value any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(value)
}

func main() {
	store, err := openStore(".pricing/go-quotes.json")
	if err != nil {
		log.Fatal(err)
	}
	router := chi.NewRouter()
	router.Get("/v1/quotes", func(w http.ResponseWriter, r *http.Request) {
		send(w, http.StatusOK, map[string]any{"quotes": store.list()})
	})
	router.Post("/v1/quote", func(w http.ResponseWriter, r *http.Request) {
		var payload struct {
			SKU     *string  `json:"sku"`
			Percent *float64 `json:"percent"`
		}
		decoder := json.NewDecoder(r.Body)
		decoder.DisallowUnknownFields()
		if err := decoder.Decode(&payload); err != nil || payload.SKU == nil || payload.Percent == nil {
			send(w, http.StatusBadRequest, map[string]string{"error": "body must contain exactly sku and percent"})
			return
		}
		if err := decoder.Decode(new(any)); err != io.EOF {
			send(w, http.StatusBadRequest, map[string]string{"error": "expected one JSON object"})
			return
		}
		quote, err := store.issue(*payload.SKU, *payload.Percent)
		if err != nil {
			status := http.StatusInternalServerError
			if errors.Is(err, errInvalidQuote) {
				status = http.StatusBadRequest
			}
			send(w, status, map[string]string{"error": err.Error()})
			return
		}
		send(w, http.StatusCreated, map[string]any{
			"id": quote.ID, "sku": quote.SKU, "percent": quote.Percent,
			"price": quote.Price, "currency": "USD",
			"receipt_id": w.Header().Get("X-Chio-Receipt-Id"),
		})
	})
	sidecar := os.Getenv("CHIO_SIDECAR_URL")
	if sidecar == "" {
		sidecar = "http://127.0.0.1:9101"
	}
	protected := chio.Protect(router, chio.WithSidecarURL(sidecar))
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/healthz" {
			send(w, http.StatusOK, map[string]string{"status": "ready", "framework": "go"})
			return
		}
		// Cap bytes before the adapter collects and hashes the body.
		r.Body = http.MaxBytesReader(w, r.Body, 8192)
		protected.ServeHTTP(w, r)
	})
	server := &http.Server{
		Addr: "127.0.0.1:8101", Handler: handler,
		ReadHeaderTimeout: 5 * time.Second,
	}
	log.Print("Go pricing: http://127.0.0.1:8101")
	log.Fatal(server.ListenAndServe())
}
Read the price table and quote store 92 lines · complete file
pricing_store.go · completego
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"os"
	"path/filepath"
	"sync"
)

var errInvalidQuote = errors.New("invalid quote request")

// Larkfield list prices in cents, as main holds them after the Quickstart.
var prices = map[string]int64{"sku-118": 2400, "sku-204": 5800, "sku-301": 14000}

type Quote struct {
	ID      int     `json:"id"`
	SKU     string  `json:"sku"`
	Percent float64 `json:"percent"`
	Price   string  `json:"price"`
}

type QuoteStore struct {
	sync.Mutex
	path   string
	quotes []Quote
}

func openStore(path string) (*QuoteStore, error) {
	if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
		return nil, err
	}
	store := &QuoteStore{path: path, quotes: []Quote{}}
	data, err := os.ReadFile(path)
	if errors.Is(err, os.ErrNotExist) {
		return store, nil
	}
	if err != nil {
		return nil, err
	}
	return store, json.Unmarshal(data, &store.quotes)
}

func (s *QuoteStore) list() []Quote {
	s.Lock()
	defer s.Unlock()
	return append([]Quote{}, s.quotes...)
}

func (s *QuoteStore) issue(sku string, percent float64) (Quote, error) {
	cents, ok := prices[sku]
	if !ok {
		return Quote{}, fmt.Errorf("%w: unknown sku %s", errInvalidQuote, sku)
	}
	if percent < 0 || percent > 100 {
		return Quote{}, fmt.Errorf("%w: percent must be between 0 and 100", errInvalidQuote)
	}
	// Basis points keep the arithmetic exact; the last step rounds half up to the cent.
	basis := int64(math.Round(percent * 100))
	price := (cents*(10000-basis) + 5000) / 10000
	s.Lock()
	defer s.Unlock()
	quote := Quote{ID: len(s.quotes) + 1, SKU: sku, Percent: percent, Price: fmt.Sprintf("%d.%02d", price/100, price%100)}
	next := append(append([]Quote{}, s.quotes...), quote)
	data, err := json.MarshalIndent(next, "", "  ")
	if err != nil {
		return Quote{}, err
	}
	file, err := os.CreateTemp(filepath.Dir(s.path), "quotes-*.tmp")
	if err != nil {
		return Quote{}, err
	}
	defer os.Remove(file.Name())
	if _, err = file.Write(data); err != nil {
		file.Close()
		return Quote{}, err
	}
	if err = file.Sync(); err != nil {
		file.Close()
		return Quote{}, err
	}
	if err = file.Close(); err != nil {
		return Quote{}, err
	}
	if err = os.Rename(file.Name(), s.path); err != nil {
		return Quote{}, err
	}
	s.quotes = next
	return quote, nil
}

The handler reads the admitted receipt ID from the X-Chio-Receipt-Id header on the response writer; the Go adapter adds nothing to the request context.

C++: govern named Drogon routes

The Drogon service serves the same contract on port 8102. Each governed route names ChioMiddleware in its registration; /healthz does not. Stop the Go service, then:

Terminal 1bash
python3 run.py --framework drogon

The setup script builds JsonCpp and Drogon into .deps/prefix, and CMake fetches the Drogon adapter and its Rust FFI from the released v0.2.0 tag. You need CMake 3.24, a C++17 compiler, Rust, and the OpenSSL, libcurl, UUID, and zlib development libraries; Drogon's installation guide lists them per platform.

cpp/main.cpp · in maincpp
    chio::drogon::Options options;
    const auto sidecar = std::getenv("CHIO_SIDECAR_URL");
    options.sidecar_url = sidecar ? sidecar : "http://127.0.0.1:9101";
    chio::drogon::configure(std::move(options));

Require the adapter

Read CMakeLists.txt · complete 16 lines · complete file
CMakeLists.txt · completecmake
cmake_minimum_required(VERSION 3.24)
project(LarkfieldPricingNative LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CHIO_DROGON_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(CHIO_DROGON_REQUIRE_DEPS ON CACHE BOOL "" FORCE)
include(FetchContent)
# The Drogon adapter and its Rust FFI come from the released Chio tag.
FetchContent_Declare(chio_drogon
  GIT_REPOSITORY https://github.com/backbay-labs/chio.git
  GIT_TAG v0.2.0
  GIT_SHALLOW TRUE
  SOURCE_SUBDIR sdks/cpp/chio-drogon)
FetchContent_MakeAvailable(chio_drogon)
add_executable(pricing-drogon cpp/main.cpp)
target_link_libraries(pricing-drogon PRIVATE ChioDrogon::chio_drogon)

The build links ChioDrogon::chio_drogon and fails when Drogon is missing, so a service never starts without its admission check.

The complete C++ service

Read cpp/main.cpp · complete 131 lines · complete file
cpp/main.cpp · completecpp
#include <drogon/drogon.h>
#include <chio/drogon.hpp>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <map>
#include <mutex>
#include <stdexcept>

namespace fs = std::filesystem;

// Larkfield list prices in cents, as main holds them after the Quickstart.
const std::map<std::string, long long> prices{{"sku-118", 2400}, {"sku-204", 5800}, {"sku-301", 14000}};

class QuoteStore {
    std::mutex gate;
    fs::path path = ".pricing/drogon-quotes.json";
    Json::Value quotes{Json::arrayValue};
public:
    QuoteStore() {
        if (fs::create_directories(path.parent_path())) {
            fs::permissions(path.parent_path(), fs::perms::owner_all);
        }
        if (fs::exists(path)) {
            std::ifstream input(path);
            Json::CharReaderBuilder reader;
            std::string errors;
            if (!Json::parseFromStream(reader, input, &quotes, &errors) || !quotes.isArray()) {
                throw std::runtime_error("Cannot read issued quotes: " + errors);
            }
        }
    }
    Json::Value list() {
        std::lock_guard<std::mutex> lock(gate);
        return quotes;
    }
    Json::Value issue(const std::string& sku, double percent) {
        // Basis points keep the arithmetic exact; the last step rounds half up to the cent.
        const auto basis = static_cast<long long>(std::llround(percent * 100));
        const auto cents = (prices.at(sku) * (10000 - basis) + 5000) / 10000;
        char price[32];
        std::snprintf(price, sizeof price, "%lld.%02lld", cents / 100, cents % 100);
        std::lock_guard<std::mutex> lock(gate);
        Json::Value quote;
        quote["id"] = quotes.size() + 1;
        quote["sku"] = sku;
        quote["percent"] = percent;
        quote["price"] = price;
        quote["currency"] = "USD";
        auto next = quotes;
        next.append(quote);
        const auto temporary = path.string() + ".tmp";
        try {
            std::ofstream output(temporary, std::ios::trunc | std::ios::binary);
            output.exceptions(std::ios::failbit | std::ios::badbit);
            Json::StreamWriterBuilder writer;
            output << Json::writeString(writer, next);
            output.close();
            fs::rename(temporary, path);
        } catch (...) {
            fs::remove(temporary);
            throw;
        }
        quotes = next;
        return quote;
    }
};

using Reply = std::function<void(const drogon::HttpResponsePtr&)>;
void reply(Reply& callback, Json::Value body, drogon::HttpStatusCode status) {
    auto response = drogon::HttpResponse::newHttpJsonResponse(body);
    response->setStatusCode(status);
    callback(response);
}
void error(Reply& callback, const std::string& message, drogon::HttpStatusCode status) {
    Json::Value body;
    body["error"] = message;
    reply(callback, body, status);
}

int main() {
    auto store = std::make_shared<QuoteStore>();
    chio::drogon::Options options;
    const auto sidecar = std::getenv("CHIO_SIDECAR_URL");
    options.sidecar_url = sidecar ? sidecar : "http://127.0.0.1:9101";
    chio::drogon::configure(std::move(options));

    drogon::app().registerHandler("/healthz",
        [](const drogon::HttpRequestPtr&, Reply&& callback) {
            Json::Value body;
            body["status"] = "ready";
            body["framework"] = "drogon";
            reply(callback, body, drogon::k200OK);
        }, {drogon::Get});
    drogon::app().registerHandler("/v1/quotes",
        [store](const drogon::HttpRequestPtr&, Reply&& callback) {
            Json::Value body;
            body["quotes"] = store->list();
            reply(callback, body, drogon::k200OK);
        }, {drogon::Get, "chio::drogon::ChioMiddleware"});
    drogon::app().registerHandler("/v1/quote",
        [store](const drogon::HttpRequestPtr& request, Reply&& callback) {
            const auto payload = request->getJsonObject();
            if (!payload || !payload->isObject() || payload->size() != 2
                || !(*payload)["sku"].isString() || !(*payload)["percent"].isNumeric()) {
                error(callback, "body must contain exactly sku and percent", drogon::k400BadRequest);
                return;
            }
            const auto sku = (*payload)["sku"].asString();
            const auto percent = (*payload)["percent"].asDouble();
            if (prices.find(sku) == prices.end()) {
                error(callback, "unknown sku " + sku, drogon::k400BadRequest);
                return;
            }
            if (percent < 0 || percent > 100) {
                error(callback, "percent must be between 0 and 100", drogon::k400BadRequest);
                return;
            }
            try {
                auto quote = store->issue(sku, percent);
                quote["receipt_id"] = chio::drogon::receipt_id(request);
                reply(callback, quote, drogon::k201Created);
            } catch (const std::exception& failure) {
                error(callback, failure.what(), drogon::k500InternalServerError);
            }
        }, {drogon::Post, "chio::drogon::ChioMiddleware"});

    drogon::app().setClientMaxBodySize(8192).addListener("127.0.0.1", 8102).run();
}

chio::drogon::receipt_id(request) gives the handler its receipt ID.

Make the governed routes explicit

ConcernGoDrogon
Address
127.0.0.1:8101
127.0.0.1:8102
Governed routes
Everything inside the wrapper; /healthz answered first
Routes that name ChioMiddleware
Quotes on disk
.pricing/go-quotes.json
.pricing/drogon-quotes.json
Receipt in the handler
X-Chio-Receipt-Id on the response writer
chio::drogon::receipt_id(request)

Admission and the quote write are separate steps; the example does not claim one transaction across them.

Read your own receipts

Terminal 2 · pricing-go-cppbash
python3 receipts.py --decision deny
Output
71c0…e2d4  DENY       POST   /v1/quote  HTTP 403

receipts.py opens .pricing/receipts.db read-only; pass a receipt ID in place of --decision to print that complete signed record, and post it to /chio/verify as HTTP receipt verification shows. A request for sku-999 with a valid grant is admitted and then rejected by the handler with 400; its receipt records the decision, not the handler's answer:

The sku-999 receipt · selected fieldsjson
{
  "verdict": { "verdict": "allow" },
  "response_status": 200,
  "metadata": { "chio_http_status_scope": "decision" }
}

A refused quote never reaches the handler

Go · POST /v1/quote without a grantjson
{
  "error": "chio_access_denied",
  "message": "side-effect route requires a capability token",
  "receipt_id": "71c0…e2d4"
}

Read each failure at its boundary

SituationHTTP resultMeaning
No grant
403
Chio refused admission; nothing is recorded.
Grant, known SKU
201
The handler issued and recorded the quote.
Grant, sku-999
400
Admission allowed; the handler rejected the SKU.
Body over 8192 bytes
Go 400, Drogon 413
Stopped before admission; no receipt.
Sidecar down
502; /healthz stays 200
No decision was possible. Both adapters fail closed.

Check the whole contract in one run

With the launcher stopped, python3 run.py check refuses a quote with no grant, issues one with Basalt's grant, rejects sku-999 after admission, revokes the grant, refuses again, and confirms the refused requests recorded nothing. Add --framework drogon for the C++ service.

Require a grant to read quotes

Issued quotes are commercial terms Basalt should not browse freely. Mark the read operation side-effecting in openapi.yaml, restart the launcher with the same .pricing state, and read with and without the grant.

openapi.yaml · the GET operationyaml
paths:
  /v1/quotes:
    get:
      operationId: listQuotes
      x-chio-side-effects: true
      responses:
        "200": { description: Up to 100 issued quotes }
  # Keep the existing /v1/quote post operation here.
Terminal 2 · pricing-go-cppbash
curl -sS -w ' HTTP %{http_code}\n' http://127.0.0.1:8101/v1/quotes
curl -sS http://127.0.0.1:8101/v1/quotes \
  -H "X-Chio-Capability: $(jq -c . .pricing/capability.json)"
Output
{"error":"chio_access_denied","message":"side-effect route requires a capability token","receipt_id":"a90d…11f3"} HTTP 403
{"quotes":[{"id":1,"sku":"sku-204","percent":25,"price":"43.50"}]}

The read without a grant is refused, and the read with Basalt's grant returns the same quote. This changes the matched operation only: the OpenAPI document is not an exhaustive allowlist, and unmatched safe methods keep the method-based default.

Next: the same service on the JVM and .NET