💎 API Gateway with TuskLang and Ruby

Ruby Documentation

API Gateway with TuskLang and Ruby

🌐 Gateway to Your Digital Empire

TuskLang enables sophisticated API gateway functionality for Ruby applications, providing intelligent routing, authentication, rate limiting, and comprehensive API management. Build gateways that scale, secure, and optimize your API ecosystem.

🚀 Quick Start: Basic API Gateway

Gateway Configuration

config/api_gateway.tsk

[api_gateway] enabled: @env("API_GATEWAY_ENABLED", "true") port: @env("API_GATEWAY_PORT", "8080") host: @env("API_GATEWAY_HOST", "0.0.0.0") environment: @env("API_GATEWAY_ENVIRONMENT", "production")

[routing] default_timeout: @env("ROUTING_DEFAULT_TIMEOUT", "30") retry_attempts: @env("ROUTING_RETRY_ATTEMPTS", "3") circuit_breaker_enabled: @env("CIRCUIT_BREAKER_ENABLED", "true") load_balancing: @env("LOAD_BALANCING_STRATEGY", "round_robin")

[authentication] enabled: @env("AUTHENTICATION_ENABLED", "true") jwt_secret: @env.secure("JWT_SECRET") api_key_required: @env("API_KEY_REQUIRED", "true") oauth_enabled: @env("OAUTH_ENABLED", "true")

[rate_limiting] enabled: @env("RATE_LIMITING_ENABLED", "true") default_limit: @env("RATE_LIMIT_DEFAULT", "1000") window_size: @env("RATE_LIMIT_WINDOW", "3600") burst_limit: @env("RATE_LIMIT_BURST", "100")

[monitoring] metrics_enabled: @env("GATEWAY_METRICS_ENABLED", "true") logging_enabled: @env("GATEWAY_LOGGING_ENABLED", "true") tracing_enabled: @env("GATEWAY_TRACING_ENABLED", "true")

Core API Gateway Implementation

lib/api_gateway.rb

require 'sinatra' require 'tusk' require 'redis' require 'jwt' require 'json' require 'net/http' require 'uri'

class APIGateway < Sinatra::Base def initialize(config_path = 'config/api_gateway.tsk') @config = Tusk.load(config_path) @redis = Redis.new(url: @config['redis']['url']) @router = APIRouter.new(@config) @authenticator = APIAuthenticator.new(@config) @rate_limiter = RateLimiter.new(@config) @monitor = GatewayMonitor.new(@config) super() end

before do start_request_timer authenticate_request unless skip_authentication? check_rate_limit unless skip_rate_limiting? end

after do log_request record_metrics end

# Dynamic route handling get '/*' do handle_request('GET', params[:splat].first) end

post '/*' do handle_request('POST', params[:splat].first) end

put '/*' do handle_request('PUT', params[:splat].first) end

delete '/*' do handle_request('DELETE', params[:splat].first) end

patch '/*' do handle_request('PATCH', params[:splat].first) end

# Health check endpoint get '/health' do health_status = { status: 'healthy', timestamp: Time.now.iso8601, version: @config['api_gateway']['version'], uptime: get_uptime, services: check_service_health }

content_type :json health_status.to_json end

# Metrics endpoint get '/metrics' do content_type :text @monitor.get_metrics end

private

def handle_request(method, path) begin # Route the request route = @router.find_route(method, path) return route_not_found(path) unless route

# Forward the request response = forward_request(route, method, path) # Set response headers set_response_headers(response) # Return response status response[:status] response[:body] rescue => e handle_error(e) end end

def forward_request(route, method, path) target_url = build_target_url(route, path) headers = build_request_headers(route) body = request.body.read if request.body

@monitor.record_request_start(method, path, route[:service])

begin response = make_http_request(method, target_url, headers, body) @monitor.record_request_success(method, path, route[:service]) response rescue => e @monitor.record_request_error(method, path, route[:service], e) raise e end end

def make_http_request(method, url, headers, body = nil) uri = URI(url) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == 'https' http.read_timeout = @config['routing']['default_timeout'].to_i

request_class = case method.upcase when 'GET' then Net::HTTP::Get when 'POST' then Net::HTTP::Post when 'PUT' then Net::HTTP::Put when 'DELETE' then Net::HTTP::Delete when 'PATCH' then Net::HTTP::Patch else Net::HTTP::Get end

request = request_class.new(uri.request_uri) headers.each { |key, value| request[key] = value } request.body = body if body

response = http.request(request) { status: response.code.to_i, headers: response.to_hash, body: response.body } end

def authenticate_request return unless @config['authentication']['enabled'] == 'true'

auth_result = @authenticator.authenticate(request) unless auth_result[:authenticated] halt 401, { error: 'Authentication failed', details: auth_result[:reason] }.to_json end

# Store user context for downstream services request.env['user_context'] = auth_result[:user_context] end

def check_rate_limit return unless @config['rate_limiting']['enabled'] == 'true'

client_id = get_client_id rate_limit_result = @rate_limiter.check_limit(client_id, request.request_method, request.path)

unless rate_limit_result[:allowed] halt 429, { error: 'Rate limit exceeded', retry_after: rate_limit_result[:retry_after], limit: rate_limit_result[:limit], remaining: rate_limit_result[:remaining] }.to_json end

# Set rate limit headers response.headers['X-RateLimit-Limit'] = rate_limit_result[:limit].to_s response.headers['X-RateLimit-Remaining'] = rate_limit_result[:remaining].to_s response.headers['X-RateLimit-Reset'] = rate_limit_result[:reset_time].to_s end

def skip_authentication? # Skip authentication for health checks and metrics request.path == '/health' || request.path == '/metrics' end

def skip_rate_limiting? # Skip rate limiting for health checks and metrics request.path == '/health' || request.path == '/metrics' end

def route_not_found(path) status 404 { error: 'Route not found', path: path }.to_json end

def handle_error(error) @monitor.record_error(error) status 500 { error: 'Internal server error', message: error.message, timestamp: Time.now.iso8601 }.to_json end

def build_target_url(route, path) base_url = route[:target_url] path_suffix = path.gsub(route[:path_pattern], '') "#{base_url}#{path_suffix}" end

def build_request_headers(route) headers = {} # Forward relevant headers ['Authorization', 'Content-Type', 'Accept', 'User-Agent'].each do |header| headers[header] = request.env["HTTP_#{header.upcase.gsub('-', '_')}"] if request.env["HTTP_#{header.upcase.gsub('-', '_')}"] end

# Add user context if available if request.env['user_context'] headers['X-User-Context'] = request.env['user_context'].to_json end

# Add gateway headers headers['X-Gateway-Version'] = @config['api_gateway']['version'] headers['X-Request-ID'] = request.env['HTTP_X_REQUEST_ID'] || SecureRandom.uuid

headers end

def set_response_headers(response) response[:headers].each do |key, values| values.each { |value| response.headers[key] = value } end end

def get_client_id # Extract client ID from various sources request.env['HTTP_X_CLIENT_ID'] || request.env['HTTP_AUTHORIZATION']&.split(' ')&.last || request.ip end

def start_request_timer request.env['REQUEST_START_TIME'] = Time.now end

def log_request duration = Time.now - request.env['REQUEST_START_TIME'] log_entry = { timestamp: Time.now.iso8601, method: request.request_method, path: request.path, status: response.status, duration: duration, client_ip: request.ip, user_agent: request.user_agent }

Rails.logger.info "API Gateway: #{log_entry.to_json}" end

def record_metrics duration = Time.now - request.env['REQUEST_START_TIME'] @monitor.record_request_metrics(request.request_method, request.path, response.status, duration) end

def get_uptime @start_time ||= Time.now Time.now - @start_time end

def check_service_health # Implementation to check downstream service health { status: 'healthy' } end end

🛣️ API Router

Intelligent Request Routing

lib/api_router.rb

require 'tusk' require 'redis' require 'json'

class APIRouter def initialize(config) @config = config @redis = Redis.new(url: @config['redis']['url']) @routes = load_routes @circuit_breakers = {} end

def find_route(method, path) # Find matching route route = @routes.find do |r| r[:method] == method && path.match?(r[:path_pattern]) end

return nil unless route

# Check if service is healthy return nil unless service_healthy?(route[:service])

# Check circuit breaker return nil if circuit_breaker_open?(route[:service])

route end

def add_route(method, path_pattern, target_url, service, options = {}) route = { id: SecureRandom.uuid, method: method.upcase, path_pattern: Regexp.new(path_pattern), target_url: target_url, service: service, timeout: options[:timeout] || @config['routing']['default_timeout'].to_i, retry_attempts: options[:retry_attempts] || @config['routing']['retry_attempts'].to_i, circuit_breaker: options[:circuit_breaker] || @config['routing']['circuit_breaker_enabled'] == 'true', load_balancing: options[:load_balancing] || @config['routing']['load_balancing'], created_at: Time.now.iso8601 }

@routes << route store_route(route) route end

def remove_route(route_id) @routes.reject! { |r| r[:id] == route_id } @redis.hdel('api_routes', route_id) end

def update_route(route_id, updates) route = @routes.find { |r| r[:id] == route_id } return nil unless route

updates.each { |key, value| route[key] = value } store_route(route) route end

def get_routes @routes end

def get_route_stats stats = {} @routes.each do |route| stats[route[:service]] = { total_requests: get_service_request_count(route[:service]), error_rate: get_service_error_rate(route[:service]), average_response_time: get_service_avg_response_time(route[:service]), circuit_breaker_status: get_circuit_breaker_status(route[:service]) } end stats end

private

def load_routes routes_data = @redis.hgetall('api_routes') routes = []

routes_data.each do |route_id, route_json| route_data = JSON.parse(route_json) route_data['path_pattern'] = Regexp.new(route_data['path_pattern']) routes << route_data end

routes end

def store_route(route) route_data = route.dup route_data[:path_pattern] = route[:path_pattern].source @redis.hset('api_routes', route[:id], route_data.to_json) end

def service_healthy?(service_name) health_status = @redis.get("service_health:#{service_name}") health_status == 'healthy' end

def circuit_breaker_open?(service_name) return false unless @config['routing']['circuit_breaker_enabled'] == 'true'

circuit_breaker = get_circuit_breaker(service_name) circuit_breaker.open? end

def get_circuit_breaker(service_name) @circuit_breakers[service_name] ||= CircuitBreaker.new( failure_threshold: 5, timeout: 60, service_name: service_name ) end

def get_service_request_count(service_name) @redis.get("service_requests:#{service_name}").to_i end

def get_service_error_rate(service_name) total_requests = get_service_request_count(service_name) error_requests = @redis.get("service_errors:#{service_name}").to_i return 0 if total_requests == 0 (error_requests.to_f / total_requests * 100).round(2) end

def get_service_avg_response_time(service_name) response_times = @redis.lrange("service_response_times:#{service_name}", 0, 99) return 0 if response_times.empty?

total_time = response_times.map(&:to_f).sum (total_time / response_times.length).round(2) end

def get_circuit_breaker_status(service_name) circuit_breaker = get_circuit_breaker(service_name) circuit_breaker.status end end

class CircuitBreaker def initialize(failure_threshold: 5, timeout: 60, service_name: nil) @failure_threshold = failure_threshold @timeout = timeout @service_name = service_name @failure_count = 0 @last_failure_time = nil @state = :closed end

def call(&block) case @state when :open raise CircuitBreakerOpenError if Time.now - @last_failure_time < @timeout @state = :half_open end

result = yield on_success result rescue => e on_failure raise e end

def open? @state == :open end

def status @state end

private

def on_success @failure_count = 0 @state = :closed end

def on_failure @failure_count += 1 @last_failure_time = Time.now if @failure_count >= @failure_threshold @state = :open end end end

class CircuitBreakerOpenError < StandardError; end

🔐 API Authentication

Multi-Method Authentication

lib/api_authenticator.rb

require 'tusk' require 'jwt' require 'redis' require 'json'

class APIAuthenticator def initialize(config) @config = config @redis = Redis.new(url: @config['redis']['url']) end

def authenticate(request) # Try different authentication methods auth_methods = [ :authenticate_jwt, :authenticate_api_key, :authenticate_oauth, :authenticate_basic_auth ]

auth_methods.each do |method| result = send(method, request) return result if result[:authenticated] end

{ authenticated: false, reason: 'No valid authentication method found' } end

def authenticate_jwt(request) auth_header = request.env['HTTP_AUTHORIZATION'] return { authenticated: false, reason: 'No Authorization header' } unless auth_header

token = auth_header.gsub(/^Bearer\s/, '') return { authenticated: false, reason: 'Invalid token format' } if token.empty?

begin decoded = JWT.decode(token, @config['authentication']['jwt_secret'], true, { algorithm: 'HS256' }) payload = decoded[0]

# Check if token is expired if payload['exp'] && Time.now.to_i > payload['exp'] return { authenticated: false, reason: 'Token expired' } end

# Check if token is blacklisted if token_blacklisted?(token) return { authenticated: false, reason: 'Token blacklisted' } end

{ authenticated: true, user_context: { user_id: payload['user_id'], email: payload['email'], roles: payload['roles'] || [], permissions: payload['permissions'] || [] } } rescue JWT::DecodeError => e { authenticated: false, reason: "JWT decode error: #{e.message}" } end end

def authenticate_api_key(request) return { authenticated: false, reason: 'API key authentication disabled' } unless @config['authentication']['api_key_required'] == 'true'

api_key = extract_api_key(request) return { authenticated: false, reason: 'No API key provided' } unless api_key

# Validate API key api_key_data = get_api_key_data(api_key) return { authenticated: false, reason: 'Invalid API key' } unless api_key_data

# Check if API key is active return { authenticated: false, reason: 'API key inactive' } unless api_key_data['active']

# Check rate limits for API key return { authenticated: false, reason: 'API key rate limit exceeded' } if api_key_rate_limit_exceeded?(api_key)

{ authenticated: true, user_context: { api_key_id: api_key_data['id'], client_id: api_key_data['client_id'], permissions: api_key_data['permissions'] || [] } } end

def authenticate_oauth(request) return { authenticated: false, reason: 'OAuth authentication disabled' } unless @config['authentication']['oauth_enabled'] == 'true'

# Implementation for OAuth authentication { authenticated: false, reason: 'OAuth not implemented' } end

def authenticate_basic_auth(request) auth_header = request.env['HTTP_AUTHORIZATION'] return { authenticated: false, reason: 'No Authorization header' } unless auth_header

return { authenticated: false, reason: 'Invalid Basic auth format' } unless auth_header.start_with?('Basic ')

credentials = Base64.decode64(auth_header.gsub(/^Basic\s/, '')).split(':') return { authenticated: false, reason: 'Invalid credentials format' } if credentials.length != 2

username, password = credentials

# Validate credentials user = validate_basic_auth_credentials(username, password) return { authenticated: false, reason: 'Invalid credentials' } unless user

{ authenticated: true, user_context: { user_id: user['id'], username: user['username'], roles: user['roles'] || [] } } end

def authorize_request(user_context, resource, action) permissions = user_context[:permissions] || [] required_permission = "#{resource}:#{action}"

has_permission = permissions.include?(required_permission) || permissions.include?(':') || permissions.include?("#{resource}:*")

{ authorized: has_permission, reason: has_permission ? nil : "Insufficient permissions for #{required_permission}" } end

private

def extract_api_key(request) request.env['HTTP_X_API_KEY'] || request.env['HTTP_AUTHORIZATION']&.gsub(/^ApiKey\s/, '') || request.params['api_key'] end

def get_api_key_data(api_key) api_key_data = @redis.hget('api_keys', api_key) return nil unless api_key_data

JSON.parse(api_key_data) end

def token_blacklisted?(token) @redis.sismember('blacklisted_tokens', token) end

def api_key_rate_limit_exceeded?(api_key) # Implementation to check API key rate limits false end

def validate_basic_auth_credentials(username, password) # Implementation to validate basic auth credentials nil end end

🚦 Rate Limiting

Advanced Rate Limiting

lib/rate_limiter.rb

require 'tusk' require 'redis' require 'json'

class RateLimiter def initialize(config) @config = config @redis = Redis.new(url: @config['redis']['url']) end

def check_limit(client_id, method, path) # Get rate limit configuration for this endpoint limit_config = get_rate_limit_config(method, path) # Create rate limit key key = generate_rate_limit_key(client_id, method, path) # Check current usage current_usage = get_current_usage(key) limit = limit_config[:limit] window_size = limit_config[:window_size]

if current_usage >= limit { allowed: false, limit: limit, remaining: 0, retry_after: get_retry_after(key, window_size), reset_time: get_reset_time(key, window_size) } else # Increment usage increment_usage(key, window_size) { allowed: true, limit: limit, remaining: limit - current_usage - 1, retry_after: nil, reset_time: get_reset_time(key, window_size) } end end

def set_rate_limit(client_id, method, path, limit, window_size) limit_config = { client_id: client_id, method: method, path: path, limit: limit, window_size: window_size, created_at: Time.now.iso8601 }

key = "rate_limit_config:#{client_id}:#{method}:#{path}" @redis.setex(key, 86400, limit_config.to_json) # 24 hours limit_config end

def get_rate_limit_stats(client_id = nil) if client_id get_client_rate_limit_stats(client_id) else get_all_rate_limit_stats end end

def reset_rate_limit(client_id, method, path) key = generate_rate_limit_key(client_id, method, path) @redis.del(key) config_key = "rate_limit_config:#{client_id}:#{method}:#{path}" @redis.del(config_key) end

private

def get_rate_limit_config(method, path) # Try to get client-specific config first client_id = get_client_id config_key = "rate_limit_config:#{client_id}:#{method}:#{path}" config_data = @redis.get(config_key)

if config_data JSON.parse(config_data) else # Use default config { limit: @config['rate_limiting']['default_limit'].to_i, window_size: @config['rate_limiting']['window_size'].to_i, burst_limit: @config['rate_limiting']['burst_limit'].to_i } end end

def generate_rate_limit_key(client_id, method, path) window_start = (Time.now.to_i / 3600) * 3600 # Hourly windows "rate_limit:#{client_id}:#{method}:#{path}:#{window_start}" end

def get_current_usage(key) @redis.get(key).to_i end

def increment_usage(key, window_size) @redis.multi do |multi| multi.incr(key) multi.expire(key, window_size) end end

def get_retry_after(key, window_size) window_start = key.split(':').last.to_i window_end = window_start + window_size [window_end - Time.now.to_i, 0].max end

def get_reset_time(key, window_size) window_start = key.split(':').last.to_i window_start + window_size end

def get_client_id # Implementation to get client ID 'default' end

def get_client_rate_limit_stats(client_id) keys = @redis.keys("rate_limit:#{client_id}:*") stats = {}

keys.each do |key| parts = key.split(':') method = parts[2] path = parts[3] stats["#{method}:#{path}"] = { current_usage: get_current_usage(key), limit: get_rate_limit_config(method, path)[:limit] } end

stats end

def get_all_rate_limit_stats keys = @redis.keys("rate_limit:*") stats = {}

keys.each do |key| parts = key.split(':') client_id = parts[1] method = parts[2] path = parts[3] stats[client_id] ||= {} stats[client_id]["#{method}:#{path}"] = { current_usage: get_current_usage(key), limit: get_rate_limit_config(method, path)[:limit] } end

stats end end

📊 Gateway Monitoring

Comprehensive Monitoring

lib/gateway_monitor.rb

require 'prometheus/client' require 'tusk' require 'redis' require 'json'

class GatewayMonitor def initialize(config) @config = config @redis = Redis.new(url: @config['redis']['url']) setup_metrics end

def record_request_start(method, path, service) @request_counter.increment(labels: { method: method, path: path, service: service }) end

def record_request_success(method, path, service) @success_counter.increment(labels: { method: method, path: path, service: service }) end

def record_request_error(method, path, service, error) @error_counter.increment(labels: { method: method, path: path, service: service, error: error.class.name }) end

def record_request_metrics(method, path, status, duration) @request_duration.observe(duration, labels: { method: method, path: path, status: status.to_s }) @response_size.observe(get_response_size, labels: { method: method, path: path }) end

def record_error(error) @gateway_errors.increment(labels: { error_type: error.class.name }) end

def get_metrics Prometheus::Client::Formats::Text.marshal(@registry) end

def get_gateway_stats { total_requests: get_total_requests, success_rate: calculate_success_rate, average_response_time: calculate_average_response_time, error_rate: calculate_error_rate, active_connections: get_active_connections, top_endpoints: get_top_endpoints, service_health: get_service_health } end

private

def setup_metrics @registry = Prometheus::Client.registry

@request_counter = @registry.counter( :gateway_requests_total, docstring: 'Total number of gateway requests', labels: [:method, :path, :service] )

@success_counter = @registry.counter( :gateway_requests_success_total, docstring: 'Total number of successful gateway requests', labels: [:method, :path, :service] )

@error_counter = @registry.counter( :gateway_requests_error_total, docstring: 'Total number of failed gateway requests', labels: [:method, :path, :service, :error] )

@request_duration = @registry.histogram( :gateway_request_duration_seconds, docstring: 'Gateway request duration in seconds', labels: [:method, :path, :status] )

@response_size = @registry.histogram( :gateway_response_size_bytes, docstring: 'Gateway response size in bytes', labels: [:method, :path] )

@gateway_errors = @registry.counter( :gateway_errors_total, docstring: 'Total number of gateway errors', labels: [:error_type] ) end

def get_total_requests # Implementation to get total requests 0 end

def calculate_success_rate # Implementation to calculate success rate 0.0 end

def calculate_average_response_time # Implementation to calculate average response time 0.0 end

def calculate_error_rate # Implementation to calculate error rate 0.0 end

def get_active_connections # Implementation to get active connections 0 end

def get_top_endpoints # Implementation to get top endpoints [] end

def get_service_health # Implementation to get service health {} end

def get_response_size # Implementation to get response size 0 end end

🎯 Configuration Management

API Gateway Configuration

config/api_gateway_features.tsk

[api_gateway] enabled: @env("API_GATEWAY_ENABLED", "true") port: @env("API_GATEWAY_PORT", "8080") host: @env("API_GATEWAY_HOST", "0.0.0.0") environment: @env("API_GATEWAY_ENVIRONMENT", "production") version: @env("API_GATEWAY_VERSION", "1.0.0")

[routing] default_timeout: @env("ROUTING_DEFAULT_TIMEOUT", "30") retry_attempts: @env("ROUTING_RETRY_ATTEMPTS", "3") circuit_breaker_enabled: @env("CIRCUIT_BREAKER_ENABLED", "true") load_balancing: @env("LOAD_BALANCING_STRATEGY", "round_robin") service_discovery: @env("SERVICE_DISCOVERY_ENABLED", "true")

[authentication] enabled: @env("AUTHENTICATION_ENABLED", "true") jwt_secret: @env.secure("JWT_SECRET") api_key_required: @env("API_KEY_REQUIRED", "true") oauth_enabled: @env("OAUTH_ENABLED", "true") basic_auth_enabled: @env("BASIC_AUTH_ENABLED", "false")

[rate_limiting] enabled: @env("RATE_LIMITING_ENABLED", "true") default_limit: @env("RATE_LIMIT_DEFAULT", "1000") window_size: @env("RATE_LIMIT_WINDOW", "3600") burst_limit: @env("RATE_LIMIT_BURST", "100") per_client_limits: @env("PER_CLIENT_LIMITS_ENABLED", "true")

[monitoring] metrics_enabled: @env("GATEWAY_METRICS_ENABLED", "true") logging_enabled: @env("GATEWAY_LOGGING_ENABLED", "true") tracing_enabled: @env("GATEWAY_TRACING_ENABLED", "true") alerting_enabled: @env("GATEWAY_ALERTING_ENABLED", "true")

[security] cors_enabled: @env("CORS_ENABLED", "true") ssl_required: @env("SSL_REQUIRED", "true") request_validation: @env("REQUEST_VALIDATION_ENABLED", "true") response_validation: @env("RESPONSE_VALIDATION_ENABLED", "true")

🎯 Summary

This comprehensive guide covers API gateway implementation with TuskLang and Ruby, including:

- Core API Gateway: Dynamic routing, request forwarding, and response handling - API Router: Intelligent routing with circuit breakers and load balancing - Authentication: Multi-method authentication (JWT, API keys, OAuth, Basic Auth) - Rate Limiting: Advanced rate limiting with per-client and per-endpoint limits - Gateway Monitoring: Comprehensive metrics collection and monitoring - Configuration Management: Enterprise-grade API gateway configuration

The API gateway features with TuskLang provide a robust foundation for building scalable, secure, and monitored API gateways that can handle complex routing scenarios and provide comprehensive API management capabilities.