httpserver package
Package httpserver provides a modular HTTP server built on Gin and the gosoline kernel. It offers structured request binding, typed responses, middleware, SSE streaming, and dependency-injected handler registration.
import "github.com/gosoline-project/httpserver"
Application helpers
RunDefaultServer()
Usage
httpserver.RunDefaultServer(func(ctx context.Context, config cfg.Config, logger log.Logger, router *httpserver.Router) error {
router.GET("/ping", handler)
return nil
})
Description
Runs a single HTTP server named "default", reading configuration from httpserver.default. This is the simplest way to start a server.
RunServers()
Usage
httpserver.RunServers(map[string]httpserver.RouterFactory{
"public": publicDefiner,
"admin": adminDefiner,
})
Description
Runs multiple named HTTP servers. Each server reads its configuration from httpserver.<name>.
Server construction
NewServer()
Usage
application.WithModuleFactory("http", httpserver.NewServer("default", myRouterFactory))
Description
Creates a kernel.ModuleFactory for an HTTP server. The server is registered as a kernel module and runs at the Service stage. The name parameter determines both the config key (httpserver.<name>) and the module name (httpserver-<name>).
Router
RouterFactory
type RouterFactory func(ctx context.Context, config cfg.Config, logger log.Logger, router *Router) error
Implement this function to define your routes. Return an error to prevent the server from starting.
Router methods
Handle()
router.Handle(httpMethod, relativePath, handlers...)
Registers a route for the given HTTP method and path.
GET() / POST() / PUT() / DELETE() / PATCH() / OPTIONS()
router.GET("/users", handler)
router.POST("/users", handler)
Convenience methods for registering routes with specific HTTP methods.
Group()
api := router.Group("/api")
api.GET("/users", handler)
Creates a route group with a common path prefix. Groups can be nested.
Use()
router.Use(middlewareFunc)
Adds middleware to the router or group.
UseFactory()
router.UseFactory(func(ctx context.Context, config cfg.Config, logger log.Logger, settings *httpserver.Settings) (gin.HandlerFunc, error) {
// create middleware with access to config, logger, and server settings
})
Adds a middleware factory that is resolved lazily when the router is built. settings.Name contains the server name used for server-scoped configuration.
HandleWith()
router.Group("/api").HandleWith(httpserver.With(NewHandler, func(r *httpserver.Router, h *Handler) {
r.GET("/items", httpserver.Bind(h.ListItems))
}))
Registers routes using a RegisterFactoryFunc (typically created with With()).
Handler registration
With[H]()
Usage
router.HandleWith(httpserver.With(NewMyHandler, func(r *httpserver.Router, h *MyHandler) {
r.GET("/items", httpserver.Bind(h.ListItems))
r.POST("/items", httpserver.Bind(h.CreateItem))
}))
Description
Generic function that creates a handler instance via the factory function, then calls the registration function with both a sub-router and the handler. This is the primary pattern for dependency-injected route registration.
The handler factory must have this signature:
func NewMyHandler(ctx context.Context, config cfg.Config, logger log.Logger) (*MyHandler, error)
Since httpserver v0.6.4, handlers registered through With that implement io.Closer are closed after the server stops serving requests. Implement Close() error as blocking, idempotent cleanup. See the released registration contract.
Request binding
Bind functions
| Function | Handler signature | Use case |
|---|---|---|
Bind[I, O any] | func(ctx context.Context, input *I) (O, error) | Bind request to input and render typed output |
BindR[I, O any] | func(ctx context.Context, request *http.Request, input *I) (O, error) | Bind with raw request access |
BindN[O any] | func(ctx context.Context) (O, error) | No input binding |
BindNR[O any] | func(ctx context.Context, request *http.Request) (O, error) | No input, with raw request |
BindSse[I] | (ctx, *I, *SseWriter) error | SSE with input binding |
BindSseR[I] | (ctx, *http.Request, *I, *SseWriter) error | SSE with input + raw request |
BindSseN | (ctx, *SseWriter) error | SSE with no input |
BindSseNR | (ctx, *http.Request, *SseWriter) error | SSE with raw request, no input |
The four response binding helpers accept a typed result O and negotiate its representation from the request's Accept header. The default renderer supports JSON only, uses JSON when Accept is missing, and returns 406 Not Acceptable when no accepted media type is supported.
See Customizing responses in Build an HTTP service for response metadata and explicit Response overrides.
Typed outputs are negotiated before their StatusCode() metadata is applied. For a bodyless 204 No Content response that must be preserved when Accept is unsupported, return NewStatusResponse(http.StatusNoContent); explicit responses bypass negotiation. Other typed outputs, including those with custom status codes, remain negotiated.
Binding tags
Input structs use standard tags to specify the data source:
| Tag | Source | Example |
|---|---|---|
uri | Path parameters | uri:"id" |
json | JSON body | json:"name" |
form | Query string or form body | form:"limit" |
xml | XML body | xml:"item" |
yaml | YAML body | yaml:"config" |
header | HTTP headers | header:"X-Request-Id" |
binding | Validation rules | binding:"required,email" |
Binding and validation failures return 400 Bad Request. Validation bind failures are logged at warning level, not error level, with field, tag, invalid value, and parameter details; invalid values are truncated before logging.
Responses
Response constructors
| Constructor | Description |
|---|---|
NewJsonResponse[T](body T, opts ...ResponseOption) *jsonResponse[T] | JSON response with Content-Type: application/json |
NewTextResponse(text string, opts ...ResponseOption) *response | Plain text response |
NewStatusResponse(statusCode int, opts ...ResponseOption) *response | Status-only response (no body) |
NewResponse(opts ...ResponseOption) *response | Base response constructor |
Response options
| Option | Description |
|---|---|
WithStatusCode(code int) | Set the HTTP status code |
WithHeader(key, value string) | Add a single header |
WithHeaders(headers http.Header) | Merge multiple headers |
WithBody(body []byte) | Set raw response body |
SSE
SseWriter
Methods
| Method | Description |
|---|---|
Send(data string) error | Send a simple SSE data event |
SendEvent(event SseEvent) error | Send a structured SSE event |
Close() | Close the writer and stop heartbeat |
SseEvent
| Field | Type | Description |
|---|---|---|
Event | string | Event type name |
Data | string | Event payload |
Id | string | Event ID for reconnection |
Retry | int | Reconnection hint (ms) |
Error handling
WithErrorHandler()
httpserver.WithErrorHandler(func(statusCode int, err error) any {
return map[string]any{"error": err.Error()}
})
Pass this option to NewServer or NewServerWithSettings, or add it to ServerDefinition.Options for RunServers. The callback runs after middleware selects a status from the last recorded error.
Status selection checks ErrorWithStatus first, then the first matching WithErrorMapper. Validation errors use 400 Bad Request. All other errors use 500 Internal Server Error.
The callback returns the body to send to the client, not another Go error. The endpoint still returns its error through its normal (output, error) result. A returned struct or map keeps the selected HTTP status. Private 5xx privacy passes internal server error to the callback.
A struct, map, or other serializable value that does not implement httpserver.Response uses the server's response negotiator. It selects a format from Accept, serializes the value, and sets Content-Type. JSON is the default. XML needs a negotiator that includes XMLRepresentation() and a value that encoding/xml can encode. In v0.6.4, error middleware falls back to JSON with the selected status if negotiation or encoding fails.
An explicit httpserver.Response bypasses negotiation and sends its format regardless of Accept. It owns the status and headers, including Content-Type. It does not inherit the selected error status. For fixed JSON with that status, return httpserver.NewJsonResponse(body, httpserver.WithStatusCode(statusCode)).
NewErrorWithStatus()
Return a zero output and a wrapped error to set an expected client-error status:
return nil, httpserver.NewErrorWithStatus(http.StatusBadRequest, err)
For a struct output, replace nil with that struct's zero value.
Middleware can attach the same wrapped error to the Gin context:
ginCtx.Error(httpserver.NewErrorWithStatus(http.StatusBadRequest, err))
Middleware
Cors()
corsMiddleware, err := httpserver.Cors(config, "default")
Creates a CORS middleware from httpserver.<name>.cors settings.
CorsFactory()
router.UseFactory(httpserver.CorsFactory)
Creates CORS middleware as a settings-aware middleware factory. The factory uses settings.Name, so reusable route setup automatically reads the CORS settings for the current named server.
CreateEmbeddedStaticServe()
router.UseFactory(httpserver.CreateEmbeddedStaticServe(fs, "public", "/api"))
Creates a middleware that serves embedded static files with SPA fallback.
Built-in middleware
The following middleware is automatically applied to every server:
| Middleware | Purpose |
|---|---|
| Sampling | Apply sampling decisions |
| Metric | Record request metrics |
| Logging | Log requests (fingers-crossed) |
| Compression | Gzip compression |
| Max body size | Limit incoming request bodies |
| Error | Catch and format errors |
| Recovery | Panic recovery |
| Connection lifecycle | Manage connection age |
Validation
AddCustomValidators()
httpserver.AddCustomValidators([]httpserver.CustomValidator{
{Name: "phone", Validator: validatePhone},
})
Register custom field validators for use in binding tags.
Related functions
AddStructValidators([]StructValidator)— Register whole-struct validatorsAddCustomTypeFuncs([]CustomTypeFunc)— Register custom type functionsAddValidateAlias([]ValidateAlias)— Register validation tag aliases
Configuration
Settings
Config key: httpserver.<name>
| Field | Type | Default | Description |
|---|---|---|---|
port | string | "8080" | Listening port. Use "0" for random port. |
mode | string | "release" | Gin mode: debug, release, test. |
compression | CompressionSettings | - | Gzip configuration. |
cors | CorsSettings | - | CORS middleware configuration. |
timeout | TimeoutSettings | - | IO timeouts. |
logging | LoggingSettings | - | Request logging configuration. |
errors | ErrorsSettings | - | Error response privacy configuration. |
router | RouterSettings | - | Router configuration. |
max_body_bytes | int | 10485760 | Maximum incoming request body size in bytes. 0 disables the limit. |
concurrency | ConcurrencySettings | - | Request and connection pressure limits. |
connection_lifecycle | ConnectionLifeCycleAdvisorSettings | - | Connection age and request count limits. |
chaos | ChaosSettings | - | Chaos middleware for resilience testing. |
TimeoutSettings
| Field | Type | Default | Description |
|---|---|---|---|
read | duration | 60s | Max duration for reading entire request. Min 1s. |
write | duration | 60s | Max duration for writing response. Min 1s. |
idle | duration | 60s | Max idle time for keep-alive. Min 1s. |
drain | duration | 0s | Wait time before graceful shutdown. |
shutdown | duration | 60s | Max time for graceful shutdown. Min 1s. |
CompressionSettings
| Field | Type | Default | Description |
|---|---|---|---|
level | string | "default" | "none", "default", "fast", "best", "0"-"9". |
decompression | bool | true | Decompress gzip request bodies. |
exclude | CompressionExcludeSettings | - | Paths/extensions to exclude. |
CompressionExcludeSettings
| Field | Type | Description |
|---|---|---|
extension | string array | File extensions to exclude. |
path | string array | Exact paths to exclude. |
path_regex | string array | Regex patterns to exclude. |
CorsSettings
| Field | Type | Description |
|---|---|---|
allowed_origin_pattern | string | Regular expression matched against the full Origin value. |
allowed_headers | string array | Allowed request headers for CORS requests. |
allowed_methods | string array | Allowed HTTP methods for CORS requests. |
The origin pattern is anchored internally. For example, https://example\.com allows https://example.com, but not https://example.com.evil.com.
LoggingSettings
| Field | Type | Default | Description |
|---|---|---|---|
request_body | bool | false | Log request bodies. |
request_body_base64 | bool | false | Base64-encode request body in logs. |
request_headers | string array | [] | Header names to include in logs. Only the configured headers are logged. |
ErrorsSettings
| Field | Type | Default | Description |
|---|---|---|---|
privacy | string | "private" | "private" hides internal error details for 5xx responses. "public" exposes them. |
RouterSettings
| Field | Type | Default | Description |
|---|---|---|---|
use_raw_path | bool | false | Use the raw URL path (not decoded) for route matching. |
ConcurrencySettings
| Field | Type | Default | Description |
|---|---|---|---|
max_requests | int | 0 | Maximum concurrent requests. 0 disables the limit. |
max_connections | int | 0 | Target maximum open TCP connections. 0 disables connection pressure management. |
overload_status_code | int | 503 | HTTP status code returned when max_requests is reached. |
retry_after | duration | 0 | Value for the Retry-After response header when overloaded. 0 omits the header. |
See Concurrency and connection pressure for detailed behaviour.
ConnectionLifeCycleAdvisorSettings
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Enable connection lifecycle management. |
max_age | duration | 1m | Max connection age before signalling close. 0 disables age-based closing. |
max_request_count | int | 0 | Max requests per connection before signalling close. 0 disables count-based closing. |
See Concurrency and connection pressure for detailed behaviour.
ChaosSettings
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | false | Enable chaos middleware. |
reject | ChaosRejectSettings | - | Random HTTP error responses. |
delay | ChaosDelaySettings | - | Random pre-processing delays. |
drop | ChaosDropSettings | - | Random TCP connection drops. |
slow_response | ChaosSlowResponseSettings | - | Random trickle responses. |
truncate | ChaosTruncateSettings | - | Random response truncation. |
ChaosRejectSettings
| Field | Type | Default | Description |
|---|---|---|---|
percent | int | 3 | Probability (0–100) of rejection. |
status_codes | int array | [499, 500, 502, 503, 504] | Status codes to randomly pick from. |
ChaosDelaySettings
| Field | Type | Default | Description |
|---|---|---|---|
percent | int | 3 | Probability (0–100) of delay. |
min_duration | duration | 0 | Minimum delay. |
max_duration | duration | 60s | Maximum delay. |
ChaosDropSettings
| Field | Type | Default | Description |
|---|---|---|---|
percent | int | 3 | Probability (0–100) of connection drop. |
ChaosSlowResponseSettings
| Field | Type | Default | Description |
|---|---|---|---|
percent | int | 3 | Probability (0–100) of throttled response. |
delay | duration | 1s | Pause between chunks. |
chunk_size | int | 64 | Bytes per chunk. |
ChaosTruncateSettings
| Field | Type | Default | Description |
|---|---|---|---|
percent | int | 3 | Probability (0–100) of truncation. |
max_bytes | int | 512 | Maximum body bytes before dropping. |
See the Chaos middleware how-to for usage examples and safety notes.