Skip to content

Instantly share code, notes, and snippets.

@alexey-sh
Last active May 10, 2025 18:37
Show Gist options
  • Save alexey-sh/a0c7f361f117e0c054a963fa47693869 to your computer and use it in GitHub Desktop.
Save alexey-sh/a0c7f361f117e0c054a963fa47693869 to your computer and use it in GitHub Desktop.
Go auth service
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gorilla/mux"
"github.com/redis/go-redis/v9"
)
// Config holds server configuration
type Config struct {
Port string
RedisAddr string
RedisPass string
ReadTimeout time.Duration
}
// Server holds dependencies
type Server struct {
config Config
redis *redis.Client
httpServer *http.Server
}
// NewServer initializes the server with dependencies
func NewServer(config Config) (*Server, error) {
// Initialize Redis client with enhanced options
rdb := redis.NewClient(&redis.Options{
Addr: config.RedisAddr,
Password: config.RedisPass,
DB: 0,
PoolSize: 100,
MinIdleConns: 10,
MaxRetries: 3,
DialTimeout: 5 * time.Second,
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
PoolTimeout: 4 * time.Second,
ConnMaxLifetime: 30 * time.Minute,
})
// Verify Redis connection
ctx := context.Background()
if err := rdb.Ping(ctx).Err(); err != nil {
return nil, fmt.Errorf("failed to connect to redis: %w", err)
}
// Initialize router
r := mux.NewRouter()
// Add middleware
r.Use(requestIDMiddleware)
r.Use(realIPMiddleware)
r.Use(recoverMiddleware)
r.Use(timeoutMiddleware(5 * time.Second))
// Initialize server
s := &Server{
config: config,
redis: rdb,
}
// Setup routes
r.HandleFunc("/auth", s.handleAuth).Methods("GET")
// Configure HTTP server
s.httpServer = &http.Server{
Addr: ":" + config.Port,
Handler: r,
ReadTimeout: config.ReadTimeout,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
// Start background health check
go s.monitorRedisHealth(ctx)
return s, nil
}
// requestIDMiddleware adds a unique request ID to each request
func requestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Use a simple UUID or timestamp-based ID for simplicity
reqID := fmt.Sprintf("%d", time.Now().UnixNano())
r.Header.Set("X-Request-ID", reqID)
w.Header().Set("X-Request-ID", reqID)
next.ServeHTTP(w, r)
})
}
// realIPMiddleware sets the real client IP based on X-Forwarded-For or RemoteAddr
func realIPMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if forwarded := r.Header.Get("X-Forwarded-For"); forwarded != "" {
r.RemoteAddr = forwarded
}
next.ServeHTTP(w, r)
})
}
// recoverMiddleware catches panics and returns a 500 error
func recoverMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic recovered: %v", err)
http.Error(w, "internal server error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
// timeoutMiddleware enforces a request timeout
func timeoutMiddleware(timeout time.Duration) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.TimeoutHandler(next, timeout, "request timed out")
}
}
// handleAuth processes GET /auth requests
func (s *Server) handleAuth(w http.ResponseWriter, r *http.Request) {
// Extract cookie
cookie, err := r.Cookie("sess")
if err != nil {
http.Error(w, "missing sess cookie", http.StatusUnauthorized)
return
}
// Extract header
bc := r.Header.Get("bc")
if bc == "" {
http.Error(w, "missing bc header", http.StatusUnauthorized)
return
}
// Create Redis key
key := cookie.Value + bc
// Query Redis with retry
ctx := r.Context()
var userID string
for attempt := 1; attempt <= 1; attempt++ {
userID, err = s.redis.HGet(ctx, key, "user_id").Result()
if err == nil {
break
}
if err == redis.Nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
log.Printf("Redis attempt %d failed: %v", attempt, err)
time.Sleep(time.Millisecond * 100 * time.Duration(attempt))
}
if err != nil {
log.Printf("Redis error after retries: %v", err)
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
// Set response header
w.Header().Set("user_id", userID)
w.WriteHeader(http.StatusOK)
}
// monitorRedisHealth periodically checks Redis connectivity
func (s *Server) monitorRedisHealth(ctx context.Context) {
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if err := s.redis.Ping(ctx).Err(); err != nil {
log.Printf("Redis health check failed: %v", err)
}
}
}
}
// Start runs the server
func (s *Server) Start() error {
log.Printf("Starting server on port %s", s.config.Port)
return s.httpServer.ListenAndServe()
}
// Shutdown gracefully stops the server
func (s *Server) Shutdown(ctx context.Context) error {
// Close Redis connection
if err := s.redis.Close(); err != nil {
log.Printf("Failed to close redis: %v", err)
}
// Shutdown HTTP server
return s.httpServer.Shutdown(ctx)
}
func main() {
// Load configuration
config := Config{
Port: getEnv("PORT", "8080"),
RedisAddr: getEnv("REDIS_ADDR", "localhost:6379"),
RedisPass: getEnv("REDIS_PASS", ""),
ReadTimeout: 5 * time.Second,
}
// Initialize server
server, err := NewServer(config)
if err != nil {
log.Fatalf("Failed to initialize server: %v", err)
}
// Handle graceful shutdown
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
go func() {
if err := server.Start(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server failed: %v", err)
}
}()
<-stop
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server shutdown failed: %v", err)
}
log.Println("Server stopped")
}
// getEnv retrieves environment variable with fallback
func getEnv(key, fallback string) string {
if value, exists := os.LookupEnv(key); exists {
return value
}
return fallback
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment