ultra-lite-hub 0.0.1
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 +7 -0
- data/roda-3.105.0/MIT-LICENSE +21 -0
- data/roda-3.105.0/lib/roda/cache.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/_after_hook.rb +11 -0
- data/roda-3.105.0/lib/roda/plugins/_base64.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/_before_hook.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/_optimized_matching.rb +227 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
- data/roda-3.105.0/lib/roda/plugins/additional_render_engines.rb +61 -0
- data/roda-3.105.0/lib/roda/plugins/additional_view_directories.rb +66 -0
- data/roda-3.105.0/lib/roda/plugins/all_verbs.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/assets.rb +902 -0
- data/roda-3.105.0/lib/roda/plugins/assets_preloading.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/assume_ssl.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
- data/roda-3.105.0/lib/roda/plugins/backtracking_array.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/bearer_token.rb +28 -0
- data/roda-3.105.0/lib/roda/plugins/branch_locals.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/break.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/caching.rb +206 -0
- data/roda-3.105.0/lib/roda/plugins/capture_erb.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/chunked.rb +331 -0
- data/roda-3.105.0/lib/roda/plugins/class_level_routing.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/class_matchers.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/common_logger.rb +80 -0
- data/roda-3.105.0/lib/roda/plugins/conditional_sessions.rb +67 -0
- data/roda-3.105.0/lib/roda/plugins/content_for.rb +103 -0
- data/roda-3.105.0/lib/roda/plugins/content_security_policy.rb +329 -0
- data/roda-3.105.0/lib/roda/plugins/cookie_flags.rb +157 -0
- data/roda-3.105.0/lib/roda/plugins/cookies.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/csrf.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/custom_block_results.rb +85 -0
- data/roda-3.105.0/lib/roda/plugins/custom_matchers.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/default_headers.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/default_status.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/delay_build.rb +18 -0
- data/roda-3.105.0/lib/roda/plugins/delegate.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
- data/roda-3.105.0/lib/roda/plugins/direct_call.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
- data/roda-3.105.0/lib/roda/plugins/drop_body.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/each_part.rb +87 -0
- data/roda-3.105.0/lib/roda/plugins/early_hints.rb +25 -0
- data/roda-3.105.0/lib/roda/plugins/empty_root.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins/environments.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/erb_h.rb +43 -0
- data/roda-3.105.0/lib/roda/plugins/error_email.rb +152 -0
- data/roda-3.105.0/lib/roda/plugins/error_handler.rb +133 -0
- data/roda-3.105.0/lib/roda/plugins/error_mail.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/exception_page.rb +445 -0
- data/roda-3.105.0/lib/roda/plugins/filter_common_logger.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/flash.rb +117 -0
- data/roda-3.105.0/lib/roda/plugins/h.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/halt.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/hash_branches.rb +145 -0
- data/roda-3.105.0/lib/roda/plugins/hash_matcher.rb +39 -0
- data/roda-3.105.0/lib/roda/plugins/hash_paths.rb +128 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public.rb +125 -0
- data/roda-3.105.0/lib/roda/plugins/hash_public_cache.rb +91 -0
- data/roda-3.105.0/lib/roda/plugins/hash_routes.rb +304 -0
- data/roda-3.105.0/lib/roda/plugins/head.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/header_matchers.rb +88 -0
- data/roda-3.105.0/lib/roda/plugins/heartbeat.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/hmac_paths.rb +413 -0
- data/roda-3.105.0/lib/roda/plugins/hooks.rb +92 -0
- data/roda-3.105.0/lib/roda/plugins/host_authorization.rb +155 -0
- data/roda-3.105.0/lib/roda/plugins/host_routing.rb +190 -0
- data/roda-3.105.0/lib/roda/plugins/hsts.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/indifferent_params.rb +124 -0
- data/roda-3.105.0/lib/roda/plugins/inject_erb.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/invalid_request_body.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/ip_from_header.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/json.rb +119 -0
- data/roda-3.105.0/lib/roda/plugins/json_parser.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/link_to.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/mail_processor.rb +629 -0
- data/roda-3.105.0/lib/roda/plugins/mailer.rb +283 -0
- data/roda-3.105.0/lib/roda/plugins/map_matcher.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/match_affix.rb +64 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/match_hook_args.rb +93 -0
- data/roda-3.105.0/lib/roda/plugins/middleware.rb +225 -0
- data/roda-3.105.0/lib/roda/plugins/middleware_stack.rb +101 -0
- data/roda-3.105.0/lib/roda/plugins/module_include.rb +96 -0
- data/roda-3.105.0/lib/roda/plugins/multi_public.rb +95 -0
- data/roda-3.105.0/lib/roda/plugins/multi_route.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/multi_run.rb +144 -0
- data/roda-3.105.0/lib/roda/plugins/multi_view.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
- data/roda-3.105.0/lib/roda/plugins/named_routes.rb +167 -0
- data/roda-3.105.0/lib/roda/plugins/named_templates.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/not_allowed.rb +149 -0
- data/roda-3.105.0/lib/roda/plugins/not_found.rb +55 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
- data/roda-3.105.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/padrino_render.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/param_matchers.rb +83 -0
- data/roda-3.105.0/lib/roda/plugins/params_capturing.rb +116 -0
- data/roda-3.105.0/lib/roda/plugins/part.rb +76 -0
- data/roda-3.105.0/lib/roda/plugins/partials.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/pass.rb +47 -0
- data/roda-3.105.0/lib/roda/plugins/path.rb +255 -0
- data/roda-3.105.0/lib/roda/plugins/path_matchers.rb +54 -0
- data/roda-3.105.0/lib/roda/plugins/path_rewriter.rb +111 -0
- data/roda-3.105.0/lib/roda/plugins/permissions_policy.rb +336 -0
- data/roda-3.105.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
- data/roda-3.105.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/precompile_templates.rb +156 -0
- data/roda-3.105.0/lib/roda/plugins/public.rb +182 -0
- data/roda-3.105.0/lib/roda/plugins/r.rb +35 -0
- data/roda-3.105.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
- data/roda-3.105.0/lib/roda/plugins/redirect_path.rb +59 -0
- data/roda-3.105.0/lib/roda/plugins/relative_path.rb +74 -0
- data/roda-3.105.0/lib/roda/plugins/render.rb +998 -0
- data/roda-3.105.0/lib/roda/plugins/render_coverage.rb +105 -0
- data/roda-3.105.0/lib/roda/plugins/render_each.rb +224 -0
- data/roda-3.105.0/lib/roda/plugins/render_locals.rb +102 -0
- data/roda-3.105.0/lib/roda/plugins/request_aref.rb +77 -0
- data/roda-3.105.0/lib/roda/plugins/request_headers.rb +81 -0
- data/roda-3.105.0/lib/roda/plugins/response_attachment.rb +94 -0
- data/roda-3.105.0/lib/roda/plugins/response_content_type.rb +79 -0
- data/roda-3.105.0/lib/roda/plugins/response_request.rb +29 -0
- data/roda-3.105.0/lib/roda/plugins/route_block_args.rb +50 -0
- data/roda-3.105.0/lib/roda/plugins/route_csrf.rb +388 -0
- data/roda-3.105.0/lib/roda/plugins/run_append_slash.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_handler.rb +51 -0
- data/roda-3.105.0/lib/roda/plugins/run_require_slash.rb +46 -0
- data/roda-3.105.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
- data/roda-3.105.0/lib/roda/plugins/send_file.rb +127 -0
- data/roda-3.105.0/lib/roda/plugins/sessions.rb +537 -0
- data/roda-3.105.0/lib/roda/plugins/shared_vars.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/sinatra_helpers.rb +443 -0
- data/roda-3.105.0/lib/roda/plugins/slash_path_empty.rb +26 -0
- data/roda-3.105.0/lib/roda/plugins/static.rb +42 -0
- data/roda-3.105.0/lib/roda/plugins/static_routing.rb +89 -0
- data/roda-3.105.0/lib/roda/plugins/status_303.rb +34 -0
- data/roda-3.105.0/lib/roda/plugins/status_handler.rb +86 -0
- data/roda-3.105.0/lib/roda/plugins/streaming.rb +170 -0
- data/roda-3.105.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_matchers.rb +188 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_status.rb +32 -0
- data/roda-3.105.0/lib/roda/plugins/symbol_views.rb +37 -0
- data/roda-3.105.0/lib/roda/plugins/timestamp_public.rb +78 -0
- data/roda-3.105.0/lib/roda/plugins/type_routing.rb +215 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params.rb +1183 -0
- data/roda-3.105.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
- data/roda-3.105.0/lib/roda/plugins/unescape_path.rb +41 -0
- data/roda-3.105.0/lib/roda/plugins/view_options.rb +201 -0
- data/roda-3.105.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
- data/roda-3.105.0/lib/roda/plugins.rb +68 -0
- data/roda-3.105.0/lib/roda/request.rb +722 -0
- data/roda-3.105.0/lib/roda/response.rb +221 -0
- data/roda-3.105.0/lib/roda/session_middleware.rb +183 -0
- data/roda-3.105.0/lib/roda/version.rb +18 -0
- data/roda-3.105.0/lib/roda.rb +586 -0
- data/ultra-lite-hub.gemspec +12 -0
- metadata +201 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The status_handler plugin adds a +status_handler+ method which sets a
|
|
7
|
+
# block that is called whenever a response with the relevant response code
|
|
8
|
+
# with an empty body would be returned.
|
|
9
|
+
#
|
|
10
|
+
# This plugin does not support providing the blocks with the plugin call;
|
|
11
|
+
# you must provide them to status_handler calls afterwards:
|
|
12
|
+
#
|
|
13
|
+
# plugin :status_handler
|
|
14
|
+
#
|
|
15
|
+
# status_handler(403) do
|
|
16
|
+
# "You are forbidden from seeing that!"
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# status_handler(404) do
|
|
20
|
+
# "Where did it go?"
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# status_handler(405, keep_headers: ['Accept']) do
|
|
24
|
+
# "Use a different method!"
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# Before a block is called, any existing headers on the response will be
|
|
28
|
+
# cleared, unless the +:keep_headers+ option is used. If the +:keep_headers+
|
|
29
|
+
# option is used, the value should be an array, and only the headers listed
|
|
30
|
+
# in the array will be kept.
|
|
31
|
+
module StatusHandler
|
|
32
|
+
CLEAR_HEADERS = :clear.to_proc
|
|
33
|
+
private_constant :CLEAR_HEADERS
|
|
34
|
+
|
|
35
|
+
def self.configure(app)
|
|
36
|
+
app.opts[:status_handler] ||= {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module ClassMethods
|
|
40
|
+
# Install the given block as a status handler for the given HTTP response code.
|
|
41
|
+
def status_handler(code, opts=OPTS, &block)
|
|
42
|
+
# For backwards compatibility, pass request argument if block accepts argument
|
|
43
|
+
arity = block.arity == 0 ? 0 : 1
|
|
44
|
+
handle_headers = case keep_headers = opts[:keep_headers]
|
|
45
|
+
when nil, false
|
|
46
|
+
CLEAR_HEADERS
|
|
47
|
+
when Array
|
|
48
|
+
if Rack.release >= '3'
|
|
49
|
+
keep_headers = keep_headers.map(&:downcase)
|
|
50
|
+
end
|
|
51
|
+
lambda{|headers| headers.delete_if{|k,_| !keep_headers.include?(k)}}
|
|
52
|
+
else
|
|
53
|
+
raise RodaError, "Invalid :keep_headers option"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
meth = define_roda_method(:"_roda_status_handler__#{code}", arity, &block)
|
|
57
|
+
self.opts[:status_handler][code] = define_roda_method(:"_roda_status_handler_#{code}", 1) do |result|
|
|
58
|
+
res = @_response
|
|
59
|
+
res.status = result[0]
|
|
60
|
+
handle_headers.call(res.headers)
|
|
61
|
+
result.replace(_roda_handle_route{arity == 1 ? send(meth, @_request) : send(meth)})
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Freeze the hash of status handlers so that there can be no thread safety issues at runtime.
|
|
66
|
+
def freeze
|
|
67
|
+
opts[:status_handler].freeze
|
|
68
|
+
super
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
module InstanceMethods
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# If routing returns a response we have a handler for, call that handler.
|
|
76
|
+
def _roda_after_20__status_handler(result)
|
|
77
|
+
if result && (meth = opts[:status_handler][result[0]]) && (v = result[2]).is_a?(Array) && v.empty?
|
|
78
|
+
send(meth, result)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
register_plugin(:status_handler, StatusHandler)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The streaming plugin adds support for streaming responses
|
|
7
|
+
# from roda using the +stream+ method:
|
|
8
|
+
#
|
|
9
|
+
# plugin :streaming
|
|
10
|
+
#
|
|
11
|
+
# route do |r|
|
|
12
|
+
# stream do |out|
|
|
13
|
+
# ['a', 'b', 'c'].each{|v| out << v; sleep 1}
|
|
14
|
+
# end
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# In order for streaming to work, any webservers used in
|
|
18
|
+
# front of the roda app must not buffer responses.
|
|
19
|
+
#
|
|
20
|
+
# The stream method takes the following options:
|
|
21
|
+
#
|
|
22
|
+
# :callback :: A callback proc to call when the connection is closed.
|
|
23
|
+
# :loop :: Whether to call the stream block continuously until the connection is closed.
|
|
24
|
+
# :async :: Whether to call the stream block in a separate thread (default: false). Only supported on Ruby 2.3+.
|
|
25
|
+
# :queue :: A queue object to use for asynchronous streaming (default: `SizedQueue.new(10)`).
|
|
26
|
+
#
|
|
27
|
+
# If the :loop option is used, you can override the
|
|
28
|
+
# handle_stream_error method to change how exceptions
|
|
29
|
+
# are handled during streaming. This method is passed the
|
|
30
|
+
# exception and the stream. By default, this method
|
|
31
|
+
# just reraises the exception, but you can choose to output
|
|
32
|
+
# the an error message to the stream, before raising:
|
|
33
|
+
#
|
|
34
|
+
# def handle_stream_error(e, out)
|
|
35
|
+
# out << 'ERROR!'
|
|
36
|
+
# raise e
|
|
37
|
+
# end
|
|
38
|
+
#
|
|
39
|
+
# Ignore errors completely while streaming:
|
|
40
|
+
#
|
|
41
|
+
# def handle_stream_error(e, out)
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# or handle the errors in some other way.
|
|
45
|
+
module Streaming
|
|
46
|
+
# Class of the response body in case you use #stream.
|
|
47
|
+
class Stream
|
|
48
|
+
include Enumerable
|
|
49
|
+
|
|
50
|
+
# Handle streaming options, see Streaming for details.
|
|
51
|
+
def initialize(opts=OPTS, &block)
|
|
52
|
+
@block = block
|
|
53
|
+
@out = nil
|
|
54
|
+
@callback = opts[:callback]
|
|
55
|
+
@closed = false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Add output to the streaming response body. Returns number of bytes written.
|
|
59
|
+
def write(data)
|
|
60
|
+
data = data.to_s
|
|
61
|
+
@out.call(data)
|
|
62
|
+
data.bytesize
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Add output to the streaming response body. Returns self.
|
|
66
|
+
def <<(data)
|
|
67
|
+
write(data)
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# If not already closed, close the connection, and call
|
|
72
|
+
# any callbacks.
|
|
73
|
+
def close
|
|
74
|
+
return if closed?
|
|
75
|
+
@closed = true
|
|
76
|
+
@callback.call if @callback
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Whether the connection has already been closed.
|
|
80
|
+
def closed?
|
|
81
|
+
@closed
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Yield values to the block as they are passed in via #<<.
|
|
85
|
+
def each(&out)
|
|
86
|
+
@out = out
|
|
87
|
+
@block.call(self)
|
|
88
|
+
ensure
|
|
89
|
+
close
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Class of the response body if you use #stream with :async set to true.
|
|
94
|
+
# Uses a separate thread that pushes streaming results to a queue, so that
|
|
95
|
+
# data can be streamed to clients while it is being prepared by the application.
|
|
96
|
+
class AsyncStream
|
|
97
|
+
include Enumerable
|
|
98
|
+
|
|
99
|
+
# Handle streaming options, see Streaming for details.
|
|
100
|
+
def initialize(opts=OPTS, &block)
|
|
101
|
+
@stream = Stream.new(opts, &block)
|
|
102
|
+
@queue = opts[:queue] || SizedQueue.new(10) # have some default backpressure
|
|
103
|
+
@thread = Thread.new { enqueue_chunks }
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Continue streaming data until the stream is finished.
|
|
107
|
+
def each(&out)
|
|
108
|
+
dequeue_chunks(&out)
|
|
109
|
+
@thread.join
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Stop streaming.
|
|
113
|
+
def close
|
|
114
|
+
@queue.close # terminate the producer thread
|
|
115
|
+
@stream.close
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
# Push each streaming chunk onto the queue.
|
|
121
|
+
def enqueue_chunks
|
|
122
|
+
@stream.each do |chunk|
|
|
123
|
+
@queue.push(chunk)
|
|
124
|
+
end
|
|
125
|
+
rescue ClosedQueueError
|
|
126
|
+
# connection was closed
|
|
127
|
+
ensure
|
|
128
|
+
@queue.close
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Pop each streaming chunk from the queue and yield it.
|
|
132
|
+
def dequeue_chunks
|
|
133
|
+
while chunk = @queue.pop
|
|
134
|
+
yield chunk
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
module InstanceMethods
|
|
140
|
+
# Immediately return a streaming response using the current response
|
|
141
|
+
# status and headers, calling the block to get the streaming response.
|
|
142
|
+
# See Streaming for details.
|
|
143
|
+
def stream(opts=OPTS, &block)
|
|
144
|
+
if opts[:loop]
|
|
145
|
+
block = proc do |out|
|
|
146
|
+
until out.closed?
|
|
147
|
+
begin
|
|
148
|
+
yield(out)
|
|
149
|
+
rescue => e
|
|
150
|
+
handle_stream_error(e, out)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
stream_class = (opts[:async] && RUBY_VERSION >= '2.3') ? AsyncStream : Stream
|
|
157
|
+
|
|
158
|
+
throw :halt, @_response.finish_with_body(stream_class.new(opts, &block))
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Handle exceptions raised while streaming when using :loop
|
|
162
|
+
def handle_stream_error(e, out)
|
|
163
|
+
raise e
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
register_plugin(:streaming, Streaming)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The strip_path_prefix plugin makes Roda strip a given prefix off internal absolute paths,
|
|
7
|
+
# turning them to relative paths. Roda by default stores internal paths as absolute paths.
|
|
8
|
+
# The main reason to use this plugin is when the internal absolute path could change at
|
|
9
|
+
# runtime, either due to a symlink change or chroot call, or you really want to use
|
|
10
|
+
# relative paths instead of absolute paths.
|
|
11
|
+
#
|
|
12
|
+
# Examples:
|
|
13
|
+
#
|
|
14
|
+
# plugin :strip_path_prefix # Defaults to Dir.pwd
|
|
15
|
+
# plugin :strip_path_prefix, File.dirname(Dir.pwd)
|
|
16
|
+
module StripPathPrefix
|
|
17
|
+
# Set the regexp to use when stripping prefixes from internal paths.
|
|
18
|
+
def self.configure(app, prefix=Dir.pwd)
|
|
19
|
+
prefix += '/' unless prefix.end_with?("/")
|
|
20
|
+
app.opts[:strip_path_prefix] = /\A#{Regexp.escape(prefix)}/
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module ClassMethods
|
|
24
|
+
# Strip the path prefix from the gien path if it starts with the prefix.
|
|
25
|
+
def expand_path(path, root=opts[:root])
|
|
26
|
+
super.sub(opts[:strip_path_prefix], '')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
register_plugin(:strip_path_prefix, StripPathPrefix)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The symbol_matchers plugin allows you do define custom regexps to use
|
|
7
|
+
# for specific symbols. For example, if you have a route such as:
|
|
8
|
+
#
|
|
9
|
+
# r.on :username do |username|
|
|
10
|
+
# # ...
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# By default this will match all nonempty segments. However, if your usernames
|
|
14
|
+
# must be 6-20 characters, and can only contain +a-z+ and +0-9+, you can do:
|
|
15
|
+
#
|
|
16
|
+
# plugin :symbol_matchers
|
|
17
|
+
# symbol_matcher :username, /([a-z0-9]{6,20})/
|
|
18
|
+
#
|
|
19
|
+
# Then the route will only if the path is +/foobar123+, but not if it is
|
|
20
|
+
# +/foo+, +/FooBar123+, or +/foobar_123+.
|
|
21
|
+
#
|
|
22
|
+
# You can provide a <tt>segment: true</tt> option to symbol_matcher to speed up
|
|
23
|
+
# the matching on Ruby 2.4+:
|
|
24
|
+
#
|
|
25
|
+
# symbol_matcher :username, /([a-z0-9]{6,20})/, segment: true
|
|
26
|
+
#
|
|
27
|
+
# Use of <tt>segment: true</tt> requires that the regexp not match more than
|
|
28
|
+
# one segment (i.e. it cannot match +/+). Additionally, the entire segment will
|
|
29
|
+
# be captured, so any capture groups in the regexp will be ignored.
|
|
30
|
+
#
|
|
31
|
+
# By default, this plugin sets up the following symbol matchers:
|
|
32
|
+
#
|
|
33
|
+
# :d :: <tt>/(\d+)/</tt>, a decimal segment
|
|
34
|
+
# :rest :: <tt>/(.*)/</tt>, all remaining characters, if any
|
|
35
|
+
# :w :: <tt>/(\w+)/</tt>, an alphanumeric segment
|
|
36
|
+
#
|
|
37
|
+
# If the placeholder_string_matchers plugin is loaded, this feature also applies to
|
|
38
|
+
# placeholders in strings, so the following:
|
|
39
|
+
#
|
|
40
|
+
# r.on "users/:username" do |username|
|
|
41
|
+
# # ...
|
|
42
|
+
# end
|
|
43
|
+
#
|
|
44
|
+
# Would match +/users/foobar123+, but not +/users/foo+, +/users/FooBar123+,
|
|
45
|
+
# or +/users/foobar_123+.
|
|
46
|
+
#
|
|
47
|
+
# If using this plugin with the params_capturing plugin, this plugin should
|
|
48
|
+
# be loaded first.
|
|
49
|
+
#
|
|
50
|
+
# You can provide a block when calling +symbol_matcher+, and it will be called
|
|
51
|
+
# for all matches to allow for type conversion:
|
|
52
|
+
#
|
|
53
|
+
# symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
|
|
54
|
+
# Date.new(y.to_i, m.to_i, d.to_i)
|
|
55
|
+
# end
|
|
56
|
+
#
|
|
57
|
+
# route do |r|
|
|
58
|
+
# r.on :date do |date|
|
|
59
|
+
# # date is an instance of Date
|
|
60
|
+
# end
|
|
61
|
+
# end
|
|
62
|
+
#
|
|
63
|
+
# If you have a segment match the passed regexp, but decide during block
|
|
64
|
+
# processing that you do not want to treat it as a match, you can have the
|
|
65
|
+
# block return nil or false. This is useful if you want to make sure you
|
|
66
|
+
# are using valid data:
|
|
67
|
+
#
|
|
68
|
+
# symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
|
|
69
|
+
# y = y.to_i
|
|
70
|
+
# m = m.to_i
|
|
71
|
+
# d = d.to_i
|
|
72
|
+
# Date.new(y, m, d) if Date.valid_date?(y, m, d)
|
|
73
|
+
# end
|
|
74
|
+
#
|
|
75
|
+
# You can have the block return an array to yield multiple captures.
|
|
76
|
+
#
|
|
77
|
+
# The second argument to symbol_matcher can be a symbol already registered
|
|
78
|
+
# as a symbol matcher. This can DRY up code that wants a conversion
|
|
79
|
+
# performed by an existing class matcher or to use the same regexp:
|
|
80
|
+
#
|
|
81
|
+
# symbol_matcher :employee_id, :d do |id|
|
|
82
|
+
# id.to_i
|
|
83
|
+
# end
|
|
84
|
+
# symbol_matcher :employee, :employee_id do |id|
|
|
85
|
+
# Employee[id]
|
|
86
|
+
# end
|
|
87
|
+
#
|
|
88
|
+
# With the above example, the :d matcher matches only decimal strings, but
|
|
89
|
+
# yields them as string. The registered :employee_id matcher converts the
|
|
90
|
+
# decimal string to an integer. The registered :employee matcher builds
|
|
91
|
+
# on that and uses the integer to lookup the related employee. If there is
|
|
92
|
+
# no employee with that id, then the :employee matcher will not match.
|
|
93
|
+
#
|
|
94
|
+
# If using the class_matchers plugin, you can provide a recognized class
|
|
95
|
+
# matcher as the second argument to symbol_matcher, and it will work in
|
|
96
|
+
# a similar manner:
|
|
97
|
+
#
|
|
98
|
+
# symbol_matcher :employee, Integer do |id|
|
|
99
|
+
# Employee[id]
|
|
100
|
+
# end
|
|
101
|
+
#
|
|
102
|
+
# Blocks passed to the symbol matchers plugin are evaluated in route
|
|
103
|
+
# block context.
|
|
104
|
+
#
|
|
105
|
+
# If providing a block to the symbol_matchers plugin, the symbol may
|
|
106
|
+
# not work with the params_capturing plugin. Note that the use of
|
|
107
|
+
# symbol matchers inside strings when using the placeholder_string_matchers
|
|
108
|
+
# plugin only uses the regexp, it does not respect the conversion blocks
|
|
109
|
+
# registered with the symbols.
|
|
110
|
+
module SymbolMatchers
|
|
111
|
+
def self.load_dependencies(app)
|
|
112
|
+
app.plugin :_symbol_regexp_matchers
|
|
113
|
+
app.plugin :_symbol_class_matchers
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.configure(app)
|
|
117
|
+
app.opts[:symbol_matchers] ||= {}
|
|
118
|
+
app.symbol_matcher(:d, /(\d+)/, segment: true)
|
|
119
|
+
app.symbol_matcher(:w, /(\w+)/, segment: true)
|
|
120
|
+
app.symbol_matcher(:rest, /(.*)/)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
module ClassMethods
|
|
124
|
+
# Set the matcher and block to use for the given class.
|
|
125
|
+
# The matcher can be a regexp, registered symbol matcher, or registered class
|
|
126
|
+
# matcher (if using the class_matchers plugin).
|
|
127
|
+
#
|
|
128
|
+
# If providing a regexp, the block given will be called with all regexp captures.
|
|
129
|
+
# If providing a registered symbol or class, the block will be called with the
|
|
130
|
+
# captures returned by the block for the registered symbol or class, or the regexp
|
|
131
|
+
# captures if no block was registered with the symbol or class. In either case,
|
|
132
|
+
# if a block is given, it should return an array with the captures to yield to
|
|
133
|
+
# the match block.
|
|
134
|
+
def symbol_matcher(s, matcher, opts=OPTS, &block)
|
|
135
|
+
_symbol_class_matcher(Symbol, s, matcher, block, opts) do |meth, array|
|
|
136
|
+
define_method(meth){array}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
nil
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Freeze the class_matchers hash when freezing the app.
|
|
143
|
+
def freeze
|
|
144
|
+
opts[:symbol_matchers].freeze
|
|
145
|
+
super
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
module RequestMethods
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
# Use regular expressions to the symbol-specific regular expression
|
|
153
|
+
# if the symbol is registered. Otherwise, call super for the default
|
|
154
|
+
# behavior.
|
|
155
|
+
def _match_symbol(s)
|
|
156
|
+
meth = :"match_symbol_#{s}"
|
|
157
|
+
if respond_to?(meth, true)
|
|
158
|
+
# Allow calling private match methods
|
|
159
|
+
_, re, convert_meth, consume_meth = send(meth)
|
|
160
|
+
if re
|
|
161
|
+
send(consume_meth, re, convert_meth)
|
|
162
|
+
else
|
|
163
|
+
# defined in class_matchers plugin
|
|
164
|
+
_consume_segment(convert_meth)
|
|
165
|
+
end
|
|
166
|
+
else
|
|
167
|
+
super
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Return the symbol-specific regular expression if one is registered.
|
|
172
|
+
# Otherwise, call super for the default behavior.
|
|
173
|
+
def _match_symbol_regexp(s)
|
|
174
|
+
meth = :"match_symbol_#{s}"
|
|
175
|
+
if respond_to?(meth, true)
|
|
176
|
+
# Allow calling private match methods
|
|
177
|
+
re, = send(meth)
|
|
178
|
+
re
|
|
179
|
+
else
|
|
180
|
+
super
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
register_plugin(:symbol_matchers, SymbolMatchers)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require 'rack/utils'
|
|
4
|
+
|
|
5
|
+
class Roda
|
|
6
|
+
module RodaPlugins
|
|
7
|
+
# The symbol_status plugin patches the +status=+ response method to
|
|
8
|
+
# accept the status name as a symbol. If given an integer value,
|
|
9
|
+
# the default behaviour is used.
|
|
10
|
+
#
|
|
11
|
+
# Examples:
|
|
12
|
+
# r.is "needs_authorization" do
|
|
13
|
+
# response.status = :unauthorized
|
|
14
|
+
# end
|
|
15
|
+
# r.is "nothing" do
|
|
16
|
+
# response.status = :no_content
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# The conversion is done through <tt>Rack::Utils.status_code</tt>.
|
|
20
|
+
module SymbolStatus
|
|
21
|
+
module ResponseMethods
|
|
22
|
+
# Sets the response status code by fixnum or symbol name
|
|
23
|
+
def status=(code)
|
|
24
|
+
code = Rack::Utils.status_code(code) if code.is_a?(Symbol)
|
|
25
|
+
super(code)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
register_plugin(:symbol_status, SymbolStatus)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The symbol_views plugin allows match blocks to return
|
|
7
|
+
# symbols, and consider those symbols as views to use for the
|
|
8
|
+
# response body. So you can take code like:
|
|
9
|
+
#
|
|
10
|
+
# r.root do
|
|
11
|
+
# view :index
|
|
12
|
+
# end
|
|
13
|
+
# r.is "foo" do
|
|
14
|
+
# view :foo
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# and DRY it up:
|
|
18
|
+
#
|
|
19
|
+
# r.root do
|
|
20
|
+
# :index
|
|
21
|
+
# end
|
|
22
|
+
# r.is "foo" do
|
|
23
|
+
# :foo
|
|
24
|
+
# end
|
|
25
|
+
module SymbolViews
|
|
26
|
+
def self.load_dependencies(app)
|
|
27
|
+
app.plugin :custom_block_results
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.configure(app)
|
|
31
|
+
app.opts[:custom_block_results][Symbol] = :view
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
register_plugin(:symbol_views, SymbolViews)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The timestamp_public plugin adds a +timestamp_path+ method for constructing
|
|
7
|
+
# timestamp paths, and a +r.timestamp_public+ routing method to serve static files
|
|
8
|
+
# from a directory (using the public plugin). This plugin is useful when you want
|
|
9
|
+
# to modify the path to static files when the modify timestamp on the file changes,
|
|
10
|
+
# ensuring that requests for the static file will not be cached.
|
|
11
|
+
#
|
|
12
|
+
# Note that while this plugin will not serve files outside of the public directory,
|
|
13
|
+
# for performance reasons it does not check the path of the file is inside the public
|
|
14
|
+
# directory when getting the modify timestamp. If the +timestamp_path+ method is
|
|
15
|
+
# called with untrusted input, it is possible for an attacker to get the modify
|
|
16
|
+
# timestamp for any file on the file system.
|
|
17
|
+
#
|
|
18
|
+
# Examples:
|
|
19
|
+
#
|
|
20
|
+
# # Use public folder as location of files, and static as the path prefix
|
|
21
|
+
# plugin :timestamp_public
|
|
22
|
+
#
|
|
23
|
+
# # Use /path/to/app/static as location of files, and public as the path prefix
|
|
24
|
+
# opts[:root] = '/path/to/app'
|
|
25
|
+
# plugin :public, root: 'static', prefix: 'public'
|
|
26
|
+
#
|
|
27
|
+
# # Assuming public is the location of files, and static is the path prefix
|
|
28
|
+
# route do
|
|
29
|
+
# # Make GET /static/1238099123/images/foo.png look for public/images/foo.png
|
|
30
|
+
# r.timestamp_public
|
|
31
|
+
#
|
|
32
|
+
# r.get "example" do
|
|
33
|
+
# # "/static/1238099123/images/foo.png"
|
|
34
|
+
# timestamp_path("images/foo.png")
|
|
35
|
+
# end
|
|
36
|
+
# end
|
|
37
|
+
module TimestampPublic
|
|
38
|
+
def self.load_dependencies(app, opts=OPTS)
|
|
39
|
+
app.plugin :public, opts
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Use options given to setup timestamped file serving. The following option is
|
|
43
|
+
# recognized by the plugin:
|
|
44
|
+
#
|
|
45
|
+
# :prefix :: The prefix for paths, before the timestamp segment
|
|
46
|
+
#
|
|
47
|
+
# The options given are also passed to the public plugin.
|
|
48
|
+
def self.configure(app, opts=OPTS)
|
|
49
|
+
app.opts[:timestamp_public_prefix] = (opts[:prefix] || app.opts[:timestamp_public_prefix] || "static").dup.freeze
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
module InstanceMethods
|
|
53
|
+
# Return a path to the static file that could be served by r.timestamp_public.
|
|
54
|
+
# This does not check the file is inside the directory for performance reasons,
|
|
55
|
+
# so this should not be called with untrusted input.
|
|
56
|
+
def timestamp_path(file)
|
|
57
|
+
mtime = File.mtime(File.join(opts[:public_root], file))
|
|
58
|
+
"/#{opts[:timestamp_public_prefix]}/#{sprintf("%i%06i", mtime.to_i, mtime.usec)}/#{file}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
module RequestMethods
|
|
63
|
+
# Serve files from the public directory if the file exists,
|
|
64
|
+
# it includes the timestamp_public prefix segment followed by
|
|
65
|
+
# a integer segment for the timestamp, and this is a GET request.
|
|
66
|
+
def timestamp_public
|
|
67
|
+
if is_get?
|
|
68
|
+
on roda_class.opts[:timestamp_public_prefix], Integer do |_|
|
|
69
|
+
public
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
register_plugin(:timestamp_public, TimestampPublic)
|
|
77
|
+
end
|
|
78
|
+
end
|