hanami-controller 1.3.0 → 2.0.0.alpha2

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +83 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +297 -538
  5. data/hanami-controller.gemspec +6 -5
  6. data/lib/hanami/action.rb +129 -73
  7. data/lib/hanami/action/application_action.rb +111 -0
  8. data/lib/hanami/action/application_configuration.rb +92 -0
  9. data/lib/hanami/action/application_configuration/cookies.rb +29 -0
  10. data/lib/hanami/action/application_configuration/sessions.rb +46 -0
  11. data/lib/hanami/action/base_params.rb +2 -2
  12. data/lib/hanami/action/cache.rb +1 -139
  13. data/lib/hanami/action/cache/cache_control.rb +4 -4
  14. data/lib/hanami/action/cache/conditional_get.rb +7 -2
  15. data/lib/hanami/action/cache/directives.rb +1 -1
  16. data/lib/hanami/action/cache/expires.rb +3 -3
  17. data/lib/hanami/action/configuration.rb +430 -0
  18. data/lib/hanami/action/cookie_jar.rb +3 -3
  19. data/lib/hanami/action/cookies.rb +3 -62
  20. data/lib/hanami/action/csrf_protection.rb +214 -0
  21. data/lib/hanami/action/flash.rb +102 -207
  22. data/lib/hanami/action/glue.rb +5 -31
  23. data/lib/hanami/action/halt.rb +12 -0
  24. data/lib/hanami/action/mime.rb +78 -485
  25. data/lib/hanami/action/params.rb +3 -3
  26. data/lib/hanami/action/rack/file.rb +1 -1
  27. data/lib/hanami/action/request.rb +30 -20
  28. data/lib/hanami/action/response.rb +193 -0
  29. data/lib/hanami/action/session.rb +11 -128
  30. data/lib/hanami/action/standalone_action.rb +581 -0
  31. data/lib/hanami/action/validatable.rb +2 -2
  32. data/lib/hanami/action/view_name_inferrer.rb +46 -0
  33. data/lib/hanami/controller.rb +0 -227
  34. data/lib/hanami/controller/version.rb +1 -1
  35. data/lib/hanami/http/status.rb +2 -2
  36. metadata +47 -30
  37. data/lib/hanami-controller.rb +0 -1
  38. data/lib/hanami/action/callable.rb +0 -92
  39. data/lib/hanami/action/callbacks.rb +0 -214
  40. data/lib/hanami/action/configurable.rb +0 -50
  41. data/lib/hanami/action/exposable.rb +0 -126
  42. data/lib/hanami/action/exposable/guard.rb +0 -104
  43. data/lib/hanami/action/head.rb +0 -121
  44. data/lib/hanami/action/rack.rb +0 -399
  45. data/lib/hanami/action/rack/callable.rb +0 -47
  46. data/lib/hanami/action/redirect.rb +0 -59
  47. data/lib/hanami/action/throwable.rb +0 -196
  48. data/lib/hanami/controller/configuration.rb +0 -763
@@ -1,104 +0,0 @@
1
- require 'hanami/controller/error'
2
-
3
- module Hanami
4
- module Controller
5
- # Exposure of reserved words
6
- #
7
- # @since 0.7.1
8
- class IllegalExposureError < Error
9
- end
10
- end
11
-
12
- module Action
13
- module Exposable
14
- # Guard for Exposures API.
15
- # Prevents exposure of reserved words
16
- #
17
- # @since 0.7.1
18
- # @api private
19
- #
20
- # @see Hanami::Action::Exposable::Guard::ClassMethods#expose
21
- # @see Hanami::Action::Exposable::Guard::ClassMethods#reserved_word?
22
- module Guard
23
- # Override Ruby's hook for modules.
24
- # It prepends a guard for the exposures logic
25
- #
26
- # @param base [Class] the target action
27
- #
28
- # @since 0.7.1
29
- # @api private
30
- #
31
- # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
32
- def self.included(base)
33
- class << base
34
- prepend ClassMethods
35
- end
36
- end
37
-
38
- # Exposures API Guard class methods
39
- #
40
- # @since 0.7.1
41
- # @api private
42
- module ClassMethods
43
- # Prevents exposure if names contain a reserved word.
44
- #
45
- # @param names [Array<Symbol>] the name(s) of the attribute(s) to be
46
- # exposed
47
- #
48
- # @return [void]
49
- #
50
- # @since 0.7.1
51
- # @api private
52
- def expose(*names)
53
- detect_reserved_words!(names)
54
-
55
- super
56
- end
57
-
58
- private
59
-
60
- # Raises error if given names contain a reserved word.
61
- #
62
- # @param names [Array<Symbol>] a list of names to be checked.
63
- #
64
- # @return [void]
65
- #
66
- # @raise [IllegalExposeError] if names contain one or more of reserved
67
- # words
68
- #
69
- # @since 0.7.1
70
- # @api private
71
- def detect_reserved_words!(names)
72
- names.each do |name|
73
- if reserved_word?(name)
74
- raise Hanami::Controller::IllegalExposureError.new("#{name} is a reserved word. It cannot be exposed")
75
- end
76
- end
77
- end
78
-
79
- # Checks if a string is a reserved word
80
- #
81
- # Reserved word is a name of the method defined in one of the modules
82
- # of a given namespace.
83
- #
84
- # @param name [Symbol] the word to be checked
85
- # @param namespace [String] the namespace containing internal modules
86
- #
87
- # @return [true, false]
88
- #
89
- # @since 0.7.1
90
- # @api private
91
- def reserved_word?(name, namespace = 'Hanami')
92
- if method_defined?(name) || private_method_defined?(name)
93
- method_owner = instance_method(name).owner
94
-
95
- Utils::String.namespace(method_owner) == namespace
96
- else
97
- false
98
- end
99
- end
100
- end
101
- end
102
- end
103
- end
104
- end
@@ -1,121 +0,0 @@
1
- module Hanami
2
- module Action
3
- # Ensures to not send body or headers for HEAD requests and/or for status
4
- # codes that doesn't allow them.
5
- #
6
- # @since 0.3.2
7
- #
8
- # @see http://www.ietf.org/rfc/rfc2616.txt
9
- module Head
10
-
11
- # Status codes that by RFC must not include a message body
12
- #
13
- # @since 0.3.2
14
- # @api private
15
- HTTP_STATUSES_WITHOUT_BODY = Set.new((100..199).to_a << 204 << 205 << 304).freeze
16
-
17
-
18
- # Entity headers allowed in blank body responses, according to
19
- # RFC 2616 - Section 10 (HTTP 1.1).
20
- #
21
- # "The response MAY include new or updated metainformation in the form
22
- # of entity-headers".
23
- #
24
- # @since 0.4.0
25
- # @api private
26
- #
27
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5
28
- # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html
29
- ENTITY_HEADERS = {
30
- 'Allow' => true,
31
- 'Content-Encoding' => true,
32
- 'Content-Language' => true,
33
- 'Content-Location' => true,
34
- 'Content-MD5' => true,
35
- 'Content-Range' => true,
36
- 'Expires' => true,
37
- 'Last-Modified' => true,
38
- 'extension-header' => true
39
- }.freeze
40
-
41
- # Ensures to not send body or headers for HEAD requests and/or for status
42
- # codes that doesn't allow them.
43
- #
44
- # @since 0.3.2
45
- # @api private
46
- #
47
- # @see Hanami::Action#finish
48
- def finish
49
- super
50
-
51
- if _requires_no_body?
52
- @_body = nil
53
- @headers.reject! {|header,_| !keep_response_header?(header) }
54
- end
55
- end
56
-
57
- protected
58
- # @since 0.3.2
59
- # @api private
60
- def _requires_no_body?
61
- HTTP_STATUSES_WITHOUT_BODY.include?(@_status) || head?
62
- end
63
-
64
- private
65
- # According to RFC 2616, when a response MUST have an empty body, it only
66
- # allows Entity Headers.
67
- #
68
- # For instance, a <tt>204</tt> doesn't allow <tt>Content-Type</tt> or any
69
- # other custom header.
70
- #
71
- # This restriction is enforced by <tt>Hanami::Action::Head#finish</tt>.
72
- #
73
- # However, there are cases that demand to bypass this rule to set meta
74
- # informations via headers.
75
- #
76
- # An example is a <tt>DELETE</tt> request for a JSON API application.
77
- # It returns a <tt>204</tt> but still wants to specify the rate limit
78
- # quota via <tt>X-Rate-Limit</tt>.
79
- #
80
- # @since 0.5.0
81
- #
82
- # @see Hanami::Action::HEAD#finish
83
- #
84
- # @example
85
- # require 'hanami/controller'
86
- #
87
- # module Books
88
- # class Destroy
89
- # include Hanami::Action
90
- #
91
- # def call(params)
92
- # # ...
93
- # self.headers.merge!(
94
- # 'Last-Modified' => 'Fri, 27 Nov 2015 13:32:36 GMT',
95
- # 'X-Rate-Limit' => '4000',
96
- # 'Content-Type' => 'application/json',
97
- # 'X-No-Pass' => 'true'
98
- # )
99
- #
100
- # self.status = 204
101
- # end
102
- #
103
- # private
104
- #
105
- # def keep_response_header?(header)
106
- # super || header == 'X-Rate-Limit'
107
- # end
108
- # end
109
- # end
110
- #
111
- # # Only the following headers will be sent:
112
- # # * Last-Modified - because we used `super' in the method that respects the HTTP RFC
113
- # # * X-Rate-Limit - because we explicitely allow it
114
- #
115
- # # Both Content-Type and X-No-Pass are removed because they're not allowed
116
- def keep_response_header?(header)
117
- ENTITY_HEADERS.include?(header)
118
- end
119
- end
120
- end
121
- end
@@ -1,399 +0,0 @@
1
- require 'securerandom'
2
- require 'hanami/action/request'
3
- require 'hanami/action/base_params'
4
- require 'hanami/action/rack/callable'
5
- require 'hanami/action/rack/file'
6
- require 'hanami/utils/deprecation'
7
-
8
- module Hanami
9
- module Action
10
- # Rack integration API
11
- #
12
- # @since 0.1.0
13
- module Rack
14
- # Rack SPEC response code
15
- #
16
- # @since 1.0.0
17
- # @api private
18
- RESPONSE_CODE = 0
19
-
20
- # Rack SPEC response headers
21
- #
22
- # @since 1.0.0
23
- # @api private
24
- RESPONSE_HEADERS = 1
25
-
26
- # Rack SPEC response body
27
- #
28
- # @since 1.0.0
29
- # @api private
30
- RESPONSE_BODY = 2
31
-
32
- # The default HTTP response code
33
- #
34
- # @since 0.1.0
35
- # @api private
36
- DEFAULT_RESPONSE_CODE = 200
37
-
38
- # Not Found
39
- #
40
- # @since 1.0.0
41
- # @api private
42
- NOT_FOUND = 404
43
-
44
- # The default Rack response body
45
- #
46
- # @since 0.1.0
47
- # @api private
48
- DEFAULT_RESPONSE_BODY = []
49
-
50
- # The default HTTP Request ID length
51
- #
52
- # @since 0.3.0
53
- # @api private
54
- #
55
- # @see Hanami::Action::Rack#request_id
56
- DEFAULT_REQUEST_ID_LENGTH = 16
57
-
58
- # The request method
59
- #
60
- # @since 0.3.2
61
- # @api private
62
- REQUEST_METHOD = 'REQUEST_METHOD'.freeze
63
-
64
- # The Content-Length HTTP header
65
- #
66
- # @since 1.0.0
67
- # @api private
68
- CONTENT_LENGTH = 'Content-Length'.freeze
69
-
70
- # The non-standard HTTP header to pass the control over when a resource
71
- # cannot be found by the current endpoint
72
- #
73
- # @since 1.0.0
74
- # @api private
75
- X_CASCADE = 'X-Cascade'.freeze
76
-
77
- # HEAD request
78
- #
79
- # @since 0.3.2
80
- # @api private
81
- HEAD = 'HEAD'.freeze
82
-
83
- # The key that returns router parsed body from the Rack env
84
- ROUTER_PARSED_BODY = 'router.parsed_body'.freeze
85
-
86
- # Override Ruby's hook for modules.
87
- # It includes basic Hanami::Action modules to the given class.
88
- #
89
- # @param base [Class] the target action
90
- #
91
- # @since 0.1.0
92
- # @api private
93
- #
94
- # @see http://www.ruby-doc.org/core-2.1.2/Module.html#method-i-included
95
- def self.included(base)
96
- base.class_eval do
97
- extend ClassMethods
98
- prepend InstanceMethods
99
- end
100
- end
101
-
102
- # @api private
103
- module ClassMethods
104
- # Build rack builder
105
- #
106
- # @return [Rack::Builder]
107
- # @api private
108
- def rack_builder
109
- @rack_builder ||= begin
110
- extend Hanami::Action::Rack::Callable
111
- rack_builder = ::Rack::Builder.new
112
- rack_builder.run ->(env) { self.new.call(env) }
113
- rack_builder
114
- end
115
- end
116
-
117
- # Use a Rack middleware
118
- #
119
- # The middleware will be used as it is.
120
- #
121
- # At the runtime, the middleware be invoked with the raw Rack env.
122
- #
123
- # Multiple middlewares can be employed, just by using multiple times
124
- # this method.
125
- #
126
- # @param middleware [#call] A Rack middleware
127
- # @param args [Array] Array arguments for middleware
128
- #
129
- # @since 0.2.0
130
- #
131
- # @see Hanami::Action::Callbacks::ClassMethods#before
132
- #
133
- # @example Middleware
134
- # require 'hanami/controller'
135
- #
136
- # module Sessions
137
- # class Create
138
- # include Hanami::Action
139
- # use OmniAuth
140
- #
141
- # def call(params)
142
- # # ...
143
- # end
144
- # end
145
- # end
146
- def use(middleware, *args, &block)
147
- rack_builder.use middleware, *args, &block
148
- end
149
-
150
- # Returns the class which defines the params
151
- #
152
- # Returns the class which has been provided to define the
153
- # params. By default this will be Hanami::Action::Params.
154
- #
155
- # @return [Class] A params class (when whitelisted) or
156
- # Hanami::Action::Params
157
- #
158
- # @api private
159
- # @since 0.7.0
160
- def params_class
161
- @params_class ||= BaseParams
162
- end
163
- end
164
-
165
- # @since 0.7.0
166
- # @api private
167
- module InstanceMethods
168
- # @since 0.7.0
169
- # @api private
170
- def initialize(*)
171
- super
172
- @_status = nil
173
- @_body = nil
174
- end
175
- end
176
-
177
- protected
178
- # Gets the headers from the response
179
- #
180
- # @return [Hash] the HTTP headers from the response
181
- #
182
- # @since 0.1.0
183
- #
184
- # @example
185
- # require 'hanami/controller'
186
- #
187
- # class Show
188
- # include Hanami::Action
189
- #
190
- # def call(params)
191
- # # ...
192
- # self.headers # => { ... }
193
- # self.headers.merge!({'X-Custom' => 'OK'})
194
- # end
195
- # end
196
- def headers
197
- @headers
198
- end
199
-
200
- # Returns a serialized Rack response (Array), according to the current
201
- # status code, headers, and body.
202
- #
203
- # @return [Array] the serialized response
204
- #
205
- # @since 0.1.0
206
- # @api private
207
- #
208
- # @see Hanami::Action::Rack::DEFAULT_RESPONSE_CODE
209
- # @see Hanami::Action::Rack::DEFAULT_RESPONSE_BODY
210
- # @see Hanami::Action::Rack#status=
211
- # @see Hanami::Action::Rack#headers
212
- # @see Hanami::Action::Rack#body=
213
- def response
214
- [ @_status || DEFAULT_RESPONSE_CODE, headers, @_body || DEFAULT_RESPONSE_BODY.dup ]
215
- end
216
-
217
- # Calculates an unique ID for the current request
218
- #
219
- # @return [String] The unique ID
220
- #
221
- # @since 0.3.0
222
- def request_id
223
- # FIXME make this number configurable and document the probabilities of clashes
224
- @request_id ||= SecureRandom.hex(DEFAULT_REQUEST_ID_LENGTH)
225
- end
226
-
227
- # Returns a Hanami specialized rack request
228
- #
229
- # @return [Hanami::Action::Request] The request
230
- #
231
- # @since 0.3.1
232
- #
233
- # @example
234
- # require 'hanami/controller'
235
- #
236
- # class Create
237
- # include Hanami::Action
238
- #
239
- # def call(params)
240
- # ip = request.ip
241
- # secure = request.ssl?
242
- # end
243
- # end
244
- def request
245
- @request ||= ::Hanami::Action::Request.new(@_env)
246
- end
247
-
248
- # Return parsed request body
249
- #
250
- # @deprecated
251
- def parsed_request_body
252
- Hanami::Utils::Deprecation.new('#parsed_request_body is deprecated and it will be removed in future versions')
253
- @_env.fetch(ROUTER_PARSED_BODY, nil)
254
- end
255
-
256
- private
257
-
258
- # Sets the HTTP status code for the response
259
- #
260
- # @param status [Fixnum] an HTTP status code
261
- # @return [void]
262
- #
263
- # @since 0.1.0
264
- #
265
- # @example
266
- # require 'hanami/controller'
267
- #
268
- # class Create
269
- # include Hanami::Action
270
- #
271
- # def call(params)
272
- # # ...
273
- # self.status = 201
274
- # end
275
- # end
276
- def status=(status)
277
- @_status = status
278
- end
279
-
280
- # Sets the body of the response
281
- #
282
- # @param body [String] the body of the response
283
- # @return [void]
284
- #
285
- # @since 0.1.0
286
- #
287
- # @example
288
- # require 'hanami/controller'
289
- #
290
- # class Show
291
- # include Hanami::Action
292
- #
293
- # def call(params)
294
- # # ...
295
- # self.body = 'Hi!'
296
- # end
297
- # end
298
- def body=(body)
299
- body = Array(body) unless body.respond_to?(:each)
300
- @_body = body
301
- end
302
-
303
- # Send a file as response.
304
- # <tt>This method only sends files from the public directory</tt>
305
- #
306
- # It automatically handle the following cases:
307
- #
308
- # * <tt>Content-Type</tt> and <tt>Content-Length</tt>
309
- # * File Not found (returns a 404)
310
- # * Conditional GET (via <tt>If-Modified-Since</tt> header)
311
- # * Range requests (via <tt>Range</tt> header)
312
- #
313
- # @param path [String, Pathname] the body of the response
314
- # @return [void]
315
- #
316
- # @since 0.4.3
317
- #
318
- # @example
319
- # require 'hanami/controller'
320
- #
321
- # class Show
322
- # include Hanami::Action
323
- #
324
- # def call(params)
325
- # # ...
326
- # send_file Pathname.new('path/to/file')
327
- # end
328
- # end
329
- def send_file(path)
330
- _send_file(
331
- File.new(path, self.class.configuration.public_directory).call(@_env)
332
- )
333
- end
334
-
335
- # Send a file as response from anywhere in the file system.
336
- #
337
- # @see Hanami::Action::Rack#send_file
338
- #
339
- # @param path [String, Pathname] path to the file to be sent
340
- # @return [void]
341
- #
342
- # @since 1.0.0
343
- #
344
- # @example
345
- # require 'hanami/controller'
346
- #
347
- # class Show
348
- # include Hanami::Action
349
- #
350
- # def call(params)
351
- # # ...
352
- # unsafe_send_file Pathname.new('/tmp/path/to/file')
353
- # end
354
- # end
355
- def unsafe_send_file(path)
356
- directory = self.class.configuration.root_directory if Pathname.new(path).relative?
357
-
358
- _send_file(
359
- File.new(path, directory).call(@_env)
360
- )
361
- end
362
-
363
- # Check if the current request is a HEAD
364
- #
365
- # @return [TrueClass,FalseClass] the result of the check
366
- #
367
- # @since 0.3.2
368
- def head?
369
- request_method == HEAD
370
- end
371
-
372
- # NOTE: <tt>Hanami::Action::CSRFProtection</tt> (<tt>hanamirb</tt> gem) depends on this.
373
- #
374
- # @api private
375
- # @since 0.4.4
376
- def request_method
377
- @_env[REQUEST_METHOD]
378
- end
379
-
380
- # @since 1.0.0
381
- # @api private
382
- def _send_file(response)
383
- headers.merge!(response[RESPONSE_HEADERS])
384
-
385
- if response[RESPONSE_CODE] == NOT_FOUND
386
- headers.delete(X_CASCADE)
387
- headers.delete(CONTENT_LENGTH)
388
- halt NOT_FOUND
389
- else
390
- # FIXME: this is a fix for https://github.com/hanami/controller/issues/240
391
- # It's here to maintain the backward compat with 1.1, as we can't remove `#halt`
392
- # We should review the workflow for 2.0, because I don't like callbacks to be referenced from here.
393
- _run_after_callbacks(params)
394
- halt response[RESPONSE_CODE], response[RESPONSE_BODY]
395
- end
396
- end
397
- end
398
- end
399
- end