pronounced "HTTP" because comedic genius was involved here
π API Reference
- π 30-Second Quick Start
- π¦ Root Utilities
- π₯οΈ Full Server
- π WebSocket Magic
- π Static Files & Uploads
- π οΈ Middleware System
- βοΈ Configuration
- π¨ Troubleshooting
- π Production Deployment
aichteeteapee is a collection of HTTP utilities that don't suck. It's got two main parts:
- Root package: Common HTTP utilities you can use anywhere - JSON responses, request parsing, headers, error codes, etc.
- Server package: A complete batteries-included web server with WebSocket support, middleware, static files, and all that jazz
Use just the utilities with your existing server, or go full beast mode with the complete server. Your call.
go get github.com/psyb0t/aichteeteapee
go mod init myapp && go get github.com/psyb0t/aichteeteapee
package main
import (
"context"
"net/http"
"github.com/psyb0t/aichteeteapee/server"
)
func main() {
s, _ := server.New()
router := &server.Router{
Groups: []server.GroupConfig{{
Path: "/",
Routes: []server.RouteConfig{{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello World!"))
},
}},
}},
}
s.Start(context.Background(), router) // Server running on :8080
}
BOOM! You have a production-ready HTTP server with CORS, logging, security headers, and graceful shutdown.
Quick reference for main types - see full API docs for complete details.
Server Package Types
// Server is the main HTTP server
type Server struct {
// Exported methods:
Start(ctx context.Context, router *Router) error
Stop(ctx context.Context) error
GetRootGroup() *Group
GetMux() *http.ServeMux
GetHTTPListenerAddr() net.Addr
GetHTTPSListenerAddr() net.Addr
// Built-in handlers:
HealthHandler(w http.ResponseWriter, r *http.Request)
EchoHandler(w http.ResponseWriter, r *http.Request)
FileUploadHandler(uploadsDir string, opts ...FileUploadHandlerOption) http.HandlerFunc
}
// Router defines your complete server configuration
type Router struct {
// Applied to all routes
GlobalMiddlewares []middleware.Middleware
// Static file serving configs
Static []StaticRouteConfig
// Route groups
Groups []GroupConfig
}
// StaticRouteConfig for serving static files
type StaticRouteConfig struct {
// "./static" - directory to serve
Dir string
// "/static" - URL path prefix
Path string
// HTML, JSON, or None
DirectoryIndexingType DirectoryIndexingType
}
// GroupConfig for organizing routes
type GroupConfig struct {
// "/api/v1" - group path prefix
Path string
// Group-specific middleware
Middlewares []middleware.Middleware
// Routes in this group
Routes []RouteConfig
// Nested groups (recursive)
Groups []GroupConfig
}
// RouteConfig defines individual routes
type RouteConfig struct {
// http.MethodGet, http.MethodPost, etc.
Method string
// "/users/{id}" - route pattern
Path string
// Your handler function
Handler http.HandlerFunc
}
// Group provides fluent API for route registration
type Group struct {
// Methods:
Use(middlewares ...middleware.Middleware)
Group(subPrefix string, middlewares ...middleware.Middleware) *Group
Handle(method, pattern string, handler http.Handler, middlewares ...middleware.Middleware)
HandleFunc(method, pattern string, handler http.HandlerFunc, middlewares ...middleware.Middleware)
GET(pattern string, handler http.HandlerFunc, middlewares ...middleware.Middleware)
POST(pattern string, handler http.HandlerFunc, middlewares ...middleware.Middleware)
PUT(pattern string, handler http.HandlerFunc, middlewares ...middleware.Middleware)
PATCH(pattern string, handler http.HandlerFunc, middlewares ...middleware.Middleware)
DELETE(pattern string, handler http.HandlerFunc, middlewares ...middleware.Middleware)
OPTIONS(pattern string, handler http.HandlerFunc, middlewares ...middleware.Middleware)
}
// Server constructors
func New() (*Server, error)
func NewWithLogger(logger *logrus.Logger) (*Server, error)
func NewWithConfig(config Config) (*Server, error)
func NewWithConfigAndLogger(config Config, logger *logrus.Logger) (*Server, error)
WebSocket Package Types
// Hub manages WebSocket clients and routes events
type Hub interface {
Name() string
Close()
AddClient(client *Client)
RemoveClient(clientID uuid.UUID)
GetClient(clientID uuid.UUID) *Client
GetOrCreateClient(clientID uuid.UUID, opts ...ClientOption) (*Client, bool)
GetAllClients() map[uuid.UUID]*Client
RegisterEventHandler(eventType EventType, handler EventHandler)
RegisterEventHandlers(handlers map[EventType]EventHandler)
UnregisterEventHandler(eventType EventType)
ProcessEvent(client *Client, event *Event)
BroadcastToAll(event *Event)
BroadcastToClients(clientIDs []uuid.UUID, event *Event)
BroadcastToSubscribers(eventType EventType, event *Event)
Done() <-chan struct{}
}
// Client represents a WebSocket client with multiple connections
type Client struct {
// Exported methods:
ID() uuid.UUID
SendEvent(event *Event)
ConnectionCount() int
GetConnections() map[uuid.UUID]*Connection
IsSubscribedTo(eventType EventType) bool
}
// Connection represents a single WebSocket connection (clients can have multiple)
type Connection struct {
// Exported methods:
Send(event *Event)
Stop()
GetHubName() string
GetClientID() uuid.UUID
}
// Event is the core message structure
type Event struct {
// Auto-generated UUID
ID uuid.UUID `json:"id"`
// String event type
Type EventType `json:"type"`
// Your event payload
Data json.RawMessage `json:"data"`
// Unix timestamp
Timestamp int64 `json:"timestamp"`
// Key-value metadata
Metadata *EventMetadataMap `json:"metadata"`
}
// EventHandler processes incoming events
type EventHandler func(hub Hub, client *Client, event *Event) error
// EventType is a string-based event type
type EventType string
// Built-in event types
const (
EventTypeSystemLog EventType = "system.log"
EventTypeShellExec EventType = "shell.exec"
EventTypeEchoRequest EventType = "echo.request"
EventTypeEchoReply EventType = "echo.reply"
EventTypeError EventType = "error"
)
// WebSocket constructors and functions
func NewHub(name string) Hub
func NewEvent(eventType EventType, data any) *Event
func UpgradeHandler(hub Hub, opts ...HandlerOption) http.HandlerFunc
Middleware Package Types
// Middleware is just the standard http middleware pattern
type Middleware func(http.Handler) http.Handler
// Chain composes middlewares around a handler
func Chain(h http.Handler, middlewares ...Middleware) http.Handler
// Built-in middlewares (all return Middleware)
func Recovery() Middleware // Panic recovery
func RequestID() Middleware // Request ID generation
func Logger(opts ...LoggerOption) Middleware // Request logging
func SecurityHeaders() Middleware // Security headers (XSS, CSRF, etc.)
func CORS(opts ...CORSOption) Middleware // CORS handling
func Timeout(duration time.Duration) Middleware // Request timeout
func BasicAuth(users map[string]string, opts ...BasicAuthOption) Middleware // Basic auth
func EnforceRequestContentType(contentType string) Middleware // Content-Type enforcement
The base aichteeteapee
package gives you all the HTTP essentials:
import "github.com/psyb0t/aichteeteapee"
// Pretty JSON responses with proper headers
aichteeteapee.WriteJSON(w, 200, map[string]string{"status": "winning"})
// Smart client IP extraction (handles proxies, load balancers, etc.)
clientIP := aichteeteapee.GetClientIP(r)
// Content type checking that actually works
if aichteeteapee.IsRequestContentTypeJSON(r) {
// Handle JSON like a boss
}
// Request ID for tracing (if you set it in context)
requestID := aichteeteapee.GetRequestID(r)
// Predefined error responses that don't make you cry
aichteeteapee.WriteJSON(w, 404, aichteeteapee.ErrorResponseFileNotFound)
What you get:
- β
WriteJSON()
- JSON responses with pretty formatting - β
GetClientIP()
- Smart IP extraction (X-Forwarded-For β X-Real-IP β RemoteAddr) - β
IsRequestContentTypeJSON/XML/FormData()
- Content type checking that works - β
GetRequestID()
- Request ID extraction from context - β
HTTP header constants (
HeaderNameContentType
, etc.) - β
Content type constants (
ContentTypeJSON
, etc.) - β
Predefined error responses (
ErrorResponseBadRequest
, etc.) - β Context keys for request metadata
Want everything? Here's a complete server setup:
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"github.com/psyb0t/aichteeteapee/server"
"github.com/psyb0t/aichteeteapee/server/middleware"
"github.com/psyb0t/aichteeteapee/server/websocket"
)
func main() {
// Create server
s, err := server.New()
if err != nil {
log.Fatal(err)
}
// Create WebSocket hub for real-time features
hub := websocket.NewHub("my-app")
// Setup WebSocket event handlers
hub.RegisterEventHandler(websocket.EventTypeEchoRequest, func(hub websocket.Hub, client *websocket.Client, event *websocket.Event) error {
// Echo it back to the sender
return client.SendEvent(websocket.NewEvent(websocket.EventTypeEchoReply, event.Data))
})
hub.RegisterEventHandler("file.delete", func(hub websocket.Hub, client *websocket.Client, event *websocket.Event) error {
type deleteMsg struct {
FilePath string `json:"filePath"`
}
var msg deleteMsg
json.Unmarshal(event.Data, &msg)
// Do the file delete
if err := os.Remove(msg.FilePath); err != nil {
// Broadcast error to all clients
hub.BroadcastToAll(websocket.NewEvent("file.delete.error", map[string]string{
"error": err.Error(),
"file": msg.FilePath,
}))
return nil
}
// Broadcast success to all clients
hub.BroadcastToAll(websocket.NewEvent("file.delete.success", map[string]string{
"file": msg.FilePath,
}))
return nil
})
// Define your complete server structure using Router struct
router := &server.Router{
GlobalMiddlewares: []middleware.Middleware{
middleware.Recovery(), // Panic recovery
middleware.RequestID(), // Request tracing
middleware.Logger(), // Request logging
middleware.SecurityHeaders(), // Security headers
middleware.CORS(), // CORS handling
},
Static: []server.StaticRouteConfig{
{
Dir: "./static", // Serve static files
Path: "/static",
},
{
Dir: "./uploads",
Path: "/files",
DirectoryIndexingType: server.DirectoryIndexingTypeJSON, // Browseable uploads
},
},
Groups: []server.GroupConfig{
{
Path: "/",
Routes: []server.RouteConfig{
{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Welcome to the fucking show!"))
},
},
{
Method: http.MethodGet,
Path: "/ws",
Handler: websocket.UpgradeHandler(hub), // WebSocket endpoint
},
{
Method: http.MethodPost,
Path: "/upload",
Handler: s.FileUploadHandler("./uploads"), // File uploads
},
},
},
},
}
// Start the beast
log.Println("Starting server...")
if err := s.Start(context.Background(), router); err != nil {
log.Fatal(err)
}
}
That's it. No, seriously. THAT'S FUCKING IT.
You now have:
- β
HTTP server on
:8080
- β
HTTPS server on
:8443
(if you enable and configure TLS certs) - β CORS that doesn't hate you
- β Request logging that makes sense
- β Security headers that actually secure
- β
Static file serving from
./static
- β
File uploads at
/upload
with UUID filenames - β
Directory browsing at
/files
(JSON format) - β
WebSocket support at
/ws
- β Panic recovery (your server won't die)
- β Request ID tracing
- β Graceful shutdown
Fine, you minimalist bastard:
func main() {
s, _ := server.New()
// Just use the Router struct with basic routes
router := &server.Router{
Groups: []server.GroupConfig{
{
Path: "/",
Routes: []server.RouteConfig{
{
Method: http.MethodGet,
Path: "/",
Handler: func(w http.ResponseWriter, r *http.Request) {
aichteeteapee.WriteJSON(w, 200, map[string]string{
"message": "Hello World",
})
},
},
},
},
},
}
s.Start(context.Background(), router)
}
Create a hub and register event handlers:
hub := websocket.NewHub("my-hub")
// Built-in echo handler for testing
hub.RegisterEventHandler(websocket.EventTypeEchoRequest, func(hub websocket.Hub, client *websocket.Client, event *websocket.Event) error {
return client.SendEvent(websocket.NewEvent(websocket.EventTypeEchoReply, event.Data))
})
// Custom event handlers
hub.RegisterEventHandler("user.login", func(hub websocket.Hub, client *websocket.Client, event *websocket.Event) error {
// Process login event
hub.BroadcastToAll(websocket.NewEvent("user.online", map[string]string{
"userId": "123",
"status": "online",
}))
return nil
})
Create events like this:
// Basic event creation
event := websocket.NewEvent("user.message", map[string]string{
"message": "Hello world!",
"username": "john",
})
// Or chain metadata:
event = websocket.NewEvent("system.alert", alertData).
WithMetadata("priority", "high").
WithMetadata("source", "monitoring")
Broadcasting options:
client.SendEvent(event)
- Send to specific client onlyhub.BroadcastToAll(event)
- Send to everyone in the hubhub.BroadcastToClients([]uuid.UUID{id1, id2}, event)
- Send to specific clientshub.BroadcastToSubscribers(eventType, event)
- Send to subscribers of event type
You can run multiple specialized hubs:
func setupMultipleHubs() (websocket.Hub, websocket.Hub, websocket.Hub) {
// Different hubs for different features
chatHub := websocket.NewHub("chat-system")
notificationHub := websocket.NewHub("notifications")
systemHub := websocket.NewHub("system-monitoring")
// Chat hub handles user messages
chatHub.RegisterEventHandler("chat.message", func(hub websocket.Hub, client *websocket.Client, event *websocket.Event) error {
type ChatMsg struct {
Username string `json:"username"`
Message string `json:"message"`
Room string `json:"room"`
}
var msg ChatMsg
json.Unmarshal(event.Data, &msg)
// Broadcast to all clients in the chat hub
return hub.BroadcastToAll(websocket.NewEvent("chat.broadcast", msg))
})
// Notification hub handles alerts
notificationHub.RegisterEventHandler("alert.create", func(hub websocket.Hub, client *websocket.Client, event *websocket.Event) error {
// Only broadcast high-priority alerts
if priority := event.Metadata.Get("priority"); priority == "high" {
return hub.BroadcastToAll(event)
}
return client.SendEvent(event) // Send only to requesting client
})
return chatHub, notificationHub, systemHub
}
// Then in your Router:
router := &server.Router{
Groups: []server.GroupConfig{
{
Path: "/",
Routes: []server.RouteConfig{
{Method: http.MethodGet, Path: "/ws/chat", Handler: websocket.UpgradeHandler(chatHub)},
{Method: http.MethodGet, Path: "/ws/notifications", Handler: websocket.UpgradeHandler(notificationHub)},
{Method: http.MethodGet, Path: "/ws/system", Handler: websocket.UpgradeHandler(systemHub)},
},
},
},
}
In your event handlers, you get a *websocket.Client
that represents the WebSocket client:
hub.RegisterEventHandler("user.action", func(hub websocket.Hub, client *websocket.Client, event *websocket.Event) error {
// Client has these useful methods:
clientID := client.ID() // Get client UUID
connectionCount := client.ConnectionCount() // How many connections this client has
// Send event only to this specific client
if err := client.SendEvent(websocket.NewEvent("response", responseData)); err != nil {
return err
}
// Check if client is subscribed to event types (always true by default)
if client.IsSubscribedTo("notifications") {
if err := client.SendEvent(notificationEvent); err != nil {
return err
}
}
// Get all connections for this client (for advanced use cases)
connections := client.GetConnections() // map[uuid.UUID]*Connection
return nil
})
Multi-Connection Clients:
A single client can have multiple WebSocket connections (e.g., multiple browser tabs):
hub.RegisterEventHandler("user.status", func(hub websocket.Hub, client *websocket.Client, event *websocket.Event) error {
connectionCount := client.ConnectionCount()
if connectionCount > 1 {
// User has multiple tabs open, send tab-specific response
return client.SendEvent(websocket.NewEvent("multi.tab.warning", map[string]any{
"message": fmt.Sprintf("You have %d tabs open", connectionCount),
}))
}
return client.SendEvent(websocket.NewEvent("single.tab.response", responseData))
})
Static file serving using StaticRouteConfig:
Static: []server.StaticRouteConfig{
{
Dir: "./public",
Path: "/assets",
DirectoryIndexingType: server.DirectoryIndexingTypeHTML, // or JSON, or None
},
}
File uploads with options:
Handler: s.FileUploadHandler("./uploads",
server.WithFilenamePrependType(server.FilenamePrependTypeDateTime), // datetime_originalname.ext
server.WithFileUploadHandlerPostprocessor(func(data map[string]any) (map[string]any, error) {
// Process uploaded file data
return data, nil
}),
)
Built-in middleware:
GlobalMiddlewares: []middleware.Middleware{
middleware.Recovery(), // Panic recovery
middleware.RequestID(), // Request ID generation
middleware.Logger(), // Request logging
middleware.SecurityHeaders(), // Security headers (XSS, CSRF, etc.)
middleware.CORS(), // CORS with sensible defaults
middleware.Timeout(30 * time.Second), // Request timeout
middleware.BasicAuth(map[string]string{"user": "pass"}), // Basic auth
}
Per-group middleware using GroupConfig:
Groups: []server.GroupConfig{
{
Path: "/admin",
Middlewares: []middleware.Middleware{
middleware.BasicAuth(adminUsers),
},
Routes: []server.RouteConfig{...},
},
}
Health check:
{
Method: http.MethodGet,
Path: "/health",
Handler: s.HealthHandler, // Returns {"status": "ok"}
}
Echo endpoint:
{
Method: http.MethodPost,
Path: "/echo",
Handler: s.EchoHandler, // Echoes request body back
}
router := &server.Router{
// Global middleware applies to everything
GlobalMiddlewares: []middleware.Middleware{
middleware.Recovery(),
middleware.RequestID(),
middleware.Logger(),
middleware.CORS(),
},
// Multiple static file routes
Static: []server.StaticRouteConfig{
{
Dir: "./public",
Path: "/assets",
DirectoryIndexingType: server.DirectoryIndexingTypeHTML,
},
{
Dir: "./uploads",
Path: "/files",
DirectoryIndexingType: server.DirectoryIndexingTypeJSON,
},
},
Groups: []server.GroupConfig{
// Public routes (no auth)
{
Path: "/",
Routes: []server.RouteConfig{
{Method: http.MethodGet, Path: "/health", Handler: healthHandler},
{Method: http.MethodGet, Path: "/ws", Handler: websocket.UpgradeHandler(hub)},
},
},
// API with JSON enforcement
{
Path: "/api/v1",
Middlewares: []middleware.Middleware{
middleware.EnforceRequestContentType("application/json"),
},
Routes: []server.RouteConfig{
{Method: http.MethodGet, Path: "/users", Handler: getUsersHandler},
{Method: http.MethodPost, Path: "/users", Handler: createUserHandler},
},
},
// Admin routes with auth
{
Path: "/admin",
Middlewares: []middleware.Middleware{
middleware.BasicAuth(map[string]string{"admin": "secret"}),
},
Routes: []server.RouteConfig{
{Method: http.MethodGet, Path: "/stats", Handler: adminStatsHandler},
{Method: http.MethodDelete, Path: "/users/{id}", Handler: deleteUserHandler},
},
// Nested group for super admin
Groups: []server.GroupConfig{
{
Path: "/super",
Middlewares: []middleware.Middleware{
superAdminAuthMiddleware,
},
Routes: []server.RouteConfig{
{Method: http.MethodPost, Path: "/reset", Handler: systemResetHandler},
},
},
},
},
},
}
Environment variables (with sensible defaults):
export HTTP_SERVER_LISTENADDRESS="0.0.0.0:8080" # HTTP server address
export HTTP_SERVER_TLSENABLED="true" # Enable TLS/HTTPS
export HTTP_SERVER_TLSLISTENADDRESS="0.0.0.0:8443" # HTTPS server address
export HTTP_SERVER_TLSCERTFILE="/path/to/cert.pem" # TLS certificate file
export HTTP_SERVER_TLSKEYFILE="/path/to/key.pem" # TLS private key file
export HTTP_SERVER_READTIMEOUT="30s" # Request read timeout
export HTTP_SERVER_WRITETIMEOUT="30s" # Response write timeout
export HTTP_SERVER_IDLETIMEOUT="60s" # Connection idle timeout
export HTTP_SERVER_FILEUPLOADMAXMEMORY="33554432" # Max upload memory in bytes (32MB)
Or use custom config:
s, err := server.NewWithConfig(server.Config{
ListenAddress: "127.0.0.1:9000",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
})
π¨ Server won't start / Address already in use
# Error: bind: address already in use
# Solution: Use different port or kill the process using it
export HTTP_SERVER_LISTENADDRESS="127.0.0.1:8081" # Different port
# or
sudo lsof -i :8080 # Find process using port 8080
kill -9 <PID> # Kill the process
π¨ WebSocket connections fail / CORS issues
// Make sure CORS is configured for WebSocket origins
GlobalMiddlewares: []middleware.Middleware{
middleware.CORS(middleware.WithCORSAllowOrigins([]string{"https://mydomain.com"})),
}
π¨ File uploads fail / Request body too large
# Increase file upload memory limit (default is 32MB)
export HTTP_SERVER_FILEUPLOADMAXMEMORY="104857600" # 100MB in bytes
π¨ TLS/HTTPS server won't start
# Make sure cert and key files exist and are readable
ls -la /path/to/cert.pem /path/to/key.pem
chmod 644 /path/to/cert.pem /path/to/key.pem
# Test with self-signed cert for development
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
π¨ High memory usage / Memory leaks
// Make sure to properly close WebSocket hubs
defer hub.Close()
// Set reasonable timeouts
s, _ := server.NewWithConfig(server.Config{
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
})
π¨ Static files not serving / 404 errors
// Make sure directory exists and is readable
Static: []server.StaticRouteConfig{{
Dir: "./public", // Must exist
Path: "/assets", // URL prefix
}},
Enable Debug Logging:
import "github.com/sirupsen/logrus"
logger := logrus.New()
logger.SetLevel(logrus.DebugLevel) // Enable debug logs
s, _ := server.NewWithLogger(logger)
Check Server Status:
# Health check endpoint (if enabled)
curl http://localhost:8080/health
# Check what's listening on your ports
netstat -tuln | grep :8080
WebSocket Connection Testing:
// Browser console test for WebSocket connections
const ws = new WebSocket("ws://localhost:8080/ws");
ws.onopen = () => console.log("Connected");
ws.onmessage = (e) => console.log("Message:", e.data);
ws.send(JSON.stringify({ type: "echo.request", data: "test" }));
Dockerfile:
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
COPY --from=builder /app/static ./static
EXPOSE 8080 8443
CMD ["./main"]
docker-compose.yml:
version: "3.8"
services:
app:
build: .
ports:
- "8080:8080"
- "8443:8443"
environment:
- HTTP_SERVER_LISTENADDRESS=0.0.0.0:8080
- HTTP_SERVER_TLSENABLED=true
- HTTP_SERVER_TLSLISTENADDRESS=0.0.0.0:8443
- HTTP_SERVER_TLSCERTFILE=/certs/cert.pem
- HTTP_SERVER_TLSKEYFILE=/certs/key.pem
volumes:
- ./certs:/certs:ro
- ./uploads:/app/uploads
Environment Variables:
# Server binding (use 0.0.0.0 in containers)
export HTTP_SERVER_LISTENADDRESS="0.0.0.0:8080"
# Enable TLS/HTTPS in production
export HTTP_SERVER_TLSENABLED="true"
export HTTP_SERVER_TLSLISTENADDRESS="0.0.0.0:8443"
export HTTP_SERVER_TLSCERTFILE="/etc/ssl/certs/server.pem"
export HTTP_SERVER_TLSKEYFILE="/etc/ssl/private/server.key"
# Timeouts for production
export HTTP_SERVER_READTIMEOUT="30s"
export HTTP_SERVER_WRITETIMEOUT="30s"
export HTTP_SERVER_IDLETIMEOUT="120s"
# File upload limits
export HTTP_SERVER_FILEUPLOADMAXMEMORY="104857600" # 100MB
# Service name for logging
export HTTP_SERVER_SERVICENAME="my-production-api"
π¨ Security Warning - Built-in Handlers Are NOT Secured:
The library includes security middleware but built-in handlers have NO authentication by default:
// β οΈ THESE ARE UNSECURED BY DEFAULT:
s.HealthHandler // Anyone can access /health
s.EchoHandler // Anyone can echo requests (exposes headers!)
s.FileUploadHandler // Anyone can upload files!
// β οΈ WEBSOCKET ACCEPTS ALL ORIGINS BY DEFAULT:
websocket.UpgradeHandler(hub) // Allows connections from ANY website! (CSRF risk)
You MUST add authentication to sensitive endpoints:
// β
SECURE VERSION - Add auth middleware to sensitive routes
Groups: []server.GroupConfig{
{
Path: "/",
Routes: []server.RouteConfig{
{Method: http.MethodGet, Path: "/health", Handler: s.HealthHandler}, // Public OK
},
},
{
Path: "/admin",
Middlewares: []middleware.Middleware{
middleware.BasicAuth(map[string]string{"admin": "secret123"}), // ADD AUTH!
},
Routes: []server.RouteConfig{
{Method: http.MethodPost, Path: "/echo", Handler: s.EchoHandler}, // Now secured
{Method: http.MethodPost, Path: "/upload", Handler: s.FileUploadHandler("./uploads")}, // Now secured
},
},
}
// β
SECURE WEBSOCKET - Configure CheckOrigin for production:
hub := websocket.NewHub("secure-hub")
secureUpgradeHandler := websocket.UpgradeHandler(hub, websocket.WithCheckOrigin(func(r *http.Request) bool {
origin := r.Header.Get("Origin")
// Only allow your trusted domains
allowedOrigins := []string{
"https://yourdomain.com",
"https://app.yourdomain.com",
}
for _, allowed := range allowedOrigins {
if origin == allowed {
return true
}
}
return false // Reject all other origins
}))
Security Best Practices:
// Production middleware stack
GlobalMiddlewares: []middleware.Middleware{
middleware.Recovery(), // Panic recovery
middleware.RequestID(), // Request tracing
middleware.Logger(), // Request logging
middleware.SecurityHeaders(), // Security headers
middleware.CORS(middleware.WithCORSAllowOrigins([]string{
"https://yourdomain.com", // Only allow your domain
})),
middleware.Timeout(30 * time.Second), // Request timeout
},
File Upload Security:
- File uploads have no size limits by default except
HTTP_SERVER_FILEUPLOADMAXMEMORY
- No file type validation - users can upload executables, scripts, etc.
- No authentication - anyone can upload if endpoint is exposed
- Files are stored with UUID prefixes to prevent overwrites, but directory is world-readable
// Add your own validation:
Handler: s.FileUploadHandler("./uploads",
server.WithFileUploadHandlerPostprocessor(func(data map[string]any) (map[string]any, error) {
// Add your validation here:
filename := data["filename"].(string)
if !isAllowedFileType(filename) {
return nil, fmt.Errorf("file type not allowed")
}
return data, nil
}),
)
WebSocket Security:
- CheckOrigin returns
true
for ALL origins by default - allows any website to connect - This creates CSRF vulnerabilities where malicious sites can connect to your WebSocket
- No authentication on WebSocket upgrade by default
- All event handlers run without authentication unless you add it manually
// β οΈ DEFAULT BEHAVIOR - DANGEROUS:
websocket.UpgradeHandler(hub) // Accepts connections from evil-site.com!
// β
PRODUCTION CONFIGURATION:
secureHandler := websocket.UpgradeHandler(hub,
websocket.WithCheckOrigin(func(r *http.Request) bool {
origin := r.Header.Get("Origin")
return origin == "https://yourtrustedsite.com"
}),
)
// β
ADD AUTHENTICATION TO EVENT HANDLERS:
hub.RegisterEventHandler("sensitive.action", func(hub websocket.Hub, client *websocket.Client, event *websocket.Event) error {
// Validate user permissions here before processing
userID := client.GetUserID() // You need to implement this
if !isAuthorized(userID, "sensitive.action") {
return fmt.Errorf("unauthorized")
}
// Process event...
return nil
})
Graceful Shutdown:
func main() {
s, err := server.New()
if err != nil {
log.Fatal(err)
}
// Setup signal handling for graceful shutdown
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down server...")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
defer shutdownCancel()
if err := s.Stop(shutdownCtx); err != nil {
log.Printf("Error during shutdown: %v", err)
}
cancel()
}()
if err := s.Start(ctx, router); err != nil {
log.Fatal(err)
}
}
Load Balancer Setup (nginx):
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081; # Multiple instances
}
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# WebSocket support
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Health Checks & Monitoring:
// Add health check endpoint
{
Method: http.MethodGet,
Path: "/health",
Handler: s.HealthHandler, // Returns {"status": "ok"}
}
// Add metrics endpoint (if using prometheus)
{
Method: http.MethodGet,
Path: "/metrics",
Handler: promhttp.Handler(),
}
This isn't another "minimal framework" that makes you implement everything. This is a batteries-included HTTP utilities library that handles the 90% of shit you always end up building anyway:
- π₯ Production-ready defaults - TLS support, security headers, CORS, logging, graceful shutdown
- π Zero-configuration startup - Just create server, define routes, start
- π‘οΈ Security by default - XSS protection, CSRF headers, secure defaults
- π Structured logging - Request IDs, client IPs, timing, status codes
- π CORS that works - Sensible defaults, fully configurable
- π Static files + uploads - Directory indexing, file caching, UUID filenames
- β‘ WebSocket support - Hub system, event handling, broadcasting
- π§ Completely customizable - Override any default, add custom middleware
- π§ͺ 90%+ test coverage - Battle-tested and production-ready
Because saying "HTTP" is boring, but aichteeteapee
makes you go "what the fuck is this?" and then you realize it's phonetically "HTTP" and you either laugh or hate it. Either way, you remember it.
Also, all the good names were taken.
MIT - Because sharing is caring, and lawyers are expensive.
"Finally, an HTTP library that doesn't make me want to switch careers." - Some Developer, Probably
"I went from 200 lines of boilerplate to 20 lines of actual code." - Another Developer, Definitely
"It just fucking works." - Everyone Who Uses This