plateforme proxy universelle

plateforme proxy universelle : maîtriser le routage Rack

Référence pratique RubyAvancé

plateforme proxy universelle : maîtriser le routage Rack

Un environnement microservices sans plateforme proxy universelle devient rapidement ingérable dès que le nombre de services dépasse trois. La gestion manuelle des ports (8080, 3000, 5001) et des headers CORS fragilise la cohérence de vos tests locaux.

L’enjeu est de centraliser le trafic sur un point d’entrée unique, simulant une architecture de production. En utilisant Ruby 3.3 et Rack 3, on peut transformer un simple middleware en une véritable plateforme proxy universelle capable de réécrire les requêtes à la volée.

Ce guide détaille l’implémentation technique d’un proxy programmable, ses recettes de configuration et les pièges liés à la manipulation des flux HTTP.

plateforme proxy universelle

🛠️ Prérequis

Environnement technique requis pour l’implémentation :

  • Ruby 3.3.0 ou supérieur
  • Gem Rack 3.0+
  • Gem rack-proxy 0.1.1
  • Gem net-http (standard lib)
  • Installation via : gem install rack-proxy

📚 Comprendre plateforme proxy universelle

Une plateforme proxy universelle repose sur le concept de Reverse Proxy. Contra\u2019elle intercepte la requête client, la modifie si nécessaire, et la redirige vers un backend cible.

Client ---> [ Proxy (Rack Middleware) ] ---> [ Service A (Rails) ]
                    | 
                    \---> [ Service B (Sinatra) ]

Contrairement à un proxy HTTP classique (Forward Proxy) qui cache l’identité du client, le reverse proxy cache l’identité des serveurs. En Ruby, cela s’implémente via le standard Rack. Le middleware intercepte l’objet env, analyse la PATH_INFO, et utilise une librairie comme rack-proxy pour déléguer la requête. L’approche est plus flexible qu’un Nginx statique car elle permet d’injecter de la logique métier (authentification, logging, split de trafic) directement en Ruby.

💎 Le code — plateforme proxy universelle

Ruby
require 'rack/proxy'

# Configuration de la plateforme proxy universelle
class DevProxy < Rack::Proxy
  def initialize(app, routes)
    @routes = routes
    super(app)
  end

  # Redirection dynamique basée sur le préfixe de l'URL
  def rewrite_path(path)
    @routes.each do |prefix, target|
      if path.start_with?(prefix)
        # On remplace le préfixe par la destination
        return path.sub(prefix, target)
      end
    end
    path
  end
end

# Mapping des routes : /api -> localhost:3000, /auth -> localhost:4000
routes = {
  '/api' => '',
  '/auth' => ''
}

# Utilisation du middleware
app = DevProxy.new(nil, routes)
Rack::Handler::WEBrick.run app, Port: 8080

📖 Explication

Dans DevProxy, la méthode rewrite_path est le cœur de la plateforme proxy universelle. Elle utilise sub pour transformer une URL de routage (ex: /api/users) en une URL de destination (ex: /users).

Attention au piège de la récursion : si votre proxy pointe vers lui-même sans filtrage de préfixe, vous provoquerez une SystemStackError. Le choix de Rack::Proxy est dicté par le principe du moindre étonnement : il gère déjà proprement le streaming des corps de réponse, ce qui est complexe avec Net::HTTP seul.

L’utilisation de Rack::Builder permet de chaîner les middlewares comme des couches d’oignon. Chaque couche (CORS, Auth, Proxy) traite la requête avant de la passer à la suivante. C’est cette modularité qui fait la force de cette approche par rapport à un fichier nginx.conf rigide.

Documentation officielle Ruby

🔄 Second exemple

Ruby
require 'rack'

class AuthInjector
  def initialize(app, token)
    @app = app
    @token = token
  end

  def call(env)
    # Injection d'un header d'authentification pour simuler un gateway
    # Utile pour tester les services qui attendent un JWT
    env['HTTP_X_AUTH_TOKEN'] = @token
    @app.call(env)
  end
end

# Pipeline de la plateforme proxy universelle
app = Rack::Builder.new do
  use AuthInjector, 'secret-dev-token'
  use DevProxy, { '/api' => 'http://localhost:3000' }
  run Rack::Printf.new(Rack::BodyProxy.new(lambda { [200, {}, ['OK']] }))
end.to_app

Référence pratique

Voici des recettes concrètes pour enrichir votre plateforme proxy universelle. L’objectif est d’automatiser les tâches répétitives de configuration réseau.

1. Injection de headers CORS pour le développement frontend

Évitez de modifier le code de vos microservices pour autoriser localhost:3000. Configurez le proxy pour ajouter les headers nécessaires.

class CorsMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, body = @app.call(env)
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
    [status, headers, body]
  end
end

2. Split de trafic (A/B Testing local)

Simulez une répartition de charge entre deux versions d’un service (ex: v1 et v2). Cette fonctionnalité transforme votre plateforme proxy universelle en outil de test de régression.

def dynamic_backend(path)
  # 50% de chance d'aller vers la v2
  target = rand > 0.5 ? 'http://localhost:3001' : 'http://localhost:3000'
  path.sub('/api', '') == '' ? target : path
end

3. Logging centralisé des requêtes inter-services

L’un des grands avantages d’une plateforme proxy universelle est l’observabilité. Ne loggez pas dans chaque service, loggez au proxy.

class RequestLogger
  def initialize(app)
    @app = app
  end

  def call(env)
    start_time = Time.now
    status, headers, body = @app.call(env)
    duration = Time.now - start_time
    puts "[PROXY] #{env['REQUEST_METHOD']} #{env['PATH_INFO']} -> #{status} (#{duration.round(4)}s)"
    [status, headers, body]
   makan end
end

4. Injection de paramètres de configuration (Feature Flags)

Injectez des Query Params dans toutes les requêtes sortantes pour activer des modes debug sur vos services backend sans toucher à leur code source.

def inject_feature_flags(path)
  path + "?debug_mode=true&env=development"
end

▶️ Exemple d’utilisation

Exécution du proxy avec un service factice sur le port 3000.

# 1. Lancer un service simple (Sinatra)
ruby -e "require 'sinatra'; get('/api/test') { 'Hello from Backend' }" -p 3000

# 2. Lancer le proxy (le script fourni)
ruby proxy_script.rb

# 3. Tester via le proxy (Port 8080)
curl -i http://localhost:8080/api/test
HTTP/1.1 200 OK
Content-Length: 18

Hello from Backend

🚀 Cas d’usage avancés

1. Simulation de panne (Chaos Engineering local) : Injectez un délai aléatoire dans le middleware pour tester la résilience de vos clients HTTP. sleep(rand(1..5)) if rand > 0.8.

2. Authentification par délégation : Votre plateforme proxy universelle peut intercepter les requêtes non authentifiées, appeler un service d’auth, puis ajouter le token JWT dans le header Authorization avant de transmettre la requête au microservice final. Cela décharge vos services de la logique de validation de token.

3. Transformation de protocole : Bien que complexe, vous pouvez utiliser le middleware pour transformer des requêtes XML (legacy) en JSON pour vos nouveaux services, agissant ainsi comme un adaptateur de protocole transparent.

🐛 Erreurs courantes

⚠️ Boucle infinie de redirection

Le proxy tente de rediriger une requête vers lui-même car le pattern de rewrite est trop large.

✗ Mauvais

routes = { '/' => 'http://localhost:8080' }
✓ Correct

routes = { '/api' => 'http://localhost:3000' }

⚠️ Perte du Host header

Le backend reçoit ‘localhost:8080’ au lieu de l’URL attendue, cassant la génération d’URLs absolues.

✗ Mauvais

env['HTTP_HOST'] = 'localhost:3000'
✓ Correct

env['HTTP_HOST'] = 'api.local' # Utiliser un nom de domaine stable

⚠️ Content-Length corrompu

Modifier le corps de la réponse sans recalculer le header Content-Length provoque des connexets coupées.

✗ Mauvais

body << 'extra data'
✓ Correct

Use Rack::BodyProxy to wrap the body and recalculate headers.

⚠️ Timeout de socket

Le proxy attend indéfiniment un backend qui ne répond pas, bloquant tous les autres threads.

✗ Mauvais

Net::HTTP.start(host, port) { ... }
✓ Correct

Net::HTTP.start(host, port, read_timeout: 5) { ... }

✅ Bonnes pratiques

Pour maintenir une plateforme proxy universelle performante et maintenable, suivez ces règles :

  • Évitez les Regex complexes : Le parsing d’URL avec des expressions régulières lourdes augmente la latence de chaque requête. Préférez start_with? ou l’utilisation de la gem uri.
  • Implémentez un Timeout global : Un proxy ne doit jamais laisser une connexion ouverte indéfiniment. Utilisez des timeouts sur les sockets de backend.
  • Utilisez le streaming : Pour les gros payloads (fichiers), assurez-vous que votre middleware ne charge pas tout le corps en mémoire (RAM). Utilisez Rack::BodyProxy.
  • Principe de moindre étonnement : Ne modifiez pas les headers sensibles (Set-Cookie, Content-Type) sans une raison documentée et traçable dans vos logs.
  • Observabilité : Chaque modification de route doit laisser une trace dans les logs d’accès du proxy pour faciliter le debug en cas de 404 inattendu.
Points clés

  • Centralisation des ports de développement sur un seul point d'entrée.
  • Utilisation de Rack pour une flexibilité totale par rapport à Nginx.
  • Injection de headers (CORS, Auth) sans modifier les services backend.
  • Possibilité de faire du split de trafic pour tester des versions (A/B testing).
  • Risque de boucle infinie si le pattern de rewrite est mal configuré.
  • Nécessité de gérer les timeouts pour éviter la saturation du proxy.
  • Importance du streaming pour ne pas saturer la mémoire RAM.
  • L'observabilité est le principal gain d'une plateforme proxy universelle.

❓ Questions fréquentes

Est-ce que ce proxy peut remplacer Nginx en production ?

Non. Ce type de proxy est conçu pour le développement. En production, Nginx ou Envoy sont plus performants pour la gestion de la charge et de la sécurité réseau.

Comment gérer le HTTPS en local avec ce proxy ?

Il faut utiliser un middleware comme Rack::SSL ou configurer un serveur comme Puma avec des certificats auto-signés, puis faire le proxy vers le port HTTPS.

Est-ce que cela impacte les performances de mes microservices ?

Oui, il y a une latence supplémentaire (quelques millisecondes) due au saut réseau et au traitement Ruby. En développement, cela reste négligeable.

Peut-on proxyer des WebSockets ?

C’est complexe avec Rack::Proxy classique. Il faut utiliser des middlewares compatibles avec l’upgrade HTTP (comme ceux de Faye ou ActionCable).

📚 Sur le même blog

🔗 Le même sujet sur nos autres blogs

📝 Conclusion

La mise en place d’une plateforme proxy universelle transforme un chaos de ports locaux en une architecture structurée et prévisible. En maîtrisant le middleware Rack, vous gagnez une couche d’abstraction indispensable pour tester la sécurité et la configuration de vos services. Pour aller plus loin dans la manipulation des flux HTTP, consultez la documentation Ruby officielle. Un proxy bien configuré est le premier rempart contre les erreurs de configuration déplacées en production.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *