puma

package module
v0.0.0-...-1d06259 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: BSD-3-Clause Imports: 12 Imported by: 0

README

go-ruby-puma/puma

puma — go-ruby-puma

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of Ruby's puma — the threaded Rack web server — built on the Go standard library's net/http, and matching the shape of the MRI puma gem. It accepts HTTP connections, shapes each request into a Rack environment Hash, runs the application through a bounded thread pool, and writes the returned [status, headers, body] tuple back to the client — without any Ruby runtime.

The Rack application is an injectable host seam: a RackApp is any value implementing the Rack call(env) contract. go-embedded-ruby supplies the Ruby Rack/Sinatra app through this seam; here the app is a plain Go value, so the whole server is exercised in-process with real HTTP round-trips.

It is the server for go-embedded-ruby, a sibling of go-ruby-rack (the Rack value types), go-ruby-sinatra and go-ruby-regexp.

What it is — and isn't. The threaded, single-process server is implemented faithfully: listeners, the Rack env, the thread pool, response writing, keep-alive, graceful shutdown and the lowlevel_error path. Cluster (multi-worker fork) mode is out of scope for a single-process embed and is a documented follow-up; the workers option is carried for surface parity.

MRI-faithful surface

Ruby (puma) Go (puma)
Puma::Server.new(app, options) puma.NewServer(app, opts)
#add_tcp_listener(host, port) Server.AddTCPListener(host, port)
#add_ssl_listener(host, port, ctx) Server.AddSSLListener(host, port, cfg)
unix:// bind Server.AddUnixListener(path)
#run / #stop / #halt Server.Run() / Stop() / Halt()
graceful shutdown Server.GracefulShutdown()
Puma::ThreadPool.new(min, max) puma.NewThreadPool(min, max)
Puma::Configuration / Puma::DSL puma.NewConfiguration(func(*DSL))
bind / port / threads / workers DSL.Bind / Port / Threads / Workers
environment / on_worker_boot DSL.Environment / OnWorkerBoot
Puma::Launcher.new(conf)#run puma.NewLauncher(conf, app).Run()
Puma::Const::HTTP_STATUS_CODES puma.HTTPStatusCodes / StatusText
Puma::HttpParserError puma.HTTPParserError
#lowlevel_error(e, env) Options.Lowlevel (LowlevelError)

The Rack-app seam

type RackApp interface {
    Call(env map[string]any) (status int, headers map[string][]string, body [][]byte)
}

RackAppFunc adapts a plain function (like a Ruby ->(env){ ... } Proc). The server builds the env — REQUEST_METHOD, SCRIPT_NAME, PATH_INFO, QUERY_STRING, SERVER_NAME/SERVER_PORT, HTTP_* headers, CONTENT_TYPE/ CONTENT_LENGTH, REMOTE_ADDR, rack.input, rack.url_scheme, rack.errors, rack.multithread … — invokes Call, and writes the tuple back (each body chunk flushed in order to model streaming).

Usage

app := puma.RackAppFunc(func(env map[string]any) (int, map[string][]string, [][]byte) {
    return 200, map[string][]string{"Content-Type": {"text/plain"}}, [][]byte{[]byte("hello")}
})

srv := puma.NewServer(app, &puma.Options{MinThreads: 0, MaxThreads: 16})
addr, _ := srv.AddTCPListener("127.0.0.1", 0) // ephemeral port
srv.Run()
defer srv.Stop() // graceful: drains in-flight requests, then the thread pool

resp, _ := http.Get("http://" + addr.String() + "/")

Or drive it from a config block, as in a config/puma.rb:

conf := puma.NewConfiguration(func(c *puma.DSL) {
    c.Bind("tcp://127.0.0.1:0")
    c.Threads(1, 8)
    c.Environment("production")
    c.OnWorkerBoot(func(i int) { /* warm up */ })
})
launcher := puma.NewLauncher(conf, app)
launcher.Run()
defer launcher.Stop()

Tests & coverage

go test -race drives real loopback HTTP round-trips (GET/POST with body, query strings, custom headers), asserts every standard Rack env key, exercises thread-pool concurrency bounding, keep-alive reuse, graceful shutdown draining and the lowlevel_error (panic → 500) path — all in-process, no external network. Coverage is 100% and enforced in CI across Linux, macOS and Windows, and the suite runs on all six 64-bit targets (amd64, arm64, riscv64, loong64, ppc64le, s390x — including big-endian s390x) under qemu.

License

BSD-3-Clause — see LICENSE. Copyright (c) 2026, the go-ruby-puma/puma authors.

WebAssembly

Being pure Go (CGO=0), this library also compiles to WebAssembly — both GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI). CI builds both targets on every push, alongside the six 64-bit native/qemu arches.

GOOS=js     GOARCH=wasm go build ./...   # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./...   # WASI (wasmtime, wasmer, wasmedge, …)

Documentation

Overview

Package puma is a pure-Go (no cgo) reimplementation of Ruby's puma gem — the threaded Rack web server — matching the shape of the MRI `puma` gem.

It builds the server machinery on the Go standard library's net/http, and treats the Rack application as an injectable host seam: a RackApp is any value implementing the Rack `call(env)` contract

Call(env map[string]any) (status int, headers map[string][]string, body [][]byte)

The server accepts HTTP connections, translates each request into a Rack environment Hash (REQUEST_METHOD, SCRIPT_NAME, PATH_INFO, QUERY_STRING, SERVER_NAME/PORT, HTTP_* headers, rack.input, rack.url_scheme, CONTENT_TYPE / CONTENT_LENGTH, REMOTE_ADDR, …), invokes the application through a bounded ThreadPool, and writes the returned status/headers/body back to the client.

The MRI-faithful surface mirrors puma's Ruby classes: Server (with AddTCPListener / AddSSLListener / Run / Stop / Halt / GracefulShutdown), Configuration and DSL (bind, port, threads, workers, environment, on_worker_boot), ThreadPool, Launcher and [Const].

The application seam keeps the package free of any Ruby runtime: go-embedded-ruby supplies the Ruby Rack app through RackApp; here the app is a plain Go value, so the whole server is exercised in-process with real HTTP round-trips. Cluster (multi-worker fork) mode is out of scope for a single-process embed; the threaded single-process server is implemented faithfully.

This is the server for go-embedded-ruby, a sibling of go-ruby-rack (the Rack value types), go-ruby-sinatra and go-ruby-regexp.

Index

Constants

View Source
const ServerSoftware = "puma " + Version

ServerSoftware is the SERVER_SOFTWARE / Server-header value, mirroring Puma::Const::PUMA_SERVER_STRING ("puma <version>").

View Source
const Version = "6.4.2"

Version is the puma-compatible version string reported in SERVER_SOFTWARE and the Server header. It mirrors Puma::Const::PUMA_VERSION / VERSION.

Variables

View Source
var HTTPStatusCodes = map[int]string{
	100: "Continue",
	101: "Switching Protocols",
	102: "Processing",
	200: "OK",
	201: "Created",
	202: "Accepted",
	203: "Non-Authoritative Information",
	204: "No Content",
	205: "Reset Content",
	206: "Partial Content",
	207: "Multi-Status",
	208: "Already Reported",
	226: "IM Used",
	300: "Multiple Choices",
	301: "Moved Permanently",
	302: "Found",
	303: "See Other",
	304: "Not Modified",
	305: "Use Proxy",
	307: "Temporary Redirect",
	308: "Permanent Redirect",
	400: "Bad Request",
	401: "Unauthorized",
	402: "Payment Required",
	403: "Forbidden",
	404: "Not Found",
	405: "Method Not Allowed",
	406: "Not Acceptable",
	407: "Proxy Authentication Required",
	408: "Request Timeout",
	409: "Conflict",
	410: "Gone",
	411: "Length Required",
	412: "Precondition Failed",
	413: "Payload Too Large",
	414: "URI Too Long",
	415: "Unsupported Media Type",
	416: "Range Not Satisfiable",
	417: "Expectation Failed",
	418: "I'm A Teapot",
	421: "Misdirected Request",
	422: "Unprocessable Entity",
	423: "Locked",
	424: "Failed Dependency",
	426: "Upgrade Required",
	428: "Precondition Required",
	429: "Too Many Requests",
	431: "Request Header Fields Too Large",
	451: "Unavailable For Legal Reasons",
	500: "Internal Server Error",
	501: "Not Implemented",
	502: "Bad Gateway",
	503: "Service Unavailable",
	504: "Gateway Timeout",
	505: "HTTP Version Not Supported",
	506: "Variant Also Negotiates",
	507: "Insufficient Storage",
	508: "Loop Detected",
	510: "Not Extended",
	511: "Network Authentication Required",
}

HTTPStatusCodes maps an HTTP status code to its reason phrase. It mirrors Puma::Const::HTTP_STATUS_CODES and is used when writing the status line.

Functions

func StatusText

func StatusText(code int) string

StatusText returns the reason phrase for an HTTP status code, mirroring Puma's lookup into HTTP_STATUS_CODES with an "CUSTOM" fallback for unknown codes (as puma writes "<code> <text>" on the status line).

Types

type Configuration

type Configuration struct {
	// contains filtered or unexported fields
}

Configuration is a faithful port of Puma::Configuration: the evaluated result of a config block, exposing the accumulated Options.

func NewConfiguration

func NewConfiguration(blocks ...func(*DSL)) *Configuration

NewConfiguration evaluates zero or more config blocks against a fresh DSL seeded with DefaultOptions, mirroring Puma::Configuration.new { |c| ... }.

func (*Configuration) Options

func (c *Configuration) Options() *Options

Options returns the configuration's accumulated options.

type DSL

type DSL struct {
	// contains filtered or unexported fields
}

DSL is a faithful port of Puma::DSL: the receiver of a puma config block. Each method records an option; the accumulated options are read back through a Configuration. Mirrors the puma config file DSL (bind, port, threads, …).

func (*DSL) Bind

func (d *DSL) Bind(u string)

Bind adds a listener URL ("tcp://host:port", "ssl://host:port", "unix:///path"). Mirrors Puma::DSL#bind.

func (*DSL) Environment

func (d *DSL) Environment(env string)

Environment sets the Rack environment, mirroring Puma::DSL#environment.

func (*DSL) OnWorkerBoot

func (d *DSL) OnWorkerBoot(hook func(int))

OnWorkerBoot registers a hook run once at worker boot, mirroring Puma::DSL#on_worker_boot.

func (*DSL) Port

func (d *DSL) Port(port int, host ...string)

Port binds a TCP listener on the given port (host defaults to "0.0.0.0"), mirroring Puma::DSL#port(port, host = nil).

func (*DSL) Threads

func (d *DSL) Threads(min, max int)

Threads sets the min/max thread-pool bounds, mirroring Puma::DSL#threads.

func (*DSL) Workers

func (d *DSL) Workers(n int)

Workers sets the cluster worker count (surface parity; single-process embed runs one worker). Mirrors Puma::DSL#workers.

type HTTPParserError

type HTTPParserError struct {
	Message string
}

HTTPParserError mirrors Puma::HttpParserError — the error raised when an incoming request cannot be parsed into a valid HTTP message.

func NewHTTPParserError

func NewHTTPParserError(msg string) *HTTPParserError

NewHTTPParserError builds an HTTPParserError with the given message.

func (*HTTPParserError) Error

func (e *HTTPParserError) Error() string

type Launcher

type Launcher struct {
	// contains filtered or unexported fields
}

Launcher is a faithful port of Puma::Launcher: it takes a Configuration and a Rack app, opens the configured binds as listeners, runs the on_worker_boot hooks and starts the Server. Cluster (multi-process) mode is out of scope; the single worker runs in-process.

func NewLauncher

func NewLauncher(config *Configuration, app RackApp) *Launcher

NewLauncher builds a launcher for the configuration and Rack app, mirroring Puma::Launcher.new(conf). The app is the Rack seam the launcher serves.

func (*Launcher) Halt

func (l *Launcher) Halt()

Halt immediately stops the launched server, mirroring Puma::Launcher#halt.

func (*Launcher) Run

func (l *Launcher) Run() error

Run opens every configured bind, fires the on_worker_boot hooks and starts serving, returning immediately (the server runs in the background). Mirrors Puma::Launcher#run for the single-process case. Use Stop / Halt to tear down.

func (*Launcher) Server

func (l *Launcher) Server() *Server

Server returns the underlying Server (nil until Run has been called).

func (*Launcher) Stop

func (l *Launcher) Stop()

Stop gracefully stops the launched server, mirroring Puma::Launcher#stop.

func (*Launcher) WithTLSConfig

func (l *Launcher) WithTLSConfig(cfg *tls.Config) *Launcher

WithTLSConfig sets the tls.Config used to open ssl:// binds, returning the launcher for chaining.

type LowlevelError

type LowlevelError func(err any, env map[string]any) (status int, headers map[string][]string, body [][]byte)

LowlevelError is the hook invoked when a Rack call raises (panics) or the server cannot otherwise produce a response, mirroring Puma::Server#lowlevel_error(e, env). It returns the Rack tuple written to the client. err is the recovered value; env is the request environment.

type Options

type Options struct {
	// Min / Max threads bound the Rack-call thread pool.
	MinThreads int
	MaxThreads int
	// Workers is the cluster worker count. Cluster mode (fork) is out of scope
	// for the single-process embed; the value is carried for surface parity.
	Workers int
	// Environment names the Rack environment ("development" / "production").
	Environment string
	// Binds are the listener URLs ("tcp://host:port", "ssl://host:port",
	// "unix:///path"). Consumed by [Launcher].
	Binds []string
	// OnWorkerBoot hooks run once per (single) worker at boot, receiving the
	// worker index (always 0 in single-process mode).
	OnWorkerBoot []func(int)
	// Lowlevel overrides the error handler; nil uses [defaultLowlevelError].
	Lowlevel LowlevelError
	// contains filtered or unexported fields
}

Options configures a Server / Launcher, mirroring the puma options Hash.

func DefaultOptions

func DefaultOptions() *Options

DefaultOptions returns the puma defaults: threads 0..5, one worker, the "development" environment.

type RackApp

type RackApp interface {
	Call(env map[string]any) (status int, headers map[string][]string, body [][]byte)
}

RackApp is the injectable host seam: any value implementing the Rack `call(env)` contract. The server translates each incoming HTTP request into a Rack environment Hash, invokes Call, and writes the returned tuple back.

It returns the Rack `[status, headers, body]` triple: an integer status, a header map (each key mapping to one-or-more values), and a body as a sequence of byte chunks (each chunk is written and flushed in order, modelling Rack's enumerable body / streaming).

type RackAppFunc

type RackAppFunc func(env map[string]any) (int, map[string][]string, [][]byte)

RackAppFunc adapts a plain function to the RackApp interface, mirroring how a Ruby Proc (`->(env){ ... }`) is a valid Rack app.

func (RackAppFunc) Call

func (f RackAppFunc) Call(env map[string]any) (int, map[string][]string, [][]byte)

Call implements RackApp.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server is a faithful port of Puma::Server: a threaded Rack web server over one or more listeners. It accepts connections through net/http, shapes each request into a Rack env, runs the app through a bounded ThreadPool and writes the response back.

func NewServer

func NewServer(app RackApp, opts *Options) *Server

NewServer builds a server for app with the given options (nil uses DefaultOptions), mirroring Puma::Server.new(app, options).

func (*Server) AddSSLListener

func (s *Server) AddSSLListener(host string, port int, cfg *tls.Config) (net.Addr, error)

AddSSLListener binds a TLS listener on host:port with the given config and returns the resolved local address, mirroring Puma::Server#add_ssl_listener.

func (*Server) AddTCPListener

func (s *Server) AddTCPListener(host string, port int) (net.Addr, error)

AddTCPListener binds a plain-HTTP TCP listener on host:port and returns the resolved local address, mirroring Puma::Server#add_tcp_listener. A port of 0 selects an ephemeral port.

func (*Server) AddUnixListener

func (s *Server) AddUnixListener(path string) (net.Addr, error)

AddUnixListener binds a Unix-domain listener at path, mirroring puma's `unix://` binds. It returns the listener address. Unix sockets are only available on non-Windows platforms; see addUnixListener.

func (*Server) GracefulShutdown

func (s *Server) GracefulShutdown()

GracefulShutdown is an alias for a bounded graceful stop, mirroring puma's graceful shutdown that drains in-flight requests before exiting.

func (*Server) Halt

func (s *Server) Halt()

Halt performs an immediate shutdown: connections are closed at once and the pool is torn down without waiting to drain. Mirrors Puma::Server#halt.

func (*Server) Run

func (s *Server) Run()

Run starts serving on every added listener and returns immediately, mirroring Puma::Server#run (which returns a thread handle). Use Stop / Halt / GracefulShutdown to tear the server down. It is a no-op if already running.

func (*Server) Running

func (s *Server) Running() bool

Running reports whether the server is currently accepting connections.

func (*Server) Stop

func (s *Server) Stop()

Stop performs a graceful shutdown: it stops accepting new connections, lets in-flight requests finish, then drains the thread pool. Mirrors Puma::Server#stop(true) / graceful stop.

func (*Server) ThreadPool

func (s *Server) ThreadPool() *ThreadPool

ThreadPool returns the server's Rack-call thread pool.

type ThreadPool

type ThreadPool struct {
	// contains filtered or unexported fields
}

ThreadPool is a faithful port of Puma::ThreadPool: a pool of worker threads (goroutines) that runs scheduled blocks, growing on demand from Min up to Max and reaping idle workers back down to Min. Concurrency of Rack calls is bounded by Max; excess work waits in the backlog.

func NewThreadPool

func NewThreadPool(min, max int) *ThreadPool

NewThreadPool builds a pool bounded by [min, max] worker threads and immediately spawns the minimum, mirroring Puma::ThreadPool.new(min, max).

func (*ThreadPool) Backlog

func (tp *ThreadPool) Backlog() int

Backlog returns the number of scheduled blocks not yet picked up by a worker.

func (*ThreadPool) Schedule

func (tp *ThreadPool) Schedule(work func())

Schedule enqueues a block to run on the pool, spawning an extra worker (up to Max) when the backlog outgrows the idle workers. It is a no-op after Shutdown.

func (*ThreadPool) Shutdown

func (tp *ThreadPool) Shutdown()

Shutdown stops accepting new work, wakes every worker and blocks until all in-flight and backlogged blocks have drained. Mirrors Puma::ThreadPool#shutdown.

func (*ThreadPool) Spawned

func (tp *ThreadPool) Spawned() int

Spawned returns the current number of live worker threads.

func (*ThreadPool) Trim

func (tp *ThreadPool) Trim(force bool)

Trim requests that one idle worker above Min exit. With force it also trims when no worker is currently idle. Mirrors Puma::ThreadPool#trim.

Jump to

Keyboard shortcuts

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