Complete development documentation, architecture decisions, and technical context for the Serve static file server project.
- Project Overview
- Architecture
- File Structure
- Core Components
- Design Decisions
- Security Implementation
- Performance Optimizations
- Configuration System
- Middleware Chain
- Logging System
- Future Enhancements
- Development Guide
Serve is a production-ready static file server written in Go, designed to be a single-binary solution for serving files with advanced security, performance, and configuration options.
- Simplicity: Single executable, no dependencies
- Security: Built-in protection against common attacks
- Performance: Efficient compression, caching, and resource handling
- Flexibility: Comprehensive configuration options
- Production-Ready: Suitable for development and production environments
- Single Binary: Compiles to a single executable without runtime dependencies
- Cross-Platform: Native support for Linux, Windows, macOS, ARM
- Performance: Fast execution, low memory footprint
- Standard Library: Excellent HTTP server implementation built-in
- Maintainability: Simple, readable code with strong typing
┌─────────────────────────────────────────────────────────┐
│ Client Request │
└────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Middleware Chain │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 1. Logging Middleware │ │
│ │ 2. Security Headers │ │
│ │ 3. Custom Headers │ │
│ │ 4. IP Filtering (Whitelist/Blacklist) │ │
│ │ 5. Rate Limiting │ │
│ │ 6. Basic Authentication │ │
│ │ 7. CORS │ │
│ │ 8. Path Traversal Protection │ │
│ │ 9. Hidden Files Blocking │ │
│ │ 10. Compression (Gzip) │ │
│ │ 11. Cache Headers │ │
│ └─────────────────────────────────────────────────┘ │
└────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ File Handler │
│ ┌─────────────────────────────────────────────────┐ │
│ │ • Path Resolution │ │
│ │ • File/Directory Detection │ │
│ │ • Index File Serving │ │
│ │ • Directory Listing │ │
│ │ • SPA Mode Handling │ │
│ │ • ETag Generation │ │
│ │ • Error Pages │ │
│ └─────────────────────────────────────────────────┘ │
└────────────────────────┬────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ HTTP Response │
└─────────────────────────────────────────────────────────┘
┌───────────────────────┐
│ cmd/koryx-serv/main.go│ CLI entrypoint, flags, lifecycle
└───────────┬───────────┘
│ imports
▼
┌───────────────────────────────────────────────┐
│ package koryxserv (module root) │
│ config.go, logger.go, server.go, middleware.go│
└───────────────────────────────────────────────┘
koryx-serv/
├── cmd/
│ └── koryx-serv/
│ └── main.go # CLI entry point, flag parsing, application lifecycle
├── config.go # Configuration structures and JSON loading
├── server.go # HTTP server, file serving logic, directory listing
├── middleware.go # All middleware implementations
├── logger.go # Logging system with colors and levels
├── *_test.go # Unit tests for library and CLI behavior
├── go.mod # Go module definition
├── config.example.json # Example configuration file
├── Makefile # Build automation
├── .gitignore # Git ignore rules
├── LICENSE # MIT License
├── README.md # English documentation
├── README.pt-BR.md # Portuguese documentation
├── CONTEXT.md # This file - development context
└── .github/
└── workflows/
└── release.yml # GitHub Actions for automated releases
Purpose: Application entry point and CLI interface
Key Responsibilities:
- Parse command-line flags
- Load configuration from file or defaults
- Validate configuration
- Initialize logger and server
- Handle graceful shutdown (SIGINT, SIGTERM)
Important Functions:
main(): Entry point, orchestrates initializationloadConfiguration(): Loads config from file or returns defaultsvalidateConfig(): Validates all configuration optionsprintHelp(): Displays help message
Configuration Priority (highest to lowest):
- Command-line flags
-configfile pathKORYX_CONFIGenvironment variable/app/config.json(container default)- Built-in defaults
Purpose: Configuration data structures and persistence
Key Structures:
Config
├── Server (ServerConfig) # Host, port, root directory, timeouts
├── Security (SecurityConfig) # HTTPS, auth, CORS, rate limit, IP filtering
├── Performance (PerformanceConfig) # Compression, cache, ETags, headers
├── Logging (LoggingConfig) # Level, output, colors
└── Features (FeaturesConfig) # Directory listing, SPA mode, error pagesKey Functions:
DefaultConfig(): Returns sensible defaultsLoadConfig(): Loads from JSON fileSaveConfig(): Saves to JSON fileGetReadTimeout(),GetWriteTimeout(): Convert to time.Duration
Design Decisions:
- Uses pointer types for optional configs (BasicAuth, CORS, RateLimit)
- Falls back to defaults if file doesn't exist
- Supports partial configuration (merges with defaults)
Purpose: HTTP server setup and file serving logic
Key Components:
Server struct:
type Server struct {
config *Config
logger *Logger
mux *http.ServeMux
}Key Functions:
NewServer(): Creates server instanceNewHandler(): Returns a reusablehttp.Handlerfor embedding in other Go servicesHandler(): Builds the middleware + routing stack without starting a dedicated listenerStart(): Starts HTTP/HTTPS serversetupHandlers(): Configures middleware chaincreateFileHandler(): Main file serving logicserveDirectory(): Directory handling (index files, listing)serveFile(): File serving with ETag supportserveSPAIndex(): SPA mode handlingserveDirectoryListing(): HTML directory listingserveError(): Custom error pages
File Serving Logic:
- Clean and validate path
- Check if file/directory exists
- If directory: try index files → directory listing → 403
- If file: serve with ETags and caching
- If not found: SPA mode or 404
Directory Listing:
- Beautiful HTML template
- Sorts directories first, then files
- Shows file size and modification time
- Filters hidden files if configured
- Mobile-responsive design
Purpose: HTTP middleware for security and performance
Available Middlewares:
- LoggingMiddleware: Request/response logging
- SecurityHeadersMiddleware: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection
- BlockHiddenFilesMiddleware: Blocks access to files starting with "."
- PathTraversalMiddleware: Prevents ".." in paths
- BasicAuthMiddleware: HTTP Basic Authentication with constant-time comparison
- CORSMiddleware: Cross-Origin Resource Sharing
- RateLimitMiddleware: Token bucket rate limiting per IP
- IPFilterMiddleware: IP whitelist/blacklist
- CompressionMiddleware: Gzip compression
- CustomHeadersMiddleware: User-defined headers
- CacheMiddleware: Cache-Control headers
Middleware Chain Pattern:
type Middleware func(http.Handler) http.Handler
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
h = middlewares[i](h)
}
return h
}Rate Limiter Implementation:
- Token bucket algorithm
- Per-IP tracking
- Configurable requests/minute and burst size
- Automatic cleanup of old entries
- Thread-safe with mutex
Purpose: Logging system with colors and levels
Features:
- Color-coded console output (can be disabled)
- Multiple log levels: DEBUG, INFO, WARN, ERROR
- Separate access and error logs
- File output support (with multi-writer)
- Request logging with timing
- Beautiful startup banner
Log Levels:
- DEBUG: Development/troubleshooting
- INFO: Normal operations
- WARN: Warnings
- ERROR: Errors only
Access Log Format:
[timestamp] METHOD PATH - STATUS - DURATION - REMOTE_ADDR
Colors:
- Green: Success (2xx)
- Yellow: Client errors (4xx)
- Red: Server errors (5xx)
- Blue: Methods
- Cyan: Paths
- Gray: Timestamps/metadata
Pros:
- Compiles to single binary (easy distribution)
- Excellent standard library for HTTP
- Fast compilation and execution
- Great concurrency support
- Cross-platform by default
Cons Considered:
- Larger binary size than C/Rust (~12MB)
- Garbage collector (minimal impact for this use case)
Decision: Go's benefits far outweigh the cons for this use case.
Considered: YAML, TOML, JSON
Chosen: JSON
Reasons:
- No external dependencies (encoding/json is stdlib)
- Universal format
- Good tooling support
- Simple to parse and generate
Pattern: Function wrapping
Reasons:
- Composable
- Order-independent definition (chain determines order)
- Easy to add/remove middleware
- Standard Go idiom
Algorithm: Token bucket (not sliding window, not fixed window)
Reasons:
- Allows bursts
- Simple implementation
- Fair resource distribution
- Memory efficient
Decision: Custom logger
Reasons:
- No external dependencies
- Full control over format
- Color support built-in
- Exactly what we need, nothing more
6. Hidden File Protection
Implementation: Path component checking (not regex)
Reasons:
- Faster than regex
- More secure (can't be bypassed with encoding tricks)
- Clearer intent
Implementation: filepath.Clean + ".." detection
Reasons:
- Standard library handles OS-specific path separators
- Simple and effective
- Catches encoded attempts
Attack: ../../etc/passwd
Defense:
cleanPath := filepath.Clean(r.URL.Path)
if strings.Contains(cleanPath, "..") {
return 403
}Why it works:
filepath.Cleannormalizes paths- Explicit check for ".." catches any remaining attempts
2. Hidden Files Protection
Attack: /.env, /.git/config
Defense:
parts := strings.Split(filepath.Clean(r.URL.Path), "/")
for _, part := range parts {
if strings.HasPrefix(part, ".") && part != "." && part != ".." {
return 403
}
}Why it works:
- Checks each path component separately
- Can't be bypassed with encoding
Attack: Timing attacks to guess credentials
Defense:
usernameMatch := subtle.ConstantTimeCompare([]byte(username), []byte(config.Username)) == 1
passwordMatch := subtle.ConstantTimeCompare([]byte(password), []byte(config.Password)) == 1Why it works:
subtle.ConstantTimeCompareprevents timing attacks- Takes same time regardless of match position
Attack: DDoS, brute force
Defense: Token bucket per IP with configurable rate and burst
Why it works:
- Limits requests per time period
- Allows bursts for legitimate use
- Per-IP tracking prevents single attacker from consuming all resources
Attack: Cross-origin requests from unauthorized domains
Defense: Whitelist of allowed origins
Why it works:
- Only explicitly allowed origins can make requests
- Credentials only sent to trusted origins
Headers Set:
X-Content-Type-Options: nosniff- Prevents MIME sniffingX-Frame-Options: DENY- Prevents clickjackingX-XSS-Protection: 1; mode=block- XSS protection
Implementation: Wrapping writer with gzip
Benefit: 60-90% size reduction for text files
Configuration: Compression level 1-9 (default: 6)
Trade-offs:
- Level 1: Fastest, lower compression
- Level 9: Slowest, best compression
- Level 6: Good balance
Implementation: "modtime-size" hash
Benefit: Client can skip downloads if file unchanged
Flow:
- Generate ETag from file modtime and size
- Client sends
If-None-Matchheader - If matches: return 304 Not Modified
- If different: send full file
Implementation: Cache-Control: public, max-age=N
Benefit: Browsers cache files locally
Configuration: max-age in seconds (default: 3600)
Purpose: Prevent resource exhaustion from slow clients
Configuration:
- Read timeout: 30s (default)
- Write timeout: 30s (default)
Techniques:
- Streaming file serving (not loaded into memory)
- Gzip uses buffered writer
- Rate limiter cleanup goroutine
- No global state (thread-safe)
Command-line flags > Config file > Defaults
Philosophy: Secure by default, convenient for development
Defaults:
- Port: 8080
- Host: 0.0.0.0
- Directory listing: OFF (security)
- Hidden file blocking: ON (security)
- Compression: ON (performance)
- Logging: ON
- HTTPS: OFF (requires cert)
- Authentication: OFF (requires credentials)
Validations:
- Port range: 1-65535
- Root directory exists and is directory
- HTTPS requires cert and key files
- Basic auth requires username and password
- Compression level: 1-9
- Log level: debug, info, warn, error
The middleware chain order is carefully designed:
- Logging: First, to capture all requests
- Security Headers: Early security
- Custom Headers: User customization
- IP Filtering: Block bad IPs early
- Rate Limiting: Prevent abuse
- Authentication: Verify identity
- CORS: Cross-origin checks
- Path Traversal: Path security
- Hidden Files: File security
- Compression: Last, compress final output
- Cache: Last, set cache headers
Why This Order:
- Logging first captures everything
- Security checks before expensive operations
- Compression last to compress final output
- Cache last to set final headers
DEBUG < INFO < WARN < ERROR
Configuration:
level: "debug"- Shows everythinglevel: "info"- Normal operation (default)level: "warn"- Warnings and errorslevel: "error"- Errors only
Format:
[2025-10-28 14:30:15] GET /index.html - 200 - 15.2ms - 192.168.1.100
Information:
- Timestamp
- HTTP method
- Path
- Status code
- Response time
- Client IP
Format:
[2025-10-28 14:30:15] [ERROR] Failed to open file: no such file or directory
Implementation:
writer := io.MultiWriter(os.Stdout, file)Benefits:
- Logs to both console and file
- File preserved after restart
- Console for live monitoring
-
HTTP/2 Support
- Requires minimal changes (Go stdlib supports it)
- Better performance for modern browsers
-
Brotli Compression
- Better compression than gzip
- Need to add external library
-
WebDAV Support
- Upload/modify files
- Useful for remote file management
-
TLS Certificate Auto-Renewal
- Let's Encrypt integration
- Automatic HTTPS
-
Metrics/Prometheus
- Request counts
- Response times
- Active connections
-
Graceful Reload
- Reload config without downtime
- Signal-based (SIGHUP)
-
Virtual Hosts
- Multiple sites on one server
- Different configs per domain
-
Access Control Lists (ACL)
- Fine-grained path permissions
- User/group based access
-
Request/Response Modification
- Rewrite rules
- Header modification
- Redirect rules
-
Bandwidth Limiting
- Global bandwidth cap
- Per-IP bandwidth limiting
None currently. Code is clean and well-structured.
- Connection Pooling: Already handled by Go's stdlib
- HTTP Caching: Add Last-Modified support
- Static Compression: Pre-compress common files
- Memory Mapping: For very large files
- Go 1.21 or higher
- Make (optional, for convenience)
- Git
# Development build
go build ./cmd/koryx-serv
# Production build (optimized)
go build -ldflags="-s -w" ./cmd/koryx-serv
# With version
go build -ldflags="-s -w -X main.version=v1.0.0" ./cmd/koryx-serv
# Using Make
make build# Run tests
go test -v ./...
# Run with coverage
go test -cover ./...
# Generate coverage HTML
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out- Use
gofmtfor formatting - Run
go vetfor checks - Consider
golangci-lintfor comprehensive linting
go fmt ./...
go vet ./...
golangci-lint run- Update config.go: Add configuration options
- Update middleware.go or server.go: Implement feature
- Update cmd/koryx-serv/main.go: Add CLI flags if needed
- Update config.example.json: Document new options
- Update README.md: Document usage
- Update CONTEXT.md: Document architecture changes
# Tag version
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
# GitHub Actions will automatically:
# - Build for all platforms
# - Create release archives
# - Upload to GitHub Releases# Using Make
make build-all
make release-local
# Manual
GOOS=linux GOARCH=amd64 go build -o koryx-serv-linux-amd64 ./cmd/koryx-serv
GOOS=darwin GOARCH=arm64 go build -o koryx-serv-darwin-arm64 ./cmd/koryx-serv
GOOS=windows GOARCH=amd64 go build -o koryx-serv-windows-amd64.exe ./cmd/koryx-servEnable debug logging:
{
"logging": {
"level": "debug"
}
}Verbose request logging:
- All middleware steps logged
- File access attempts
- Configuration loading
Common Issues:
- Port already in use: Change port or kill existing process
- Permission denied: Run with sudo (ports < 1024) or use higher port
- File not found: Check root_dir is correct
- HTTPS cert errors: Verify cert and key files exist
- Basic file serving
- Directory listing
- Index file serving
- SPA mode
- 404 pages
- Custom error pages
- Basic authentication
- CORS headers
- Rate limiting (use
aborhey) - IP filtering
- Gzip compression (check headers)
- ETag support (conditional requests)
- HTTPS with self-signed cert
- Hidden file blocking
- Path traversal blocking
- Logging to file
- Configuration file loading
- CLI flag overrides
Create server_test.go with:
- Unit tests for each middleware
- Integration tests for file serving
- Security tests for attack vectors
- Performance benchmarks
Create /etc/systemd/system/koryx-serv.service:
[Unit]
Description=koryx-serv Static File Server
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www
ExecStart=/usr/local/bin/koryx-serv -config /etc/koryx-serv/config.json
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl enable koryx-serv
sudo systemctl start koryx-serv
sudo systemctl status koryx-servSee README.md for Dockerfile example.
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:8080;
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;
}
}- Run as non-root user
- Use HTTPS with valid certificates
- Enable rate limiting
- Set up IP whitelist if possible
- Block hidden files
- Use strong basic auth passwords
- Keep server updated
- Monitor logs for suspicious activity
- Request rate (req/s)
- Response times (p50, p95, p99)
- Error rate (4xx, 5xx)
- Bandwidth usage
- Active connections
- Memory usage
- CPU usage
# Count requests by status code
cat koryx-serv.log | grep -oP '\d{3}' | sort | uniq -c
# Top requested paths
cat koryx-serv.log | grep -oP 'GET \K[^ ]+' | sort | uniq -c | sort -rn | head -10
# Slow requests (>1s)
cat koryx-serv.log | awk -F' - ' '$3 > 1000 {print}'- Code follows Go conventions
- All exports have documentation comments
- Configuration is backwards compatible
- Security implications considered
- Performance impact minimal
- Tests added (when test suite exists)
- Documentation updated (README, CONTEXT)
- Example config updated if needed
<type>: <short description>
<detailed description>
<footer>
Types: feat, fix, docs, refactor, test, chore
Example:
feat: Add support for Brotli compression
Implements Brotli compression as an alternative to Gzip.
Can be enabled in config with "compression_type": "brotli".
Closes #42
MIT License - See LICENSE file
- Initial implementation by Claude Code
- Maintained by the community
Last Updated: 2025-10-28 Version: 1.0.0