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_golangrequire (
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: