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.
Files changed (162) hide show
  1. checksums.yaml +7 -0
  2. data/roda-3.105.0/MIT-LICENSE +21 -0
  3. data/roda-3.105.0/lib/roda/cache.rb +42 -0
  4. data/roda-3.105.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
  5. data/roda-3.105.0/lib/roda/plugins/_after_hook.rb +11 -0
  6. data/roda-3.105.0/lib/roda/plugins/_base64.rb +34 -0
  7. data/roda-3.105.0/lib/roda/plugins/_before_hook.rb +41 -0
  8. data/roda-3.105.0/lib/roda/plugins/_optimized_matching.rb +227 -0
  9. data/roda-3.105.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
  10. data/roda-3.105.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
  11. data/roda-3.105.0/lib/roda/plugins/additional_render_engines.rb +61 -0
  12. data/roda-3.105.0/lib/roda/plugins/additional_view_directories.rb +66 -0
  13. data/roda-3.105.0/lib/roda/plugins/all_verbs.rb +50 -0
  14. data/roda-3.105.0/lib/roda/plugins/assets.rb +902 -0
  15. data/roda-3.105.0/lib/roda/plugins/assets_preloading.rb +89 -0
  16. data/roda-3.105.0/lib/roda/plugins/assume_ssl.rb +28 -0
  17. data/roda-3.105.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
  18. data/roda-3.105.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
  19. data/roda-3.105.0/lib/roda/plugins/backtracking_array.rb +88 -0
  20. data/roda-3.105.0/lib/roda/plugins/bearer_token.rb +28 -0
  21. data/roda-3.105.0/lib/roda/plugins/branch_locals.rb +74 -0
  22. data/roda-3.105.0/lib/roda/plugins/break.rb +43 -0
  23. data/roda-3.105.0/lib/roda/plugins/caching.rb +206 -0
  24. data/roda-3.105.0/lib/roda/plugins/capture_erb.rb +94 -0
  25. data/roda-3.105.0/lib/roda/plugins/chunked.rb +331 -0
  26. data/roda-3.105.0/lib/roda/plugins/class_level_routing.rb +111 -0
  27. data/roda-3.105.0/lib/roda/plugins/class_matchers.rb +155 -0
  28. data/roda-3.105.0/lib/roda/plugins/common_logger.rb +80 -0
  29. data/roda-3.105.0/lib/roda/plugins/conditional_sessions.rb +67 -0
  30. data/roda-3.105.0/lib/roda/plugins/content_for.rb +103 -0
  31. data/roda-3.105.0/lib/roda/plugins/content_security_policy.rb +329 -0
  32. data/roda-3.105.0/lib/roda/plugins/cookie_flags.rb +157 -0
  33. data/roda-3.105.0/lib/roda/plugins/cookies.rb +51 -0
  34. data/roda-3.105.0/lib/roda/plugins/csrf.rb +81 -0
  35. data/roda-3.105.0/lib/roda/plugins/custom_block_results.rb +85 -0
  36. data/roda-3.105.0/lib/roda/plugins/custom_matchers.rb +89 -0
  37. data/roda-3.105.0/lib/roda/plugins/default_headers.rb +64 -0
  38. data/roda-3.105.0/lib/roda/plugins/default_status.rb +35 -0
  39. data/roda-3.105.0/lib/roda/plugins/delay_build.rb +18 -0
  40. data/roda-3.105.0/lib/roda/plugins/delegate.rb +77 -0
  41. data/roda-3.105.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
  42. data/roda-3.105.0/lib/roda/plugins/direct_call.rb +38 -0
  43. data/roda-3.105.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
  44. data/roda-3.105.0/lib/roda/plugins/drop_body.rb +43 -0
  45. data/roda-3.105.0/lib/roda/plugins/each_part.rb +87 -0
  46. data/roda-3.105.0/lib/roda/plugins/early_hints.rb +25 -0
  47. data/roda-3.105.0/lib/roda/plugins/empty_root.rb +49 -0
  48. data/roda-3.105.0/lib/roda/plugins/environments.rb +79 -0
  49. data/roda-3.105.0/lib/roda/plugins/erb_h.rb +43 -0
  50. data/roda-3.105.0/lib/roda/plugins/error_email.rb +152 -0
  51. data/roda-3.105.0/lib/roda/plugins/error_handler.rb +133 -0
  52. data/roda-3.105.0/lib/roda/plugins/error_mail.rb +149 -0
  53. data/roda-3.105.0/lib/roda/plugins/exception_page.rb +445 -0
  54. data/roda-3.105.0/lib/roda/plugins/filter_common_logger.rb +46 -0
  55. data/roda-3.105.0/lib/roda/plugins/flash.rb +117 -0
  56. data/roda-3.105.0/lib/roda/plugins/h.rb +59 -0
  57. data/roda-3.105.0/lib/roda/plugins/halt.rb +108 -0
  58. data/roda-3.105.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
  59. data/roda-3.105.0/lib/roda/plugins/hash_branches.rb +145 -0
  60. data/roda-3.105.0/lib/roda/plugins/hash_matcher.rb +39 -0
  61. data/roda-3.105.0/lib/roda/plugins/hash_paths.rb +128 -0
  62. data/roda-3.105.0/lib/roda/plugins/hash_public.rb +125 -0
  63. data/roda-3.105.0/lib/roda/plugins/hash_public_cache.rb +91 -0
  64. data/roda-3.105.0/lib/roda/plugins/hash_routes.rb +304 -0
  65. data/roda-3.105.0/lib/roda/plugins/head.rb +99 -0
  66. data/roda-3.105.0/lib/roda/plugins/header_matchers.rb +88 -0
  67. data/roda-3.105.0/lib/roda/plugins/heartbeat.rb +41 -0
  68. data/roda-3.105.0/lib/roda/plugins/hmac_paths.rb +413 -0
  69. data/roda-3.105.0/lib/roda/plugins/hooks.rb +92 -0
  70. data/roda-3.105.0/lib/roda/plugins/host_authorization.rb +155 -0
  71. data/roda-3.105.0/lib/roda/plugins/host_routing.rb +190 -0
  72. data/roda-3.105.0/lib/roda/plugins/hsts.rb +35 -0
  73. data/roda-3.105.0/lib/roda/plugins/indifferent_params.rb +124 -0
  74. data/roda-3.105.0/lib/roda/plugins/inject_erb.rb +33 -0
  75. data/roda-3.105.0/lib/roda/plugins/invalid_request_body.rb +107 -0
  76. data/roda-3.105.0/lib/roda/plugins/ip_from_header.rb +34 -0
  77. data/roda-3.105.0/lib/roda/plugins/json.rb +119 -0
  78. data/roda-3.105.0/lib/roda/plugins/json_parser.rb +107 -0
  79. data/roda-3.105.0/lib/roda/plugins/link_to.rb +83 -0
  80. data/roda-3.105.0/lib/roda/plugins/mail_processor.rb +629 -0
  81. data/roda-3.105.0/lib/roda/plugins/mailer.rb +283 -0
  82. data/roda-3.105.0/lib/roda/plugins/map_matcher.rb +48 -0
  83. data/roda-3.105.0/lib/roda/plugins/match_affix.rb +64 -0
  84. data/roda-3.105.0/lib/roda/plugins/match_hook.rb +34 -0
  85. data/roda-3.105.0/lib/roda/plugins/match_hook_args.rb +93 -0
  86. data/roda-3.105.0/lib/roda/plugins/middleware.rb +225 -0
  87. data/roda-3.105.0/lib/roda/plugins/middleware_stack.rb +101 -0
  88. data/roda-3.105.0/lib/roda/plugins/module_include.rb +96 -0
  89. data/roda-3.105.0/lib/roda/plugins/multi_public.rb +95 -0
  90. data/roda-3.105.0/lib/roda/plugins/multi_route.rb +156 -0
  91. data/roda-3.105.0/lib/roda/plugins/multi_run.rb +144 -0
  92. data/roda-3.105.0/lib/roda/plugins/multi_view.rb +83 -0
  93. data/roda-3.105.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
  94. data/roda-3.105.0/lib/roda/plugins/named_routes.rb +167 -0
  95. data/roda-3.105.0/lib/roda/plugins/named_templates.rb +102 -0
  96. data/roda-3.105.0/lib/roda/plugins/not_allowed.rb +149 -0
  97. data/roda-3.105.0/lib/roda/plugins/not_found.rb +55 -0
  98. data/roda-3.105.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
  99. data/roda-3.105.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
  100. data/roda-3.105.0/lib/roda/plugins/padrino_render.rb +42 -0
  101. data/roda-3.105.0/lib/roda/plugins/param_matchers.rb +83 -0
  102. data/roda-3.105.0/lib/roda/plugins/params_capturing.rb +116 -0
  103. data/roda-3.105.0/lib/roda/plugins/part.rb +76 -0
  104. data/roda-3.105.0/lib/roda/plugins/partials.rb +74 -0
  105. data/roda-3.105.0/lib/roda/plugins/pass.rb +47 -0
  106. data/roda-3.105.0/lib/roda/plugins/path.rb +255 -0
  107. data/roda-3.105.0/lib/roda/plugins/path_matchers.rb +54 -0
  108. data/roda-3.105.0/lib/roda/plugins/path_rewriter.rb +111 -0
  109. data/roda-3.105.0/lib/roda/plugins/permissions_policy.rb +336 -0
  110. data/roda-3.105.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
  111. data/roda-3.105.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
  112. data/roda-3.105.0/lib/roda/plugins/precompile_templates.rb +156 -0
  113. data/roda-3.105.0/lib/roda/plugins/public.rb +182 -0
  114. data/roda-3.105.0/lib/roda/plugins/r.rb +35 -0
  115. data/roda-3.105.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
  116. data/roda-3.105.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
  117. data/roda-3.105.0/lib/roda/plugins/redirect_path.rb +59 -0
  118. data/roda-3.105.0/lib/roda/plugins/relative_path.rb +74 -0
  119. data/roda-3.105.0/lib/roda/plugins/render.rb +998 -0
  120. data/roda-3.105.0/lib/roda/plugins/render_coverage.rb +105 -0
  121. data/roda-3.105.0/lib/roda/plugins/render_each.rb +224 -0
  122. data/roda-3.105.0/lib/roda/plugins/render_locals.rb +102 -0
  123. data/roda-3.105.0/lib/roda/plugins/request_aref.rb +77 -0
  124. data/roda-3.105.0/lib/roda/plugins/request_headers.rb +81 -0
  125. data/roda-3.105.0/lib/roda/plugins/response_attachment.rb +94 -0
  126. data/roda-3.105.0/lib/roda/plugins/response_content_type.rb +79 -0
  127. data/roda-3.105.0/lib/roda/plugins/response_request.rb +29 -0
  128. data/roda-3.105.0/lib/roda/plugins/route_block_args.rb +50 -0
  129. data/roda-3.105.0/lib/roda/plugins/route_csrf.rb +388 -0
  130. data/roda-3.105.0/lib/roda/plugins/run_append_slash.rb +51 -0
  131. data/roda-3.105.0/lib/roda/plugins/run_handler.rb +51 -0
  132. data/roda-3.105.0/lib/roda/plugins/run_require_slash.rb +46 -0
  133. data/roda-3.105.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
  134. data/roda-3.105.0/lib/roda/plugins/send_file.rb +127 -0
  135. data/roda-3.105.0/lib/roda/plugins/sessions.rb +537 -0
  136. data/roda-3.105.0/lib/roda/plugins/shared_vars.rb +86 -0
  137. data/roda-3.105.0/lib/roda/plugins/sinatra_helpers.rb +443 -0
  138. data/roda-3.105.0/lib/roda/plugins/slash_path_empty.rb +26 -0
  139. data/roda-3.105.0/lib/roda/plugins/static.rb +42 -0
  140. data/roda-3.105.0/lib/roda/plugins/static_routing.rb +89 -0
  141. data/roda-3.105.0/lib/roda/plugins/status_303.rb +34 -0
  142. data/roda-3.105.0/lib/roda/plugins/status_handler.rb +86 -0
  143. data/roda-3.105.0/lib/roda/plugins/streaming.rb +170 -0
  144. data/roda-3.105.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
  145. data/roda-3.105.0/lib/roda/plugins/symbol_matchers.rb +188 -0
  146. data/roda-3.105.0/lib/roda/plugins/symbol_status.rb +32 -0
  147. data/roda-3.105.0/lib/roda/plugins/symbol_views.rb +37 -0
  148. data/roda-3.105.0/lib/roda/plugins/timestamp_public.rb +78 -0
  149. data/roda-3.105.0/lib/roda/plugins/type_routing.rb +215 -0
  150. data/roda-3.105.0/lib/roda/plugins/typecast_params.rb +1183 -0
  151. data/roda-3.105.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
  152. data/roda-3.105.0/lib/roda/plugins/unescape_path.rb +41 -0
  153. data/roda-3.105.0/lib/roda/plugins/view_options.rb +201 -0
  154. data/roda-3.105.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
  155. data/roda-3.105.0/lib/roda/plugins.rb +68 -0
  156. data/roda-3.105.0/lib/roda/request.rb +722 -0
  157. data/roda-3.105.0/lib/roda/response.rb +221 -0
  158. data/roda-3.105.0/lib/roda/session_middleware.rb +183 -0
  159. data/roda-3.105.0/lib/roda/version.rb +18 -0
  160. data/roda-3.105.0/lib/roda.rb +586 -0
  161. data/ultra-lite-hub.gemspec +12 -0
  162. 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