Skip to content

Rails Server Proxy

For Ruby idioms see ../integrations/ruby.md.

Routes

ruby
# config/routes.rb
get '/tiles/:z/:x/:y.pbf',
    to: 'tiles#show',
    constraints: { z: /\d+/, x: /\d+/, y: /\d+/ }

Controller

ruby
# app/controllers/tiles_controller.rb
require 'openssl'
require 'http'

class TilesController < ApplicationController
  KEY    = ENV.fetch('MFD_SRV_KEY')
  SECRET = ENV.fetch('MFD_SRV_SECRET')
  BASE   = 'https://tiles.mapsfordevs.com'

  def show
    z, x, y = params.values_at(:z, :x, :y)
    path    = "/tiles/#{z}/#{x}/#{y}.pbf"
    ts      = Time.now.to_i.to_s
    sig     = OpenSSL::HMAC.hexdigest('SHA256', SECRET, "GET\n#{path}\n#{ts}")

    upstream = HTTP.headers(
      'Authorization'    => "Bearer #{KEY}",
      'X-MFD-Timestamp'  => ts,
      'X-MFD-Signature'  => sig
    ).timeout(connect: 2, read: 10).get("#{BASE}#{path}")

    response.headers['Cache-Control'] = 'public, max-age=86400'
    send_data upstream.body.to_s,
              type: 'application/x-protobuf',
              status: upstream.status,
              disposition: 'inline'
  rescue HTTP::Error => e
    Rails.logger.error("[mfd] tile fetch failed: #{e.message}")
    head :bad_gateway
  end
end

Solid Cache (Rails 8) tile cache

ruby
class TilesController < ApplicationController
  def show
    cache_key = "mfd:tile:#{params[:z]}:#{params[:x]}:#{params[:y]}"
    body = Rails.cache.fetch(cache_key, expires_in: 1.day) do
      fetch_upstream
    end

    response.headers['Cache-Control'] = 'public, max-age=86400'
    send_data body, type: 'application/x-protobuf'
  end

  private

  def fetch_upstream
    # … same signing + HTTP.get logic
    upstream.body.to_s
  end
end

CORS (rack-cors)

ruby
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins ENV.fetch('FRONTEND_ORIGIN', 'https://yourapp.com')
    resource '/tiles/*', headers: :any, methods: [:get]
  end
end

Action Controller streaming (faster)

For very high throughput, stream upstream bytes directly instead of buffering:

ruby
class TilesController < ApplicationController
  include ActionController::Live

  def show
    response.headers['Content-Type']  = 'application/x-protobuf'
    response.headers['Cache-Control'] = 'public, max-age=86400'

    HTTP.headers(sign_headers).get("#{BASE}#{path}").body.each do |chunk|
      response.stream.write(chunk)
    end
  ensure
    response.stream.close
  end
end

Production deployment

  • Run behind nginx / Caddy that handles TLS + HTTP/2.
  • Use Puma with workers * threads ≥ expected concurrency.
  • Pin mfd_srv_* source IPs in our dashboard to your egress NAT.