catch

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Apr 16, 2025 License: MIT Imports: 21 Imported by: 0

README

Catch - Go Middleware Package

The catch package provides a collection of HTTP middleware and utilities for Go applications, including request forwarding, panic recovery, security headers, structured logging, rate limiting, and RSA encryption. This README provides instructions for installing, configuring, and using the package.

Features

  • Request Forwarding Middleware: Updates request host, protocol, and client IP based on forwarded headers (X-Forwarded-Host, X-Forwarded-Proto, X-Forwarded-For, etc.).
  • Panic Recovery Middleware: Recovers from panics, logs stack traces, and returns a structured error response.
  • Security Headers Middleware: Adds HTTP security headers like Strict-Transport-Security, X-XSS-Protection, and X-Content-Type-Options.
  • Structured Logging Middleware: Logs HTTP requests with customizable fields using slog, supporting request IDs, headers, and performance metrics.
  • Rate Limiting Middleware: Implements token bucket-based rate limiting for both global and per-user requests, with encrypted user identification via cookies.
  • RSA Cryptography Utilities: Provides functions for generating RSA key pairs, encrypting/decrypting messages, and signing/verifying signatures.

Prerequisites

  • Go: Version 1.18 or higher.
  • Dependencies: The package uses external libraries, which will be installed automatically via go get.

Installation

  1. Clone the Repository (if you are contributing or want the source code directly):

    git clone https://github.com/yourusername/catch.git
    cd catch
    
  2. Install the Package (if you are using it as a dependency): Add the package to your Go project by running:

    go get github.com/yourusername/catch
    
  3. Install Dependencies: The package relies on the following external libraries, which will be fetched automatically when you build or run your project:

    • github.com/go-chi/chi/v5
    • github.com/go-chi/render Ensure your go.mod file includes these dependencies, or run:
    go mod tidy
    
  4. Verify Installation: Create a simple Go program to test the package import:

    package main
    
    import (
        "github.com/yourusername/catch"
        "net/http"
    )
    
    func main() {
        http.ListenAndServe(":8080", catch.Recoverer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte("Hello, World!"))
        })))
    }
    

    Run the program:

    go run main.go
    

    Visit http://localhost:8080 to verify the server is running.

Usage

Below are examples of how to use each component of the catch package in a Go HTTP server.

1. Request Forwarding Middleware

The RequestForwardedHostProtoMiddleware updates the request's host, protocol, and client IP based on forwarded headers, useful for applications behind proxies.

package main

import (
    "github.com/go-chi/chi/v5"
    "github.com/yourusername/catch"
    "net/http"
)

func main() {
    r := chi.NewRouter()
    r.Use(catch.RequestForwardedHostProtoMiddleware)
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Host: " + r.Host + ", Proto: " + r.URL.Scheme + ", RemoteAddr: " + r.RemoteAddr))
    })
    http.ListenAndServe(":8080", r)
}
2. Panic Recovery Middleware

The Recoverer middleware catches panics, logs them, and returns a 500 Internal Server Error response.

package main

import (
    "github.com/go-chi/chi/v5"
    "github.com/yourusername/catch"
    "net/http"
)

func main() {
    r := chi.NewRouter()
    r.Use(catch.Recoverer)
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        panic("Something went wrong!")
    })
    http.ListenAndServe(":8080", r)
}
3. Security Headers Middleware

The Protection middleware adds security headers to responses.

package main

import (
    "github.com/go-chi/chi/v5"
    "github.com/yourusername/catch"
    "net/http"
)

func main() {
    r := chi.NewRouter()
    r.Use(catch.Protection)
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Secure response"))
    })
    http.ListenAndServe(":8080", r)
}
4. Structured Logging Middleware

The NewStructuredLogger middleware logs HTTP requests with structured fields using slog.

package main

import (
    "github.com/go-chi/chi/v5"
    "github.com/yourusername/catch"
    "log/slog"
    "net/http"
    "os"
)

func main() {
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
    slog.SetDefault(logger)
    r := chi.NewRouter()
    r.Use(catch.NewStructuredLogger(logger.Handler(), false)) // Log all requests
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        catch.LogEntrySetField(r, "custom_field", "value")
        w.Write([]byte("Logged request"))
    })
    http.ListenAndServe(":8080", r)
}
5. Rate Limiting Middleware

The RateLimiter middleware enforces request limits per user and globally, using encrypted cookies for user identification.

package main

import (
    "github.com/go-chi/chi/v5"
    "github.com/yourusername/catch"
    "net/http"
)

func main() {
    config := &catch.RateLimiterConfig{
        UserRequestsPerSecond:   5.0,
        UserBurst:               10,
        GlobalRequestsPerSecond: 50.0,
        GlobalBurst:             100,
        CookieName:              "ratelimit_token",
        CookieMaxAge:            3600 * 24,
        CleanupInterval:         5 * time.Minute,
        EncryptionKey:           []byte("32-byte-long-key-1234567890123456"), // Must be 32 bytes
    }
    rl, err := catch.NewRateLimiter(config)
    if err != nil {
        panic(err)
    }
    defer rl.Stop()

    r := chi.NewRouter()
    r.Use(rl.Middleware)
    r.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Rate-limited endpoint"))
    })
    http.ListenAndServe(":8080", r)
}
6. RSA Cryptography Utilities

The rsacrypt utilities provide RSA key generation, encryption/decryption, and signing/verification.

package main

import (
    "fmt"
    "github.com/yourusername/catch"
)

func main() {
    // Generate RSA key pair
    priv, pub, err := catch.GenerateKeyPair(2048)
    if err != nil {
        panic(err)
    }

    // Encrypt a message
    msg := []byte("Secret message")
    ciphertext, err := catch.EncryptWithPublicKey(msg, pub)
    if err != nil {
        panic(err)
    }

    // Decrypt the message
    plaintext, err := catch.DecryptWithPrivateKey(ciphertext, priv)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Decrypted: %s\n", plaintext)

    // Sign a message
    signature, err := catch.SignWithPrivateKey(msg, priv)
    if err != nil {
        panic(err)
    }

    // Verify the signature
    err = catch.VerifyWithPublicKey(msg, signature, pub)
    if err == nil {
        fmt.Println("Signature verified")
    } else {
        fmt.Println("Signature verification failed")
    }
}

Configuration

Rate Limiter Configuration

The RateLimiterConfig struct allows customization of the rate limiter:

config := &catch.RateLimiterConfig{
    UserRequestsPerSecond:   10.0,           // 10 requests per second per user
    UserBurst:               20,             // Allow bursts up to 20 requests
    GlobalRequestsPerSecond: 100.0,          // 100 requests per second globally
    GlobalBurst:             200,            // Allow global bursts up to 200
    CookieName:              "ratelimit_token", // Cookie name for user tracking
    CookieMaxAge:            3600 * 24,      // Cookie lifetime (1 day)
    CleanupInterval:         5 * time.Minute, // Cleanup unused data every 5 minutes
    EncryptionKey:           []byte("32-byte-long-key-1234567890123456"), // 32-byte AES-256 key
}
Structured Logger Configuration

The NewStructuredLogger function accepts a slog.Handler and a boolean to log only errors:

logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
middleware := catch.NewStructuredLogger(logger.Handler(), true) // Log only errors

Building and Running

  1. Build the Application:

    go build -o myapp
    
  2. Run the Application:

    ./myapp
    
  3. Run Tests (if you have written tests):

    go test ./...
    

Contributing

  1. Fork the repository.
  2. Create a new branch:
    git checkout -b feature/your-feature
    
  3. Make your changes and commit:
    git commit -m "Add your feature"
    
  4. Push to your fork:
    git push origin feature/your-feature
    
  5. Create a pull request on GitHub.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Support

For issues or questions, open an issue on the GitHub repository or contact the maintainers at [your contact email].

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BytesToPrivateKey

func BytesToPrivateKey(priv []byte) (*rsa.PrivateKey, error)

BytesToPrivateKey converts PKCS#1 ASN.1 DER bytes to an RSA private key

func BytesToPublicKey

func BytesToPublicKey(pub []byte) (*rsa.PublicKey, error)

BytesToPublicKey converts PKIX ASN.1 DER bytes to an RSA public key

func DecryptWithPrivateKey

func DecryptWithPrivateKey(ciphertext []byte, priv *rsa.PrivateKey) ([]byte, error)

DecryptWithPrivateKey decrypts a message using RSA-OAEP with SHA512

func EncryptWithPublicKey

func EncryptWithPublicKey(msg []byte, pub *rsa.PublicKey) ([]byte, error)

EncryptWithPublicKey encrypts a message using RSA-OAEP with SHA512

func GenerateKeyPair

func GenerateKeyPair(bits int) (*rsa.PrivateKey, *rsa.PublicKey, error)

GenerateKeyPair generates an RSA key pair of the given bit size

func GetLogEntry

func GetLogEntry(r *http.Request) *slog.Logger

func GetRequestIdLogger

func GetRequestIdLogger(r *http.Request) *slog.Logger

GetRequestIdLogger returns a logger with request ID

func Identify

func Identify() string

Identify returns the package name, file name and line number where panic occurred

func LogAllStatuses

func LogAllStatuses(r *http.Request)

LogAllStatuses configures the logger to log all statuses, not just errors

func LogEntrySetAttrs

func LogEntrySetAttrs(r *http.Request, attrs ...any)

LogEntrySetAttrs adds multiple attributes to the log entry

func LogEntrySetField

func LogEntrySetField(r *http.Request, key string, value interface{})

LogEntrySetField adds a field to the log entry

func LogEntrySetFields

func LogEntrySetFields(r *http.Request, fields map[string]interface{})

LogEntrySetFields adds multiple fields to the log entry

func LogHeaders

func LogHeaders(r *http.Request)

LogHeaders adds request headers to the log entry

func NewStructuredLogger

func NewStructuredLogger(handler slog.Handler, onlyErrs bool) func(next http.Handler) http.Handler

StructuredLogger is a simple, but powerful implementation of a custom structured logger backed on log/slog. I encourage users to copy it, adapt it and make it their own. Also take a look at https://github.com/go-chi/httplog for a dedicated pkg based on this work, designed for context-based http routers. Example: logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) slog.SetDefault(logger) hlog := NewStructuredLogger(slog.Default().Handler(), true)

func PrivateKeyToBytes

func PrivateKeyToBytes(priv *rsa.PrivateKey) []byte

PrivateKeyToBytes converts an RSA private key to PKCS#1 ASN.1 DER bytes

func Protection

func Protection(next http.Handler) http.Handler

Protection adds security headers to responses

func PublicKeyToBytes

func PublicKeyToBytes(pub *rsa.PublicKey) ([]byte, error)

PublicKeyToBytes converts an RSA public key to PKIX ASN.1 DER bytes

func RealIPFromRequest

func RealIPFromRequest(r *http.Request) string

RealIPFromRequest returns the real client IP from request headers

func Recoverer

func Recoverer(next http.Handler) http.Handler

Recoverer is a middleware that recovers from panics

func RequestForwardedHostProtoMiddleware

func RequestForwardedHostProtoMiddleware(next http.Handler) http.Handler

RequestForwardedHostProtoMiddleware updates request host and protocol from forwarded headers

func RequestHost

func RequestHost(r *http.Request) (host string)

RequestHost returns the host from forwarded headers or fallback to request host

func RequestProto

func RequestProto(r *http.Request) (proto string)

RequestProto returns the protocol from forwarded headers or fallback to request scheme

func SignWithPrivateKey added in v1.0.1

func SignWithPrivateKey(msg []byte, priv *rsa.PrivateKey) ([]byte, error)

SignWithPrivateKey signs a message using RSA PKCS#1v1.5 with SHA256

func VerifyWithPublicKey added in v1.0.1

func VerifyWithPublicKey(msg []byte, sig []byte, pubkey *rsa.PublicKey) error

VerifyWithPublicKey verifies a message signature using RSA PKCS#1v1.5 with SHA256

Types

type ErrResponse

type ErrResponse struct {
	Err            error `json:"-"` // Low-level runtime error
	HTTPStatusCode int   `json:"-"` // HTTP response status code

	StatusText string `json:"status"`          // User-level status message
	AppCode    int64  `json:"code,omitempty"`  // Application-specific error code
	ErrorText  string `json:"error,omitempty"` // Application-level error message
}

ErrResponse represents an error response structure

func (*ErrResponse) Error

func (e *ErrResponse) Error() string

Error implements the error interface

func (*ErrResponse) Render

func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error

Render implements the render.Renderer interface

type RateLimiter added in v1.0.1

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

RateLimiter implements a middleware for request rate limiting

func NewRateLimiter added in v1.0.1

func NewRateLimiter(config *RateLimiterConfig) (*RateLimiter, error)

NewRateLimiter creates a new RateLimiter instance

func (*RateLimiter) Middleware added in v1.0.1

func (rl *RateLimiter) Middleware(next http.Handler) http.Handler

Middleware returns a middleware function for request rate limiting

func (*RateLimiter) Stop added in v1.0.1

func (rl *RateLimiter) Stop()

Stop terminates background processes of the rate limiter

type RateLimiterConfig added in v1.0.1

type RateLimiterConfig struct {
	UserRequestsPerSecond   float64       // Requests per second limit per user
	UserBurst               int           // Maximum burst requests allowed per user
	GlobalRequestsPerSecond float64       // Global requests per second limit
	GlobalBurst             int           // Global burst limit
	CookieName              string        // Cookie name for user identification
	CookieMaxAge            int           // Cookie lifetime in seconds
	CleanupInterval         time.Duration // Interval for cleaning up unused data
	EncryptionKey           []byte        // Encryption key (32 bytes for AES-256)
}

RateLimiterConfig contains configuration for the rate limiter

type StructuredLogger

type StructuredLogger struct {
	Logger     slog.Handler
	OnlyErrors bool
}

StructuredLogger implements the LogFormatter interface

func (*StructuredLogger) NewLogEntry

func (l *StructuredLogger) NewLogEntry(r *http.Request) middleware.LogEntry

NewLogEntry creates a new log entry for each request

type StructuredLoggerEntry

type StructuredLoggerEntry struct {
	Ctx        context.Context
	OnlyErrors bool
	Logger     *slog.Logger
}

StructuredLoggerEntry represents a single request log entry

func (*StructuredLoggerEntry) Panic

func (l *StructuredLoggerEntry) Panic(v interface{}, stack []byte)

Panic logs panic information

func (*StructuredLoggerEntry) Write

func (l *StructuredLoggerEntry) Write(status, bytes int, header http.Header, elapsed time.Duration, extra interface{})

Write logs the completion of the request

type TokenBucket added in v1.0.1

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

TokenBucket implements the token bucket algorithm for rate limiting

func NewTokenBucket added in v1.0.1

func NewTokenBucket(rate float64, capacity int) *TokenBucket

NewTokenBucket creates a new token bucket instance

func (*TokenBucket) Take added in v1.0.1

func (tb *TokenBucket) Take() bool

Take attempts to take a token from the bucket

Jump to

Keyboard shortcuts

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