hyperserve

module
v2.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2026 License: MIT

README

HyperServe

CI Latest release Go reference

HyperServe is a Go server library built on net/http. It leaves routing and handlers alone, then fills in the work that tends to collect around them: request binding and validation, middleware, health and readiness, shutdown, templates and static files, Server-Sent Events, WebSockets, and optional Model Context Protocol (MCP).

Most Go services start comfortably with a mux and a few handlers. Later they need probes, coordinated shutdown, request limits, validation, streaming, or another listener. You can wire those pieces separately. HyperServe is for applications that would rather keep them in one server with one configuration and shutdown path, without taking on a framework-specific router or request context.

For a small service with a handful of routes, plain net/http is usually the better choice. HyperServe also does not provide an ORM, a frontend framework, browser sessions, or application authorization. Its auth package establishes an identity; the application still decides what that identity may do.

Quick start

HyperServe requires Go 1.27.

go get github.com/osauer/hyperserve/v2@latest

Public package APIs on the v2 module line follow semantic versioning. Applications upgrading from v1 should start with the v2 migration guide. See the examples for runnable variants and the production guide before deployment.

package main

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "os"
    "os/signal"

    "github.com/osauer/hyperserve/v2/pkg/server"
)

func main() {
    // This executable turns Ctrl+C into cancellation. HyperServe follows ctx;
    // it does not install process-signal handlers of its own.
    ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
    defer stop()

    srv, err := server.NewServer()
    if err != nil {
        log.Fatal(err)
    }

    // Routes use Go's method-aware ServeMux patterns, and handlers remain
    // ordinary net/http functions—there is no framework-specific context.
    srv.GET("/hello/{name}", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, %s!\n", r.PathValue("name"))
    })

    // The application owns cancellation; HyperServe owns orderly cleanup of
    // the listeners and workers it starts.
    if err := srv.Run(ctx); err != nil {
        log.Fatal(err)
    }
}

Run the server:

go run .

Then, from another terminal:

curl -sS http://localhost:8080/hello/Ada

Expected response:

Hello, Ada!

That is the basic shape: keep standard net/http handlers while HyperServe applies request logging, request metrics, and panic recovery. Cancelling the application context stops the listeners, workers, filesystem roots, and shutdown hooks owned by that server.

For typed request bodies, validation, and JSON responses, continue with the binding example. Those helpers are optional; they do not replace Go's handler or request types.

Why use it?

HyperServe does not introduce an application model. Routes use Go's method-aware ServeMux patterns, handlers are http.Handler values, and request cancellation travels through context.Context. Existing handlers and httptest continue to work.

It also keeps the pieces it starts on the same lifecycle. The HTTP server, health listener, shutdown hooks, internal workers, filesystem roots, and MCP streams are closed through the same shutdown path. Startup failures run that cleanup too.

Configuration is explicit. A bare NewServer() does not read a configuration file, environment variables, or specially named asset directories. Applications opt into those sources and decide their precedence.

There is still an ownership line. HyperServe handles transport and server mechanics, and can establish a request identity. The application chooses the identity provider and credential policy, then owns authorization, data access, browser sessions, WebSocket reconnection, and deployment topology.

Routes, pages, and assets

Existing handlers can be registered directly. The assembled server is also available as an http.Handler:

// No adapter is needed for an existing handler.
srv.Handle("/admin/", existingHandler)

// This includes the mux and HyperServe middleware. It can be wrapped, mounted
// in another server, or passed directly to httptest.
handler := srv.Handler()

GET, POST, PUT, PATCH, and the other method helpers use standard ServeMux patterns, including path values. Handle and HandleFunc remain available when one handler covers several methods.

JSONHandler is the short path for typed JSON endpoints. BindJSON, BindQuery, BindForm, and Validate are available when a handler needs custom headers, streaming, or its own response shape. See the binding example.

For HTML applications, HyperServe can render html/template files and serve static assets. Disk roots are off until the application selects them with WithTemplateDir or WithStaticDir. Static files are confined with os.Root; HandleStatic returns an error and leaves the route closed if the root cannot be opened. Embedded assets can be served through an ordinary handler.

SSE or WebSockets?

Use HTTP for normal request/response work. For a long-lived connection, the direction of communication usually decides:

Need Use
The server pushes progress, notifications, logs, or dashboard updates Server-Sent Events (SSE)
The client and server can both send at any time WebSocket

SSEMessage formats event names and string, byte, or JSON data:

// HyperServe formats the event. The application still chooses authorization,
// event cadence, buffering, and resume behavior.
msg := server.NewSSEMessage(map[string]any{
    "progress": 75,
    "status":   "indexing",
})
msg.Event = "progress"

fmt.Fprint(w, msg)
flusher.Flush() // Send this event without closing the response.

An SSE handler must stop when the request context is cancelled and use server and proxy timeouts that permit a long response. The HTMX + SSE example includes the full loop.

For WebSockets, the server-owned upgrader applies the default same-origin check and records the upgrade with the server's request metrics:

// Prefer the server-owned upgrader to a standalone websocket.Upgrader: it
// keeps the same-origin default and records upgrades with server metrics.
upgrader := srv.WebSocketUpgrader()

Server and outbound client connections have a 1 MiB message limit unless the application changes it. websocket.Dial accepts caller-owned HTTP clients and supports headers, subprotocols, TLS verification, and bounded redirects. Reconnect behavior is left to the application. See the WebSocket guide and browser echo example.

Startup, shutdown, and configuration

Run(ctx) blocks until the context is cancelled or the server exits. The application owns process signals because a library cannot know whether one signal should stop one server, several servers, or a larger application. RunStdio() is the explicit entry point for MCP over standard input/output. Shutdown(ctx) is available when another component coordinates the deadline.

In a standalone main, context.Background() is a natural root. If a service runner or a larger application already provides a parent context, pass that to Run, or use it as the parent of signal.NotifyContext. The context passed to Run controls the server's lifetime; it is not copied into requests. Handlers continue to use r.Context() for request cancellation and request-scoped data.

WithHealthServer puts health, readiness, and liveness on a separate listener. WithDeferredInit keeps readiness false while a database, cache, or other dependency starts.

Configuration options are applied from left to right:

// Configuration sources are ignored unless the application opts into them.
// Options run in order, so the final address cannot be replaced by the file
// or environment.
srv, err := server.NewServer(
    server.WithConfigFile(configPath),
    server.WithEnvironment(),
    server.WithAddr("127.0.0.1:8080"),
)

NewServer() already applies HyperServe's defaults, so most applications pass only the options they want to change. DefaultOptions() with WithOptions is for an embedding application that deliberately wants to inspect, modify, and bind one complete configuration snapshot. The configuration example covers the precedence rules.

Security

Browser security headers are opt-in:

// Build browser headers from this server's configuration, then apply them to
// every route. TLS, sessions, and authorization remain separate decisions.
srv.Use(server.SecureWeb(srv.Options()))

Here, server is the imported HyperServe package and srv is the configured *server.Server created by server.NewServer. SecureWeb accepts the defensive snapshot returned by srv.Options() because its headers depend on that server's TLS, CSP, CORS, and optional Server header settings. Keeping the snapshot explicit also leaves the result as ordinary, reusable net/http middleware rather than coupling it to mutable server state.

Authentication composes from small, named pieces:

verifier := auth.TokenVerifierFunc(verifyToken)
bearerIdentity := auth.Bearer(verifier)
requireIdentity := auth.Require(bearerIdentity)
srv.UsePrefix("/api", requireIdentity, server.RateLimitMiddleware(srv))

Use applies middleware to every request. UsePrefix reserves application policy for one path tree, such as /api and its descendants.

SecureWeb emits a Content Security Policy and other defensive browser headers, applies configured CORS policy, and emits HSTS when HyperServe serves TLS. Construct it after passing all options to server.NewServer, as shown above. auth.Require validates credentials and stores an issuer/subject principal on the request. It does not define users, roles, sessions, login redirects, or resource authorization. The federated authentication example connects that seam to an OpenID Connect provider without adding OIDC dependencies to the runtime module.

The production guide documents TLS, proxies, health endpoints, filesystem roots, and the remaining application responsibilities.

MCP

MCP is optional and does not change the HTTP or WebSocket APIs:

// MCP shares the HTTP server's middleware and shutdown path. Enabling the
// endpoint does not enable demonstration tools or resources.
srv, err := server.NewServer(
    server.WithMCPSupport("payments", "1.0.0"),
)

Applications must add authorization middleware in front of /mcp. HyperServe supports Streamable HTTP, request-scoped SSE subscriptions, stdio, typed tools, resources, namespaces, and discovery. Transport versions, limits, and authorization boundaries are in the MCP guide.

Packages and trade-offs

Import path Purpose
github.com/osauer/hyperserve/v2/pkg/server HTTP server, middleware, lifecycle, pages, and MCP wiring
github.com/osauer/hyperserve/v2/pkg/auth Provider-neutral request authentication and stable principals
github.com/osauer/hyperserve/v2/pkg/websocket WebSocket upgrader, connection, and outbound dialer
github.com/osauer/hyperserve/v2/pkg/mcp MCP handler, transports, discovery, tools, and resources
github.com/osauer/hyperserve/v2/pkg/mcp/builtin Opt-in demonstration tools and resources
github.com/osauer/hyperserve/v2/pkg/jsonrpc Standalone JSON-RPC 2.0 engine

The runtime module has one external dependency, golang.org/x/time, for rate limiting. WebSocket, JSON-RPC, and MCP are maintained in this repository. That means fewer packages for an application to assemble, but more protocol code for HyperServe to maintain.

The server, auth, websocket, mcp, and jsonrpc package APIs follow semantic versioning on the v2 module line. Examples, generated layouts, commands, and builtin demonstrations are maintained and tested but are not stable import surfaces. See API stability.

HyperServe does not publish a general throughput number. Its microbenchmarks and reproducible loopback load profiles are useful for comparing revisions on the same machine, not for predicting an application's production performance. See the performance guide.

Scaffold a service

go install github.com/osauer/hyperserve/v2/cmd/hyperserve-init@latest
hyperserve-init --module github.com/acme/payments
cd payments
go run ./cmd/server

The generator creates a Go module, server entry point, and tests. MCP is off because the generator cannot choose the application's authorization policy.

Documentation

MIT — see LICENSE. Bugs and usage questions belong in GitHub Issues.

Directories

Path Synopsis
benchmarks
load command
Command load runs a bounded concurrent HTTP workload using only the Go standard library.
Command load runs a bounded concurrent HTTP workload using only the Go standard library.
server command
Command server is the maintained loopback fixture for HyperServe load tests.
Command server is the maintained loopback fixture for HyperServe load tests.
cmd
hyperserve-init command
examples
best-practices command
Package main demonstrates best practices for using serverpkg.
Package main demonstrates best practices for using serverpkg.
binding command
Example: request binding + validation, three ways.
Example: request binding + validation, three ways.
complete command
configuration command
deferred-init command
Deferred-init example.
Deferred-init example.
devops command
Example demonstrating DevOps features: debug logging and MCP resources
Example demonstrating DevOps features: debug logging and MCP resources
enterprise command
Enterprise example demonstrating FIPS 140-3 compliance and enhanced security features
Enterprise example demonstrating FIPS 140-3 compliance and enhanced security features
hello-world command
htmx-dynamic command
htmx-stream command
json-api command
mcp-basic command
Smallest MCP-enabled HyperServe binary: built-in MCP tools/resources, a custom tool, a custom resource, and a sandboxed file-tool root.
Smallest MCP-enabled HyperServe binary: built-in MCP tools/resources, a custom tool, a custom resource, and a sandboxed file-tool root.
mcp-cli command
Example: Using command-line flags to configure MCP
Example: Using command-line flags to configure MCP
mcp-discovery command
mcp-extensions command
Example: typed MCP tools — one tool per verb.
Example: typed MCP tools — one tool per verb.
mcp-sse command
Example: legacy HyperServe routed SSE.
Example: legacy HyperServe routed SSE.
mcp-stdio command
Package main demonstrates hyperserve's MCP support as a stdio server for Claude Desktop.
Package main demonstrates hyperserve's MCP support as a stdio server for Claude Desktop.
static-files command
web-worker-csp command
websocket-demo command
internal
validate
Package validate implements the tag-driven struct validator used by pkg/server (HTTP request binding) and pkg/mcp (typed-tool argument binding).
Package validate implements the tag-driven struct validator used by pkg/server (HTTP request binding) and pkg/mcp (typed-tool argument binding).
pkg
auth
Package auth provides the small authentication boundary needed by HTTP applications without owning identity-provider setup, sessions, or application authorization.
Package auth provides the small authentication boundary needed by HTTP applications without owning identity-provider setup, sessions, or application authorization.
jsonrpc
Package jsonrpc implements JSON-RPC 2.0 request parsing and method dispatch.
Package jsonrpc implements JSON-RPC 2.0 request parsing and method dispatch.
mcp
Typed MCP tools.
Typed MCP tools.
mcp/builtin
Package builtin provides ready-to-register MCP tools and resources.
Package builtin provides ready-to-register MCP tools and resources.
server
Package server provides a net/http-shaped Go server with lifecycle, middleware, typed request binding, WebSocket integration, and optional Model Context Protocol (MCP) endpoints.
Package server provides a net/http-shaped Go server with lifecycle, middleware, typed request binding, WebSocket integration, and optional Model Context Protocol (MCP) endpoints.
websocket
Package websocket implements RFC 6455 WebSocket servers and outbound clients for net/http applications.
Package websocket implements RFC 6455 WebSocket servers and outbound clients for net/http applications.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL