framework

package module
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Jan 8, 2026 License: MIT Imports: 9 Imported by: 0

README

Cosmos: Framework

A lightweight HTTP framework for Go that extends the standard library with error-returning handlers, middleware composition, and comprehensive utilities for building web applications. Designed to feel natural to Go developers while providing modern conveniences.

Overview

The framework module provides:

  • Error-Returning Handlers: Handlers that return errors for centralized error handling
  • Middleware System: Composable middleware with hooks for lifecycle events
  • Session Management: Thread-safe sessions with cache-backed storage
  • Cryptography: AES-GCM and ChaCha20-Poly1305 encryption
  • Password Hashing: Argon2 and Bcrypt implementations
  • Database Wrapper: SQL database interface with transaction support
  • Cache Implementations: Memory (go-cache) and Redis backends
  • Event Broker: Publish/subscribe messaging with Memory, Redis, RabbitMQ, MQTT, and NATS backends
  • Response Utilities: JSON, streaming, and static file helpers
  • Security Middleware: CSRF protection, panic recovery, logging

Installation

go get github.com/studiolambda/cosmos/framework

This will also install the required dependencies:

  • github.com/studiolambda/cosmos/router
  • github.com/studiolambda/cosmos/problem
  • github.com/studiolambda/cosmos/contract

Quick Start

package main

import (
    "log/slog"
    "net/http"

    "github.com/studiolambda/cosmos/framework"
    "github.com/studiolambda/cosmos/framework/middleware"
    "github.com/studiolambda/cosmos/contract/request"
    "github.com/studiolambda/cosmos/contract/response"
)

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

func getUser(w http.ResponseWriter, r *http.Request) error {
    id := request.Param(r, "id")
    user := User{ID: 1, Name: "Alice"}
    return response.JSON(w, http.StatusOK, user)
}

func main() {
    app := framework.New()

    // Add middleware
    app.Use(middleware.Logger(slog.Default()))
    app.Use(middleware.Recover())

    // Define routes
    app.Get("/users/{id}", getUser)

    // Start server
    http.ListenAndServe(":8080", app)
}

Core Concepts

Error-Returning Handlers

Unlike standard http.HandlerFunc, framework handlers return errors:

type Handler func(w http.ResponseWriter, r *http.Request) error

This enables centralized error handling:

func handler(w http.ResponseWriter, r *http.Request) error {
    user, err := fetchUser()
    if err != nil {
        return err // Error handled by framework
    }
    return response.JSON(w, http.StatusOK, user)
}

Errors implementing the HTTPStatus() int interface can specify custom status codes.

Middleware Composition

Middleware wraps handlers in layers:

func MyMiddleware() framework.Middleware {
    return func(next framework.Handler) framework.Handler {
        return func(w http.ResponseWriter, r *http.Request) error {
            // Before handler execution
            
            err := next(w, r) // Call next handler
            
            // After handler execution
            
            return err // Propagate error
        }
    }
}

app.Use(MyMiddleware())

Execution order: A → B → C → handler → C → B → A

Hooks System

Hooks allow middleware to inject behavior at key lifecycle points:

hooks := request.Hooks(r)

// Capture status code
hooks.BeforeWriteHeader(func(w http.ResponseWriter, status int) {
    log.Printf("Status: %d", status)
})

// Log after completion
hooks.AfterResponse(func(err error) {
    log.Printf("Completed with error: %v", err)
})

Middleware

Logger

Logs HTTP requests with structured logging:

import "log/slog"

logger := slog.Default()
app.Use(middleware.Logger(logger))
Recover

Recovers from panics and returns 500 Internal Server Error:

app.Use(middleware.Recover())
CSRF Protection

Validates Origin and Sec-Fetch-Site headers:

app.Use(middleware.CSRF("https://example.com", "https://app.example.com"))

Returns 403 Forbidden if request is from untrusted origin.

HTTP Adapter

Adapts standard http.Handler to framework handlers:

standardHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello"))
})

app.Get("/hello", middleware.HTTP(standardHandler))
Provide

Injects dependencies into request context:

db := database.NewSQL(...)
app.Use(middleware.Provide(dbKey, db))

// Access in handler
db := r.Context().Value(dbKey).(contract.Database)

Session Management

Sessions are thread-safe and track changes automatically:

import (
    "github.com/studiolambda/cosmos/framework/session"
    "github.com/studiolambda/cosmos/framework/cache"
)

// Create cache-backed session driver
cacheImpl := cache.NewMemory(5*time.Minute, 10*time.Minute)
driver := session.NewCache(cacheImpl, 24*time.Hour)

// Add session middleware
app.Use(session.Middleware(driver, "session_id"))

// Use in handlers
func handler(w http.ResponseWriter, r *http.Request) error {
    sess := request.Session(r)
    
    // Store data
    sess.Put("user_id", 123)
    
    // Retrieve data
    if val, ok := sess.Get("user_id"); ok {
        userID := val.(int)
    }
    
    // Regenerate after authentication
    sess.Regenerate()
    
    return response.JSON(w, http.StatusOK, data)
}

Caching

Memory Cache

In-memory cache using go-cache:

import "github.com/studiolambda/cosmos/framework/cache"

cache := cache.NewMemory(
    5*time.Minute,  // default expiration
    10*time.Minute, // cleanup interval
)

// Basic operations
cache.Put(ctx, "key", "value", 1*time.Hour)
value, err := cache.Get(ctx, "key")
cache.Delete(ctx, "key")

// Remember pattern (lazy-loading)
value, err := cache.Remember(ctx, "expensive", 1*time.Hour, func() (any, error) {
    return expensiveComputation(), nil
})
Redis Cache

Redis-backed cache:

import (
    "github.com/studiolambda/cosmos/framework/cache"
    "github.com/redis/go-redis/v9"
)

client := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
})

cache := cache.NewRedis(client)

// Same interface as memory cache
cache.Put(ctx, "key", "value", 1*time.Hour)

Event Broker

Event brokers provide publish/subscribe messaging for decoupled communication between application components.

Memory Broker

Pure in-memory pub/sub with zero dependencies, ideal for testing and local development:

import "github.com/studiolambda/cosmos/framework/event"

// Create broker (no configuration needed!)
broker := event.NewMemoryBroker()
defer broker.Close()

// Publish events
err := broker.Publish(ctx, "user.created", map[string]any{
    "id":    123,
    "email": "user@example.com",
})

// Subscribe to events
type UserCreated struct {
    ID    int    `json:"id"`
    Email string `json:"email"`
}

unsubscribe, err := broker.Subscribe(ctx, "user.created", func(payload contract.EventPayload) {
    var event UserCreated
    if err := payload(&event); err != nil {
        log.Printf("Failed to unmarshal: %v", err)
        return
    }
    log.Printf("User created: %d - %s", event.ID, event.Email)
})
defer unsubscribe()

// Subscribe with wildcards
broker.Subscribe(ctx, "user.*", handler)       // Matches: user.created, user.deleted
broker.Subscribe(ctx, "logs.#", handler)       // Matches: logs, logs.error, logs.info.debug

Features:

  • Zero dependencies (stdlib only)
  • Zero configuration required
  • Thread-safe concurrent access
  • Async message delivery with panic recovery
  • Native fan-out (all subscribers receive messages)
  • Wildcard subscriptions (* single-level, # multi-level)
  • Perfect for unit tests and local development

Note: No persistence - messages exist only in memory and are lost on restart.

Redis Broker

Redis-backed pub/sub messaging:

import (
    "github.com/studiolambda/cosmos/framework/event"
    "github.com/redis/go-redis/v9"
)

// Create Redis client
client := redis.NewClient(&redis.Options{
    Addr: "localhost:6379",
})

// Create broker
broker := event.NewRedisBrokerFrom(client)
defer broker.Close()

// Publish events
err := broker.Publish(ctx, "user.created", map[string]any{
    "id":    123,
    "email": "user@example.com",
})

// Subscribe to events
type UserCreated struct {
    ID    int    `json:"id"`
    Email string `json:"email"`
}

unsubscribe := broker.Subscribe(ctx, "user.created", func(payload contract.EventPayload) {
    var event UserCreated
    if err := payload(&event); err != nil {
        log.Printf("Failed to unmarshal event: %v", err)
        return
    }
    log.Printf("User created: %d - %s", event.ID, event.Email)
})

// Unsubscribe when done
defer unsubscribe()
AMQP/RabbitMQ Broker

RabbitMQ-backed pub/sub messaging with topic exchange:

import "github.com/studiolambda/cosmos/framework/event"

// Create broker with default exchange name "cosmos.events"
broker, err := event.NewAMQPBroker("amqp://guest:guest@localhost:5672/")
if err != nil {
    log.Fatal(err)
}
defer broker.Close()

// Or with custom options
broker, err := event.NewAMQPBrokerWithOptions(&event.AMQPBrokerOptions{
    URL:      "amqp://guest:guest@localhost:5672/",
    Exchange: "my-events",
})

// Publish events (broadcasted to all subscribers)
err := broker.Publish(ctx, "order.placed", map[string]any{
    "order_id": "123",
    "amount":   99.99,
})

// Subscribe to specific events
type OrderPlaced struct {
    OrderID string  `json:"order_id"`
    Amount  float64 `json:"amount"`
}

unsubscribe := broker.Subscribe(ctx, "order.placed", func(payload contract.EventPayload) {
    var event OrderPlaced
    if err := payload(&event); err != nil {
        log.Printf("Failed to unmarshal event: %v", err)
        return
    }
    log.Printf("Order placed: %s - $%.2f", event.OrderID, event.Amount)
})

defer unsubscribe()
MQTT Broker

MQTT v5 pub/sub messaging with automatic reconnection and clean sessions:

import "github.com/studiolambda/cosmos/framework/event"

// Simple connection with defaults (QoS 1, clean sessions)
broker, err := event.NewMQTTBroker("mqtt://localhost:1883")
if err != nil {
    log.Fatal(err)
}
defer broker.Close()

// Or with custom options
broker, err := event.NewMQTTBrokerWith(&event.MQTTBrokerOptions{
    URLs: []string{
        "mqtt://broker1:1883",
        "mqtt://broker2:1883", // Failover support
    },
    QoS:      1,
    Username: "user",
    Password: "pass",
})

// Publish events (topics auto-converted: . → /, * → +)
err := broker.Publish(ctx, "sensor.temperature", map[string]any{
    "device_id": "sensor1",
    "value":     23.5,
})

// Subscribe to specific topics
type TempReading struct {
    DeviceID string  `json:"device_id"`
    Value    float64 `json:"value"`
}

unsubscribe, err := broker.Subscribe(ctx, "sensor.temperature", func(payload contract.EventPayload) {
    var reading TempReading
    if err := payload(&reading); err != nil {
        log.Printf("Error: %v", err)
        return
    }
    log.Printf("Temperature: %.1f", reading.Value)
})
defer unsubscribe()

// Subscribe with wildcards (auto-converted: * → +)
broker.Subscribe(ctx, "sensor.*.status", handler) // Becomes: sensor/+/status
broker.Subscribe(ctx, "device.#", handler)        // Becomes: device/#

Topic Conversion: Event names are automatically converted to MQTT format:

  • . (dot) → / (MQTT topic separator)
  • * (asterisk) → + (MQTT single-level wildcard)
  • # (hash) → # (MQTT multi-level wildcard, unchanged)

Examples:

  • user.createduser/created
  • user.*.eventsuser/+/events
  • logs.#logs/#
NATS Broker

High-performance, lightweight pub/sub messaging with NATS:

import "github.com/studiolambda/cosmos/framework/event"

// Simple connection with defaults
broker, err := event.NewNATSBroker("nats://localhost:4222")
if err != nil {
    log.Fatal(err)
}
defer broker.Close()

// Or with custom options (clustering, auth, TLS)
broker, err := event.NewNATSBrokerWith(&event.NATSBrokerOptions{
    URLs: []string{
        "nats://server1:4222",
        "nats://server2:4222", // Automatic failover
    },
    Name:          "myapp-service",
    MaxReconnects: -1,                    // Unlimited reconnection
    ReconnectWait: 2 * time.Second,
    Username:      "user",
    Password:      "pass",
})

// Or from existing connection
conn, _ := nats.Connect("nats://localhost:4222")
broker := event.NewNATSBrokerFrom(conn)

// Publish events (subject uses dot notation)
err := broker.Publish(ctx, "order.placed", map[string]any{
    "order_id": "12345",
    "total":    149.99,
})

// Subscribe to specific subjects
type OrderPlaced struct {
    OrderID string  `json:"order_id"`
    Total   float64 `json:"total"`
}

unsubscribe, err := broker.Subscribe(ctx, "order.placed", func(payload contract.EventPayload) {
    var order OrderPlaced
    if err := payload(&order); err != nil {
        log.Printf("Error: %v", err)
        return
    }
    log.Printf("Order %s placed: $%.2f", order.OrderID, order.Total)
})
defer unsubscribe()

// Subscribe with wildcards (auto-converted: # → >)
broker.Subscribe(ctx, "order.*", handler)      // Matches: order.placed, order.shipped
broker.Subscribe(ctx, "logs.#", handler)       // Matches: logs.error, logs.info.debug

Subject Conversion: Event patterns are automatically converted to NATS format:

  • . (dot) - NATS subject separator (no conversion needed)
  • * (asterisk) - Matches single token (no conversion needed)
  • # (hash) → > (NATS multi-level wildcard)

Examples:

  • user.createduser.created
  • user.*.eventsuser.*.events
  • logs.#logs.>

Authentication Options:

  • Username/password
  • Token-based authentication
  • NKey cryptographic authentication
  • JWT credentials file (recommended for production)
  • TLS with client certificates

Features:

  • Automatic reconnection with configurable backoff
  • Cluster support with automatic failover
  • Native fan-out (all subscribers receive messages)
  • Graceful shutdown with message draining
  • Lightweight and high-performance
Event Broker Patterns

Multiple Subscribers: All subscribers to an event receive their own copy:

// Start multiple workers processing the same events
for i := 0; i < 3; i++ {
    broker.Subscribe(ctx, "task.created", func(payload contract.EventPayload) {
        var task Task
        payload(&task)
        processTask(task)
    })
}

Event Fan-out: Publish once, multiple handlers react:

// Email notification handler
broker.Subscribe(ctx, "user.registered", sendWelcomeEmail)

// Analytics handler
broker.Subscribe(ctx, "user.registered", trackUserRegistration)

// Provisioning handler
broker.Subscribe(ctx, "user.registered", createUserResources)

// Single publish reaches all handlers
broker.Publish(ctx, "user.registered", userData)

Context Cancellation: Subscriptions respect context cancellation:

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour)
defer cancel()

unsubscribe := broker.Subscribe(ctx, "events", handler)

// Subscription automatically stops when context is cancelled or times out

Cryptography

AES-GCM Encryption
import "github.com/studiolambda/cosmos/framework/crypto"

// Key must be 16, 24, or 32 bytes (AES-128/192/256)
key := []byte("your-32-byte-key-here-padding!!")
aes := crypto.NewAES(key)

// Encrypt
plaintext := []byte("secret message")
ciphertext, err := aes.Encrypt(ctx, plaintext)

// Decrypt
plaintext, err := aes.Decrypt(ctx, ciphertext)
ChaCha20-Poly1305 Encryption
import "github.com/studiolambda/cosmos/framework/crypto"

// Key must be 32 bytes
key := []byte("your-32-byte-key-here-padding!!")
chacha := crypto.NewChaCha20(key)

ciphertext, err := chacha.Encrypt(ctx, plaintext)
plaintext, err := chacha.Decrypt(ctx, ciphertext)

Password Hashing

Argon2

Recommended for new applications (memory-hard):

import "github.com/studiolambda/cosmos/framework/hash"

hasher := hash.NewArgon2()

// Hash password
password := []byte("user-password")
hashed, err := hasher.Hash(ctx, password)

// Verify password
err := hasher.Verify(ctx, password, hashed)
if err != nil {
    // Password incorrect
}
Bcrypt

Compatible with existing systems:

import "github.com/studiolambda/cosmos/framework/hash"

hasher := hash.NewBcrypt(10) // cost factor

hashed, err := hasher.Hash(ctx, password)
err := hasher.Verify(ctx, password, hashed)

Database

SQL database wrapper built on sqlx:

import "github.com/studiolambda/cosmos/framework/database"

db := database.NewSQL("postgres", "connection-string")

// Single row query
var user User
err := db.Find(ctx, "SELECT * FROM users WHERE id = $1", &user, userID)

// Multiple rows
var users []User
err := db.Select(ctx, "SELECT * FROM users", &users)

// Execute statement
affected, err := db.Exec(ctx, "DELETE FROM users WHERE id = $1", userID)

// Named parameters
query := "INSERT INTO users (name, email) VALUES (:name, :email)"
arg := map[string]any{"name": "Alice", "email": "alice@example.com"}
affected, err := db.ExecNamed(ctx, query, arg)

// Transactions
err := db.WithTransaction(ctx, func(tx contract.Database) error {
    _, err := tx.Exec(ctx, "INSERT INTO ...", args...)
    if err != nil {
        return err // Automatic rollback
    }
    return nil // Automatic commit
})

Routing

The framework uses the Cosmos router with full support for:

// HTTP methods
app.Get("/path", handler)
app.Post("/path", handler)
app.Put("/path", handler)
app.Delete("/path", handler)
app.Patch("/path", handler)

// Path parameters
app.Get("/users/{id}", handler)
app.Get("/posts/{id}/comments/{commentID}", handler)

// Wildcards
app.Get("/static/{path...}", handler)

// Route groups
app.Group("/api", func(api *framework.Router) {
    api.Use(authMiddleware)
    api.Get("/users", listUsers)
    api.Post("/users", createUser)
})

// Conditional middleware
app.With(middleware.CSRF()).Post("/users", createUser)

Response Helpers

JSON Responses
import "github.com/studiolambda/cosmos/contract/response"

type User struct {
    ID   int    `json:"id"`
    Name string `json:"name"`
}

user := User{ID: 1, Name: "Alice"}
return response.JSON(w, http.StatusOK, user)
Streaming Responses
reader := strings.NewReader("streaming data")
return response.Stream(w, http.StatusOK, "text/plain", reader)
Static Files
return response.Static(w, r, "/path/to/file.pdf")

Request Helpers

import "github.com/studiolambda/cosmos/contract/request"

// Path parameters
id := request.Param(r, "id")

// Query strings
page := request.Query(r, "page")
tags := request.Queries(r, "tag") // multiple values

// Headers
auth := request.Header(r, "Authorization")

// Cookies
sessionID, err := request.Cookie(r, "session_id")

// JSON body
var data RequestData
err := request.Body(r, &data)

Testing

# Run all tests
go test ./...

# Run tests with coverage
go test -cover ./...

# Run specific package tests
go test ./middleware/...

# Run specific test
go test -run TestMiddlewareName ./middleware/

Security Considerations

  1. CSRF Protection: Use middleware.CSRF() for state-changing endpoints
  2. Password Hashing: Use Argon2 for new applications
  3. Encryption: Use AES-GCM or ChaCha20-Poly1305 (authenticated encryption)
  4. Session Security: Regenerate session after authentication
  5. Panic Recovery: Use middleware.Recover() to prevent crashes

Performance Tips

  1. Cache Strategy: Use Remember pattern for expensive operations
  2. Database: Close connections with defer db.Close()
  3. Middleware Order: Place cheap middleware first
  4. Sessions: Set appropriate expiration times

License

MIT License - Copyright (c) 2025 Erik C. Fores

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type HTTPStatus added in v0.2.0

type HTTPStatus interface {
	HTTPStatus() int
}

HTTPStatus is an interface that errors can implement to specify a custom HTTP status code when they are returned from a handler. This allows for more precise error handling and appropriate HTTP response codes based on the type of error that occurred.

When a handler returns an error that implements HTTPStatus, the ServeHTTP method will use the returned status code instead of the default 500 Internal Server Error.

Example usage:

type NotFoundError struct {
    Resource string
}

func (e NotFoundError) Error() string {
    return fmt.Sprintf("resource not found: %s", e.Resource)
}

func (e NotFoundError) HTTPStatus() int {
    return http.StatusNotFound
}

type Handler

type Handler func(w http.ResponseWriter, r *http.Request) error

Handler defines the function signature for HTTP request handlers in Cosmos. Unlike the standard http.HandlerFunc, Cosmos handlers return an error to enable centralized error handling and cleaner error propagation throughout the application middleware chain.

The error return value allows handlers to:

  • Return structured errors that can be handled by error middleware
  • Avoid having to manually write error responses in every handler
  • Enable consistent error formatting across the application

Parameters:

  • w: The HTTP response writer for sending the response
  • r: The HTTP request containing client data and context

Returns an error if the request handling fails, nil on success.

func (Handler) Record added in v0.3.0

func (handler Handler) Record(r *http.Request) *http.Response

Record executes the handler with the given request and returns the resulting HTTP response. It uses httptest.NewRecorder() to capture the response that would be written to a client, making it useful for testing HTTP handlers without starting a server.

func (Handler) ServeHTTP

func (handler Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface, allowing Cosmos handlers to be used with the standard HTTP server. It bridges the gap between Cosmos's error-returning handlers and Go's standard http.Handler interface.

This method calls the handler and provides basic error handling as a fallback. If the handler returns an error, it attempts to send a 500 Internal Server Error response to the client. However, if the response has already been partially written (e.g., during streaming), the error response may not be deliverable.

type Hooks added in v0.8.0

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

Hooks is a structure that can be used to attach specific hooks to the request / response lifecycle. It requires the use of the hooks middleware.

It is safe for concurrent use.

func NewHooks added in v0.8.0

func NewHooks() *Hooks

func (*Hooks) AfterResponse added in v0.8.0

func (h *Hooks) AfterResponse(callbacks ...contract.AfterResponseHook)

func (*Hooks) AfterResponseFuncs added in v0.8.0

func (h *Hooks) AfterResponseFuncs() []contract.AfterResponseHook

func (*Hooks) BeforeWrite added in v0.8.0

func (h *Hooks) BeforeWrite(callbacks ...contract.BeforeWriteHook)

func (*Hooks) BeforeWriteFuncs added in v0.8.0

func (h *Hooks) BeforeWriteFuncs() []contract.BeforeWriteHook

func (*Hooks) BeforeWriteHeader added in v0.8.0

func (h *Hooks) BeforeWriteHeader(callbacks ...contract.BeforeWriteHeaderHook)

func (*Hooks) BeforeWriteHeaderFuncs added in v0.8.0

func (h *Hooks) BeforeWriteHeaderFuncs() []contract.BeforeWriteHeaderHook

type Middleware

type Middleware = router.Middleware[Handler]

Middleware defines the signature for HTTP middleware functions in Cosmos. Middleware can intercept requests before they reach handlers, modify requests/responses, handle authentication, logging, CORS, and other cross-cutting concerns.

This is an alias for router.Middleware[Handler] from the router package.

type ResponseWriter added in v0.8.0

type ResponseWriter struct {
	http.ResponseWriter
	*Hooks
	// contains filtered or unexported fields
}

func (*ResponseWriter) Write added in v0.8.0

func (w *ResponseWriter) Write(content []byte) (int, error)

func (*ResponseWriter) WriteHeader added in v0.8.0

func (w *ResponseWriter) WriteHeader(status int)

func (*ResponseWriter) WriteHeaderCalled added in v0.8.0

func (w *ResponseWriter) WriteHeaderCalled() bool

type ResponseWriterFlusher added in v0.8.0

type ResponseWriterFlusher struct {
	*ResponseWriter
	http.Flusher
}

type Router

type Router = router.Router[Handler]

Router is the HTTP router type used by Cosmos applications. It provides routing functionality with support for path parameters, middleware, and handler composition. The router uses generics to work with Cosmos-specific Handler types.

This is an alias for router.Router[Handler] from the router package.

func New

func New() *Router

New creates a new Cosmos application router instance. The returned router implements http.Handler and can be used directly with http.Server or other HTTP server implementations.

The router supports:

  • RESTful routing with HTTP methods (GET, POST, PUT, DELETE, etc.)
  • Path parameters and wildcards
  • Middleware composition
  • Route groups for organizing related endpoints

Example usage:

app := framework.New()
app.Get("/users/{id}", getUserHandler)
http.ListenAndServe(":8080", app)

type WrappedResponseWriter added in v0.8.0

type WrappedResponseWriter interface {
	http.ResponseWriter
	WriteHeaderCalled() bool
}

func NewResponseWriter added in v0.8.0

func NewResponseWriter(w http.ResponseWriter, h *Hooks) WrappedResponseWriter

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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