Class: Rubino::API::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/api/server.rb

Overview

Rack app entry point. Wires the middleware stack + router and runs it under Puma.

Reads RUBINO_API_KEY from the environment when no key is passed explicitly; start! refuses to boot without one so the bearer-auth middleware is never bypassed. The pure Rack app (no Puma) is exposed via .build_app for tests and embedding.

server = Rubino::API::Server.new(port: 4820)
server.start!

Constant Summary collapse

DEFAULT_PORT =
4820
DEFAULT_HOST =

Loopback by default (#69): the server speaks to a shell tool, so a routable bind is opt-in (–host 0.0.0.0 / RUBINO_API_HOST).

"127.0.0.1"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(port: DEFAULT_PORT, host: DEFAULT_HOST, api_key: nil, router: nil, logger: nil, tls_cert: nil, tls_key: nil) ⇒ Server

Returns a new instance of Server.

Parameters:

  • port (Integer) (defaults to: DEFAULT_PORT)

    TCP port (default 4820, or pass via constructor)

  • host (String) (defaults to: DEFAULT_HOST)

    bind address (default 127.0.0.1)

  • api_key (String, nil) (defaults to: nil)

    bearer token; falls back to ENV

  • tls_cert (String, nil) (defaults to: nil)

    path to a TLS cert PEM; when set (with tls_key) the listener serves HTTPS via ssl_bind instead of plain TCP

  • tls_key (String, nil) (defaults to: nil)

    path to the matching private-key PEM



32
33
34
35
36
37
38
39
40
41
# File 'lib/rubino/api/server.rb', line 32

def initialize(port: DEFAULT_PORT, host: DEFAULT_HOST, api_key: nil, router: nil, logger: nil,
               tls_cert: nil, tls_key: nil)
  @port = port
  @host = host
  @api_key = api_key || ENV.fetch("RUBINO_API_KEY", nil)
  @router = router || Router.new
  @logger = logger || Rubino.logger
  @tls_cert = tls_cert
  @tls_key = tls_key
end

Class Method Details

.bind_url(host:, port:, tls_cert: nil, tls_key: nil) ⇒ #call, String

Composes the Rack middleware stack around the router. Order matters: Observability is outermost (sees every status, including 500s from ErrorHandler), then ErrorHandler, then RateLimit (so /v1/health and /v1/metrics also get a per-IP ceiling before Auth waves them through), then JsonParser, then Auth closest to the router so unauthorized requests never reach operations.

Builds the Puma bind URL. When a TLS cert+key are configured it returns an ssl:// bind so Puma terminates TLS with the self-signed cert; the web client pins that cert (see Rubino::API::TLS). Otherwise it returns a plain tcp:// bind (local dev / fake stay HTTP).

Returns:

  • (#call)

    a Rack-compatible app

  • (String)

    a Puma bind URL (“tcp://…” or “ssl://…”)



83
84
85
86
87
88
# File 'lib/rubino/api/server.rb', line 83

def self.bind_url(host:, port:, tls_cert: nil, tls_key: nil)
  return "tcp://#{host}:#{port}" if tls_cert.nil? || tls_key.nil?

  query = URI.encode_www_form(cert: tls_cert, key: tls_key)
  "ssl://#{host}:#{port}?#{query}"
end

.build_app(router:, api_key:, logger: Rubino.logger) ⇒ Object



90
91
92
93
94
95
96
97
98
99
# File 'lib/rubino/api/server.rb', line 90

def self.build_app(router:, api_key:, logger: Rubino.logger)
  Rack::Builder.new do
    use Middleware::Observability, logger: logger
    use Middleware::ErrorHandler, logger: logger
    use Middleware::RateLimit
    use Middleware::JsonParser
    use Middleware::Auth, api_key: api_key
    run router
  end.to_app
end

Instance Method Details

#start!Object

Boots Puma and blocks. Fails fast if no API key is configured.

Raises:



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/rubino/api/server.rb', line 51

def start!
  if @api_key.nil? || @api_key.empty?
    raise ConfigurationError,
          "RUBINO_API_KEY must be set to start the API server"
  end

  app = self.class.build_app(router: @router, api_key: @api_key, logger: @logger)
  @logger.info(event: "api.server.starting", host: @host, port: @port, tls: tls?)

  bind_url = self.class.bind_url(host: @host, port: @port, tls_cert: @tls_cert, tls_key: @tls_key)
  config = Puma::Configuration.new do |c|
    c.bind(bind_url)
    c.app(app)
    c.quiet
  end
  Puma::Launcher.new(config).run
end

#tls?Boolean

Returns whether this server will serve over TLS.

Returns:

  • (Boolean)

    whether this server will serve over TLS



44
45
46
# File 'lib/rubino/api/server.rb', line 44

def tls?
  !@tls_cert.nil? && !@tls_key.nil?
end