ratalada 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6d020d5668bf545f9babae240d0aed21f6fea5edc6b1f157b95403b3f29ae1d9
4
- data.tar.gz: 706e4faeaa540a52e8d71de16a00332828ce81808b43d0bde4d9d84d785fa4be
3
+ metadata.gz: 1803a4b3f845951ed01752e1423b0c37c943b07ca1364f4b9d43e3c0a3add365
4
+ data.tar.gz: 104cf13b9244ecc47c975e3ea2a021db2869797bc7754fd2e745e016311780cc
5
5
  SHA512:
6
- metadata.gz: 651da70b2bbca675a448cf91f247c00ac9f74be3c2a871a9cd1978cc615444df0b685edd048cd8e28b16a3b2d1fb9750ffd707818ba57025e3fe07d7044b81c4
7
- data.tar.gz: 302f09422321099f66e839d8274251269e3b828cedb7edbc6bb1c1fa90fa6cfe6dc61ef1f64945477bd6cd3d90aa4396829be9bdd908d14c651eef7646ae99b4
6
+ metadata.gz: 5705db0d6d6e6a5145f5af7882fdb3c1cbd1abca7f4ac27bcfce1ecf576ef58ce35129bab5f4e4bd27a436796c53bfb49c3b87aee7ed0126f5ac5990db74727c
7
+ data.tar.gz: e42d339d4e674109a13276972d6513dddf8c2ce90c6c5e6d75f50ead33c4a9dc4932e5f9ab64e33c63882970138f0dc0d5df010726ac763427e23259e6d2a617
@@ -27,20 +27,26 @@ module Ratalada
27
27
  middleware = ::Falcon::Server.rack_middleware(app, cache: cache)
28
28
 
29
29
  environment = Async::Service::Environment.new(::Falcon::Environment::Server).with(
30
- name: "ratalada",
31
- url: "http://#{host}:#{port}",
32
- middleware: -> { middleware },
30
+ name: "ratalada",
31
+ url: "http://#{host}:#{port}",
32
+ middleware: -> { middleware },
33
33
  container_options: { count: count, restart: true },
34
34
  # async-service >= 0.25 reads `root` in Managed::Service#preload!
35
35
  # unconditionally (the default `preload` is `[]`, which is truthy),
36
36
  # so an environment without one raises NoMethodError on boot.
37
- root: Dir.pwd,
37
+ root: Dir.pwd,
38
38
  )
39
39
 
40
40
  configuration = Async::Service::Configuration.new
41
41
  configuration.add(environment)
42
42
 
43
- warn "ratalada: falcon listening on http://#{host}:#{port}#{" (#{count} workers)" if count > 1}"
43
+ if count > 1
44
+ workers = " (#{count} workers)"
45
+ else
46
+ workers = ""
47
+ end
48
+
49
+ warn "ratalada: falcon listening on http://#{host}:#{port}#{workers}"
44
50
  Async::Service::Controller.run(configuration, container_class: Async::Container.best_container_class)
45
51
  rescue Interrupt
46
52
  # clean shutdown
data/lib/ratalada/puma.rb CHANGED
@@ -9,7 +9,9 @@ module Ratalada
9
9
  module_function
10
10
 
11
11
  def run(app, host:, port:, count: 1)
12
- warn "ratalada: puma backend ignores count: (not yet implemented)" if count > 1
12
+ if count > 1
13
+ warn "ratalada: puma backend ignores count: (not yet implemented)"
14
+ end
13
15
  server = ::Puma::Server.new(app)
14
16
  server.add_tcp_listener(host, port)
15
17
  warn "ratalada: puma listening on http://#{host}:#{port}"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ratalada
4
- VERSION = "2.0.1"
4
+ VERSION = "2.2.0"
5
5
  end
data/lib/ratalada.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rack"
4
+ require "dry-configurable"
3
5
  require_relative "ratalada/version"
4
6
 
5
7
  module Ratalada
@@ -7,6 +9,8 @@ module Ratalada
7
9
  DEFAULT_PORT = Integer(ENV.fetch("PORT", "9292"))
8
10
  DEFAULT_COUNT = Integer(ENV.fetch("COUNT", "1"))
9
11
 
12
+ extend Dry::Configurable
13
+
10
14
  class Error < StandardError; end
11
15
 
12
16
  class NoBackendError < Error
@@ -35,18 +39,22 @@ module Ratalada
35
39
  end
36
40
  end
37
41
 
38
- # Wraps the Rack env with just enough sugar to pattern match on.
39
- class Request
40
- attr_reader :env
41
-
42
- def initialize(env)
43
- @env = env
44
- end
45
-
42
+ # Rack::Request plus just enough sugar to pattern match on, so everything
43
+ # rack already parses — params, cookies, headers, host, ip, session — comes
44
+ # along for free.
45
+ class Request < ::Rack::Request
46
+ # #path is PATH_INFO, not rack's own #path (SCRIPT_NAME + PATH_INFO):
47
+ # mounted under a `map`, routing here should match the path within this
48
+ # app, not the one the outer app was reached by.
46
49
  def verb = env["REQUEST_METHOD"]
47
50
  def path = env["PATH_INFO"]
48
51
  def query = env["QUERY_STRING"]
49
- def body = @body ||= env["rack.input"]&.read
52
+
53
+ # Rack's own #body is the rack.input stream; this is the whole of it as a
54
+ # String, memoized because rack 3 input reads once and does not rewind.
55
+ # To stream instead, chunk through #read and never touch #body.
56
+ def body = @body ||= read
57
+ def read(...) = env["rack.input"].read(...)
50
58
 
51
59
  # Enables `in ["GET", "/"]`
52
60
  def deconstruct = [verb, path]
@@ -62,6 +70,20 @@ module Ratalada
62
70
  # triplet. No match (nil, or a fall-through `case ... in`) means 404.
63
71
  module Routes
64
72
  def self.build(block)
73
+ # Rack::Utils' helpers (escape_html, parse_query, set_cookie_header,
74
+ # ...) are callable unqualified inside the block, with no include at the
75
+ # call site and without instance_exec'ing it — self and ivars in the
76
+ # block stay the caller's. The include goes on the singleton of the
77
+ # object the block was written in, so nothing else in the process sees
78
+ # it, and since Rack::Utils is module_function'd they arrive as private
79
+ # methods that a receiver's own escape/status_code still overrides.
80
+ # ArgumentError: a C-level proc, which has no binding (&:symbol, #curry).
81
+ # TypeError: a frozen receiver. Neither can take the include; skip it.
82
+ begin
83
+ block.binding.receiver.singleton_class.include(::Rack::Utils)
84
+ rescue ArgumentError, TypeError
85
+ nil
86
+ end
65
87
  App.new(block)
66
88
  end
67
89
 
@@ -72,10 +94,10 @@ module Ratalada
72
94
 
73
95
  def call(env)
74
96
  request = Request.new(env)
75
- handler = begin
76
- @router.call(request)
97
+ begin
98
+ handler = @router.call(request)
77
99
  rescue NoMatchingPatternError
78
- nil
100
+ handler = nil
79
101
  end
80
102
  respond(handler, request)
81
103
  end
@@ -114,8 +136,11 @@ module Ratalada
114
136
  # count runs that many worker processes accepting from a shared socket,
115
137
  # like node's cluster module. Each worker has its own state — anything
116
138
  # shared (sessions, caches) needs an external store or count: 1.
117
- def run(host: DEFAULT_HOST, port: DEFAULT_PORT, count: DEFAULT_COUNT, &block)
118
- Stack.new.run(host: host, port: port, count: count, &block)
139
+ #
140
+ # host:, port: and count: are written through to Ratalada.config before
141
+ # boot, so Server.run(port: 3000) is shorthand for configuring first.
142
+ def run(**options, &block)
143
+ Stack.new.run(**options, &block)
119
144
  end
120
145
 
121
146
  # The chain Server.use returns. Collects middleware until run ends it.
@@ -129,11 +154,29 @@ module Ratalada
129
154
  self
130
155
  end
131
156
 
132
- def run(host: DEFAULT_HOST, port: DEFAULT_PORT, count: DEFAULT_COUNT, &block)
133
- raise ArgumentError, "Server.run requires a block" unless block
134
- raise ArgumentError, "count must be a positive Integer" unless count.is_a?(Integer) && count.positive?
157
+ # Every option is a Ratalada.config setting name (host, port, count);
158
+ # each is applied to the config verbatim — coercion and rejection of
159
+ # unknown names are the settings' own. Skipped entirely when no
160
+ # options are given so a finalized config can still boot.
161
+ def run(**options, &block)
162
+ unless block
163
+ raise ArgumentError, "Server.run requires a block"
164
+ end
135
165
 
136
- Ratalada.backend.run(to_app(block), host: host, port: port, count: count)
166
+ if options.any?
167
+ Ratalada.configure do |config|
168
+ options.each do |key, value|
169
+ config[key] = value
170
+ end
171
+ end
172
+ end
173
+
174
+ config = Ratalada.config
175
+ unless config.count.is_a?(Integer) && config.count.positive?
176
+ raise ArgumentError, "count must be a positive Integer"
177
+ end
178
+
179
+ Ratalada.backend.run(to_app(block), host: config.host, port: config.port, count: config.count)
137
180
  end
138
181
 
139
182
  # First `use` in the chain is the outermost, as in Rack::Builder.
@@ -147,4 +190,12 @@ module Ratalada
147
190
  end
148
191
 
149
192
  # The whole point is a zero-ceremony top-level DSL.
150
- Server = Ratalada::Server unless defined?(Server)
193
+ unless defined?(Server)
194
+ Server = Ratalada::Server
195
+ end
196
+
197
+ # Core settings. Contrib gems (ratalada-contrib et al.) register theirs the
198
+ # same way — Ratalada.setting at the bottom of their own file.
199
+ Ratalada.setting :host, default: Ratalada::DEFAULT_HOST
200
+ Ratalada.setting :port, default: Ratalada::DEFAULT_PORT, constructor: ->(value) { Integer(value) }
201
+ Ratalada.setting :count, default: Ratalada::DEFAULT_COUNT, constructor: ->(value) { Integer(value) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ratalada
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K
@@ -10,19 +10,47 @@ cert_chain: []
10
10
  date: 1980-01-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
- name: minitest
13
+ name: rack
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '5.0'
18
+ version: '3.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: dry-configurable
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.4'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.4'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rspec
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.13'
19
47
  type: :development
20
48
  prerelease: false
21
49
  version_requirements: !ruby/object:Gem::Requirement
22
50
  requirements:
23
51
  - - "~>"
24
52
  - !ruby/object:Gem::Version
25
- version: '5.0'
53
+ version: '3.13'
26
54
  - !ruby/object:Gem::Dependency
27
55
  name: rake
28
56
  requirement: !ruby/object:Gem::Requirement