Skip to content

HTTP Server Integration Examples

Comprehensive examples showing how to combine Blueprint's HTTP server components for common use cases including REST APIs, web applications, and microservices.

Example 1: REST API Server

Complete REST API with JWT authentication, rate limiting, and security headers.

package main

import (
    "context"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/oddbit-project/blueprint/provider/httpserver"
    "github.com/oddbit-project/blueprint/provider/httpserver/auth"
    "github.com/oddbit-project/blueprint/provider/httpserver/response"
    "github.com/oddbit-project/blueprint/provider/jwtprovider"
    "github.com/oddbit-project/blueprint/crypt/secure"
    "github.com/oddbit-project/blueprint/log"
)

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

func main() {
    logger := log.New("rest-api")

    // Server configuration
    config := httpserver.NewServerConfig()
    config.Port = 8080
    config.Debug = false

    // Create server
    server, err := httpserver.NewServer(config, logger)
    if err != nil {
        logger.Fatal(err, "failed to create server")
    }

    // Setup middleware and routes
    setupRESTAPIMiddleware(server, logger)
    setupRESTAPIRoutes(server)

    // Start with graceful shutdown
    startWithGracefulShutdown(server, logger)
}

func setupRESTAPIMiddleware(server *httpserver.Server, logger *log.Logger) {
    // 1. Security headers
    server.UseDefaultSecurityHeaders()

    // 2. Rate limiting - 100 requests per minute
    server.UseRateLimiting(100)

    // 3. Request ID middleware
    server.AddMiddleware(func(c *gin.Context) {
        requestID := generateRequestID()
        c.Set("request_id", requestID)
        c.Header("X-Request-ID", requestID)
        c.Next()
    })
}

func setupRESTAPIRoutes(server *httpserver.Server) {
    router := server.Route()

    // Health check endpoint
    router.GET("/health", func(c *gin.Context) {
        c.JSON(200, response.JSONResponse{
            Success: true,
            Data:    gin.H{"status": "healthy", "timestamp": time.Now()},
        })
    })

    // Authentication endpoint
    router.POST("/auth/login", loginHandler)

    // Protected API routes
    jwtProvider := setupJWTProvider()
    jwtAuth := auth.NewAuthJWT(jwtProvider)

    api := server.Group("/api/v1")
    api.Use(auth.AuthMiddleware(jwtAuth))
    {
        // User endpoints
        api.GET("/users", listUsersHandler)
        api.GET("/users/:id", getUserHandler)
        api.POST("/users", createUserHandler)
        api.PUT("/users/:id", updateUserHandler)
        api.DELETE("/users/:id", deleteUserHandler)

        // Profile endpoints
        api.GET("/profile", getProfileHandler)
        api.PUT("/profile", updateProfileHandler)
    }
}

func setupJWTProvider() jwtprovider.JWTParser {
    config := jwtprovider.NewJWTConfig()
    config.SigningAlgorithm = jwtprovider.HS256
    config.CfgSigningKey = &secure.DefaultCredentialConfig{
        Password: "your-jwt-secret-key-here",
    }
    config.Issuer = "rest-api"
    config.Audience = "api-users"
    config.ExpirationSeconds = 3600 // 1 hour

    provider, err := jwtprovider.NewProvider(config)
    if err != nil {
        panic(err)
    }

    return provider
}

// Authentication handler
func loginHandler(c *gin.Context) {
    var loginRequest struct {
        Email    string `json:"email" binding:"required,email"`
        Password string `json:"password" binding:"required"`
    }

    if err := c.ShouldBindJSON(&loginRequest); err != nil {
        response.ValidationError(c, err)
        return
    }

    // Validate credentials (implement your logic)
    user, err := authenticateUser(loginRequest.Email, loginRequest.Password)
    if err != nil {
        response.Http401(c)
        return
    }

    // Generate JWT token
    token, err := generateJWTToken(user)
    if err != nil {
        response.Http500(c, err)
        return
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data: gin.H{
            "token": token,
            "user":  user,
        },
    })
}

// User CRUD handlers
func listUsersHandler(c *gin.Context) {
    users := []User{
        {ID: 1, Name: "John Doe", Email: "john@example.com"},
        {ID: 2, Name: "Jane Smith", Email: "jane@example.com"},
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    users,
    })
}

func getUserHandler(c *gin.Context) {
    userID := c.Param("id")

    // Get user from database (implement your logic)
    user, err := getUserByID(userID)
    if err != nil {
        response.Http404(c)
        return
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    user,
    })
}

func createUserHandler(c *gin.Context) {
    var newUser User
    if err := c.ShouldBindJSON(&newUser); err != nil {
        response.ValidationError(c, err)
        return
    }

    // Create user in database (implement your logic)
    createdUser, err := createUser(newUser)
    if err != nil {
        response.Http500(c, err)
        return
    }

    c.JSON(201, response.JSONResponse{
        Success: true,
        Data:    createdUser,
    })
}

func updateUserHandler(c *gin.Context) {
    userID := c.Param("id")
    var updateData User

    if err := c.ShouldBindJSON(&updateData); err != nil {
        response.ValidationError(c, err)
        return
    }

    // Update user in database (implement your logic)
    updatedUser, err := updateUser(userID, updateData)
    if err != nil {
        response.Http500(c, err)
        return
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    updatedUser,
    })
}

func deleteUserHandler(c *gin.Context) {
    userID := c.Param("id")

    // Delete user from database (implement your logic)
    if err := deleteUser(userID); err != nil {
        response.Http500(c, err)
        return
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    gin.H{"deleted": true},
    })
}

func getProfileHandler(c *gin.Context) {
    // Get JWT claims from context
    claimsValue, exists := c.Get(auth.ContextJwtClaims)
    if !exists {
        response.Http401(c)
        return
    }

    claims, ok := claimsValue.(*jwtprovider.Claims)
    if !ok {
        response.Http401(c)
        return
    }

    // Get user profile
    user, err := getUserByID(claims.Subject)
    if err != nil {
        response.Http404(c)
        return
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    user,
    })
}

func updateProfileHandler(c *gin.Context) {
    // Implementation similar to updateUserHandler but using JWT claims for user ID
    // ... implementation here
}

// Helper functions (implement according to your needs)
func generateRequestID() string {
    // Generate unique request ID
    return "req-" + time.Now().Format("20060102150405")
}

func authenticateUser(email, password string) (*User, error) {
    // Implement user authentication logic
    return &User{ID: 1, Name: "Test User", Email: email}, nil
}

func generateJWTToken(user *User) (string, error) {
    // Implement JWT token generation
    return "jwt-token-here", nil
}

func getUserByID(id string) (*User, error) {
    // Implement user lookup
    return &User{ID: 1, Name: "Test User", Email: "test@example.com"}, nil
}

func createUser(user User) (*User, error) {
    // Implement user creation
    user.ID = 123
    return &user, nil
}

func updateUser(id string, user User) (*User, error) {
    // Implement user update
    return &user, nil
}

func deleteUser(id string) error {
    // Implement user deletion
    return nil
}

func startWithGracefulShutdown(server *httpserver.Server, logger *log.Logger) {
    // Start server in goroutine
    go func() {
        logger.Info("starting REST API server", "port", server.Config.Port)
        if err := server.Start(); err != nil {
            logger.Error(err, "server failed")
        }
    }()

    // Wait for interrupt signal
    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    logger.Info("shutting down server...")

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    if err := server.Shutdown(ctx); err != nil {
        logger.Error(err, "forced shutdown")
    }

    logger.Info("server stopped")
}

Example 2: Web Application with Sessions

Complete web application with session management, CSRF protection, and form handling.

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
    "github.com/oddbit-project/blueprint/provider/httpserver"
    "github.com/oddbit-project/blueprint/provider/httpserver/security"
    "github.com/oddbit-project/blueprint/provider/httpserver/session"
    "github.com/oddbit-project/blueprint/provider/kv"
    "github.com/oddbit-project/blueprint/log"
)

func main() {
    logger := log.New("web-app")

    // Server configuration
    config := httpserver.NewServerConfig()
    config.Port = 8080
    config.Debug = true // Enable for template development

    server, err := httpserver.NewServer(config, logger)
    if err != nil {
        logger.Fatal(err, "failed to create server")
    }

    // Setup web application
    setupWebAppMiddleware(server, logger)
    setupWebAppRoutes(server)

    // Load HTML templates
    server.Route().LoadHTMLGlob("templates/*")

    // Serve static files
    server.Route().Static("/static", "./static")

    logger.Info("starting web application", "port", config.Port)
    if err := server.Start(); err != nil {
        logger.Fatal(err, "server failed")
    }
}

func setupWebAppMiddleware(server *httpserver.Server, logger *log.Logger) {
    // 1. Security headers for web content
    securityConfig := security.DefaultSecurityConfig()
    securityConfig.CSP = "default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'nonce-{nonce}' 'unsafe-inline'"
    server.UseSecurityHeaders(securityConfig)

    // 2. Rate limiting
    server.UseRateLimiting(60)

    // 3. Session management
    backend := kv.NewMemoryKV()
    sessionConfig := session.NewConfig()
    sessionConfig.Secure = false // For development over HTTP
    sessionManager := server.UseSession(sessionConfig, backend, logger)

    // 4. CSRF protection
    server.UseCSRFProtection()
}

func setupWebAppRoutes(server *httpserver.Server) {
    router := server.Route()

    // Home page
    router.GET("/", homeHandler)

    // Authentication routes
    router.GET("/login", loginFormHandler)
    router.POST("/login", loginPostHandler)
    router.POST("/logout", logoutHandler)

    // User registration
    router.GET("/register", registerFormHandler)
    router.POST("/register", registerPostHandler)

    // Protected user area
    protected := server.Group("/dashboard")
    protected.Use(authRequiredMiddleware())
    {
        protected.GET("/", dashboardHandler)
        protected.GET("/profile", profileHandler)
        protected.POST("/profile", updateProfileHandler)
    }
}

// Page handlers
func homeHandler(c *gin.Context) {
    sess := session.Get(c)
    isLoggedIn := sess.Has("user_id")

    c.HTML(http.StatusOK, "home.html", gin.H{
        "title":      "Welcome",
        "loggedIn":   isLoggedIn,
        "user":       sess.Get("user_name"),
        "flashMsg":   getFlashMessage(c),
        "csrfToken":  security.GenerateCSRFToken(c),
    })
}

func loginFormHandler(c *gin.Context) {
    sess := session.Get(c)
    if sess.Has("user_id") {
        c.Redirect(http.StatusFound, "/dashboard")
        return
    }

    c.HTML(http.StatusOK, "login.html", gin.H{
        "title":     "Login",
        "csrfToken": security.GenerateCSRFToken(c),
        "flashMsg":  getFlashMessage(c),
    })
}

func loginPostHandler(c *gin.Context) {
    var loginForm struct {
        Email    string `form:"email" binding:"required,email"`
        Password string `form:"password" binding:"required"`
    }

    if err := c.ShouldBind(&loginForm); err != nil {
        setFlashMessage(c, "error", "Please provide valid email and password")
        c.Redirect(http.StatusFound, "/login")
        return
    }

    // Authenticate user (implement your logic)
    user, err := authenticateWebUser(loginForm.Email, loginForm.Password)
    if err != nil {
        setFlashMessage(c, "error", "Invalid email or password")
        c.Redirect(http.StatusFound, "/login")
        return
    }

    // Create session
    sess := session.Get(c)
    sess.Set("user_id", user.ID)
    sess.Set("user_name", user.Name)
    sess.Set("user_email", user.Email)

    // Regenerate session ID for security
    if manager, exists := c.Get("session_manager"); exists {
        if sessionManager, ok := manager.(*session.SessionManager); ok {
            sessionManager.Regenerate(c)
        }
    }

    setFlashMessage(c, "success", "Welcome back, "+user.Name+"!")
    c.Redirect(http.StatusFound, "/dashboard")
}

func logoutHandler(c *gin.Context) {
    // Clear session
    if manager, exists := c.Get("session_manager"); exists {
        if sessionManager, ok := manager.(*session.SessionManager); ok {
            sessionManager.Clear(c)
        }
    }

    setFlashMessage(c, "info", "You have been logged out")
    c.Redirect(http.StatusFound, "/")
}

func registerFormHandler(c *gin.Context) {
    c.HTML(http.StatusOK, "register.html", gin.H{
        "title":     "Register",
        "csrfToken": security.GenerateCSRFToken(c),
        "flashMsg":  getFlashMessage(c),
    })
}

func registerPostHandler(c *gin.Context) {
    var registerForm struct {
        Name            string `form:"name" binding:"required"`
        Email           string `form:"email" binding:"required,email"`
        Password        string `form:"password" binding:"required,min=6"`
        ConfirmPassword string `form:"confirm_password" binding:"required"`
    }

    if err := c.ShouldBind(&registerForm); err != nil {
        setFlashMessage(c, "error", "Please check your input")
        c.Redirect(http.StatusFound, "/register")
        return
    }

    if registerForm.Password != registerForm.ConfirmPassword {
        setFlashMessage(c, "error", "Passwords do not match")
        c.Redirect(http.StatusFound, "/register")
        return
    }

    // Create user (implement your logic)
    user, err := createWebUser(registerForm.Name, registerForm.Email, registerForm.Password)
    if err != nil {
        setFlashMessage(c, "error", "Registration failed: "+err.Error())
        c.Redirect(http.StatusFound, "/register")
        return
    }

    setFlashMessage(c, "success", "Registration successful! Please log in.")
    c.Redirect(http.StatusFound, "/login")
}

func dashboardHandler(c *gin.Context) {
    sess := session.Get(c)

    c.HTML(http.StatusOK, "dashboard.html", gin.H{
        "title":     "Dashboard",
        "user":      sess.Get("user_name"),
        "email":     sess.Get("user_email"),
        "flashMsg":  getFlashMessage(c),
        "csrfToken": security.GenerateCSRFToken(c),
    })
}

func profileHandler(c *gin.Context) {
    sess := session.Get(c)

    c.HTML(http.StatusOK, "profile.html", gin.H{
        "title":     "Profile",
        "user":      sess.Get("user_name"),
        "email":     sess.Get("user_email"),
        "flashMsg":  getFlashMessage(c),
        "csrfToken": security.GenerateCSRFToken(c),
    })
}

func updateProfileHandler(c *gin.Context) {
    var profileForm struct {
        Name  string `form:"name" binding:"required"`
        Email string `form:"email" binding:"required,email"`
    }

    if err := c.ShouldBind(&profileForm); err != nil {
        setFlashMessage(c, "error", "Invalid input")
        c.Redirect(http.StatusFound, "/dashboard/profile")
        return
    }

    sess := session.Get(c)
    userID := sess.GetInt("user_id")

    // Update user profile (implement your logic)
    err := updateWebUserProfile(userID, profileForm.Name, profileForm.Email)
    if err != nil {
        setFlashMessage(c, "error", "Update failed: "+err.Error())
        c.Redirect(http.StatusFound, "/dashboard/profile")
        return
    }

    // Update session data
    sess.Set("user_name", profileForm.Name)
    sess.Set("user_email", profileForm.Email)

    setFlashMessage(c, "success", "Profile updated successfully")
    c.Redirect(http.StatusFound, "/dashboard/profile")
}

// Middleware functions
func authRequiredMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        sess := session.Get(c)
        if !sess.Has("user_id") {
            setFlashMessage(c, "error", "Please log in to access this page")
            c.Redirect(http.StatusFound, "/login")
            c.Abort()
            return
        }
        c.Next()
    }
}

// Flash message helpers
func setFlashMessage(c *gin.Context, msgType, message string) {
    sess := session.Get(c)
    sess.FlashString(msgType + ":" + message)
}

func getFlashMessage(c *gin.Context) gin.H {
    sess := session.Get(c)
    if flashMsg, ok := sess.GetFlashString(); ok {
        parts := strings.SplitN(flashMsg, ":", 2)
        if len(parts) == 2 {
            return gin.H{
                "type":    parts[0],
                "message": parts[1],
            }
        }
    }
    return nil
}

// Helper functions (implement according to your needs)
func authenticateWebUser(email, password string) (*User, error) {
    // Implement web user authentication
    return &User{ID: 1, Name: "Web User", Email: email}, nil
}

func createWebUser(name, email, password string) (*User, error) {
    // Implement user creation
    return &User{ID: 123, Name: name, Email: email}, nil
}

func updateWebUserProfile(userID int, name, email string) error {
    // Implement profile update
    return nil
}

Example 3: Microservice with Health Checks

Lightweight microservice with health checks, metrics, and monitoring endpoints.

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/oddbit-project/blueprint/provider/httpserver"
    "github.com/oddbit-project/blueprint/provider/httpserver/auth"
    "github.com/oddbit-project/blueprint/provider/httpserver/response"
    "github.com/oddbit-project/blueprint/log"
)

type HealthStatus struct {
    Status      string            `json:"status"`
    Version     string            `json:"version"`
    Timestamp   string            `json:"timestamp"`
    Uptime      string            `json:"uptime"`
    Dependencies map[string]string `json:"dependencies"`
}

func main() {
    logger := log.New("microservice")

    // Minimal configuration for microservice
    config := httpserver.NewServerConfig()
    config.Port = 8080
    config.Debug = false

    server, err := httpserver.NewServer(config, logger)
    if err != nil {
        logger.Fatal(err, "failed to create server")
    }

    setupMicroserviceMiddleware(server, logger)
    setupMicroserviceRoutes(server)

    logger.Info("starting microservice", "port", config.Port)
    if err := server.Start(); err != nil {
        logger.Fatal(err, "server failed")
    }
}

func setupMicroserviceMiddleware(server *httpserver.Server, logger *log.Logger) {
    // Minimal middleware for microservice
    server.UseDefaultSecurityHeaders()
    server.UseRateLimiting(1000) // Higher limit for microservice

    // Service-to-service authentication
    tokenAuth := auth.NewAuthToken("X-Service-Token", "service-secret-key")

    // Only protect internal endpoints
    internal := server.Group("/internal")
    internal.Use(auth.AuthMiddleware(tokenAuth))
}

func setupMicroserviceRoutes(server *httpserver.Server) {
    router := server.Route()

    // Public health endpoints
    router.GET("/health", healthCheckHandler)
    router.GET("/health/ready", readinessHandler)
    router.GET("/health/live", livenessHandler)
    router.GET("/metrics", metricsHandler)

    // Business logic endpoints
    router.GET("/api/data", getDataHandler)
    router.POST("/api/process", processDataHandler)

    // Internal endpoints (protected)
    internal := server.Group("/internal")
    {
        internal.GET("/config", getConfigHandler)
        internal.POST("/refresh", refreshCacheHandler)
        internal.GET("/stats", getStatsHandler)
    }
}

func healthCheckHandler(c *gin.Context) {
    status := HealthStatus{
        Status:    "healthy",
        Version:   "1.0.0",
        Timestamp: time.Now().Format(time.RFC3339),
        Uptime:    getUptime(),
        Dependencies: map[string]string{
            "database": "healthy",
            "cache":    "healthy",
            "queue":    "healthy",
        },
    }

    c.JSON(200, status)
}

func readinessHandler(c *gin.Context) {
    // Check if service is ready to serve traffic
    if !isServiceReady() {
        c.JSON(503, gin.H{
            "status": "not ready",
            "reason": "dependencies not available",
        })
        return
    }

    c.JSON(200, gin.H{"status": "ready"})
}

func livenessHandler(c *gin.Context) {
    // Check if service is alive
    c.JSON(200, gin.H{
        "status": "alive",
        "timestamp": time.Now().Format(time.RFC3339),
    })
}

func metricsHandler(c *gin.Context) {
    // Return Prometheus-style metrics
    metrics := `
# HELP http_requests_total Total number of HTTP requests
# TYPE http_requests_total counter
http_requests_total{method="GET"} 1234
http_requests_total{method="POST"} 567

# HELP http_request_duration_seconds HTTP request latency
# TYPE http_request_duration_seconds histogram
http_request_duration_seconds_bucket{le="0.1"} 100
http_request_duration_seconds_bucket{le="0.5"} 200
http_request_duration_seconds_bucket{le="1.0"} 300
http_request_duration_seconds_bucket{le="+Inf"} 350
`

    c.String(200, metrics)
}

func getDataHandler(c *gin.Context) {
    // Business logic endpoint
    data := gin.H{
        "message": "Service is working",
        "data":    []string{"item1", "item2", "item3"},
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    data,
    })
}

func processDataHandler(c *gin.Context) {
    var request map[string]interface{}
    if err := c.ShouldBindJSON(&request); err != nil {
        response.ValidationError(c, err)
        return
    }

    // Process the data
    result := processBusinessLogic(request)

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    result,
    })
}

// Protected internal endpoints
func getConfigHandler(c *gin.Context) {
    config := gin.H{
        "database_url": "***hidden***",
        "cache_ttl":    3600,
        "worker_count": 10,
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    config,
    })
}

func refreshCacheHandler(c *gin.Context) {
    // Refresh internal caches
    err := refreshInternalCache()
    if err != nil {
        response.Http500(c, err)
        return
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    gin.H{"cache_refreshed": true},
    })
}

func getStatsHandler(c *gin.Context) {
    stats := gin.H{
        "requests_processed": 12345,
        "errors_count":      23,
        "average_latency":   "45ms",
        "memory_usage":      "128MB",
    }

    c.JSON(200, response.JSONResponse{
        Success: true,
        Data:    stats,
    })
}

// Helper functions
func getUptime() string {
    // Calculate service uptime
    return "2h 15m 30s"
}

func isServiceReady() bool {
    // Check dependencies
    return true
}

func processBusinessLogic(data map[string]interface{}) gin.H {
    // Implement business logic
    return gin.H{
        "processed": true,
        "result":    data,
    }
}

func refreshInternalCache() error {
    // Implement cache refresh
    return nil
}

Configuration Examples

Environment-based Configuration

func createServerFromEnv() (*httpserver.Server, error) {
    config := httpserver.NewServerConfig()

    // Read from environment variables
    if port := os.Getenv("SERVER_PORT"); port != "" {
        if p, err := strconv.Atoi(port); err == nil {
            config.Port = p
        }
    }

    config.Host = os.Getenv("SERVER_HOST")
    config.Debug = os.Getenv("DEBUG") == "true"

    // TLS configuration
    if certFile := os.Getenv("TLS_CERT_FILE"); certFile != "" {
        config.TLSCert = certFile
        config.TLSKey = os.Getenv("TLS_KEY_FILE")
        config.TLSEnable = true
    }

    // Options from environment
    config.Options[httpserver.OptAuthTokenSecret] = os.Getenv("API_SECRET")
    config.Options[httpserver.OptDefaultSecurityHeaders] = "true"

    logger := log.New("app")
    return httpserver.NewServer(config, logger)
}

Docker-ready Configuration

func createDockerServer() (*httpserver.Server, error) {
    config := httpserver.NewServerConfig()
    config.Host = "0.0.0.0" // Bind to all interfaces in container
    config.Port = 8080
    config.Debug = false

    // Production timeouts
    config.ReadTimeout = 30
    config.WriteTimeout = 30

    logger := log.New("docker-app")
    server, err := httpserver.NewServer(config, logger)
    if err != nil {
        return nil, err
    }

    // Production middleware
    server.UseDefaultSecurityHeaders()
    server.UseRateLimiting(100)

    return server, nil
}

These examples demonstrate complete, production-ready applications using Blueprint's HTTP server framework with all the integrated components working together seamlessly.