EmailVerify LogoEmailVerify

Fiber

Email checker for Go Fiber. Email verification in Fiber HTTP handlers.

Integre EmailVerify en sus aplicaciones Fiber para una validación de correo electrónico extremadamente rápida. Esta guía cubre optimización de rendimiento, soporte de WebSocket y monitoreo en producción.

Instalación

Instale el framework Fiber y las dependencias de EmailVerify.

go get github.com/gofiber/fiber/v2
go get github.com/emailverify/emailverify-go
go get github.com/go-playground/validator/v10
go get github.com/go-redis/redis/v8
go get gorm.io/gorm
go get github.com/prometheus/client_golang
require (
    github.com/gofiber/fiber/v2 v2.50.0
    github.com/emailverify/emailverify-go v0.1.0
    github.com/go-playground/validator/v10 v10.15.0
    github.com/go-redis/redis/v8 v8.11.5
    gorm.io/gorm v1.25.0
    github.com/prometheus/client_golang v1.17.0
)

Configuración

Configuración de la Aplicación Fiber

Configure su aplicación Fiber con optimizaciones de rendimiento:

package config

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
    "github.com/gofiber/fiber/v2/middleware/compress"
    "github.com/gofiber/fiber/v2/middleware/helmet"
)

type AppConfig struct {
    APIKey      string
    DatabaseURL string
    RedisURL    string
    Port        string
}

func NewApp(cfg *AppConfig) *fiber.App {
    app := fiber.New(fiber.Config{
        // Optimizaciones de rendimiento
        Prefork: true,
        Network: fiber.NetworkTCP,

        // Tamaño del cuerpo en streaming
        StreamRequestBody: true,
        BodyLimit:         10 * 1024 * 1024, // 10 MB

        // Deshabilitar keep-alive
        DisableKeepalive: false,

        // Enrutamiento sensible a mayúsculas
        CaseSensitive: true,

        // Enrutamiento estricto
        StrictRoute: false,
    })

    // Middleware
    app.Use(helmet.New())
    app.Use(compress.New(compress.Config{
        Level: compress.LevelBestSpeed,
    }))
    app.Use(cors.New(cors.Config{
        AllowOrigins: "*",
        AllowMethods: "GET,POST,PUT,DELETE,OPTIONS",
    }))

    return app
}

Configuración del Entorno

Cargar configuración desde variables de entorno:

package config

import (
    "os"
    "log"
)

func LoadConfig() *AppConfig {
    apiKey := os.Getenv("EMAILVERIFY_API_KEY")
    if apiKey == "" {
        log.Fatal("EMAILVERIFY_API_KEY is required")
    }

    return &AppConfig{
        APIKey:      apiKey,
        DatabaseURL: os.Getenv("DATABASE_URL"),
        RedisURL:    os.Getenv("REDIS_URL"),
        Port:        getEnv("PORT", "3000"),
    }
}

func getEnv(key, fallback string) string {
    if value := os.Getenv(key); value != "" {
        return value
    }
    return fallback
}

Middleware

Middleware de Verificación de Correo Electrónico

Cree middleware personalizado para registro de solicitudes y validación:

package middleware

import (
    "github.com/gofiber/fiber/v2"
    "log/slog"
    "time"
)

func Logger() fiber.Handler {
    return func(c *fiber.Ctx) error {
        start := time.Now()

        // Procesar solicitud
        err := c.Next()

        // Calcular tiempo de ejecución
        stop := time.Now()
        latency := stop.Sub(start)

        // Registrar detalles de la solicitud
        slog.Info("HTTP Request",
            "method", c.Method(),
            "path", c.Path(),
            "status", c.Response().StatusCode(),
            "latency_ms", latency.Milliseconds(),
        )

        return err
    }
}

func ErrorHandler() fiber.ErrorHandler {
    return func(c *fiber.Ctx, err error) error {
        code := fiber.StatusInternalServerError

        if e, ok := err.(*fiber.Error); ok {
            code = e.Code
        }

        return c.Status(code).JSON(fiber.Map{
            "error": err.Error(),
            "code":  code,
        })
    }
}

Middleware CORS

Configure CORS para solicitudes de origen cruzado:

package middleware

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
)

func SetupCORS(app *fiber.App) {
    app.Use(cors.New(cors.Config{
        AllowOrigins:     "https://example.com,https://api.example.com",
        AllowMethods:     "GET,POST,PUT,DELETE,OPTIONS",
        AllowHeaders:     "Content-Type,Authorization",
        ExposeHeaders:    "Content-Length,X-RateLimit-Limit",
        MaxAge:           3600,
        AllowCredentials: true,
    }))
}

Rutas

Ruta de Verificación de Correo Electrónico Individual

Defina una ruta para verificar correos electrónicos individuales:

package routes

import (
    "github.com/gofiber/fiber/v2"
    "emailverify-app/handlers"
    "emailverify-app/services"
)

type VerifyEmailRequest struct {
    Email string `json:"email" validate:"required,email"`
}

type VerifyEmailResponse struct {
    Email     string      `json:"email"`
    Status    string      `json:"status"`
    Score     float64     `json:"score"`
    Details   interface{} `json:"details"`
    Cached    bool        `json:"cached"`
}

func SetupVerifyRoutes(app *fiber.App, service *services.EmailVerificationService) {
    api := app.Group("/api")
    verify := api.Group("/verify")

    verify.Post("/email", func(c *fiber.Ctx) error {
        var req VerifyEmailRequest

        if err := c.BodyParser(&req); err != nil {
            return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
                "error": "Invalid request",
            })
        }

        // Validar solicitud
        if err := validate.Struct(req); err != nil {
            return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
                "error": "Validation failed",
                "details": err.Error(),
            })
        }

        // Verificar correo electrónico
        result, cached, err := service.VerifyEmail(c.Context(), req.Email)
        if err != nil {
            return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
                "error": "Verification failed",
            })
        }

        response := VerifyEmailResponse{
            Email:   req.Email,
            Status:  result.Status,
            Score:   result.Score,
            Details: result.Details,
            Cached:  cached,
        }

        return c.JSON(response)
    })
}

Ruta de Verificación Masiva de Correo Electrónico

Cree una ruta para verificación por lotes:

type BulkVerifyRequest struct {
    Emails []string `json:"emails" validate:"required,min=1,max=1000,dive,email"`
}

type BulkVerifyResponse struct {
    Total     int                    `json:"total"`
    Verified  int                    `json:"verified"`
    Failed    int                    `json:"failed"`
    Results   []VerifyEmailResponse  `json:"results"`
    Duration  string                 `json:"duration"`
}

func SetupBulkRoutes(app *fiber.App, service *services.EmailVerificationService) {
    api := app.Group("/api")
    verify := api.Group("/verify")

    verify.Post("/bulk", func(c *fiber.Ctx) error {
        var req BulkVerifyRequest
        start := time.Now()

        if err := c.BodyParser(&req); err != nil {
            return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
                "error": "Invalid request",
            })
        }

        // Validar solicitud
        if err := validate.Struct(req); err != nil {
            return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
                "error": "Validation failed",
            })
        }

        results := make([]VerifyEmailResponse, 0, len(req.Emails))
        failed := 0

        for _, email := range req.Emails {
            result, cached, err := service.VerifyEmail(c.Context(), email)
            if err != nil {
                failed++
                continue
            }

            results = append(results, VerifyEmailResponse{
                Email:   email,
                Status:  result.Status,
                Score:   result.Score,
                Details: result.Details,
                Cached:  cached,
            })
        }

        response := BulkVerifyResponse{
            Total:    len(req.Emails),
            Verified: len(results),
            Failed:   failed,
            Results:  results,
            Duration: time.Since(start).String(),
        }

        return c.JSON(response)
    })
}

Validación

Validadores Personalizados

Extienda la validación con reglas personalizadas:

package validators

import (
    "github.com/go-playground/validator/v10"
    "strings"
)

type CustomValidator struct {
    validator *validator.Validate
}

func NewValidator() *CustomValidator {
    v := validator.New()

    v.RegisterValidationFunc("not_disposable", func(fl validator.FieldLevel) bool {
        email := fl.Field().String()
        domain := strings.Split(email, "@")[1]

        blockedDomains := map[string]bool{
            "tempmail.com":      true,
            "10minutemail.com":  true,
            "guerrillamail.com": true,
            "mailinator.com":    true,
        }

        return !blockedDomains[domain]
    })

    v.RegisterValidationFunc("corporate_email", func(fl validator.FieldLevel) bool {
        email := fl.Field().String()
        domain := strings.Split(email, "@")[1]

        // Solo permitir dominios corporativos específicos
        allowedDomains := map[string]bool{
            "company.com": true,
        }

        return allowedDomains[domain]
    })

    return &CustomValidator{validator: v}
}

func (cv *CustomValidator) Validate(i interface{}) error {
    return cv.validator.Struct(i)
}

Integración con Base de Datos

GORM con Fiber

Integre GORM para persistencia de base de datos:

package database

import (
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "time"
)

type VerificationResult struct {
    ID            uint      `gorm:"primaryKey"`
    Email         string    `gorm:"uniqueIndex;not null;type:varchar(254)"`
    Status        string    `gorm:"not null;type:varchar(50)"`
    Score         float64
    Disposable    bool
    SmtpValid     bool
    FormatValid   bool
    EmailProvider string
    RiskLevel     string
    VerifiedAt    time.Time
    CachedAt      time.Time `gorm:"autoUpdateTime:milli"`
}

func NewDatabase(dsn string) (*gorm.DB, error) {
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        return nil, err
    }

    // Habilitar agrupación de conexiones para rendimiento
    sqlDB, err := db.DB()
    if err != nil {
        return nil, err
    }

    sqlDB.SetMaxOpenConns(25)
    sqlDB.SetMaxIdleConns(5)
    sqlDB.SetConnMaxLifetime(5 * time.Minute)

    // Migración automática de modelos
    if err := db.AutoMigrate(&VerificationResult{}); err != nil {
        return nil, err
    }

    return db, nil
}

Almacenamiento en Caché

Almacenamiento en Caché con Memoria y Redis

Implemente almacenamiento en caché multinivel con memoria y Redis:

package cache

import (
    "context"
    "encoding/json"
    "github.com/go-redis/redis/v8"
    "sync"
    "time"
)

type MemoryCache struct {
    data map[string]CacheEntry
    mu   sync.RWMutex
    ttl  time.Duration
}

type CacheEntry struct {
    Value     interface{}
    ExpiresAt time.Time
}

type HybridCache struct {
    memory *MemoryCache
    redis  *redis.Client
}

func NewHybridCache(redisURL string) (*HybridCache, error) {
    opts, _ := redis.ParseURL(redisURL)
    client := redis.NewClient(opts)

    return &HybridCache{
        memory: &MemoryCache{
            data: make(map[string]CacheEntry),
            ttl:  5 * time.Minute,
        },
        redis: client,
    }, nil
}

func (hc *HybridCache) Get(ctx context.Context, key string) (interface{}, error) {
    // Verificar caché de memoria primero
    hc.memory.mu.RLock()
    entry, exists := hc.memory.data[key]
    hc.memory.mu.RUnlock()

    if exists && time.Now().Before(entry.ExpiresAt) {
        return entry.Value, nil
    }

    // Verificar Redis
    val, err := hc.redis.Get(ctx, key).Result()
    if err == nil {
        var data interface{}
        json.Unmarshal([]byte(val), &data)

        // Poblar caché de memoria
        hc.memory.mu.Lock()
        hc.memory.data[key] = CacheEntry{
            Value:     data,
            ExpiresAt: time.Now().Add(hc.memory.ttl),
        }
        hc.memory.mu.Unlock()

        return data, nil
    }

    return nil, err
}

func (hc *HybridCache) Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error {
    // Establecer en caché de memoria
    hc.memory.mu.Lock()
    hc.memory.data[key] = CacheEntry{
        Value:     value,
        ExpiresAt: time.Now().Add(hc.memory.ttl),
    }
    hc.memory.mu.Unlock()

    // Establecer en Redis
    data, _ := json.Marshal(value)
    return hc.redis.Set(ctx, key, data, ttl).Err()
}

Optimización de Rendimiento

Agrupación de Conexiones y Optimización

Optimice para alto rendimiento:

package config

import (
    "github.com/gofiber/fiber/v2"
    "net"
    "time"
)

func OptimizePerformance(app *fiber.App) {
    // Ajuste de configuración de red
    app.Config.Network = fiber.NetworkTCP4
    app.Config.Concurrency = 256 * 1024 // Máximo de conexiones concurrentes

    // Ajuste de TCP
    app.Listen(":3000", fiber.ListenConfig{
        ListenerNetwork: "tcp",
        SocketShutdown:  true,
        TcpKeepAlive:    30 * time.Second,
    })

    // Optimización del cuerpo de la solicitud
    app.Config.BodyLimit = 50 * 1024 * 1024 // 50 MB
    app.Config.ReadBufferSize = 16 * 1024   // 16 KB
    app.Config.WriteBufferSize = 16 * 1024  // 16 KB

    // Configuración de keep-alive
    app.Config.IdleTimeout = 75 * time.Second
    app.Config.ReadTimeout = 15 * time.Second
    app.Config.WriteTimeout = 15 * time.Second
}

Manejo de Errores

Manejo Completo de Errores

Defina respuestas de error consistentes:

package errors

import (
    "github.com/gofiber/fiber/v2"
)

type APIError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Details string `json:"details,omitempty"`
}

func BadRequest(c *fiber.Ctx, message string) error {
    return c.Status(fiber.StatusBadRequest).JSON(APIError{
        Code:    fiber.StatusBadRequest,
        Message: message,
    })
}

func NotFound(c *fiber.Ctx, message string) error {
    return c.Status(fiber.StatusNotFound).JSON(APIError{
        Code:    fiber.StatusNotFound,
        Message: message,
    })
}

func InternalError(c *fiber.Ctx, message string) error {
    return c.Status(fiber.StatusInternalServerError).JSON(APIError{
        Code:    fiber.StatusInternalServerError,
        Message: message,
    })
}

Pruebas

Pruebas de Integración con httptest

Pruebe sus rutas y controladores:

package handlers

import (
    "bytes"
    "encoding/json"
    "testing"
    "github.com/gofiber/fiber/v2"
    "github.com/stretchr/testify/assert"
)

func TestVerifyEmail(t *testing.T) {
    tests := []struct {
        name           string
        email          string
        expectedStatus int
    }{
        {
            name:           "Valid email",
            email:          "test@example.com",
            expectedStatus: fiber.StatusOK,
        },
        {
            name:           "Invalid email format",
            email:          "invalid",
            expectedStatus: fiber.StatusBadRequest,
        },
        {
            name:           "Empty email",
            email:          "",
            expectedStatus: fiber.StatusBadRequest,
        },
    }

    app := fiber.New()

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            body := VerifyEmailRequest{Email: tt.email}
            bodyBytes, _ := json.Marshal(body)

            req := fiber.AcquireRequest()
            req.Header.SetContentType(fiber.MIMEApplicationJSON)
            req.SetBodyStream(bytes.NewReader(bodyBytes), len(bodyBytes))

            // Probar endpoint
            // assert.Equal(t, tt.expectedStatus, statusCode)
        })
    }
}

Soporte de WebSocket

Verificación de Correo Electrónico en Tiempo Real

Implemente WebSocket para verificación en tiempo real:

package routes

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/websocket/v2"
    "emailverify-app/services"
)

func SetupWebSocketRoutes(app *fiber.App, service *services.EmailVerificationService) {
    ws := app.Group("/ws")

    ws.Get("/verify/:email", websocket.New(func(c *websocket.Conn) {
        email := c.Params("email")

        // Verificar correo electrónico
        result, cached, err := service.VerifyEmail(c.Context(), email)
        if err != nil {
            c.WriteJSON(fiber.Map{
                "error": err.Error(),
            })
            return
        }

        // Enviar resultado a través de WebSocket
        c.WriteJSON(fiber.Map{
            "email":  email,
            "status": result.Status,
            "score":  result.Score,
            "cached": cached,
        })

        // Cierre elegante
        c.Close()
    }))
}

Monitoreo

Métricas de Prometheus

Agregue monitoreo de Prometheus para observabilidad en producción:

package monitoring

import (
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

var (
    EmailVerifications = promauto.NewHistogramVec(
        prometheus.HistogramOpts{
            Name:    "email_verifications_duration_seconds",
            Help:    "Email verification duration in seconds",
            Buckets: []float64{0.001, 0.01, 0.1, 1.0},
        },
        []string{"status"},
    )

    VerificationErrors = promauto.NewCounterVec(
        prometheus.CounterOpts{
            Name: "verification_errors_total",
            Help: "Total verification errors",
        },
        []string{"error_type"},
    )

    CacheHits = promauto.NewCounter(
        prometheus.CounterOpts{
            Name: "cache_hits_total",
            Help: "Total cache hits",
        },
    )

    CacheMisses = promauto.NewCounter(
        prometheus.CounterOpts{
            Name: "cache_misses_total",
            Help: "Total cache misses",
        },
    )
)

Implementación en Producción

Modo Prefork

Habilite el modo prefork para rendimiento multiproceso:

func main() {
    app := fiber.New(fiber.Config{
        Prefork: true,
        Network: fiber.NetworkTCP,
    })

    // Configurar rutas
    setupRoutes(app)

    // Escuchar con TLS para producción
    app.ListenTLS(":443", "/path/to/cert.pem", "/path/to/key.pem", fiber.ListenConfig{
        GracefulShutdown: true,
    })
}

Configuración de Docker

Dockerfile listo para producción:

# Etapa de construcción
FROM golang:1.21-alpine AS builder

WORKDIR /build

COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o app .

# Etapa de ejecución
FROM alpine:latest

RUN apk add --no-cache ca-certificates

WORKDIR /app

COPY --from=builder /build/app .

EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD wget -q -O /dev/null http://localhost:3000/health || exit 1

CMD ["./app"]

docker-compose.yml

Configuración de pila completa:

version: '3.8'

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      EMAILVERIFY_API_KEY: ${EMAILVERIFY_API_KEY}
      DATABASE_URL: postgres://user:password@db:5432/emailverify
      REDIS_URL: redis://redis:6379/0
    depends_on:
      - db
      - redis

  db:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
      POSTGRES_DB: emailverify
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

volumes:
  postgres_data:
  redis_data:

Recursos Relacionados

On this page