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,206 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
class Roda
|
|
7
|
+
module RodaPlugins
|
|
8
|
+
# The caching plugin adds methods related to HTTP caching.
|
|
9
|
+
#
|
|
10
|
+
# For proper caching, you should use either the +last_modified+ or
|
|
11
|
+
# +etag+ request methods.
|
|
12
|
+
#
|
|
13
|
+
# r.get 'albums', Integer do |album_id|
|
|
14
|
+
# @album = Album[album_id]
|
|
15
|
+
# r.last_modified @album.updated_at
|
|
16
|
+
# view('album')
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# # or
|
|
20
|
+
#
|
|
21
|
+
# r.get 'albums', Integer do |album_id|
|
|
22
|
+
# @album = Album[album_id]
|
|
23
|
+
# r.etag @album.sha1
|
|
24
|
+
# view('album')
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# Both +last_modified+ or +etag+ will immediately halt processing
|
|
28
|
+
# if there have been no modifications since the last time the
|
|
29
|
+
# client requested the resource, assuming the client uses the
|
|
30
|
+
# appropriate HTTP 1.1 request headers.
|
|
31
|
+
#
|
|
32
|
+
# This plugin also includes the +cache_control+ and +expires+
|
|
33
|
+
# response methods. The +cache_control+ method sets the
|
|
34
|
+
# Cache-Control header using the given hash:
|
|
35
|
+
#
|
|
36
|
+
# response.cache_control public: true, max_age: 60
|
|
37
|
+
# # Cache-Control: public, max-age=60
|
|
38
|
+
#
|
|
39
|
+
# The +expires+ method is similar, but in addition
|
|
40
|
+
# to setting the HTTP 1.1 Cache-Control header, it also sets
|
|
41
|
+
# the HTTP 1.0 Expires header:
|
|
42
|
+
#
|
|
43
|
+
# response.expires 60, public: true
|
|
44
|
+
# # Cache-Control: public, max-age=60
|
|
45
|
+
# # Expires: Mon, 29 Sep 2014 21:25:47 GMT
|
|
46
|
+
#
|
|
47
|
+
# The implementation was originally taken from Sinatra,
|
|
48
|
+
# which is also released under the MIT License:
|
|
49
|
+
#
|
|
50
|
+
# Copyright (c) 2007, 2008, 2009 Blake Mizerany
|
|
51
|
+
# Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase
|
|
52
|
+
#
|
|
53
|
+
# Permission is hereby granted, free of charge, to any person
|
|
54
|
+
# obtaining a copy of this software and associated documentation
|
|
55
|
+
# files (the "Software"), to deal in the Software without
|
|
56
|
+
# restriction, including without limitation the rights to use,
|
|
57
|
+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
58
|
+
# copies of the Software, and to permit persons to whom the
|
|
59
|
+
# Software is furnished to do so, subject to the following
|
|
60
|
+
# conditions:
|
|
61
|
+
#
|
|
62
|
+
# The above copyright notice and this permission notice shall be
|
|
63
|
+
# included in all copies or substantial portions of the Software.
|
|
64
|
+
#
|
|
65
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
66
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
67
|
+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
68
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
69
|
+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
70
|
+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
71
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
72
|
+
# OTHER DEALINGS IN THE SOFTWARE.
|
|
73
|
+
module Caching
|
|
74
|
+
module RequestMethods
|
|
75
|
+
# Set the last modified time of the resource using the Last-Modified header.
|
|
76
|
+
# The +time+ argument should be a Time instance.
|
|
77
|
+
#
|
|
78
|
+
# If the current request includes an If-Modified-Since header that is
|
|
79
|
+
# equal or later than the time specified, immediately returns a response
|
|
80
|
+
# with a 304 status.
|
|
81
|
+
#
|
|
82
|
+
# If the current request includes an If-Unmodified-Since header that is
|
|
83
|
+
# before than the time specified, immediately returns a response
|
|
84
|
+
# with a 412 status.
|
|
85
|
+
def last_modified(time)
|
|
86
|
+
return unless time
|
|
87
|
+
res = response
|
|
88
|
+
e = env
|
|
89
|
+
res[RodaResponseHeaders::LAST_MODIFIED] = time.httpdate
|
|
90
|
+
return if e['HTTP_IF_NONE_MATCH']
|
|
91
|
+
status = res.status
|
|
92
|
+
|
|
93
|
+
if (!status || status == 200) && (ims = time_from_header(e['HTTP_IF_MODIFIED_SINCE'])) && ims >= time.to_i
|
|
94
|
+
res.status = 304
|
|
95
|
+
halt
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if (!status || (status >= 200 && status < 300) || status == 412) && (ius = time_from_header(e['HTTP_IF_UNMODIFIED_SINCE'])) && ius < time.to_i
|
|
99
|
+
res.status = 412
|
|
100
|
+
halt
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Set the response entity tag using the ETag header.
|
|
105
|
+
#
|
|
106
|
+
# The +value+ argument is an identifier that uniquely
|
|
107
|
+
# identifies the current version of the resource.
|
|
108
|
+
# Options:
|
|
109
|
+
# :weak :: Use a weak cache validator (a strong cache validator is the default)
|
|
110
|
+
# :new_resource :: Whether this etag should match an etag of * (true for POST, false otherwise)
|
|
111
|
+
#
|
|
112
|
+
# When the current request includes an If-None-Match header with a
|
|
113
|
+
# matching etag, immediately returns a response with a 304 or 412 status,
|
|
114
|
+
# depending on the request method.
|
|
115
|
+
#
|
|
116
|
+
# When the current request includes an If-Match header with a
|
|
117
|
+
# etag that doesn't match, immediately returns a response with a 412 status.
|
|
118
|
+
def etag(value, opts=OPTS)
|
|
119
|
+
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
|
|
120
|
+
weak = opts[:weak]
|
|
121
|
+
new_resource = opts.fetch(:new_resource){post?}
|
|
122
|
+
|
|
123
|
+
res = response
|
|
124
|
+
e = env
|
|
125
|
+
res[RodaResponseHeaders::ETAG] = etag = "#{'W/' if weak}\"#{value}\""
|
|
126
|
+
status = res.status
|
|
127
|
+
|
|
128
|
+
if (!status || (status >= 200 && status < 300) || status == 304)
|
|
129
|
+
if etag_matches?(e['HTTP_IF_NONE_MATCH'], etag, new_resource)
|
|
130
|
+
res.status = (request_method =~ /\AGET|HEAD|OPTIONS|TRACE\z/i ? 304 : 412)
|
|
131
|
+
halt
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if ifm = e['HTTP_IF_MATCH']
|
|
135
|
+
unless etag_matches?(ifm, etag, new_resource)
|
|
136
|
+
res.status = 412
|
|
137
|
+
halt
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
# Helper method checking if a ETag value list includes the current ETag.
|
|
146
|
+
def etag_matches?(list, etag, new_resource)
|
|
147
|
+
return unless list
|
|
148
|
+
return !new_resource if list == '*'
|
|
149
|
+
list.to_s.split(/\s*,\s*/).include?(etag)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Helper method parsing a time value from an HTTP header, returning the
|
|
153
|
+
# time as an integer.
|
|
154
|
+
def time_from_header(t)
|
|
155
|
+
Time.httpdate(t).to_i if t
|
|
156
|
+
rescue ArgumentError
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
module ResponseMethods
|
|
161
|
+
# Specify response freshness policy for using the Cache-Control header.
|
|
162
|
+
# Options can can any non-value directives (:public, :private, :no_cache,
|
|
163
|
+
# :no_store, :must_revalidate, :proxy_revalidate), with true as the value.
|
|
164
|
+
# Options can also contain value directives (:max_age, :s_maxage).
|
|
165
|
+
#
|
|
166
|
+
# response.cache_control public: true, max_age: 60
|
|
167
|
+
# # => Cache-Control: public, max-age=60
|
|
168
|
+
#
|
|
169
|
+
# See RFC 2616 / 14.9 for more on standard cache control directives:
|
|
170
|
+
# http://tools.ietf.org/html/rfc2616#section-14.9.1
|
|
171
|
+
def cache_control(opts)
|
|
172
|
+
values = []
|
|
173
|
+
opts.each do |k, v|
|
|
174
|
+
next unless v
|
|
175
|
+
k = k.to_s.tr('_', '-')
|
|
176
|
+
values << (v == true ? k : "#{k}=#{v}")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
@headers[RodaResponseHeaders::CACHE_CONTROL] = values.join(', ') unless values.empty?
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Set Cache-Control header with the max_age given. max_age should
|
|
183
|
+
# be an integer number of seconds that the current request should be
|
|
184
|
+
# cached for. Also sets the Expires header, useful if you have
|
|
185
|
+
# HTTP 1.0 clients (Cache-Control is an HTTP 1.1 header).
|
|
186
|
+
def expires(max_age, opts=OPTS)
|
|
187
|
+
cache_control(Hash[opts].merge!(:max_age=>max_age))
|
|
188
|
+
@headers[RodaResponseHeaders::EXPIRES] = (Time.now + max_age).httpdate
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Remove Content-Type and Content-Length for 304 responses.
|
|
192
|
+
def finish
|
|
193
|
+
a = super
|
|
194
|
+
if a[0] == 304
|
|
195
|
+
h = a[1]
|
|
196
|
+
h.delete(RodaResponseHeaders::CONTENT_TYPE)
|
|
197
|
+
h.delete(RodaResponseHeaders::CONTENT_LENGTH)
|
|
198
|
+
end
|
|
199
|
+
a
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
register_plugin(:caching, Caching)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The capture_erb plugin allows you to capture the content of a block
|
|
7
|
+
# in an ERB template, and return it as a value, instead of
|
|
8
|
+
# injecting the template block into the template output.
|
|
9
|
+
#
|
|
10
|
+
# <% value = capture_erb do %>
|
|
11
|
+
# Some content here.
|
|
12
|
+
# <% end %>
|
|
13
|
+
#
|
|
14
|
+
# +capture_erb+ can be used inside other methods that are called
|
|
15
|
+
# inside templates. It can be combined with the inject_erb plugin
|
|
16
|
+
# to wrap template blocks with arbitrary output and then inject the
|
|
17
|
+
# wrapped output into the template.
|
|
18
|
+
#
|
|
19
|
+
# If the output buffer object responds to +capture+ and is not
|
|
20
|
+
# an instance of String (e.g. when +erubi/capture_block+ is being
|
|
21
|
+
# used as the template engine), this will call +capture+ on the
|
|
22
|
+
# output buffer object, instead of setting the output buffer object
|
|
23
|
+
# temporarily to a new object.
|
|
24
|
+
#
|
|
25
|
+
# By default, capture_erb returns the value of the block, converted
|
|
26
|
+
# to a string. However, that can cause issues with code such as:
|
|
27
|
+
#
|
|
28
|
+
# <% value = capture_erb do %>
|
|
29
|
+
# Some content here.
|
|
30
|
+
# <% if something %>
|
|
31
|
+
# Some more content here.
|
|
32
|
+
# <% end %>
|
|
33
|
+
# <% end %>
|
|
34
|
+
#
|
|
35
|
+
# In this case, the block may return nil, instead of the content of
|
|
36
|
+
# the template. To handle this case, you can provide the
|
|
37
|
+
# <tt>returns: :buffer</tt> option when calling the method (to handle
|
|
38
|
+
# that specific call, or when loading the plugin (to default to that
|
|
39
|
+
# behavior). Note that if the output buffer object responds to
|
|
40
|
+
# +capture+ and is not an instance of String, the <tt>returns: :buffer</tt>
|
|
41
|
+
# behavior is the default and cannot be changed.
|
|
42
|
+
module CaptureERB
|
|
43
|
+
def self.load_dependencies(app, opts=OPTS)
|
|
44
|
+
app.plugin :render
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Support <tt>returns: :buffer</tt> to default to returning buffer
|
|
48
|
+
# object.
|
|
49
|
+
def self.configure(app, opts=OPTS)
|
|
50
|
+
# RODA4: make returns: :buffer the default behavior
|
|
51
|
+
app.opts[:capture_erb_returns] = opts[:returns] if opts.has_key?(:returns)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
module InstanceMethods
|
|
55
|
+
# Temporarily replace the ERB output buffer
|
|
56
|
+
# with an empty string, and then yield to the block.
|
|
57
|
+
# Return the value of the block, converted to a string.
|
|
58
|
+
# Restore the previous ERB output buffer before returning.
|
|
59
|
+
#
|
|
60
|
+
# Options:
|
|
61
|
+
# :returns :: If set to :buffer, returns the value of the
|
|
62
|
+
# template output variable, instead of the return
|
|
63
|
+
# value of the block converted to a string. This
|
|
64
|
+
# is the default behavior if the template output
|
|
65
|
+
# variable supports the +capture+ method and is not
|
|
66
|
+
# a String instance.
|
|
67
|
+
def capture_erb(opts=OPTS, &block)
|
|
68
|
+
outvar = render_opts[:template_opts][:outvar]
|
|
69
|
+
buf_was = instance_variable_get(outvar)
|
|
70
|
+
|
|
71
|
+
if buf_was.respond_to?(:capture) && !buf_was.instance_of?(String)
|
|
72
|
+
buf_was.capture(&block)
|
|
73
|
+
else
|
|
74
|
+
returns = opts.fetch(:returns) { self.opts[:capture_erb_returns] }
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
instance_variable_set(outvar, String.new)
|
|
78
|
+
if returns == :buffer
|
|
79
|
+
yield
|
|
80
|
+
instance_variable_get(outvar).to_s
|
|
81
|
+
else
|
|
82
|
+
yield.to_s
|
|
83
|
+
end
|
|
84
|
+
ensure
|
|
85
|
+
instance_variable_set(outvar, buf_was) if outvar && buf_was
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
register_plugin(:capture_erb, CaptureERB)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# frozen-string-literal: true
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
class Roda
|
|
5
|
+
module RodaPlugins
|
|
6
|
+
# The chunked plugin allows you to stream rendered views to clients.
|
|
7
|
+
# This can significantly improve performance of page rendering on the
|
|
8
|
+
# client, as it flushes the headers and top part of the layout template
|
|
9
|
+
# (generally containing references to the stylesheet and javascript assets)
|
|
10
|
+
# before rendering the content template.
|
|
11
|
+
#
|
|
12
|
+
# This allows the client to fetch the assets while the template is still
|
|
13
|
+
# being rendered. Additionally, this plugin makes it easy to defer
|
|
14
|
+
# executing code required to render the content template until after
|
|
15
|
+
# the top part of the layout has been flushed, so the client can fetch the
|
|
16
|
+
# assets while the application is still doing the necessary processing in
|
|
17
|
+
# order to render the content template, such as retrieving values from a
|
|
18
|
+
# database.
|
|
19
|
+
#
|
|
20
|
+
# There are a couple disadvantages of streaming. First is that the layout
|
|
21
|
+
# must be rendered before the content, so any state changes made in your
|
|
22
|
+
# content template will not affect the layout template. Second, error
|
|
23
|
+
# handling is reduced, since if an error occurs while rendering a template,
|
|
24
|
+
# a successful response code has already been sent.
|
|
25
|
+
#
|
|
26
|
+
# To use chunked encoding for a response, just call the chunked method
|
|
27
|
+
# instead of view:
|
|
28
|
+
#
|
|
29
|
+
# r.root do
|
|
30
|
+
# chunked(:index)
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# If you want to execute code after flushing the top part of the layout
|
|
34
|
+
# template, but before rendering the content template, pass a block to
|
|
35
|
+
# chunked:
|
|
36
|
+
#
|
|
37
|
+
# r.root do
|
|
38
|
+
# chunked(:index) do
|
|
39
|
+
# # expensive calculation here
|
|
40
|
+
# end
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# You can also call delay manually with a block, and the execution of the
|
|
44
|
+
# block will be delayed until rendering the content template. This is
|
|
45
|
+
# useful if you want to delay execution for all routes under a branch:
|
|
46
|
+
#
|
|
47
|
+
# r.on 'albums', Integer do |album_id|
|
|
48
|
+
# delay do
|
|
49
|
+
# @album = Album[album_id]
|
|
50
|
+
# end
|
|
51
|
+
# r.get 'info' do
|
|
52
|
+
# chunked(:info)
|
|
53
|
+
# end
|
|
54
|
+
# r.get 'tracks' do
|
|
55
|
+
# chunked(:tracks)
|
|
56
|
+
# end
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# If you want to chunk all responses, pass the :chunk_by_default option
|
|
60
|
+
# when loading the plugin:
|
|
61
|
+
#
|
|
62
|
+
# plugin :chunked, chunk_by_default: true
|
|
63
|
+
#
|
|
64
|
+
# then you can just use the normal view method:
|
|
65
|
+
#
|
|
66
|
+
# r.root do
|
|
67
|
+
# view(:index)
|
|
68
|
+
# end
|
|
69
|
+
#
|
|
70
|
+
# and it will chunk the response. Note that you still need to call
|
|
71
|
+
# chunked if you want to pass a block of code to be executed after flushing
|
|
72
|
+
# the layout and before rendering the content template. Also, before you
|
|
73
|
+
# enable chunking by default, you need to make sure that none of your
|
|
74
|
+
# content templates make state changes that affect the layout template.
|
|
75
|
+
# Additionally, make sure nowhere in your app are you doing any processing
|
|
76
|
+
# after the call to view.
|
|
77
|
+
#
|
|
78
|
+
# If you use :chunk_by_default, but want to turn off chunking for a view,
|
|
79
|
+
# call no_chunk!:
|
|
80
|
+
#
|
|
81
|
+
# r.root do
|
|
82
|
+
# no_chunk!
|
|
83
|
+
# view(:index)
|
|
84
|
+
# end
|
|
85
|
+
#
|
|
86
|
+
# Inside your layout or content templates, you can call the flush method
|
|
87
|
+
# to flush the current result of the template to the user, useful for
|
|
88
|
+
# streaming large datasets.
|
|
89
|
+
#
|
|
90
|
+
# <% (1..100).each do |i| %>
|
|
91
|
+
# <%= i %>
|
|
92
|
+
# <% sleep 0.1 %>
|
|
93
|
+
# <% flush %>
|
|
94
|
+
# <% end %>
|
|
95
|
+
#
|
|
96
|
+
# Note that you should not call flush from inside subtemplates of the
|
|
97
|
+
# content or layout templates, unless you are also calling flush directly
|
|
98
|
+
# before rendering the subtemplate, and also directly injecting the
|
|
99
|
+
# subtemplate into the current template without modification. So if you
|
|
100
|
+
# are using the above template code in a subtemplate, in your content
|
|
101
|
+
# template you should do:
|
|
102
|
+
#
|
|
103
|
+
# <% flush %><%= render(:subtemplate) %>
|
|
104
|
+
#
|
|
105
|
+
# If you want to use chunked encoding when rendering a template, but don't
|
|
106
|
+
# want to use a layout, pass the <tt>layout: false</tt> option to chunked.
|
|
107
|
+
#
|
|
108
|
+
# r.root do
|
|
109
|
+
# chunked(:index, layout: false)
|
|
110
|
+
# end
|
|
111
|
+
#
|
|
112
|
+
# In order to handle errors in chunked responses, you can override the
|
|
113
|
+
# handle_chunk_error method:
|
|
114
|
+
#
|
|
115
|
+
# def handle_chunk_error(e)
|
|
116
|
+
# env['rack.logger'].error(e)
|
|
117
|
+
# end
|
|
118
|
+
#
|
|
119
|
+
# It is possible to set @_out_buf to an error notification and call
|
|
120
|
+
# flush to output the message to the client inside handle_chunk_error.
|
|
121
|
+
#
|
|
122
|
+
# In order for chunking to work, you must make sure that no proxies between
|
|
123
|
+
# the application and the client buffer responses.
|
|
124
|
+
#
|
|
125
|
+
# If you are using nginx and have it set to buffer proxy responses by
|
|
126
|
+
# default, you can turn this off on a per response basis using the
|
|
127
|
+
# X-Accel-Buffering header. To set this header or similar headers for
|
|
128
|
+
# all chunked responses, pass a :headers option when loading the plugin:
|
|
129
|
+
#
|
|
130
|
+
# plugin :chunked, headers: {'X-Accel-Buffering'=>'no'}
|
|
131
|
+
#
|
|
132
|
+
# By default, this plugin does not use Transfer-Encoding: chunked, it only
|
|
133
|
+
# returns a body that will stream the response in chunks. If you would like
|
|
134
|
+
# to force the use of Transfer-Encoding: chunked, you can use the
|
|
135
|
+
# :force_chunked_encoding plugin option. If using the
|
|
136
|
+
# :force_chunked_encoding plugin option, chunking will only be used for
|
|
137
|
+
# HTTP/1.1 requests since Transfer-Encoding: chunked is only supported
|
|
138
|
+
# in HTTP/1.1 (non-HTTP/1.1 requests will have behavior similar to
|
|
139
|
+
# calling no_chunk!).
|
|
140
|
+
#
|
|
141
|
+
# The chunked plugin requires the render plugin, and only works for
|
|
142
|
+
# template engines that store their template output variable in
|
|
143
|
+
# @_out_buf. Also, it only works if the content template is directly
|
|
144
|
+
# injected into the layout template without modification.
|
|
145
|
+
#
|
|
146
|
+
# If using the chunked plugin with the flash plugin, make sure you
|
|
147
|
+
# call the flash method early in your route block. If the flash
|
|
148
|
+
# method is not called until template rendering, the flash may not be
|
|
149
|
+
# rotated.
|
|
150
|
+
module Chunked
|
|
151
|
+
# Depend on the render plugin
|
|
152
|
+
def self.load_dependencies(app, opts=OPTS)
|
|
153
|
+
app.plugin :render
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Set plugin specific options. Options:
|
|
157
|
+
# :chunk_by_default :: chunk all calls to view by default
|
|
158
|
+
# :headers :: Set default additional headers to use when calling view
|
|
159
|
+
def self.configure(app, opts=OPTS)
|
|
160
|
+
app.opts[:chunk_by_default] = opts[:chunk_by_default]
|
|
161
|
+
app.opts[:force_chunked_encoding] = opts[:force_chunked_encoding]
|
|
162
|
+
if opts[:headers]
|
|
163
|
+
app.opts[:chunk_headers] = (app.opts[:chunk_headers] || {}).merge(opts[:headers]).freeze
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Rack response body instance for chunked responses using
|
|
168
|
+
# Transfer-Encoding: chunked.
|
|
169
|
+
class Body
|
|
170
|
+
# Save the scope of the current request handling.
|
|
171
|
+
def initialize(scope)
|
|
172
|
+
@scope = scope
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# For each response chunk yielded by the scope,
|
|
176
|
+
# yield it it to the caller in chunked format, starting
|
|
177
|
+
# with the size of the request in ASCII hex format, then
|
|
178
|
+
# the chunk. After all chunks have been yielded, yield
|
|
179
|
+
# a 0 sized chunk to finish the response.
|
|
180
|
+
def each
|
|
181
|
+
@scope.each_chunk do |chunk|
|
|
182
|
+
next if !chunk || chunk.empty?
|
|
183
|
+
yield("%x\r\n" % chunk.bytesize)
|
|
184
|
+
yield(chunk)
|
|
185
|
+
yield("\r\n")
|
|
186
|
+
end
|
|
187
|
+
ensure
|
|
188
|
+
yield("0\r\n\r\n")
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Rack response body instance for chunked responses not
|
|
193
|
+
# using Transfer-Encoding: chunked.
|
|
194
|
+
class StreamBody
|
|
195
|
+
# Save the scope of the current request handling.
|
|
196
|
+
def initialize(scope)
|
|
197
|
+
@scope = scope
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Yield each non-empty chunk as the body.
|
|
201
|
+
def each(&block)
|
|
202
|
+
@scope.each_chunk do |chunk|
|
|
203
|
+
yield chunk if chunk && !chunk.empty?
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
module InstanceMethods
|
|
209
|
+
# Disable chunking for the current request. Mostly useful when
|
|
210
|
+
# chunking is turned on by default.
|
|
211
|
+
def no_chunk!
|
|
212
|
+
@_chunked = false
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# If chunking by default, call chunked if it hasn't yet been
|
|
216
|
+
# called and chunking is not specifically disabled.
|
|
217
|
+
def view(*a)
|
|
218
|
+
if opts[:chunk_by_default] && !defined?(@_chunked) && !defined?(yield)
|
|
219
|
+
chunked(*a)
|
|
220
|
+
else
|
|
221
|
+
super
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Render a response to the user in chunks. See Chunked for
|
|
226
|
+
# an overview. If a block is given, it is passed to #delay.
|
|
227
|
+
def chunked(template, opts=OPTS, &block)
|
|
228
|
+
unless defined?(@_chunked)
|
|
229
|
+
@_chunked = !self.opts[:force_chunked_encoding] || @_request.http_version == "HTTP/1.1"
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
if block
|
|
233
|
+
delay(&block)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
unless @_chunked
|
|
237
|
+
# If chunking is disabled, do a normal rendering of the view.
|
|
238
|
+
run_delayed_blocks
|
|
239
|
+
return view(template, opts)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
if template.is_a?(Hash)
|
|
243
|
+
if opts.empty?
|
|
244
|
+
opts = template
|
|
245
|
+
else
|
|
246
|
+
opts = Hash[opts].merge!(template)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Hack so that the arguments don't need to be passed
|
|
251
|
+
# through the response and body objects.
|
|
252
|
+
@_each_chunk_args = [template, opts]
|
|
253
|
+
|
|
254
|
+
res = response
|
|
255
|
+
headers = res.headers
|
|
256
|
+
if chunk_headers = self.opts[:chunk_headers]
|
|
257
|
+
headers.merge!(chunk_headers)
|
|
258
|
+
end
|
|
259
|
+
if self.opts[:force_chunked_encoding]
|
|
260
|
+
res[RodaResponseHeaders::TRANSFER_ENCODING] = 'chunked'
|
|
261
|
+
body = Body.new(self)
|
|
262
|
+
else
|
|
263
|
+
body = StreamBody.new(self)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
throw :halt, res.finish_with_body(body)
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Delay the execution of the block until right before the
|
|
270
|
+
# content template is to be rendered.
|
|
271
|
+
def delay(&block)
|
|
272
|
+
raise RodaError, "must pass a block to Roda#delay" unless block
|
|
273
|
+
(@_delays ||= []) << block
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Yield each chunk of the template rendering separately.
|
|
277
|
+
def each_chunk
|
|
278
|
+
response.body.each{|s| yield s}
|
|
279
|
+
|
|
280
|
+
template, opts = @_each_chunk_args
|
|
281
|
+
|
|
282
|
+
# Use a lambda for the flusher, so that a call to flush
|
|
283
|
+
# by a template can result in this method yielding a chunk
|
|
284
|
+
# of the response.
|
|
285
|
+
@_flusher = lambda do
|
|
286
|
+
yield @_out_buf
|
|
287
|
+
@_out_buf = String.new
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
if layout_opts = view_layout_opts(opts)
|
|
291
|
+
@_out_buf = render_template(layout_opts) do
|
|
292
|
+
flush
|
|
293
|
+
run_delayed_blocks
|
|
294
|
+
yield opts[:content] || render_template(template, opts)
|
|
295
|
+
nil
|
|
296
|
+
end
|
|
297
|
+
else
|
|
298
|
+
run_delayed_blocks
|
|
299
|
+
yield view(template, opts)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
flush
|
|
303
|
+
rescue => e
|
|
304
|
+
handle_chunk_error(e)
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# By default, raise the exception.
|
|
308
|
+
def handle_chunk_error(e)
|
|
309
|
+
raise e
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Call the flusher if one is defined. If one is not defined, this
|
|
313
|
+
# is a no-op, so flush can be used inside views without breaking
|
|
314
|
+
# things if chunking is not used.
|
|
315
|
+
def flush
|
|
316
|
+
@_flusher.call if @_flusher
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
private
|
|
320
|
+
|
|
321
|
+
# Run all delayed blocks
|
|
322
|
+
def run_delayed_blocks
|
|
323
|
+
return unless @_delays
|
|
324
|
+
@_delays.each(&:call)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
register_plugin(:chunked, Chunked)
|
|
330
|
+
end
|
|
331
|
+
end
|