homura-runtime 0.3.2 → 0.3.4

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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/exe/compile-assets +2 -2
  4. data/exe/compile-erb +5 -7
  5. data/lib/homura/runtime/build_support.rb +19 -2
  6. data/lib/homura/runtime/version.rb +1 -1
  7. data/vendor/rack/auth/abstract/handler.rb +41 -0
  8. data/vendor/rack/auth/abstract/request.rb +51 -0
  9. data/vendor/rack/auth/basic.rb +58 -0
  10. data/vendor/rack/bad_request.rb +8 -0
  11. data/vendor/rack/body_proxy.rb +63 -0
  12. data/vendor/rack/builder.rb +315 -0
  13. data/vendor/rack/cascade.rb +67 -0
  14. data/vendor/rack/common_logger.rb +94 -0
  15. data/vendor/rack/conditional_get.rb +87 -0
  16. data/vendor/rack/config.rb +22 -0
  17. data/vendor/rack/constants.rb +68 -0
  18. data/vendor/rack/content_length.rb +34 -0
  19. data/vendor/rack/content_type.rb +33 -0
  20. data/vendor/rack/deflater.rb +159 -0
  21. data/vendor/rack/directory.rb +210 -0
  22. data/vendor/rack/etag.rb +71 -0
  23. data/vendor/rack/events.rb +172 -0
  24. data/vendor/rack/files.rb +224 -0
  25. data/vendor/rack/head.rb +25 -0
  26. data/vendor/rack/headers.rb +238 -0
  27. data/vendor/rack/lint.rb +1000 -0
  28. data/vendor/rack/lock.rb +29 -0
  29. data/vendor/rack/media_type.rb +42 -0
  30. data/vendor/rack/method_override.rb +56 -0
  31. data/vendor/rack/mime.rb +694 -0
  32. data/vendor/rack/mock.rb +3 -0
  33. data/vendor/rack/mock_request.rb +161 -0
  34. data/vendor/rack/mock_response.rb +147 -0
  35. data/vendor/rack/multipart/generator.rb +99 -0
  36. data/vendor/rack/multipart/parser.rb +586 -0
  37. data/vendor/rack/multipart/uploaded_file.rb +82 -0
  38. data/vendor/rack/multipart.rb +77 -0
  39. data/vendor/rack/null_logger.rb +48 -0
  40. data/vendor/rack/protection/authenticity_token.rb +256 -0
  41. data/vendor/rack/protection/base.rb +140 -0
  42. data/vendor/rack/protection/content_security_policy.rb +80 -0
  43. data/vendor/rack/protection/cookie_tossing.rb +77 -0
  44. data/vendor/rack/protection/escaped_params.rb +93 -0
  45. data/vendor/rack/protection/form_token.rb +25 -0
  46. data/vendor/rack/protection/frame_options.rb +39 -0
  47. data/vendor/rack/protection/http_origin.rb +43 -0
  48. data/vendor/rack/protection/ip_spoofing.rb +27 -0
  49. data/vendor/rack/protection/json_csrf.rb +60 -0
  50. data/vendor/rack/protection/path_traversal.rb +45 -0
  51. data/vendor/rack/protection/referrer_policy.rb +27 -0
  52. data/vendor/rack/protection/remote_referrer.rb +22 -0
  53. data/vendor/rack/protection/remote_token.rb +24 -0
  54. data/vendor/rack/protection/session_hijacking.rb +37 -0
  55. data/vendor/rack/protection/strict_transport.rb +41 -0
  56. data/vendor/rack/protection/version.rb +7 -0
  57. data/vendor/rack/protection/xss_header.rb +27 -0
  58. data/vendor/rack/protection.rb +58 -0
  59. data/vendor/rack/query_parser.rb +261 -0
  60. data/vendor/rack/recursive.rb +66 -0
  61. data/vendor/rack/reloader.rb +112 -0
  62. data/vendor/rack/request.rb +818 -0
  63. data/vendor/rack/response.rb +403 -0
  64. data/vendor/rack/rewindable_input.rb +116 -0
  65. data/vendor/rack/runtime.rb +35 -0
  66. data/vendor/rack/sendfile.rb +197 -0
  67. data/vendor/rack/session/abstract/id.rb +533 -0
  68. data/vendor/rack/session/constants.rb +13 -0
  69. data/vendor/rack/session/cookie.rb +292 -0
  70. data/vendor/rack/session/encryptor.rb +415 -0
  71. data/vendor/rack/session/pool.rb +76 -0
  72. data/vendor/rack/session/version.rb +10 -0
  73. data/vendor/rack/session.rb +12 -0
  74. data/vendor/rack/show_exceptions.rb +433 -0
  75. data/vendor/rack/show_status.rb +121 -0
  76. data/vendor/rack/static.rb +188 -0
  77. data/vendor/rack/tempfile_reaper.rb +44 -0
  78. data/vendor/rack/urlmap.rb +99 -0
  79. data/vendor/rack/utils.rb +631 -0
  80. data/vendor/rack/version.rb +17 -0
  81. data/vendor/rack.rb +66 -0
  82. metadata +76 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b09e022b298cf614eddee1e206a91e8f1e8ac62dbd649bbaf493c17bd9867dc
4
- data.tar.gz: 8e79929b358d8273307da2522890afdd6ab2c19c9e7281cf66eb4cca0c858878
3
+ metadata.gz: deee61a6cf85e633844b5e18df6da9658294bd10db266c1d2525760369610d98
4
+ data.tar.gz: a40ad24b9012f798ee0eb90e1b11bc819e9cd8d8aa418c73482208d3ffc58303
5
5
  SHA512:
6
- metadata.gz: 4869cd52bc0e24b9a4427145bce641c678d1ffd8de391e984a0dd6238df2fc2ac2bcd67108326f079a44c23b17ce77ef7310717941575860efd024311083d92c
7
- data.tar.gz: 44a8fd2d0846fb0939b79da88ce792ce574114a22046b5cb4b6bc9420559c2b780d813a75fba2c177d489996d963142136f2bdeafb95cb347314c308dbc07e8e
6
+ metadata.gz: 5ddc0b574901b97a3c50db3389c41acbcff7ea5e2990483c4edb46cd08ddf4d2c2ee7e950acec6b4d6ea0ad09f3e470c423428cbbadc5c271bbf2cf3c1532095
7
+ data.tar.gz: 38959a44892a5ddac26d1d31e4087ee333efda1d840aed96abab9f3971449f96aa7012c22068b3da6a260c57ffb4d7e564c2d7baf753bb20c4574b185d9ac5b3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.4 (2026-05-02)
4
+
5
+ - Package the Rack vendor files in `homura-runtime` itself. The 0.3.3
6
+ release made Sinatra optional in the build path, but Rack-only apps
7
+ still needed `rack/request` through `homura/runtime/multipart`; that
8
+ worked from the monorepo checkout and failed from the published gem.
9
+
10
+ ## 0.3.3 (2026-05-02)
11
+
12
+ - Make standalone Rack-only builds work without `sinatra-homura` in the
13
+ bundle. `BuildSupport.standalone_load_paths` now treats Sinatra load
14
+ paths as optional while keeping the existing Sinatra app path unchanged.
15
+ - Keep generated empty ERB registries Sinatra-free. Apps with no `.erb`
16
+ templates no longer emit the Sinatra ERB hook, so a Rack-only app can
17
+ compile with `homura-runtime` alone.
18
+ - Guard static-asset middleware auto-installation so the generated asset
19
+ bundle only calls `Sinatra::Base.use` when a real Sinatra app is loaded.
20
+
3
21
  ## 0.3.2 (2026-04-30)
4
22
 
5
23
  - README: replace the leftover `require 'cloudflare_workers'` line and
data/exe/compile-assets CHANGED
@@ -151,8 +151,8 @@ File.open(out_path, 'w') do |io|
151
151
 
152
152
  io.puts <<~RUBY
153
153
 
154
- # Auto-install the middleware on Sinatra::Base if Sinatra is loaded.
155
- if defined?(::Sinatra::Base)
154
+ # Auto-install the middleware on Sinatra::Base if a real Sinatra app is loaded.
155
+ if defined?(::Sinatra::Base) && ::Sinatra::Base.respond_to?(:use)
156
156
  ::Sinatra::Base.use #{ns}::Middleware
157
157
  end
158
158
  RUBY
data/exe/compile-erb CHANGED
@@ -515,11 +515,9 @@ template_root = positional.find { |a| File.directory?(a) } || options[:input_dir
515
515
  write_file = !options[:stdout] && (positional.empty? || options[:output])
516
516
  out_path = options[:output]
517
517
 
518
- # An app with zero ERB templates (e.g. an example that builds HTML
519
- # inline with Ruby heredocs) is a valid case: still emit the registry
520
- # header + Sinatra dispatcher patch so `erb :foo` raises a clear error
521
- # at runtime instead of silently disappearing, and so the build
522
- # pipeline doesn't fail when there is nothing to compile.
518
+ # An app with zero ERB templates is valid, including Rack-only apps
519
+ # that do not ship sinatra-homura at all. Emit the empty registry, but
520
+ # skip the Sinatra hook so `homura-runtime` remains Sinatra-free.
523
521
  if inputs.empty?
524
522
  warn "compile-erb: no .erb files under #{options[:input_dir]}/ — writing empty registry"
525
523
  end
@@ -529,11 +527,11 @@ if write_file
529
527
  File.open(out_path, 'w') do |io|
530
528
  emit_header(io, namespace)
531
529
  emit_templates(io, inputs, namespace, template_root)
532
- emit_sinatra_patch(io, namespace)
530
+ emit_sinatra_patch(io, namespace) unless inputs.empty?
533
531
  end
534
532
  warn "compile-erb: wrote #{out_path} (#{inputs.size} templates)"
535
533
  else
536
534
  emit_header($stdout, namespace)
537
535
  emit_templates($stdout, inputs, namespace, template_root)
538
- emit_sinatra_patch($stdout, namespace)
536
+ emit_sinatra_patch($stdout, namespace) unless inputs.empty?
539
537
  end
@@ -39,6 +39,23 @@ module HomuraRuntime
39
39
  nil
40
40
  end
41
41
 
42
+ def maybe_gem_lib(name, loaded_specs: Gem.loaded_specs)
43
+ spec = loaded_spec(name, loaded_specs: loaded_specs)
44
+ return nil unless spec
45
+
46
+ File.join(spec.full_gem_path, 'lib')
47
+ end
48
+
49
+ def maybe_gem_vendor(name, loaded_specs: Gem.loaded_specs)
50
+ spec = loaded_spec(name, loaded_specs: loaded_specs)
51
+ return nil unless spec
52
+
53
+ vendor = File.join(spec.full_gem_path, 'vendor')
54
+ return vendor if Dir.exist?(vendor)
55
+
56
+ nil
57
+ end
58
+
42
59
  def runtime_file(*names, current_file: __FILE__, loaded_specs: Gem.loaded_specs)
43
60
  runtime_root(current_file: current_file, loaded_specs: loaded_specs).join('runtime', *names)
44
61
  end
@@ -71,8 +88,8 @@ module HomuraRuntime
71
88
  [
72
89
  gem_lib(RUNTIME_GEM_NAME, loaded_specs: loaded_specs),
73
90
  gem_vendor(RUNTIME_GEM_NAME, loaded_specs: loaded_specs),
74
- gem_lib(SINATRA_GEM_NAME, loaded_specs: loaded_specs),
75
- gem_vendor(SINATRA_GEM_NAME, loaded_specs: loaded_specs)
91
+ maybe_gem_lib(SINATRA_GEM_NAME, loaded_specs: loaded_specs),
92
+ maybe_gem_vendor(SINATRA_GEM_NAME, loaded_specs: loaded_specs)
76
93
  ].compact.each do |path|
77
94
  load_paths << path
78
95
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HomuraRuntime
4
- VERSION = '0.3.2'
4
+ VERSION = '0.3.4'
5
5
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../constants'
4
+
5
+ module Rack
6
+ module Auth
7
+ # Rack::Auth::AbstractHandler implements common authentication functionality.
8
+ #
9
+ # +realm+ should be set for all handlers.
10
+
11
+ class AbstractHandler
12
+
13
+ attr_accessor :realm
14
+
15
+ def initialize(app, realm = nil, &authenticator)
16
+ @app, @realm, @authenticator = app, realm, authenticator
17
+ end
18
+
19
+
20
+ private
21
+
22
+ def unauthorized(www_authenticate = challenge)
23
+ return [ 401,
24
+ { CONTENT_TYPE => 'text/plain',
25
+ CONTENT_LENGTH => '0',
26
+ 'www-authenticate' => www_authenticate.to_s },
27
+ []
28
+ ]
29
+ end
30
+
31
+ def bad_request
32
+ return [ 400,
33
+ { CONTENT_TYPE => 'text/plain',
34
+ CONTENT_LENGTH => '0' },
35
+ []
36
+ ]
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # XXX: Remove when removing AbstractRequest#request
4
+ require_relative '../../request'
5
+
6
+ module Rack
7
+ module Auth
8
+ class AbstractRequest
9
+
10
+ def initialize(env)
11
+ @env = env
12
+ end
13
+
14
+ def request
15
+ warn "Rack::Auth::AbstractRequest#request is deprecated and will be removed in a future version of rack.", uplevel: 1
16
+ @request ||= Request.new(@env)
17
+ end
18
+
19
+ def provided?
20
+ !authorization_key.nil? && valid?
21
+ end
22
+
23
+ def valid?
24
+ !@env[authorization_key].nil?
25
+ end
26
+
27
+ def parts
28
+ @parts ||= @env[authorization_key].split(' ', 2)
29
+ end
30
+
31
+ def scheme
32
+ @scheme ||= parts.first&.downcase
33
+ end
34
+
35
+ def params
36
+ @params ||= parts.last
37
+ end
38
+
39
+
40
+ private
41
+
42
+ AUTHORIZATION_KEYS = ['HTTP_AUTHORIZATION', 'X-HTTP_AUTHORIZATION', 'X_HTTP_AUTHORIZATION']
43
+
44
+ def authorization_key
45
+ @authorization_key ||= AUTHORIZATION_KEYS.detect { |key| @env.has_key?(key) }
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'abstract/handler'
4
+ require_relative 'abstract/request'
5
+
6
+ module Rack
7
+ module Auth
8
+ # Rack::Auth::Basic implements HTTP Basic Authentication, as per RFC 2617.
9
+ #
10
+ # Initialize with the Rack application that you want protecting,
11
+ # and a block that checks if a username and password pair are valid.
12
+
13
+ class Basic < AbstractHandler
14
+
15
+ def call(env)
16
+ auth = Basic::Request.new(env)
17
+
18
+ return unauthorized unless auth.provided?
19
+
20
+ return bad_request unless auth.basic?
21
+
22
+ if valid?(auth)
23
+ env['REMOTE_USER'] = auth.username
24
+
25
+ return @app.call(env)
26
+ end
27
+
28
+ unauthorized
29
+ end
30
+
31
+
32
+ private
33
+
34
+ def challenge
35
+ 'Basic realm="%s"' % realm
36
+ end
37
+
38
+ def valid?(auth)
39
+ @authenticator.call(*auth.credentials)
40
+ end
41
+
42
+ class Request < Auth::AbstractRequest
43
+ def basic?
44
+ "basic" == scheme && credentials.length == 2
45
+ end
46
+
47
+ def credentials
48
+ @credentials ||= params.unpack1('m').split(':', 2)
49
+ end
50
+
51
+ def username
52
+ credentials.first
53
+ end
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Represents a 400 Bad Request error when input data fails to meet the
5
+ # requirements.
6
+ module BadRequest
7
+ end
8
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ # Proxy for response bodies allowing calling a block when
5
+ # the response body is closed (after the response has been fully
6
+ # sent to the client).
7
+ class BodyProxy
8
+ # Set the response body to wrap, and the block to call when the
9
+ # response has been fully sent.
10
+ def initialize(body, &block)
11
+ @body = body
12
+ @block = block
13
+ @closed = false
14
+ end
15
+
16
+ # Return whether the wrapped body responds to the method.
17
+ def respond_to_missing?(method_name, include_all = false)
18
+ case method_name
19
+ when :to_str
20
+ false
21
+ else
22
+ super or @body.respond_to?(method_name, include_all)
23
+ end
24
+ end
25
+
26
+ # If not already closed, close the wrapped body and
27
+ # then call the block the proxy was initialized with.
28
+ def close
29
+ return if @closed
30
+ @closed = true
31
+ begin
32
+ @body.close if @body.respond_to?(:close)
33
+ ensure
34
+ @block.call
35
+ end
36
+ end
37
+
38
+ # Whether the proxy is closed. The proxy starts as not closed,
39
+ # and becomes closed on the first call to close.
40
+ def closed?
41
+ @closed
42
+ end
43
+
44
+ # Delegate missing methods to the wrapped body.
45
+ def method_missing(method_name, *args, &block)
46
+ case method_name
47
+ when :to_str
48
+ super
49
+ when :to_ary
50
+ begin
51
+ @body.__send__(method_name, *args, &block)
52
+ ensure
53
+ close
54
+ end
55
+ else
56
+ @body.__send__(method_name, *args, &block)
57
+ end
58
+ end
59
+ # :nocov:
60
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
61
+ # :nocov:
62
+ end
63
+ end
@@ -0,0 +1,315 @@
1
+ # backtick_javascript: true
2
+ # frozen_string_literal: true
3
+
4
+ require_relative 'urlmap'
5
+
6
+ module Rack; end
7
+ Rack::BUILDER_TOPLEVEL_BINDING = ->(builder){builder.instance_eval{binding}}
8
+
9
+ module Rack
10
+ # Rack::Builder provides a domain-specific language (DSL) to construct Rack
11
+ # applications. It is primarily used to parse +config.ru+ files which
12
+ # instantiate several middleware and a final application which are hosted
13
+ # by a Rack-compatible web server.
14
+ #
15
+ # Example:
16
+ #
17
+ # app = Rack::Builder.new do
18
+ # use Rack::CommonLogger
19
+ # map "/ok" do
20
+ # run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] }
21
+ # end
22
+ # end
23
+ #
24
+ # run app
25
+ #
26
+ # Or
27
+ #
28
+ # app = Rack::Builder.app do
29
+ # use Rack::CommonLogger
30
+ # run lambda { |env| [200, {'content-type' => 'text/plain'}, ['OK']] }
31
+ # end
32
+ #
33
+ # run app
34
+ #
35
+ # +use+ adds middleware to the stack, +run+ dispatches to an application.
36
+ # You can use +map+ to construct a Rack::URLMap in a convenient way.
37
+ class Builder
38
+
39
+ # https://stackoverflow.com/questions/2223882/whats-the-difference-between-utf-8-and-utf-8-without-bom
40
+ UTF_8_BOM = '\xef\xbb\xbf'
41
+
42
+ # Parse the given config file to get a Rack application.
43
+ #
44
+ # If the config file ends in +.ru+, it is treated as a
45
+ # rackup file and the contents will be treated as if
46
+ # specified inside a Rack::Builder block.
47
+ #
48
+ # If the config file does not end in +.ru+, it is
49
+ # required and Rack will use the basename of the file
50
+ # to guess which constant will be the Rack application to run.
51
+ #
52
+ # Examples:
53
+ #
54
+ # Rack::Builder.parse_file('config.ru')
55
+ # # Rack application built using Rack::Builder.new
56
+ #
57
+ # Rack::Builder.parse_file('app.rb')
58
+ # # requires app.rb, which can be anywhere in Ruby's
59
+ # # load path. After requiring, assumes App constant
60
+ # # is a Rack application
61
+ #
62
+ # Rack::Builder.parse_file('./my_app.rb')
63
+ # # requires ./my_app.rb, which should be in the
64
+ # # process's current directory. After requiring,
65
+ # # assumes MyApp constant is a Rack application
66
+ def self.parse_file(path, **options)
67
+ if path.end_with?('.ru')
68
+ return self.load_file(path, **options)
69
+ else
70
+ require path
71
+ return Object.const_get(::File.basename(path, '.rb').split('_').map(&:capitalize).join(''))
72
+ end
73
+ end
74
+
75
+ # Load the given file as a rackup file, treating the
76
+ # contents as if specified inside a Rack::Builder block.
77
+ #
78
+ # Ignores content in the file after +__END__+, so that
79
+ # use of +__END__+ will not result in a syntax error.
80
+ #
81
+ # Example config.ru file:
82
+ #
83
+ # $ cat config.ru
84
+ #
85
+ # use Rack::ContentLength
86
+ # require './app.rb'
87
+ # run App
88
+ def self.load_file(path, **options)
89
+ config = ::File.read(path)
90
+ config.slice!(/\A#{UTF_8_BOM}/) if config.encoding == Encoding::UTF_8
91
+
92
+ if config[/^#\\(.*)/]
93
+ fail "Parsing options from the first comment line is no longer supported: #{path}"
94
+ end
95
+
96
+ config.sub!(/^__END__\n.*\Z/m, '')
97
+
98
+ return new_from_string(config, path, **options)
99
+ end
100
+
101
+ # Evaluate the given +builder_script+ string in the context of
102
+ # a Rack::Builder block, returning a Rack application.
103
+ def self.new_from_string(builder_script, path = "(rackup)", **options)
104
+ builder = self.new(**options)
105
+
106
+ # We want to build a variant of TOPLEVEL_BINDING with self as a Rack::Builder instance.
107
+ # We cannot use instance_eval(String) as that would resolve constants differently.
108
+ binding = BUILDER_TOPLEVEL_BINDING.call(builder)
109
+ eval(builder_script, binding, path)
110
+
111
+ return builder.to_app
112
+ end
113
+
114
+ # Initialize a new Rack::Builder instance. +default_app+ specifies the
115
+ # default application if +run+ is not called later. If a block
116
+ # is given, it is evaluated in the context of the instance.
117
+ def initialize(default_app = nil, **options, &block)
118
+ @use = []
119
+ @map = nil
120
+ @run = default_app
121
+ @warmup = nil
122
+ @freeze_app = false
123
+ @options = options
124
+
125
+ instance_eval(&block) if block_given?
126
+ end
127
+
128
+ # Any options provided to the Rack::Builder instance at initialization.
129
+ # These options can be server-specific. Some general options are:
130
+ #
131
+ # * +:isolation+: One of +process+, +thread+ or +fiber+. The execution
132
+ # isolation model to use.
133
+ attr :options
134
+
135
+ # Create a new Rack::Builder instance and return the Rack application
136
+ # generated from it.
137
+ def self.app(default_app = nil, &block)
138
+ self.new(default_app, &block).to_app
139
+ end
140
+
141
+ # Specifies middleware to use in a stack.
142
+ #
143
+ # class Middleware
144
+ # def initialize(app)
145
+ # @app = app
146
+ # end
147
+ #
148
+ # def call(env)
149
+ # env["rack.some_header"] = "setting an example"
150
+ # @app.call(env)
151
+ # end
152
+ # end
153
+ #
154
+ # use Middleware
155
+ # run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }
156
+ #
157
+ # All requests through to this application will first be processed by the middleware class.
158
+ # The +call+ method in this example sets an additional environment key which then can be
159
+ # referenced in the application if required.
160
+ def use(middleware, *args, &block)
161
+ if @map
162
+ mapping, @map = @map, nil
163
+ @use << proc { |app| generate_map(app, mapping) }
164
+ end
165
+ @use << proc { |app| middleware.new(app, *args, &block) }
166
+
167
+ nil
168
+ end
169
+ # :nocov:
170
+ ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
171
+ # :nocov:
172
+
173
+ # Takes a block or argument that is an object that responds to #call and
174
+ # returns a Rack response.
175
+ #
176
+ # You can use a block:
177
+ #
178
+ # run do |env|
179
+ # [200, { "content-type" => "text/plain" }, ["Hello World!"]]
180
+ # end
181
+ #
182
+ # You can also provide a lambda:
183
+ #
184
+ # run lambda { |env| [200, { "content-type" => "text/plain" }, ["OK"]] }
185
+ #
186
+ # You can also provide a class instance:
187
+ #
188
+ # class Heartbeat
189
+ # def call(env)
190
+ # [200, { "content-type" => "text/plain" }, ["OK"]]
191
+ # end
192
+ # end
193
+ #
194
+ # run Heartbeat.new
195
+ #
196
+ def run(app = nil, &block)
197
+ raise ArgumentError, "Both app and block given!" if app && block_given?
198
+
199
+ @run = app || block
200
+
201
+ nil
202
+ end
203
+
204
+ # Takes a lambda or block that is used to warm-up the application. This block is called
205
+ # before the Rack application is returned by to_app.
206
+ #
207
+ # warmup do |app|
208
+ # client = Rack::MockRequest.new(app)
209
+ # client.get('/')
210
+ # end
211
+ #
212
+ # use SomeMiddleware
213
+ # run MyApp
214
+ def warmup(prc = nil, &block)
215
+ @warmup = prc || block
216
+ end
217
+
218
+ # Creates a route within the application. Routes under the mapped path will be sent to
219
+ # the Rack application specified by run inside the block. Other requests will be sent to the
220
+ # default application specified by run outside the block.
221
+ #
222
+ # class App
223
+ # def call(env)
224
+ # [200, {'content-type' => 'text/plain'}, ["Hello World"]]
225
+ # end
226
+ # end
227
+ #
228
+ # class Heartbeat
229
+ # def call(env)
230
+ # [200, { "content-type" => "text/plain" }, ["OK"]]
231
+ # end
232
+ # end
233
+ #
234
+ # app = Rack::Builder.app do
235
+ # map '/heartbeat' do
236
+ # run Heartbeat.new
237
+ # end
238
+ # run App.new
239
+ # end
240
+ #
241
+ # run app
242
+ #
243
+ # The +use+ method can also be used inside the block to specify middleware to run under a specific path:
244
+ #
245
+ # app = Rack::Builder.app do
246
+ # map '/heartbeat' do
247
+ # use Middleware
248
+ # run Heartbeat.new
249
+ # end
250
+ # run App.new
251
+ # end
252
+ #
253
+ # This example includes a piece of middleware which will run before +/heartbeat+ requests hit +Heartbeat+.
254
+ #
255
+ # Note that providing a +path+ of +/+ will ignore any default application given in a +run+ statement
256
+ # outside the block.
257
+ def map(path, &block)
258
+ @map ||= {}
259
+ @map[path] = block
260
+
261
+ nil
262
+ end
263
+
264
+ # Freeze the app (set using run) and all middleware instances when building the application
265
+ # in to_app.
266
+ def freeze_app
267
+ @freeze_app = true
268
+ end
269
+
270
+ # Return the Rack application generated by this instance.
271
+ def to_app
272
+ app = @map ? generate_map(@run, @map) : @run
273
+ fail "missing run or map statement" unless app
274
+ app.freeze if @freeze_app
275
+ # homura patch: neither Array#inject nor an `each` block with
276
+ # a closured local variable reliably threads the middleware
277
+ # accumulator through the chain on Opal (the closure copy of
278
+ # `acc` does not propagate). Use an index-based loop so the
279
+ # accumulator lives in a single function scope without closures.
280
+ list = @use.reverse
281
+ app = wrap_middleware_chain(list, app)
282
+ @warmup.call(app) if @warmup
283
+ app
284
+ end
285
+
286
+ def wrap_middleware_chain(list, initial)
287
+ acc = initial
288
+ i = 0
289
+ n = list.length
290
+ while i < n
291
+ acc = list[i].call(acc)
292
+ acc.freeze if @freeze_app
293
+ i += 1
294
+ end
295
+ acc
296
+ end
297
+
298
+ # Call the Rack application generated by this builder instance. Note that
299
+ # this rebuilds the Rack application and runs the warmup code (if any)
300
+ # every time it is called, so it should not be used if performance is important.
301
+ def call(env)
302
+ to_app.call(env)
303
+ end
304
+
305
+ private
306
+
307
+ # Generate a URLMap instance by generating new Rack applications for each
308
+ # map block in this instance.
309
+ def generate_map(default_app, mapping)
310
+ mapped = default_app ? { '/' => default_app } : {}
311
+ mapping.each { |r, b| mapped[r] = self.class.new(default_app, &b).to_app }
312
+ URLMap.new(mapped)
313
+ end
314
+ end
315
+ end