Sign in

    moonzero

    A service framework for MoonBit after go-zero: config-driven REST engine and middleware over moonapi, zRPC over moonrpc, discovery on etcd, consul and redis, and go-zero's breaker, shedder and limiters over moonward.

    go-zero
    microservice
    server
    middleware
    moonapi
    moonbit
    Download zip
    Version
    0.14.1
    License
    Apache-2.0
    Last updated
    3 hours ago
    Downloads
    61

    #moonzero

    A service framework for MoonBit — ← go-zero.

    Check and Test License mooncakes

    Moved on mooncakes from Lfan-ke/moonzero to moonbitstack/moonzero.

    moonzero is the integration layer of the moon* suite — the role go-zero plays for Go. It assembles a moonapi application from config, wraps it in middleware, and produces a runnable moonasgi AsgiApp that a server (mooncat) runs. It never names a server, so any ASGI host runs it.

    flowchart LR conf["ServiceConf"] --> srv["**moonzero** Server"] api["moonapi App"] --> srv mw["middleware<br/>(logging, ...)"] --> srv srv -->|"to_asgi()"| asgi(["moonasgi AsgiApp"]) asgi --> cat["mooncat serves it"]

    #Quickstart

    let app = @moonapi.App::new()
    let api = @moonzero.Group::new(app, "/api/v1") // prefix a set of routes
    api.get("/ping", _ctx => @moonapi.text(200, "pong"))

    let conf = @moonzero.ServiceConf::new(
    name="greet", host="127.0.0.1", port=12000, timeout_ms=3000, log_level=Info,
    )
    let server = @moonzero.Server::new(conf, app)
    .use_(@moonzero.cors(@moonzero.CorsConf::new())) // Access-Control-* headers
    .use_(@moonzero.request_id()) // x-request-id per request
    .use_(@moonzero.recovery) // 500 instead of a panic
    .use_(@moonzero.logging)

    server.describe() // "greet listening on 127.0.0.1:12000"
    @mooncat.serve(server.to_asgi(), host=conf.host, port=conf.port) // run it (native)

    Verified across all backends (wasm, wasm-gc, js, native) in CI, 0 warnings under --deny-warn.

    #Packages

    The layout follows go-zero's own: algorithms in packages of their own, every HTTP middleware in rest/handler, and the engine that assembles them in rest. The root re-exports the lot, so @moonzero.Breaker works as well as @breaker.Breaker.

    PackageWhatgo-zero
    clockthe millisecond clock every timing decision is a function ofcore/timex
    breakerthe adaptive circuit breaker, over moonward'score/breaker
    shedderthe adaptive load shedder, over moonward'score/load
    limittoken bucket, period limit and connection limit, in memory over moonward's or on Rediscore/limit, core/syncx
    logxlevelled, structured loggingcore/logx
    metriccounters and histograms in the Prometheus formatcore/metric
    traceW3C trace contextcore/trace
    procgraceful shutdowncore/proc
    confconfiguration loading and the service, REST and RPC configurationcore/conf
    registryendpoints, registries and balancerscore/discov
    redis, etcd, consulthe three stores' clients, and discovery on eachcore/stores/redis, core/discov
    zrpcthe zRPC server and client on moonrpczrpc
    rest/handlerthe middlewaresrest/handler
    restthe engine, the server and route groupsrest
    discovnative file, socket and watch drivers for the registries—

    #Ports

    The default listen port is 12000 for the REST engine and 12001 for the zRPC server — the family's numbers rather than go-zero's 8888 and 8080, so every example and every local run in this ecosystem points at the same place. gRPC shares a port with HTTP wherever one listener carries both; a moonzero process binds its REST and zRPC servers separately, and two listeners cannot hold one port, so the second takes the next number.

    Both are ordinary parameters over public presets (@moonzero.port, @moonzero.rpc_port): changing one is ServiceConf::new(port=8888).

    #Refusals

    Every middleware that can turn a request away takes a refusal — the status, body and headers a rejected request receives. The presets are go-zero's (unavailable 503, too_many 429, timed_out 503, too_large 413); a gateway that answers 429 where the service alone answers 503 writes shedding(s, refusal=Refusal::new(status=429)).

    #Resilience middleware

    Beyond the base onion (logging, recovery, CORS, request-id), moonzero ships go-zero's resilience set, each modelled as a pure decision core over an injected Clock so its timing is exactly testable:

    let clock = @moonzero.Clock::new(() => now_ms()) // real time at the async edge
    let server = @moonzero.Server::new(conf, app)
    .use_(@moonzero.maxbytes(1 << 20)) // 413 over 1 MiB
    .use_(@moonzero.rate_limit(@moonzero.TokenBucket::new(100.0), clock))// 429 when empty
    .use_(@moonzero.breaker(@moonzero.Breaker::new(clock))) // 503 while shedding
    .use_(@moonzero.timeout(3000L, clock)) // deadline
    .use_(@moonzero.structured_logging(clock)) // one JSON line/req

    • rate_limit / period_limit — a TokenBucket that admits a burst and refills continuously, and a PeriodLimit that counts a fixed window per key; both answer 429 once they run out. Both are process-local, so N replicas admit N quotas — for a fleet use redis_rate_limit / redis_period_limit over RedisTokenLimit / RedisPeriodLimit, which run go-zero's tokenscript.lua and periodscript.lua in redis so every replica draws on one bucket or one window. An unreachable redis leaves the token limiter running on its local bucket (go-zero's rescueLimiter) rather than opening the gate.
    • breaker — a Breaker: go-zero's googleBreaker, Google SRE's client-side throttle. It counts the last 10s in 40 buckets and sheds a fraction of calls, (total - 5 - max(w, 1.1) * accepts) / (total + 1) scaled by the run of clean buckets, answering 503 for the ones it sheds — a struggling backend keeps whatever load it can still serve instead of being cut off wholesale. The clock and the shed roll are both injected, so the decisions are exactly reproducible.
    • shedding — a Shedder: go-zero's AdaptiveShedder. While the CPU is at or above CpuThreshold, or was within the last second, it sheds once more requests are in flight than the best throughput and latency of the last five seconds can carry, answering 503; an app's own 503 is not counted as throughput.
    • timeout — a moonward Deadline per request, enforced on the response path. Preemptively aborting a hung handler needs racing it against a timer (@async.any) under the native runtime; that race is wired at the async server edge.
    • maxbytes — rejects a request whose declared Content-Length exceeds the limit with 413.
    • structured_logging — a RequestLog rendered as one JSON line per request (method, path, status, duration, request-id, client-ip, user-agent).

    #Auth, YAML config, and zRPC over the h2c transport

    // JWT HS256, on mooncred and mooncrypt
    let token = @moonzero.jwt_sign(
    Map([("sub", Json::string("alice")), ("exp", Json::number(1893456000.0))]),
    "topsecret",
    )
    let server = @moonzero.Server::new(conf, app)
    .use_(@moonzero.auth("topsecret", clock)) // 401 unless a valid Bearer JWT
    .use_(@moonzero.tracing()) // W3C traceparent in/out
    .use_(@moonzero.metrics(m, clock)) // request counter + latency histogram

    // The etc/*.yaml goctl writes, loaded and assembled the way go-zero's engine does
    let conf = @moonzero.RestConf::from_yaml(etc_yaml) // PascalCase keys, MOONZERO_* env overrides
    let server = @moonzero.RestEngine::new(conf).build(app) // the chain Middlewares asks for

    // A real zRPC call over moonrpc's h2c transport — unary and streaming
    let rpc = @moonzero.RpcServer::new(@moonzero.RpcServerConf::new(name="greeter", port=9090))
    let g = rpc.group("hello.Greeter")
    g.register("SayHello", req => handle(req)) // unary
    g.register_server_streaming("Tail", req => chunks(req)) // one in, many out
    g.register_client_streaming("Upload", msgs => summarize(msgs)) // many in, one out
    g.register_bidi_streaming("Chat", () => @moonzero.BidiStreamHandler::{
    on_message: m => [echo(m)], // a reply the instant each message arrives
    on_end: () => [b"bye"], // a farewell after the client half-closes
    })
    let ch = @moonzero.RpcChannel::connect(rpc)
    ch.call("/hello.Greeter/SayHello", request) // Ok(reply) | Err(status)
    ch.call_server_streaming("/hello.Greeter/Tail", request) // Ok([msg, ...]) | Err(status)
    ch.call_client_streaming("/hello.Greeter/Upload", [a, b]) // Ok(reply) | Err(status)
    let call = ch.open_bidi("/hello.Greeter/Chat") // stream both ways
    call.send(a) // -> the replies produced right then (interleaved)
    call.close_send() // -> Ok([on_end replies...]) | Err(status)

    • jwt_sign / jwt_verify — compact HS256 tokens through mooncred/jwt over mooncrypt's SHA-256 and HMAC: signatures compared in constant time, exp/nbf enforced, the alg:none downgrade refused, and every check a field of the policy record. Interop-verified against the canonical jwt.io token.
    • auth — the middleware that requires Authorization: Bearer <jwt> and answers 401 for an absent, malformed, tampered, or expired token.
    • ServiceConf::from_yaml — moonyaml reads the etc/*.yaml into the same Json tree the JSON loader produces, so both formats agree field-for-field.
    • Conf — the conf.Load port: keys match canonically (lowercase, _ and - ignored), so a goctl-written Name/MaxBytes/Log.Level loads as readily as name/max_bytes/log_level; dotted paths reach nested blocks; ,env= lets a variable override the file; and default= / options= / range=[a:b) behave as go-zero's tags do — a missing required field or a value outside its constraint is an error, never a quiet default.
    • RestConf / RestEngine — config-driven assembly (← rest.RestConf and newEngine): host, port, TLS files, MaxConns, MaxBytes, Timeout, CpuThreshold, Signature, TraceIgnorePaths and the eleven Middlewares flags load from one etc/*.yaml, and the engine installs exactly the layers those flags ask for, in go-zero's order, over shared connection permits, breaker and metric set. Metrics and Gunzip load but install nothing.
    • logx — go-zero's leveled structured logger over moonlog's sink: entries below the configured level are dropped unrendered, everything else is one JSON object per line with @timestamp, level, content, an optional WithDuration, and typed fields. RestEngine::new points it at the config's Log.Level, which is what finally makes that setting mean something. Point it at another moonlog sink and every other library in the family writes to the same place.
    • RpcServer / RpcGroup — config-driven zRPC groups that register moonrpc Method handlers by gRPC path.
    • RpcChannel — a client over the h2c transport: to_h2 turns the registered handlers into a moonrpc H2Server, and the call family runs real exchanges through it — HPACK-coded HEADERS, length-prefixed DATA frames, and the grpc-status trailer read back off the reply. Unary (call), server-streaming (call_server_streaming, one request then every framed reply in order), client-streaming (call_client_streaming, each request as its own DATA frame then one reply after half-close), and bidirectional streaming all round-trip through the same engine. A call to an unregistered path comes back UNIMPLEMENTED, the trailers-only response a gRPC server sends for an unknown method.
    • Bidi streaming — open_bidi opens a stream that stays open both ways: each BidiCall::send writes one request message and returns the replies the server produced right then (an echo handler answers each message as it arrives), and close_send half-closes, runs the server's on_end, and reports the final grpc-status. call_bidi_streaming drives a whole exchange in one shot, returning the interleaved replies followed by the on_end messages. The channel's HPACK decoder is advanced across every reply block, so its dynamic table stays in lockstep with the engine's encoder for the life of the call.
    • ShutdownCoordinator — graceful shutdown for a serving zRPC server: dispatch_graceful counts each call as in-flight for its duration, initiate_shutdown makes new calls come back Unavailable (a stopped listener) while in-flight ones run to completion, and is_drained reports when the last one has finished so the process may exit.

    #Discovery, metrics, tracing

    // Persisted, watchable registry (etcd v3-shaped): register, watch, snapshot
    let reg = @moonzero.PersistentRegistry::new()
    reg.watch(e => log(e)) // Put/Delete events in revision order
    reg.register("greeter", @moonzero.Endpoint::new("10.0.0.1", 9090))
    reg.register("greeter", @moonzero.Endpoint::new("10.0.0.2", 9090))
    let saved = reg.snapshot() // persist to a file/etcd; restore reloads it

    // Real registry I/O over a file (native): a publisher persists, a reader watches
    @discov.persist_registry(path, reg) // write the snapshot to a real file
    let reader = @discov.FileRegistry::load(path) // load it back
    reader.reload() // re-read -> the Put/Delete diff since last load
    reader.watch(dir, e => log(e)) // reload on every filesystem change

    // Load-balanced client: resolve an instance, then call it over h2c
    let ch = @moonzero.LoadBalancedChannel::new(reg.resolver(), cluster, "greeter")
    ch.call("/hello.Greeter/SayHello", request) // dials 10.0.0.1, then .2, cycling

    // Metrics read out for a /metrics scrape after serving
    let m = @moonzero.ServerMetrics::new()
    m.requests().value("GET /ping 200") // request count for that label
    m.latency().mean() // mean request latency, ms

    • InMemoryRegistry — a service registry shaped like go-zero's etcd discov store: a service -> instance -> endpoint map with a monotonic store revision a watcher can compare against. RoundRobin, pick_first and WeightedRoundRobin balancers select an endpoint from a resolved set.
    • WeightedRoundRobin — a weighted balancer over Endpoint::weight, using smooth weighted round-robin: over one cycle each instance is served exactly its share, and a weight-5 instance is spread through the cycle rather than handed five requests in a row. An instance weighted zero or less is never picked.
    • PersistentRegistry — the persisted, watchable registry: adds a live watch (Put/Delete events in revision order), an events_since catch-up from any revision, and snapshot/restore that round-trip the whole keyspace through an etcd v3 RangeResponse-shaped JSON document without losing a revision — the bytes a file- or etcd-backed deployment persists and reloads.
    • LoadBalancedChannel — a load-balanced zRPC client over a Resolve interface: it resolves a service, picks a live instance with the balancer, dials it through an RpcCluster, and makes the call, so instances registering or leaving between calls take effect on the next one. Any store exposing resolver() backs it; an etcd- or consul-backed store drops in unchanged. Unary, server-streaming, and bidi calls all go through the resolve-then-balance path.
    • FileRegistry — real registry I/O in the native discov sub-package (← go-zero's discov publisher/subscriber, over the filesystem instead of etcd's network): persist_registry writes the snapshot to a real file through moonbitlang/async's fs, FileRegistry::load reads it back and exposes a resolver(), reload re-reads and returns the Put/Delete diff since the last load, and watch/watch_once block on a real filesystem watcher and reload on every change. Reader and publisher share only the file, exactly as an etcd subscriber and publisher share only the keyspace, so the balancer and load-balanced channel above drive it unchanged.
    • ServerMetrics — a CounterVec of per-method/route/status request tallies and a cumulative latency Histogram (Prometheus le buckets, sum, count), driven by the metrics middleware that times each request on the clock.
    • tracing — trace-id propagation: continue an inbound W3C traceparent or start a new trace, mint a child span, and stamp traceparent + x-trace-id onto the response.

    #Roadmap (transliterating go-zero)

    A conf.Load-shaped loader (canonical keys, env overrides, default=/options=/range=) + RestConf and the engine that builds a chain from its Middlewares flags + leveled structured logging + service assembly + the base middleware onion (logging, recovery, CORS, request-id) + the resilience set (timeout, rate-limit, breaker, maxbytes, structured logging) + route groups + JWT auth + zRPC groups with real unary, server/client-streaming, and bidirectional h2c round-trips + graceful shutdown draining in-flight calls + a persisted, watchable registry with round-robin/weighted/pick-first balancers and a load-balanced client + real file-backed registry I/O with a live filesystem watcher + request metrics + trace-id propagation are here. Next: zRPC's client and server interceptors and its p2c balancer, per-route options (jwt, timeout, max bytes, prefix), and moonctl-driven scaffolding of a full moonzero service from a spec.

    #Licence

    Apache-2.0. See LICENSE.

    Balancer

    The choice of balancer for a load-balanced channel: round-robin cycles through the resolved instances (spreading load evenly), WeightedBalancer shares them out in proportion to Endpoint::weight, PickFirst pins the head instance (← gRPC's pick_first).

    BidiCall

    A live client-side bidirectional call over the h2c channel (← gRPC's ClientStream): the request stream stays open while messages flow both ways. send writes one request message and returns whatever replies the server produced right then (bidi interleaving — an echo handler answers each message as it arrives); close_send half-closes the request stream, runs the server's on_end, and reports the final grpc-status. The channel's HPACK decoder is advanced across every reply block, so its dynamic table stays in lockstep with the engine's encoder for the life of the call. pending holds DATA octets not yet split into a whole length-prefixed message (a message may straddle two DATA frames under flow control).

    BidiStreamFactory

    A factory that mints one BidiStreamHandler per call, so each stream gets its own handler state (← the fresh ServerStream gRPC hands every bidi invocation).

    BidiStreamHandler

    A live bidirectional call (← go-zero's pb.XxxServer bidi method, which reads from and writes to the same grpc.ServerStream): on_message fires once per request message and returns the replies to send right then, so responses interleave with requests; on_end runs after the client half-closes and returns the final replies before the grpc-status trailer. The moonzero-local mirror of @moonrpc.BidiHandler, so callers register bidi methods without naming the transport package.

    Breaker

    Google SRE's client-side throttling breaker (← go-zero's googleBreaker).

    The algorithm is moonward's Breaker; this binds it to a Clock and a source of draws, the shape go-zero's breaker has, so a call site asks with no arguments. go-zero's constants are moonward's preset sre.

    ClientStreamHandler

    type ClientStreamHandler = (Array[Bytes]) -> Bytes

    A client-streaming handler: every request message the client sends is collected, and after the client half-closes the handler returns one reply.

    Clock

    A monotonic time source in milliseconds, injected into the resilience middlewares (rate-limit, breaker, timeout) so their timing logic is a pure function of an explicit clock rather than a hidden wall-clock read. go-zero reads timex.Now() directly; because that is neither portable across MoonBit's backends nor testable, moonzero threads the clock as a value — the same pattern Go's clockwork/x/time/rate accept for a Clock.

    Conf

    A loaded config document (← go-zero's conf.Load): a parsed mapping read through canonical keys and dotted paths, with the ,env=NAME override and the default= / options= / range= constraints go-zero spells as struct tags.

    A field that is absent and has no default is an error, and so is a value that violates its options= or range= constraint — a bad value never degrades into the default.

    ConfigError

    A config-loading failure (← go-zero's conf.Load errors): malformed JSON or YAML, a non-mapping root, a field of the wrong type, a required field with no value, or a value outside its options=/range= constraint, with a human-readable reason.

    ConsulClient

    A consul client over a ConsulHttp, exposing the agent operations discovery uses.

    ConsulDiscovery

    A consul-backed service registry / resolver over a ConsulClient. Each instance is a consul service named service, uniquely identified per agent by its address.

    ConsulError

    A consul API call that failed — a transport error, or a non-2xx agent response.

    ConsulHttp

    A transport to a consul agent: it performs one HTTP request (method and path, with body for writes) and returns the response. Implementations own the medium — a real HTTP socket, or an in-memory fake.

    ConsulResponse

    A consul agent's HTTP response: the status code and the raw body bytes.

    CorsConf

    CORS configuration (← go-zero's cors.Middleware options): the values echoed back in the Access-Control-* preflight/response headers.

    CounterVec

    A monotonic counter (← go-zero's metric.CounterVec) partitioned by a label string. Each inc/add accrues against one label (e.g. "GET /ping 200"), so a single vector holds the per-method/route/status request tallies Prometheus scrapes. Counters only ever go up.

    Endpoint

    A service endpoint (← go-zero's discov target): the host and port an instance listens on, plus a routing weight (default 1). The weight is carried through registration and snapshots and is what WeightedRoundRobin shares traffic by; round-robin and pick-first ignore it.

    EtcdClient

    An etcd v3 client bound to a gRPC channel (real etcd, or a mock server in tests).

    EtcdDeleteRangeRequest

    A DeleteRangeRequest: delete the key at key, or the half-open range [key, range_end); prev_kv asks the server to return the deleted key/values. A discovery client sends this to deregister an instance.

    EtcdDeleteRangeResponse

    A DeleteRangeResponse: the number of keys deleted and, when prev_kv was requested, the prev_kvs that were removed.

    EtcdDiscovery

    An etcd-backed service registry / resolver. Instances of one service live under <prefix><service>/, so a Range over that prefix returns them all.

    EtcdError

    An etcd gRPC call that returned a non-OK grpc-status.

    EtcdEvent

    A watch Event (mvccpb.Event): a change to one key, carrying the resulting KeyValue (for a delete, the key with cleared metadata). This is what a discovery watcher folds into add/remove of a service instance.

    EtcdEventType

    The kind of change a watch Event reports (mvccpb.Event.EventType): a key was Put (created or updated) or Deleted.

    EtcdKeyValue

    An etcd KeyValue (mvccpb.KeyValue): a key, its value, the create/mod revisions and version that track its history, and the lease it is attached to.

    EtcdLeaseGrantRequest

    A LeaseGrantRequest: ask etcd for a lease living ttl seconds (id 0 lets the server assign one). A registered service key attaches to the lease and vanishes when the lease expires — go-zero's instance liveness mechanism.

    EtcdLeaseGrantResponse

    A LeaseGrantResponse: the granted lease id, its actual ttl, and an error string when the grant failed.

    EtcdLeaseKeepAliveRequest

    A LeaseKeepAliveRequest: renew lease id before it expires. A discovery client streams these to keep its instance registered.

    EtcdLeaseKeepAliveResponse

    A LeaseKeepAliveResponse: the renewed lease id and its remaining ttl (0 = the lease has expired).

    EtcdPutRequest

    A PutRequest: store value at key, optionally under lease.

    EtcdPutResponse

    A PutResponse: when prev_kv was requested on the PutRequest, the key/value that the put replaced (its key is empty when there was none).

    EtcdRangeRequest

    A RangeRequest (etcdserverpb): read the key at key, or the half-open range [key, range_end) when range_end is set, up to limit results (0 = no limit).

    EtcdRangeResponse

    A RangeResponse: the matched key/values and the total count in the range (which may exceed the returned kvs when a limit capped them).

    EtcdWatchCancelRequest

    A WatchCancelRequest: stop the watch stream identified by watch_id.

    EtcdWatchCreateRequest

    A WatchCreateRequest: subscribe to changes on key, or on the half-open range [key, range_end), from start_revision (0 = current). A discovery watcher opens one over the service's key prefix.

    EtcdWatchRequest

    A WatchRequest, the request_union oneof of the bidi Watch stream: either a Create to open a watch or a Cancel to close one.

    EtcdWatchResponse

    A WatchResponse: the server-assigned watch_id, the created / canceled lifecycle flags, and the batch of change events since the last response. A discovery watcher folds each event into add/remove of a service instance.

    Group

    A route group (← go-zero's RouteGroup): registers a set of routes on an underlying moonapi.App under a shared path prefix, so related endpoints (e.g. everything under /api/v1) are declared without repeating the prefix.

    Histogram

    A cumulative histogram (← go-zero's metric.HistogramVec, Prometheus semantics): a sorted list of le (less-than-or-equal) upper bounds and, for each, the count of observations that fell at or below it, plus the running sum and total count. An observation above every bound still lands in the implicit +Inf bucket that count represents.

    InMemoryRegistry

    An in-memory service registry (← go-zero's etcd discov store, minus the network): a two-level map of service -> instance-id -> endpoint and a monotonic revision bumped on every mutation, mirroring etcd's store revision so a watcher could detect change. Instance ids are <service>/<n>, the leaf of the etcd key an instance would lease.

    Layer

    One layer of the assembled chain: go-zero's name for the handler, and the middleware that stands in for it.

    LoadBalancedChannel

    A load-balanced zRPC client (← go-zero's zrpc.Client over a discovery target): it resolves a service through the Resolver, picks a live instance with the Balancer, dials it on the RpcCluster, and makes the call. The whole resolve→balance→dial→call path runs per call, so instances registering or deregistering between calls take effect on the next one.

    LogLevel

    Log verbosity (← go-zero's LogConf.Level), ordered from most to least verbose. Compare follows that order so thresholds can be tested directly.

    Logger

    A leveled, structured logger (← go-zero's logx): entries below the configured level are dropped without being rendered, and everything else reaches the sink as one record carrying @timestamp, level, content, an optional duration, and any extra fields.

    The timestamp is milliseconds from the injected Clock, not a formatted calendar time: MoonBit has no portable date formatter, and the clock is already how every other timed layer here reads the time.

    ManualClock

    A deterministic, hand-advanced clock for tests and for driving the rate-limit / breaker cores without a real time source. Wall time is replaced by an explicit advance, so a token bucket's refill or a breaker's open window can be exercised exactly.

    MaxConns

    A permit pool of max concurrent slots (← go-zero's syncx.Limit), moonward's Gate under go-zero's names. A max of zero or less admits everything, as go-zero's server does when MaxConns is unset.

    Middleware

    An AsgiApp transformer — one layer of the middleware onion.

    MiddlewaresConf

    Which of go-zero's built-in layers the engine installs (← MiddlewaresConf). Every flag defaults to true, so an etc/*.yaml that says nothing about middleware still gets the whole chain.

    PeriodLimit

    A fixed-window rate limiter kept in this process: each key may make up to quota requests per period, counted by one moonward Period per key. A key's window opens on its first request, or with align on a multiple of the period (← go-zero's Align()), and starts again once the period has passed.

    The window is local, so N replicas admit N quotas. RedisPeriodLimit is the same limiter with its window in redis and is what a fleet should run; this one is for a single process, and for the fallback a caller wants when redis is unreachable.

    PeriodResult

    The outcome of a PeriodLimit.take (← go-zero's period-limit result codes): a request within quota, the one that reaches it exactly (still admitted), or one beyond it (rejected).

    PersistentRegistry

    A persisted, watchable service registry (← go-zero's etcd discov publisher): the same two-level service -> instance-id -> endpoint store as InMemoryRegistry, plus a per-key mod-revision, an append-only event log for catch-up watchers, live watcher callbacks fired on every mutation, and snapshot/restore that round-trip the whole keyspace through an etcd v3 RangeResponse-shaped JSON document — the bytes a file- or etcd-backed deployment persists and reloads without losing a revision.

    PrivateKeyConf

    One signing key the request-signature check would verify against (← rest.PrivateKeyConf).

    Promise

    The receipt for an admitted call (← go-zero's breaker.Promise).

    RedisClient

    A redis client over a RedisConn, exposing the typed commands the discovery driver uses. Every command maps a redis error reply to RedisError.

    RedisConn

    A live redis connection: it sends an encoded command (an array of argument byte strings) and returns the decoded reply. Implementations own the transport — a real socket speaking RESP, or an in-memory fake. It is open and async so that a socket can be one: real redis I/O suspends, and a sealed synchronous trait left every implementation a test double.

    RedisDiscovery

    A redis-backed service registry / resolver. Instances of one service live under <prefix><service>/, one key per instance keyed by its dial address, so a SCAN of that prefix returns them all.

    RedisError

    A redis command that failed — a connection-level error, or a redis error reply.

    RedisPeriodLimit

    A fixed-window rate limiter whose window lives in redis (← go-zero's limit.PeriodLimit). One INCRBY-ed counter per key, given a period-second expiry the first time it appears, so every replica pointed at the same redis and key draws on one quota instead of each getting its own.

    RedisScript

    A Lua script bound to its SHA1 (← go-redis's Script, the shape go-zero's ScriptRun drives). The first run SCRIPT LOADs the source and keeps the digest, every later run is an EVALSHA that ships a 40-byte hash instead of the whole program, and a server that has forgotten it — a restart, a SCRIPT FLUSH — answers NOSCRIPT, which loads it again and retries once.

    RedisTokenLimit

    A token-bucket rate limiter whose bucket lives in redis (← go-zero's limit.TokenLimiter). The token count and the second it was last refilled sit under {key}.tokens and {key}.ts, so every replica pointed at the same redis and key spends from one bucket. When redis cannot be reached the limiter keeps limiting from rescue, its process-local TokenBucket, the way go-zero drops to its in-process rescueLimiter rather than letting an outage open the gate.

    Refusal

    The response a refused request receives: a status, a body, and the headers sent with it.

    RegistryEvent

    A change to the registry keyspace, in etcd v3's watch shape: a Put carries the instance key and its endpoint, a Delete carries the key that went away, and both carry the store revision the change produced. A watcher receives these in revision order, so a client can rebuild the live set incrementally instead of re-resolving the whole service.

    RequestLog

    A structured access-log record (← go-zero's logx HTTP access fields). Rather than a free-form line, each request is captured as typed fields and rendered as one JSON object per line — the format go-zero emits under logx and the shape log collectors (ELK, Loki) expect.

    Resolve

    The resolve half of go-zero's discovery (← discov.Discovery): a function from a service name to its live endpoints. Any store — the in-memory InMemoryRegistry, the persisted PersistentRegistry, or a future etcd/consul client — exposes one via resolver(), so a balancer and the load-balanced channel are written once against the interface and the backing store swaps by swapping the closure.

    RespError

    A malformed or truncated RESP frame.

    RespReader

    A cursor over a RESP byte stream, decoding one value at a time. A socket client feeds it a buffered reply; read advances past exactly one value, so several pipelined replies decode in sequence.

    RespValue

    A decoded RESP value. RESP2's null bulk string ($-1) and null array (*-1) both decode to Null, unifying with RESP3's explicit null (_). BulkString carries raw bytes (redis values are binary-safe); the text-line types carry the decoded string.

    RestConf

    A REST service's configuration (← go-zero's rest.RestConf), embedding ServiceConf the way go-zero's does. The name, bind address and request timeout live on that embedded config — host(), port() and timeout_ms() read them — and everything else here is RestConf's own.

    max_bytes is an Int where go-zero uses int64: it is compared against a request's Content-Length, and its own range= tag caps it at 32 MiB.

    RestEngine

    The engine that turns a RestConf into a runnable service (← go-zero's rest.engine). It owns the state the built-in layers need — the connection permits, the breaker window, the metric set, the shedder — so every request shares one of each, and it installs exactly the layers Middlewares asks for.

    RoundRobin

    A round-robin balancer (← go-zero's roundRobinBalancer) over a resolved endpoint set: successive picks cycle through the instances, spreading load evenly. Holds only a cursor, so it is cheap to keep per client.

    RpcChannel

    An in-process gRPC channel bound to a server engine — the client half of the h2c transport. A call is carried as the real HTTP/2 frames a socket-backed client would send: an HPACK-coded HEADERS block with the gRPC pseudo-headers, a length-prefixed DATA frame closing the stream, and the grpc-status trailer read back off the engine's reply. The channel's HPACK encoder pairs with the engine's decoder and vice versa, so the dynamic-table state stays in lockstep across every call on the channel.

    RpcCluster

    The in-process dial table: an endpoint address maps to the RpcServer listening there. It stands in for DNS resolution plus a socket dial in the in-process h2c transport — a real deployment opens a connection to the address instead of looking the server up here, but the resolve→balance→call path above it is the same.

    RpcGroup

    A per-service registration handle (← go-zero's service registrar closure): binds a set of methods to one package.Service on a shared RpcServer.

    RpcHandler

    type RpcHandler = (Bytes) -> Bytes

    A unary RPC handler: it maps a request message's wire bytes to a response message's wire bytes (the application/grpc+proto payload, sans the length-prefix framing that @moonrpc.encode_message adds). Streaming handlers arrive with the h2 transport; this is the unary shape zRPC registers today.

    RpcServer

    A zRPC server (← go-zero's zrpc.Server): config plus a registry mapping each method's gRPC :path (/package.Service/Method) to its handler. Handlers are registered via @moonrpc.Method descriptors — directly or through a RpcGroup — and dispatched by path, mirroring how go-zero registers service implementations on the underlying gRPC server. Unary, server-streaming, and client-streaming methods live in separate registries so one path resolves to exactly one cardinality.

    RpcServerConf

    zRPC server configuration (← go-zero's zrpc.RpcServerConf): the service name, the address it listens on, and a per-call timeout in milliseconds (0 disables it). The registry/etcd fields of go-zero's conf are modelled by the separate discovery layer; this is the transport-facing core.

    Server

    A moonzero service: its config plus the assembled application (a moonapi App with any middleware already wrapped around it).

    ServerMetrics

    The request metrics an HTTP service exposes (← go-zero's server metrics): a request counter partitioned by method/route/status and a latency histogram. Held by the caller so it can be read out for a /metrics scrape after serving.

    ServerStreamHandler

    type ServerStreamHandler = (Bytes) -> Array[Bytes]

    A server-streaming handler: one request message in, an ordered sequence of response messages out (each framed as its own length-prefixed gRPC message). Mirrors go-zero's pb.XxxServer server-streaming method, which writes to a grpc.ServerStream instead of returning one reply.

    ServiceConf

    Service configuration (← go-zero's ServiceConf): the service name, its bind address, a request timeout, and the log level. timeout_ms is the per-request budget in milliseconds; 0 disables the deadline.

    Shedder

    Load shedding under pressure (← go-zero's load.AdaptiveShedder, wired from RestConf.CpuThreshold).

    A request is refused only when the server is hot, its CPU at or over threshold per-mille or recently so, and full, with more requests in flight than it has shown it can finish. The algorithm is moonward's Shedder; this binds it to a Clock and to usage, the CPU meter. Without a meter usage reads zero and nothing is shed, since no MoonBit backend can read one alone.

    ShutdownCoordinator

    A graceful-shutdown coordinator for a zRPC server (← go-zero's proc.AddShutdownListener + gRPC's GracefulStop): once shutdown is initiated the server stops admitting new calls, but calls already in flight are allowed to run to completion. A call brackets its work between begin_call and end_call; begin_call returns false when the server is shutting down, which the transport surfaces as Unavailable — exactly the status a client sees once a server has stopped listening. The server is fully drained once shutdown has been initiated and no calls remain in flight.

    The counter is plain mutable state, which is safe under moonbitlang/async's cooperative single-threaded scheduling: begin_call/end_call never yield, so the count is only observed at await points between them.

    SignatureConf

    Request-signature settings (← rest.SignatureConf): whether an unsigned or badly-signed request is refused outright, how long a signature stays valid, and the keys it is checked against.

    moonzero loads and validates this config — a strict service with no keys is refused at assembly, as go-zero's ErrSignatureConfig does — but ships no content-signature layer to consume it.

    Ticket

    An admitted request (← go-zero's load.Promise).

    TokenBucket

    A token-bucket rate limiter kept in this process: the bucket holds up to burst tokens and refills continuously at rate a second, and each admitted request spends one. Every decision is a function of the state and now, so the limiter is testable without a real clock.

    The bucket is local, so N replicas admit N times the rate. RedisTokenLimit is the same limiter with its bucket in redis and is what a fleet should run; this one serves a single process, and is what RedisTokenLimit itself falls back to when redis is unreachable (← go-zero's rescueLimiter).

    TraceContext

    A W3C Trace Context (← go-zero's OpenTelemetry propagation): the 128-bit trace id shared across a request's whole call tree, the 64-bit span id of the current hop, and the 8-bit sampling flags.

    Unavailable

    Raised by Breaker::run for a call the throttle shed (← go-zero's ErrServiceUnavailable).

    WeightedRoundRobin

    A weighted balancer over Endpoint::weight, using smooth weighted round-robin: every pick credits each instance with its own weight, serves the highest-credited one, then charges it the total weight of the set. Across one full cycle each instance is served exactly its share of the traffic, and the picks interleave instead of arriving in runs — a weight-5 instance is not handed five requests back to back.

    Credit is keyed by address(), so an instance that leaves and returns resumes where it was rather than jumping the queue, and two instances sharing an address are treated as one. An instance whose weight is zero or negative is never picked; a set where every weight is non-positive yields None.

    auth

    JWT auth middleware (← go-zero's handler.Authorize): require every HTTP request to carry a valid Authorization: Bearer <jwt> header. The token is verified against secret under HS256 at the current time read from clock (milliseconds, converted to the JWT seconds epoch); an absent, malformed, tampered, expired, or not-yet-valid token is rejected with 401 Unauthorized before the wrapped app runs. Non-HTTP scopes (lifespan, websocket) pass through untouched.

    breaker

    Circuit-breaker middleware (← go-zero's breaker interceptor): gate each HTTP request through a shared Breaker. An admitted request's outbound HttpResponseStart status is observed — a 5xx settles its promise as a failure, anything else as a success, and that is what feeds the window. A shed request is answered 503 Service Unavailable without the wrapped app running. Non-HTTP scopes pass through untouched.

    The promise starts out a failure and only the observed status makes it a success, on a defer — the same shape go-zero's doReq uses, so a handler that raises or is cancelled before answering counts against the window instead of quietly not counting at all.

    canonical_key

    fn canonical_key(key : String) -> String

    go-zero's canonical form for a config key (← the WithCanonicalKeyFunc that conf.Load installs): lowercased, with _ and - dropped. Every lookup compares on this form, so MaxBytes, maxBytes, max_bytes and max-bytes all name one field — which is what lets a genuine go-zero etc/*.yaml, whose keys are all PascalCase, load into a config instead of silently yielding defaults.

    consul_check_id

    fn consul_check_id(service_id : String) -> String

    The check id consul assigns an inline service TTL check: service:<service-id>.

    consul_parse_health

    Parse a consul health/service JSON body into endpoints — exposed so the native HTTP-socket path decodes a real agent's response exactly as the client does.

    consul_register_body

    fn consul_register_body(id : String, name : String, address : String, port : Int, ttl_secs : Int) -> Bytes

    The JSON body of a service/register request: the instance's id, name, address, and port, plus a TTL check that consul deregisters ttl*3 seconds after it stops passing. Exposed so the native HTTP-socket path builds the exact same body.

    cors

    CORS middleware (← go-zero's cors.Middleware): wrap the outbound Send so the configured Access-Control-* headers are injected onto every HttpResponseStart, leaving the body and other events untouched.

    default_latency_buckets

    let default_latency_buckets : Array[Double]

    The default latency buckets go-zero ships (milliseconds): a request spends most of its time under a second, so the bounds cluster there.

    duration_key

    let duration_key : String

    The key the elapsed time is written under.

    entry

    fn entry(record :
    Record
    ) -> String

    One entry as the single-line JSON object go-zero emits: @timestamp, level, content, then whatever else the entry carries.

    This is the default sink's format, not a rule — hand Logger::new another moonlog sink and the same records are rendered that sink's way.

    etcd_prefix_end

    fn etcd_prefix_end(prefix : Bytes) -> Bytes

    The etcd range-end for a prefix scan: the prefix with its last byte incremented, which is the smallest key greater than every key sharing the prefix (etcd's getPrefix). An all-0xff tail scans to the end of the keyspace (\x00).

    etcd_service_prefix

    fn etcd_service_prefix(prefix : String, service : String) -> String

    The key prefix a service's instances live under: <prefix><service>/. Exposed so the native gRPC-socket path builds the exact same keys as the in-process driver.

    exposition_content_type

    let exposition_content_type : String

    The content type a /metrics scrape carries so a Prometheus server parses the body as the text exposition format (version=0.0.4).

    generate_span_id

    fn generate_span_id(seed : Int64) -> String

    A 16-hex-char (64-bit) span id from seed.

    generate_trace_id

    fn generate_trace_id(seed : Int64) -> String

    A 32-hex-char (128-bit) trace id from seed, mixing two independent words.

    jwt_authorized

    fn jwt_authorized(token : String?, secret : String, now_secs : Int64) -> Bool

    Whether a request bearing token is authorised at now_secs: the token verifies against secret under HS256 and is neither expired nor not-yet-valid. A missing token is unauthorised. Exposed as a pure decision so the middleware's accept/reject is testable without driving the transport.

    jwt_sign

    fn jwt_sign(claims : Map[String, Json], secret : String, typ? : String?, extra? : Map[String, Json], wins? :
    Wins
    , clash? :
    OnClash
    [Map[String, Json]]) -> String

    Sign a claims set as a compact HS256 JWT (← go-zero's jwt.NewWithClaims(SigningMethodHS256, ...)).

    claims is the payload; put exp/iat/nbf/sub in it as ordinary entries. typ is the header's type ("JWT" unless given; None writes no typ header at all), and extra merges further members into the header.

    Configuration has three layers, later ones winning: the library preset < the typ given here < extra. wins turns the last two around and clash decides what a collision does — by default the reserved header fields we write are the ones that stand, and overwriting one raises.

    jwt_verify

    fn jwt_verify(token : String, secret : String, now_secs : Int64, policy? :
    Policy
    ) -> Map[String, Json] raise
    Rejected

    Verify a compact HS256 JWT and return its claims (← go-zero's handler.Authorize).

    now_secs is the verification time as a Unix timestamp in seconds, the unit JWT's NumericDate uses — not the milliseconds the [Clock] seam carries, because the claim is defined in seconds.

    policy carries every check: exp and nbf are verified by default and iat is not, which is what PyJWT and jjwt both do. Hand it a different Policy to require an issuer, an audience, or particular claims.

    level_name

    fn level_name(level :
    Level
    ) -> String

    go-zero's name for a level. Fatal reads as severe, which is what go-zero calls its top level and what its collectors filter on.

    logging

    A request-logging middleware: writes METHOD path through the process logx at info for each HTTP request, then delegates to the wrapped application. It logs on the way in and knows nothing of the response; structured_logging is the layer that times the request and records its status.

    logx

    The process-wide logger (← logx's package-level logger): what the middlewares write to when no logger is handed to them, on the system clock at info. RestEngine::new points its level at the loaded Log.Level.

    max_conns

    Max-connections middleware (← go-zero's MaxConns): hold a permit for the wrapped app's duration, or answer 503 Service Unavailable when every permit is taken. Non-HTTP scopes (lifespan, websocket) pass through untouched.

    maxbytes

    Max-bytes middleware (← go-zero's MaxBytesHandler): reject any HTTP request whose declared Content-Length exceeds limit bytes with 413 Payload TooLarge, before the wrapped app runs. A limit <= 0 disables the check. Non- HTTP scopes pass through untouched.

    message_key

    let message_key : String

    The key the message is written under (← go-zero's logx.content, where zap and slog would write msg).

    metrics

    Metrics middleware (← go-zero's prometheus interceptor): time each HTTP request on the shared clock and, when the response starts, count it under "<METHOD> <path> <status>" and record its latency in milliseconds. The record is taken once per request even if a downstream (under a recovery race) emits a second start. Non-HTTP scopes pass through unmeasured.

    metrics_handler

    A handler that serves the current metric set in the text exposition format, with the Prometheus content type — the scrape target go-zero's prometheus.StartAgent publishes at /metrics.

    mount_metrics

    Register the GET /metrics scrape endpoint on app (← go-zero's prometheus.StartAgent), so a Prometheus server can pull the exposition.

    next_trace_context

    fn next_trace_context(inbound : String?, seed : Int64) ->
    TraceContext

    Derive the outgoing trace context for a request: reuse the inbound traceparent's trace id if the client sent a valid one (continuing the distributed trace), else start a new trace, and always mint a fresh child span id from seed. This is the propagation decision, pulled out as a pure function so it is testable without the transport.

    parse_traceparent

    fn parse_traceparent(value : String) ->
    TraceContext
    ?

    Parse a W3C traceparent value, or None if it is malformed. Only the four canonical fields with correct lengths are accepted; the flags default to 0 if unparseable.

    period_limit

    Fixed-window rate-limit middleware over the process-local limiter: account for each HTTP request under key and answer 429 Too Many Requests once the key is over quota for the current window, otherwise delegating to the wrapped app. The limiter is captured once per assembly, so its windows are shared across every request this layer serves — but only within this process; use redis_period_limit to share them across replicas. Non-HTTP scopes pass through untouched.

    period_script

    let period_script : String

    go-zero's core/limit/periodscript.lua, verbatim: INCRBY the key, hang the window on it the first time it appears, and answer 1 below quota, 2 exactly at it, 0 past it. Public so a caller can pre-load it, and so a redis double can evaluate the very text the limiter ships.

    pick_first

    Pick the first endpoint (← gRPC's pick_first), or None if the set is empty. A stable choice that only moves when the head instance goes away.

    plain_text

    let plain_text : Array[(String, String)]

    The headers a refusal carries unless told otherwise.

    port

    let port : Int

    The default port a moonzero service listens on.

    12000 is the family's number, not go-zero's 8888: every example, every document and every local run in this ecosystem points at the same place, and on a development machine 8888 is somebody else's already.

    rate_limit

    Rate-limit middleware over the process-local bucket: admit each HTTP request against a shared TokenBucket read at clock.now(), answering 429 Too ManyRequests when the bucket is empty and otherwise delegating to the wrapped app. The bucket is captured once per assembly, so its state is shared across every request this layer serves — but only within this process; use redis_rate_limit to share it across replicas. Non-HTTP scopes (lifespan, websocket) pass through untouched.

    recovery

    Recovery middleware (← go-zero's RecoverHandler): run the wrapped application inside a try, and if it raises, emit a 500 Internal ServerError instead of letting the failure escape to the server. A downstream that has already streamed its response start before raising will produce a second start event; recovery is a last-resort guard, so it always answers rather than trying to detect that race.

    redis_instance_key

    fn redis_instance_key(prefix : String, service : String, endpoint :
    Endpoint
    ) -> String

    The key one instance of service lives at: <prefix><service>/<address>.

    redis_period_limit

    Fixed-window rate-limit middleware over a shared redis: period_limit with the window in redis, so replicas behind one redis spend one quota between them. A redis that cannot be reached admits the request — the limiter is a guard on the service, not a dependency that should be able to close it.

    redis_rate_limit

    Rate-limit middleware over a shared redis: rate_limit with the bucket in redis, so replicas behind one redis spend from one bucket. A redis that cannot be reached leaves the limiter running on its local bucket, so the layer keeps limiting either way. Non-HTTP scopes pass through untouched.

    redis_service_pattern

    fn redis_service_pattern(prefix : String, service : String) -> String

    The SCAN MATCH glob for every instance of service: <prefix><service>/*.

    redis_service_prefix

    fn redis_service_prefix(prefix : String, service : String) -> String

    The key prefix a service's instances live under: <prefix><service>/. Exposed so the native RESP-socket path builds the exact same keys as the in-process driver.

    refusal

    The generic refusal: 503 Service Unavailable.

    request_id

    Request-ID middleware (← go-zero's trace/x-request-id handling): reuse an inbound x-request-id if the client sent one, otherwise mint a fresh monotonic id, and stamp it onto every response's HttpResponseStart. The counter is captured once per assembly, so ids stay unique across the requests this layer serves.

    resolve_one

    Resolve service on the registry and pick one endpoint with balancer — the resolve-then-balance step a zRPC client runs before each call. An etcd- or consul-backed registry with the same resolve shape drops in unchanged.

    resp_command

    fn resp_command(args : Array[String]) -> Bytes

    Encode a command given string arguments (the common case — command names and keys are text), UTF-8 encoding each.

    resp_encode_command

    fn resp_encode_command(args : Array[Bytes]) -> Bytes

    Encode a command as the client frame redis expects: an array of bulk strings, one per argument (*<n>\r\n$<len>\r\n<arg>\r\n...). Arguments are raw bytes, so binary keys and values round-trip unchanged.

    resp_kind

    fn resp_kind(value :
    RespValue
    ) -> String

    A short name for a value's RESP type, for error messages ("unexpected <kind> reply") — RespValue derives Eq/Debug but not a renderable form.

    rpc_port

    let rpc_port : Int

    The default port the zRPC server listens on.

    gRPC shares 12000 with HTTP wherever one listener carries both, but a moonzero process binds RestEngine and RpcServer separately — two listeners cannot hold one port — so the second takes the next number.

    set_log_level

    fn set_log_level(level :
    LogLevel
    ) -> Unit

    Set the process logger's level (← logx.SetLevel).

    set_log_sink

    fn set_log_sink(sink : &
    Sink
    ) -> Unit

    Send the process logger's entries to sink.

    set_log_writer

    fn set_log_writer(writer : (String) -> Unit) -> Unit

    Send the process logger's entries to writer (← logx.SetWriter).

    shedding

    Shedding middleware (← go-zero's SheddingHandler): answer 503 ServiceUnavailable without running the app while the shedder is refusing, and pass everything else through. Non-HTTP scopes are never shed.

    An admitted request whose app answers 503 is settled as failed, any other as passed, which is the one status go-zero tells its shedder apart.

    stamp_key

    let stamp_key : String

    The key the entry's time is written under (← go-zero's logx, which names it for Elasticsearch's own field).

    status_of_code

    fn status_of_code(code : Int) ->
    Status

    Map a numeric grpc-status code back to a @moonrpc.Status. Anything outside the canonical 0–16 range is reported as Unknown, matching how a gRPC client treats an unrecognised code.

    structured_logging

    Structured-logging middleware (← go-zero's LogHandler): time each HTTP request on the shared clock, capture its method/path/client-ip/user-agent and the response status observed on HttpResponseStart, and write one access entry through logger (the process logx by default) when the response starts. Access logs are info, so a service configured at error or above emits none of them. Non-HTTP scopes pass through without logging.

    timed_out

    What a request past its deadline answers with. go-zero's timeout middleware answers 503 with this body rather than 504, so that is the default.

    timeout

    Timeout middleware (← go-zero's TimeoutHandler): start a moonward Deadline of budget_ms at clock.now() for each request to the wrapped app.

    Async boundary (faithful model). Preemptively aborting an in-flight handler the instant its deadline fires requires racing the handler against a timer and cancelling the loser — in MoonBit that is @async.any([handler,timer]) with structured cancellation, which only runs under the native async runtime and cannot be driven synchronously. What this middleware does portably: it installs the deadline and enforces it on the response path — if the handler blows its budget before emitting its first event, the client receives a 503 timeout (from timeout_events) and the late response is suppressed. The remaining gap (a handler that hangs and never emits) is closed by the race/cancel wired at the async server edge. A budget_ms <= 0 disables the timeout, passing straight through.

    token_script

    let token_script : String

    go-zero's core/limit/tokenscript.lua, verbatim: read the bucket and the second it was last touched, refill by the elapsed seconds capped at capacity, spend requested if that many are there, and write both back under a TTL of two fill times. Public so a caller can pre-load it, and so a redis double can evaluate the very text the limiter ships.

    too_large

    What an over-large request body answers with (← go-zero's maxBytes).

    too_many

    What a rate-limited request answers with (← go-zero's periodlimit and tokenlimit handlers).

    tracing

    fn tracing(header? : String, ignore_paths? : Array[String]) -> ((async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit) -> (async (
    Scope
    , async () ->
    Event
    , async (
    Event
    ) -> Unit) -> Unit))

    Trace-id propagation middleware (← go-zero's trace handler): continue the inbound traceparent trace or start a new one, mint a child span, and stamp both traceparent and a convenience x-trace-id onto the response so the id flows to the client and downstream calls. The per-assembly seed counter keeps span ids distinct across the requests this layer serves. Requests whose path is listed in ignore_paths are left untraced (← WithTraceIgnorePaths, the blacklist that keeps health checks out of the trace store), as are non-HTTP scopes.

    unavailable

    What an open breaker, a shed request and a spent connection budget answer with (← go-zero's breaker, sheddingHandler, maxConns).

    Source Files