Module: OKF::Server::Runner

Defined in:
lib/okf/server/runner.rb

Overview

Runs a Rack app under WEBrick — the handful of lines a rackup dependency would otherwise bring in. WEBrick ships with Ruby up to 2.7 and as the webrick gem from 3.0 on; both work here, so the gem's server mode keeps rack's own Ruby support range (>= 2.4).

The env it builds covers what a GET-serving Rack app needs (method, path, query, headers, rack.input); it is not a general CGI bridge. Part of the shell — it opens sockets.

Class Method Summary collapse

Class Method Details

.build(app, host:, port:) ⇒ Object

A configured-but-not-started WEBrick server, so callers (and tests) can pick the port (0 = ephemeral), start it on their own thread, and shut it down deterministically.



29
30
31
32
33
34
35
36
# File 'lib/okf/server/runner.rb', line 29

def build(app, host:, port:)
  server = WEBrick::HTTPServer.new(
    BindAddress: host, Port: port,
    Logger: WEBrick::Log.new(nil, WEBrick::BasicLog::WARN), AccessLog: []
  )
  server.mount_proc("/") { |request, response| handle(app, request, response) }
  server
end

.env_for(request) ⇒ Object



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

def env_for(request)
  env = {
    "REQUEST_METHOD" => request.request_method,
    "SCRIPT_NAME" => "",
    "PATH_INFO" => request.path,
    "QUERY_STRING" => request.query_string.to_s,
    "SERVER_NAME" => request.host.to_s,
    "SERVER_PORT" => request.port.to_s,
    "SERVER_PROTOCOL" => "HTTP/#{request.http_version}",
    "rack.url_scheme" => "http",
    "rack.input" => StringIO.new(read_body(request)),
    "rack.errors" => $stderr
  }
  request.each { |name, value| env["HTTP_#{name.upcase.tr("-", "_")}"] = value }
  # Rack reads the entity headers unprefixed.
  env["CONTENT_TYPE"] = env.delete("HTTP_CONTENT_TYPE") if env.key?("HTTP_CONTENT_TYPE")
  env["CONTENT_LENGTH"] = env.delete("HTTP_CONTENT_LENGTH") if env.key?("HTTP_CONTENT_LENGTH")
  env
end

.handle(app, request, response) ⇒ Object



38
39
40
41
42
43
44
45
46
47
# File 'lib/okf/server/runner.rb', line 38

def handle(app, request, response)
  status, headers, body = app.call(env_for(request))
  response.status = status.to_i
  headers.each { |name, value| response[name] = value }
  payload = String.new
  body.each { |chunk| payload << chunk } # a Rack body only guarantees #each
  response.body = payload
ensure
  body.close if body.respond_to?(:close)
end

.read_body(request) ⇒ Object

The request body as a string; bodyless requests (every GET this server sees) read as "".



71
72
73
74
75
# File 'lib/okf/server/runner.rb', line 71

def read_body(request)
  request.body.to_s
rescue WEBrick::HTTPStatus::Status, StandardError
  ""
end

.run(app, host:, port:) ⇒ Object

Serve app until interrupted (INT/TERM shut the server down).



20
21
22
23
24
# File 'lib/okf/server/runner.rb', line 20

def run(app, host:, port:)
  server = build(app, host: host, port: port)
  %w[INT TERM].each { |signal| trap(signal) { server.shutdown } }
  server.start
end