Fiber
Email checker for Go Fiber. Email verification in Fiber HTTP handlers.
초고속 이메일 유효성 검사를 위해 Fiber 애플리케이션에 EmailVerify를 통합하세요. 이 가이드는 성능 최적화, WebSocket 지원 및 프로덕션 모니터링을 다룹니다.
설치
Fiber 프레임워크와 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
)설정
Fiber 앱 설정
성능 최적화를 적용하여 Fiber 애플리케이션을 설정하세요:
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{
// 성능 최적화
Prefork: true,
Network: fiber.NetworkTCP,
// 스트리밍 바디 사이즈
StreamRequestBody: true,
BodyLimit: 10 * 1024 * 1024, // 10 MB
// Keep-alive 비활성화
DisableKeepalive: false,
// 대소문자 구분 라우팅
CaseSensitive: true,
// 엄격한 라우팅
StrictRoute: false,
})
// 미들웨어
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
}환경 설정
환경에서 설정 로드:
package config
import (
"os"
"log"
)
func LoadConfig() *AppConfig {
apiKey := os.Getenv("EMAILVERIFY_API_KEY")
if apiKey == "" {
log.Fatal("EMAILVERIFY_API_KEY가 필요합니다")
}
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
}미들웨어
이메일 검증 미들웨어
요청 로깅 및 유효성 검사를 위한 커스텀 미들웨어 생성:
package middleware
import (
"github.com/gofiber/fiber/v2"
"log/slog"
"time"
)
func Logger() fiber.Handler {
return func(c *fiber.Ctx) error {
start := time.Now()
// 요청 처리
err := c.Next()
// 실행 시간 계산
stop := time.Now()
latency := stop.Sub(start)
// 요청 세부 정보 로깅
slog.Info("HTTP 요청",
"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,
})
}
}CORS 미들웨어
크로스 오리진 요청을 위한 CORS 설정:
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,
}))
}라우트
단일 이메일 검증 라우트
개별 이메일 검증을 위한 라우트 정의:
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": "잘못된 요청",
})
}
// 요청 유효성 검사
if err := validate.Struct(req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "유효성 검사 실패",
"details": err.Error(),
})
}
// 이메일 검증
result, cached, err := service.VerifyEmail(c.Context(), req.Email)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "검증 실패",
})
}
response := VerifyEmailResponse{
Email: req.Email,
Status: result.Status,
Score: result.Score,
Details: result.Details,
Cached: cached,
}
return c.JSON(response)
})
}대량 이메일 검증 라우트
배치 검증을 위한 라우트 생성:
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": "잘못된 요청",
})
}
// 요청 유효성 검사
if err := validate.Struct(req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "유효성 검사 실패",
})
}
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)
})
}유효성 검사
커스텀 검증기
커스텀 규칙으로 유효성 검사 확장:
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]
// 특정 회사 도메인만 허용
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)
}데이터베이스 통합
Fiber와 GORM
데이터베이스 영속성을 위한 GORM 통합:
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
}
// 성능을 위한 연결 풀링 활성화
sqlDB, err := db.DB()
if err != nil {
return nil, err
}
sqlDB.SetMaxOpenConns(25)
sqlDB.SetMaxIdleConns(5)
sqlDB.SetConnMaxLifetime(5 * time.Minute)
// 모델 자동 마이그레이션
if err := db.AutoMigrate(&VerificationResult{}); err != nil {
return nil, err
}
return db, nil
}캐싱
메모리와 Redis 캐싱
메모리와 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) {
// 먼저 메모리 캐시 확인
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
}
// Redis 확인
val, err := hc.redis.Get(ctx, key).Result()
if err == nil {
var data interface{}
json.Unmarshal([]byte(val), &data)
// 메모리 캐시 채우기
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 {
// 메모리 캐시에 저장
hc.memory.mu.Lock()
hc.memory.data[key] = CacheEntry{
Value: value,
ExpiresAt: time.Now().Add(hc.memory.ttl),
}
hc.memory.mu.Unlock()
// Redis에 저장
data, _ := json.Marshal(value)
return hc.redis.Set(ctx, key, data, ttl).Err()
}성능 최적화
연결 풀링 및 최적화
고처리량을 위한 최적화:
package config
import (
"github.com/gofiber/fiber/v2"
"net"
"time"
)
func OptimizePerformance(app *fiber.App) {
// 네트워크 설정 튜닝
app.Config.Network = fiber.NetworkTCP4
app.Config.Concurrency = 256 * 1024 // 최대 동시 연결
// TCP 튜닝
app.Listen(":3000", fiber.ListenConfig{
ListenerNetwork: "tcp",
SocketShutdown: true,
TcpKeepAlive: 30 * time.Second,
})
// 요청 바디 최적화
app.Config.BodyLimit = 50 * 1024 * 1024 // 50 MB
app.Config.ReadBufferSize = 16 * 1024 // 16 KB
app.Config.WriteBufferSize = 16 * 1024 // 16 KB
// Keep-alive 설정
app.Config.IdleTimeout = 75 * time.Second
app.Config.ReadTimeout = 15 * time.Second
app.Config.WriteTimeout = 15 * time.Second
}에러 처리
종합적인 에러 처리
일관된 에러 응답 정의:
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,
})
}테스트
httptest를 사용한 통합 테스트
라우트와 핸들러 테스트:
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: "유효한 이메일",
email: "test@example.com",
expectedStatus: fiber.StatusOK,
},
{
name: "잘못된 이메일 형식",
email: "invalid",
expectedStatus: fiber.StatusBadRequest,
},
{
name: "빈 이메일",
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))
// 엔드포인트 테스트
// assert.Equal(t, tt.expectedStatus, statusCode)
})
}
}WebSocket 지원
실시간 이메일 검증
실시간 검증을 위한 WebSocket 구현:
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")
// 이메일 검증
result, cached, err := service.VerifyEmail(c.Context(), email)
if err != nil {
c.WriteJSON(fiber.Map{
"error": err.Error(),
})
return
}
// WebSocket을 통해 결과 전송
c.WriteJSON(fiber.Map{
"email": email,
"status": result.Status,
"score": result.Score,
"cached": cached,
})
// 우아한 종료
c.Close()
}))
}모니터링
Prometheus 메트릭
프로덕션 관측성을 위한 Prometheus 모니터링 추가:
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: "이메일 검증 소요 시간(초)",
Buckets: []float64{0.001, 0.01, 0.1, 1.0},
},
[]string{"status"},
)
VerificationErrors = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "verification_errors_total",
Help: "총 검증 오류 수",
},
[]string{"error_type"},
)
CacheHits = promauto.NewCounter(
prometheus.CounterOpts{
Name: "cache_hits_total",
Help: "총 캐시 히트 수",
},
)
CacheMisses = promauto.NewCounter(
prometheus.CounterOpts{
Name: "cache_misses_total",
Help: "총 캐시 미스 수",
},
)
)프로덕션 배포
Prefork 모드
멀티 프로세스 성능을 위한 prefork 모드 활성화:
func main() {
app := fiber.New(fiber.Config{
Prefork: true,
Network: fiber.NetworkTCP,
})
// 라우트 설정
setupRoutes(app)
// 프로덕션용 TLS로 리스닝
app.ListenTLS(":443", "/path/to/cert.pem", "/path/to/key.pem", fiber.ListenConfig{
GracefulShutdown: true,
})
}Docker 설정
프로덕션용 Dockerfile:
# 빌드 스테이지
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 .
# 런타임 스테이지
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
풀 스택 설정:
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: