High-performance IP blocking system in Go with caching.
- Never commit or merge directly into
masterormainbranches. - For new features, fixes, or changes, create a new branch following the schema:
{feat,fix,doc,refactor,chore}/{name}- Example:
feat/add-dark-mode,fix/cache-leak,doc/update-readme
go run github.com/fzipp/gocyclo/cmd/gocyclo@latest -ignore "(?:.*_test.go|.test..go)" ./.. go run github.com/securego/gosec/v2/cmd/gosec@v2.23.0 ./... go run github.com/gordonklaus/ineffassign@latest ./... go run golang.org/x/tools/cmd/deadcode@latest ./... go run github.com/client9/misspell/cmd/misspell@latest -w . -j 200 go fmt ./... go vet ./...
IPGate/
├── config/
│ ├── blocklists/
│ │ └── blocklist.txt
│ ├── templates/
│ │ └── error.html
│ ├── whitelists/
│ │ └── whitelist.txt
│ ├── config.yaml
│ └── test_config.yaml
├── cmd/
│ └── ipgate/
│ ├── main.go
│ ├── main_test.go
│ └── router_integration_test.go
├── internal/
│ ├── blocklist/
│ │ ├── blocklist.go
│ │ ├── blocklist_test.go
│ │ └── watcher.go
│ ├── cache/
│ │ ├── cache.go
│ │ └── cache_test.go
│ ├── config/
│ │ ├── config.go
│ │ └── config_test.go
│ ├── handler/
│ │ ├── handlers.go
│ │ ├── debug_handlers.go
│ │ └── handler_test.go
│ ├── middleware/
│ │ ├── middleware.go
│ │ └── logging.go
│ ├── models/
│ │ ├── models.go
│ │ └── models_test.go
│ ├── routes/
│ │ ├── routes.go
│ │ └── routes_test.go
│ └── utils/
│ ├── utils.go
│ └── utils_test.go
├── docker-compose.debug.yml
├── docker-compose.yml
├── Dockerfile
├── Dockerfile.test
├── go.mod
├── go.sum
├── ipgate
├── README.md
internal/blocklist/: IP blocklist management (loading, validation, watching)internal/cache/: Caching system for blocked/allowed IPsinternal/config/: Configuration loading and managementinternal/handler/: HTTP request handlersinternal/middleware/: HTTP middleware functionsinternal/models/: Data structures and modelsinternal/routes/: Route definitions and compositioninternal/utils/: Utility functions and helpers
Purpose: HTTP middleware functions that process requests/responses
Rules:
- All middleware functions must return
func(http.Handler) http.Handler - Middleware should be stateless or use dependency injection
- Each middleware type should be in its own file:
logging.go- Request loggingauth.go- Authentication (if re-added)rate_limit.go- Rate limiting (if re-added)security.go- Security headers (if re-added)
Example:
// internal/middleware/logging.go
package middleware
import (
"net/http"
"time"
"github.com/dombyte/ipgate/internal/models"
)
func RequestLoggingMiddleware(deps *models.HandlerDeps) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Logging logic
next.ServeHTTP(w, r)
// Duration logging
})
}
}Purpose: HTTP request handlers that generate responses
Rules:
- Handlers should focus on request processing and response generation
- Business logic should be delegated to other packages
- Split handlers by function:
handlers.go- Main public API handlersdebug_handlers.go- Debug/admin endpointsauth_handlers.go- Authenticated endpoints (if re-added)
Example:
// internal/handler/handlers.go
package handler
import (
"net/http"
"github.com/dombyte/ipgate/internal/models"
)
func ErrorHandler(deps *models.HandlerDeps) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Handle IP blocking logic
})
}
func HealthHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Health check logic
}
}Purpose: Data structures and request/response models
Rules:
- All data structures should be defined here
- No business logic in models
- Split by type if needed:
response_models.go- Response structuresrequest_models.go- Request payloadserror_models.go- Error responses
Example:
// internal/models/models.go
package models
type ErrorPageData struct {
RequestID string
ClientIP string
StatusCode int
BlockReason string
}
type HandlerDeps struct {
Config *config.Config
Cache *cache.Cache
}Purpose: Reusable utility functions
Rules:
- Generic functions that don't belong in domain packages
- HTTP utilities, validation, file operations
- Split by domain:
http_utils.go- HTTP helpersip_utils.go- IP address utilitiesvalidation_utils.go- Validation functions
Example:
// internal/utils/http_utils.go
package utils
import (
"encoding/json"
"net/http"
)
func Encode(w http.ResponseWriter, status int, v any) error {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
return json.NewEncoder(w).Encode(v)
}- Single Responsibility Principle: Each package should have one clear purpose
- Dependency Direction: Dependencies should flow downward (routes → handlers → services → models)
- No Circular Dependencies: Avoid circular imports between packages
- Clear API Boundaries: Each package should expose a clean, minimal API
- Avoid Global State: Use dependency injection instead of global variables
- Testable Components: Design packages to be easily testable in isolation
- Don't mix middleware and handlers in the same package
- Don't put business logic in handler packages
- Don't create utility functions in domain packages
- Don't duplicate data structures across packages
When refactoring existing code:
- Create new packages before moving code
- Update imports in dependent packages
- Test thoroughly after each move
- Keep old files until new structure is verified
- Remove old files only after confirmation
- Config: YAML-based configuration
- Blocklist: Local/remote IP blocklist loading and validation
- Cache: In-memory cache for DENY (blocked) and ALLOW (not blocked or whitelisted) IPs with TTL and pruning
- Handler: HTTP endpoints for blocking and debugging
- Watcher: File watcher for automatic file reloading
- Models: Request/Response types and data structures (HandlerDeps, ErrorPageData)
- Routes: Centralized route management with middleware composition
- Utils: Generic utility functions for HTTP encoding/decoding
# Build
go build -o ipgate ./cmd/ipgate
# Test
go clean -testcache && go test ./... -cover -race
go fmt ./...before commits- Go conventions (camelCase, short functions)
- Comments for public functions
- Focused, testable functions
- Secure
- Performant
- Unit tests for all new code (
_test.go) -
90% coverage target
- Test edge cases (invalid IPs, empty lists, cache operations, whitelist changes, blacklist changes)
- Check for race conditions with Go’s race detector
- Test with fuzzing to uncover edge-case exploits
- Test for proper security implementation like describled in the Security Section
- Use Vet to examine suspicious constructs
- Docker for integration tests
- Validate all inputs (IPs, file paths)
- Proper file permissions
- Updated dependencies
- Harden against common go exploits and vulnerability
- Standard Go Project Structure: Follow the standard Go project layout with
cmd/,internal/, andpkg/directories - Separation of Concerns: Keep related functionality in separate packages (e.g.,
handler,models,routes,utils) - Dependency Injection: Use dependency injection pattern with a central
HandlerDepsstruct - Constructor Pattern: Implement
NewServer()or similar constructor functions for initializing components
- Formatting: Always run
go fmt ./...before committing - Naming: Use camelCase for variables and functions, PascalCase for types and exported functions
- Function Length: Keep functions short and focused (ideally < 20 lines)
- Error Handling: Return errors explicitly, don't panic
- Comments: Document public functions and types with Godoc comments
- Imports: Group imports by standard library, third-party, and project imports
- Return
http.Handler: Handler functions should returnhttp.Handlerinstead ofhttp.HandlerFuncfor better middleware compatibility - Dependency Injection: Use
HandlerDepsstruct to pass all dependencies to handlers - Middleware Pattern: Implement middleware as functions that return
func(http.Handler) http.Handler - Route Management: Centralize route definitions in
routes.go
- Test Files: Create
_test.gofiles for each package - Test Coverage: Aim for > 90% coverage
- Test Isolation: Avoid global state in tests; create fresh instances for each test
- Test Patterns:
- Use
httptest.NewRecorder()andhttp.NewRequest()for HTTP tests - Test both happy paths and error cases
- Test edge cases (empty inputs, invalid data, etc.)
- Use
t.Run()for sub-tests
- Use
- Dependency Injection: Pass dependencies as parameters rather than using global variables
- Middleware Composition: Chain middleware functions for flexible request processing
- Error Handling: Use consistent error response formats
- Configuration: Use YAML for configuration with proper struct tags
- Logging: Use structured logging with context information
This section documents common problems encountered by agents and their solutions. Always check here before spending time debugging issues that have been solved before.
Problem: Koanf fails to unmarshal complex nested structures like slices of objects
Symptoms:
- Boolean fields not being populated correctly (
debug_endpoint,watch_files_enabled) - File configurations returning empty arrays (
blacklist_files,whitelist_files) - Koanf store contains correct data but struct fields remain empty
- Debug output shows:
DEBUG: After unmarshal - BlacklistFiles: 0, WhitelistFiles: 0
Root Cause:
- Koanf uses
koanfas the default struct tag for unmarshaling - Configuration struct uses
yamltags instead ofkoanftags - Mismatch between struct tags and Koanf's default tag name
- Complex nested structures (slices of objects) require proper tag matching
Solution:
Use UnmarshalWithConf with explicit Tag: "yaml" parameter:
// Before (not working):
if err := c.k.Unmarshal("", cfg); err != nil {
return nil, err
}
// After (working):
if err := c.k.UnmarshalWithConf("", cfg, koanf.UnmarshalConf{Tag: "yaml"}); err != nil {
return nil, err
}Files Modified:
internal/config/api.go- Changed unmarshaling to use yaml tags
Verification:
- Check that file configurations are properly populated:
BlacklistFiles: 1, WhitelistFiles: 1 - Verify boolean fields are set correctly:
debug_endpoint: true - Confirm all configuration values are loaded from YAML file
Related Issues:
- Boolean field unmarshaling failures
- Empty slice unmarshaling for complex nested structures
- Struct tag mismatch between Koanf defaults and application conventions