hanami-controller 2.2.0.rc1 → 2.3.0.beta1
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 +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +83 -84
- data/hanami-controller.gemspec +2 -2
- data/lib/hanami/action/cache/cache_control.rb +1 -0
- data/lib/hanami/action/cache/conditional_get.rb +1 -1
- data/lib/hanami/action/cache/expires.rb +1 -0
- data/lib/hanami/action/config.rb +18 -0
- data/lib/hanami/action/constants.rb +12 -8
- data/lib/hanami/action/errors.rb +1 -1
- data/lib/hanami/action/mime.rb +5 -3
- data/lib/hanami/action/params.rb +1 -7
- data/lib/hanami/action/rack/file.rb +2 -2
- data/lib/hanami/action/rack_utils.rb +11 -0
- data/lib/hanami/action/request/session.rb +68 -0
- data/lib/hanami/action/request.rb +29 -3
- data/lib/hanami/action/response.rb +5 -2
- data/lib/hanami/action.rb +9 -2
- data/lib/hanami/controller/version.rb +1 -1
- metadata +11 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16424b402710c09cc6727cf6ab94c84469edaa5d8e93f12d18c1f058cb973f00
|
4
|
+
data.tar.gz: '0639c98ced9c6f1cca83891bd74650f8dc6856e64490aa3a3321069810cd86a7'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e353e4df54518fcdc9fc2ae60262b839772fdcc180335d56c3f451a26766dcfb31567ca6a45b852ffabe92e40638cdb1f259d55bb1539ec315bc15c2bfe70de7
|
7
|
+
data.tar.gz: 0434befd1175d44411ad2aa5b315ba5c212c69d47dbb2c6c990733ca07f1c4c3d1954139f21c0cd6b35d236f5be9f4b9293e286f50f189a3eac24069435fca1a
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,27 @@
|
|
2
2
|
|
3
3
|
Complete, fast and testable actions for Rack
|
4
4
|
|
5
|
+
## v2.3.0.beta1 - 2025-10-03
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
|
9
|
+
- [wuarmin] Avoid false negatives in format/content type matches by checking against the request's media type, which excludes content type parameters (e.g. "test/plain" instead of "text/plain;charset=utf-8") (#471)
|
10
|
+
|
11
|
+
### Added
|
12
|
+
|
13
|
+
- [Wout] Add `Request#subdomains`, returning an array of subdomains for the current host, and `Request#subdomain` returning a dot-delimited subdomain string for the current host. Add `config.default_tld_length` setting for configuring the TLD length for your app's expected domain (#481)
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
|
17
|
+
- [Kyle Plump, Tim Riley] Support Rack 3 in addition to Rack 2 (#460)
|
18
|
+
- [Tim Riley] `request.session` is now an instance of `Hanami::Action::Request::Session`, which wraps the session object and provides access to session values via symbol keys. This was previously handled via symbolizing and reassigning the entire session hash, which is not compatible with Rack 3 (#477)
|
19
|
+
|
20
|
+
## v2.2.0 - 2024-11-05
|
21
|
+
|
22
|
+
### Added
|
23
|
+
|
24
|
+
- [Tom de Bruijn, Tim Riley] When an action is called, add the action instance to the Rack environment under the `"hanami.action_instance"` key (#446)
|
25
|
+
|
5
26
|
## v2.2.0.rc1 - 2024-10-29
|
6
27
|
|
7
28
|
## v2.2.0.beta2 - 2024-09-25
|
data/README.md
CHANGED
@@ -54,8 +54,8 @@ They are the endpoints that respond to incoming HTTP requests.
|
|
54
54
|
|
55
55
|
```ruby
|
56
56
|
class Show < Hanami::Action
|
57
|
-
def handle(
|
58
|
-
|
57
|
+
def handle(request, response)
|
58
|
+
response[:article] = ArticleRepository.new.find(request.params[:id])
|
59
59
|
end
|
60
60
|
end
|
61
61
|
```
|
@@ -63,7 +63,7 @@ end
|
|
63
63
|
`Hanami::Action` follows the Hanami philosophy: a single purpose object with a minimal interface.
|
64
64
|
|
65
65
|
In this case, `Hanami::Action` provides the key public interface of `#call(env)`, making your actions Rack-compatible.
|
66
|
-
To provide custom behaviour when your actions are being called, you can implement `#handle(
|
66
|
+
To provide custom behaviour when your actions are being called, you can implement `#handle(request, response)`
|
67
67
|
|
68
68
|
**An action is an object** and **you have full control over it**.
|
69
69
|
In other words, you have the freedom to instantiate, inject dependencies and test it, both at the unit and integration level.
|
@@ -79,8 +79,8 @@ class Show < Hanami::Action
|
|
79
79
|
super(configuration: configuration)
|
80
80
|
end
|
81
81
|
|
82
|
-
def handle(
|
83
|
-
|
82
|
+
def handle(request, response)
|
83
|
+
response[:article] = repository.find(request.params[:id])
|
84
84
|
end
|
85
85
|
|
86
86
|
private
|
@@ -96,16 +96,16 @@ action.call(id: 23)
|
|
96
96
|
### Params
|
97
97
|
|
98
98
|
The request params are part of the request passed as an argument to the `#handle` method.
|
99
|
-
If routed with *Hanami::Router*, it extracts the relevant bits from the Rack `env` (
|
99
|
+
If routed with *Hanami::Router*, it extracts the relevant bits from the Rack `env` (e.g. the requested `:id`).
|
100
100
|
Otherwise everything is passed as is: the full Rack `env` in production, and the given `Hash` for unit tests.
|
101
101
|
|
102
102
|
With `Hanami::Router`:
|
103
103
|
|
104
104
|
```ruby
|
105
105
|
class Show < Hanami::Action
|
106
|
-
def handle(
|
106
|
+
def handle(request, *)
|
107
107
|
# ...
|
108
|
-
puts
|
108
|
+
puts request.params # => { id: 23 } extracted from Rack env
|
109
109
|
end
|
110
110
|
end
|
111
111
|
```
|
@@ -114,9 +114,9 @@ Standalone:
|
|
114
114
|
|
115
115
|
```ruby
|
116
116
|
class Show < Hanami::Action
|
117
|
-
def handle(
|
117
|
+
def handle(request, *)
|
118
118
|
# ...
|
119
|
-
puts
|
119
|
+
puts request.params # => { :"rack.version"=>[1, 2], :"rack.input"=>#<StringIO:0x007fa563463948>, ... }
|
120
120
|
end
|
121
121
|
end
|
122
122
|
```
|
@@ -125,9 +125,9 @@ Unit Testing:
|
|
125
125
|
|
126
126
|
```ruby
|
127
127
|
class Show < Hanami::Action
|
128
|
-
def handle(
|
128
|
+
def handle(request, *)
|
129
129
|
# ...
|
130
|
-
puts
|
130
|
+
puts request.params # => { id: 23, key: "value" } passed as it is from testing
|
131
131
|
end
|
132
132
|
end
|
133
133
|
|
@@ -157,18 +157,18 @@ class Signup < Hanami::Action
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
160
|
-
def handle(
|
160
|
+
def handle(request, *)
|
161
161
|
# Describe inheritance hierarchy
|
162
|
-
puts
|
163
|
-
puts
|
162
|
+
puts request.params.class # => Signup::Params
|
163
|
+
puts request.params.class.superclass # => Hanami::Action::Params
|
164
164
|
|
165
165
|
# Whitelist :first_name, but not :admin
|
166
|
-
puts
|
167
|
-
puts
|
166
|
+
puts request.params[:first_name] # => "Luca"
|
167
|
+
puts request.params[:admin] # => nil
|
168
168
|
|
169
169
|
# Whitelist nested params [:address][:line_one], not [:address][:line_two]
|
170
|
-
puts
|
171
|
-
puts
|
170
|
+
puts request.params[:address][:line_one] # => "69 Tender St"
|
171
|
+
puts request.params[:address][:line_two] # => nil
|
172
172
|
end
|
173
173
|
end
|
174
174
|
```
|
@@ -198,8 +198,8 @@ class Signup < Hanami::Action
|
|
198
198
|
optional(:avatar).filled(size?: 1..(MEGABYTE * 3))
|
199
199
|
end
|
200
200
|
|
201
|
-
def handle(
|
202
|
-
halt 400 unless
|
201
|
+
def handle(request, *)
|
202
|
+
halt 400 unless request.params.valid?
|
203
203
|
# ...
|
204
204
|
end
|
205
205
|
end
|
@@ -217,14 +217,14 @@ action = Show.new(configuration: configuration)
|
|
217
217
|
action.call({}) # => #<Hanami::Action::Response:0x00007fe8be968418 @status=200 ...>
|
218
218
|
```
|
219
219
|
|
220
|
-
This is the same `
|
220
|
+
This is the same `response` object passed to `#handle`, where you can use its accessors to explicitly set status, headers, and body:
|
221
221
|
|
222
222
|
```ruby
|
223
223
|
class Show < Hanami::Action
|
224
|
-
def handle(*,
|
225
|
-
|
226
|
-
|
227
|
-
|
224
|
+
def handle(*, response)
|
225
|
+
response.status = 201
|
226
|
+
response.body = "Hi!"
|
227
|
+
response.headers.merge!("X-Custom" => "OK")
|
228
228
|
end
|
229
229
|
end
|
230
230
|
|
@@ -239,8 +239,8 @@ By default, an action exposes the received params.
|
|
239
239
|
|
240
240
|
```ruby
|
241
241
|
class Show < Hanami::Action
|
242
|
-
def handle(
|
243
|
-
|
242
|
+
def handle(request, response)
|
243
|
+
response[:article] = ArticleRepository.new.find(request.params[:id])
|
244
244
|
end
|
245
245
|
end
|
246
246
|
|
@@ -272,9 +272,9 @@ class Show < Hanami::Action
|
|
272
272
|
# ...
|
273
273
|
end
|
274
274
|
|
275
|
-
# `
|
276
|
-
def set_article(
|
277
|
-
|
275
|
+
# `request` and `response` in the method signature is optional
|
276
|
+
def set_article(request, response)
|
277
|
+
response[:article] = ArticleRepository.new.find(request.params[:id])
|
278
278
|
end
|
279
279
|
end
|
280
280
|
```
|
@@ -284,7 +284,7 @@ Callbacks can also be expressed as anonymous lambdas:
|
|
284
284
|
```ruby
|
285
285
|
class Show < Hanami::Action
|
286
286
|
before { ... } # do some authentication stuff
|
287
|
-
before { |
|
287
|
+
before { |request, response| response[:article] = ArticleRepository.new.find(request.params[:id]) }
|
288
288
|
|
289
289
|
def handle(*)
|
290
290
|
end
|
@@ -332,15 +332,15 @@ You can also define custom handlers for exceptions.
|
|
332
332
|
class Create < Hanami::Action
|
333
333
|
handle_exception ArgumentError => :my_custom_handler
|
334
334
|
|
335
|
-
|
335
|
+
def handle(*)
|
336
336
|
raise ArgumentError.new("Invalid arguments")
|
337
337
|
end
|
338
338
|
|
339
339
|
private
|
340
340
|
|
341
|
-
def my_custom_handler(
|
342
|
-
|
343
|
-
|
341
|
+
def my_custom_handler(request, response, exception)
|
342
|
+
response.status = 400
|
343
|
+
response.body = exception.message
|
344
344
|
end
|
345
345
|
end
|
346
346
|
|
@@ -381,7 +381,7 @@ module Articles
|
|
381
381
|
|
382
382
|
private
|
383
383
|
|
384
|
-
def handle_my_exception(
|
384
|
+
def handle_my_exception(request, response, exception)
|
385
385
|
# ...
|
386
386
|
end
|
387
387
|
end
|
@@ -395,7 +395,7 @@ module Articles
|
|
395
395
|
|
396
396
|
private
|
397
397
|
|
398
|
-
def handle_standard_error(
|
398
|
+
def handle_standard_error(request, response, exception)
|
399
399
|
# ...
|
400
400
|
end
|
401
401
|
end
|
@@ -433,8 +433,8 @@ Alternatively, you can specify a custom message.
|
|
433
433
|
|
434
434
|
```ruby
|
435
435
|
class Show < Hanami::Action
|
436
|
-
def handle(
|
437
|
-
|
436
|
+
def handle(request, response)
|
437
|
+
response[:droid] = DroidRepository.new.find(request.params[:id]) or not_found
|
438
438
|
end
|
439
439
|
|
440
440
|
private
|
@@ -450,8 +450,8 @@ action.call({}) # => [404, {}, ["This is not the droid you're looking for"]]
|
|
450
450
|
|
451
451
|
### Cookies
|
452
452
|
|
453
|
-
You can read the original cookies sent from the HTTP client via `
|
454
|
-
If you want to send cookies in the response, use `
|
453
|
+
You can read the original cookies sent from the HTTP client via `request.cookies`.
|
454
|
+
If you want to send cookies in the response, use `response.cookies`.
|
455
455
|
|
456
456
|
They are read as a Hash from Rack env:
|
457
457
|
|
@@ -462,9 +462,9 @@ require "hanami/action/cookies"
|
|
462
462
|
class ReadCookiesFromRackEnv < Hanami::Action
|
463
463
|
include Hanami::Action::Cookies
|
464
464
|
|
465
|
-
def handle(
|
465
|
+
def handle(request, *)
|
466
466
|
# ...
|
467
|
-
|
467
|
+
request.cookies[:foo] # => "bar"
|
468
468
|
end
|
469
469
|
end
|
470
470
|
|
@@ -481,9 +481,9 @@ require "hanami/action/cookies"
|
|
481
481
|
class SetCookies < Hanami::Action
|
482
482
|
include Hanami::Action::Cookies
|
483
483
|
|
484
|
-
def handle(*,
|
484
|
+
def handle(*, response)
|
485
485
|
# ...
|
486
|
-
|
486
|
+
response.cookies[:foo] = "bar"
|
487
487
|
end
|
488
488
|
end
|
489
489
|
|
@@ -500,9 +500,9 @@ require "hanami/action/cookies"
|
|
500
500
|
class RemoveCookies < Hanami::Action
|
501
501
|
include Hanami::Action::Cookies
|
502
502
|
|
503
|
-
def handle(*,
|
503
|
+
def handle(*, response)
|
504
504
|
# ...
|
505
|
-
|
505
|
+
response.cookies[:foo] = nil
|
506
506
|
end
|
507
507
|
end
|
508
508
|
|
@@ -523,9 +523,9 @@ end
|
|
523
523
|
class SetCookies < Hanami::Action
|
524
524
|
include Hanami::Action::Cookies
|
525
525
|
|
526
|
-
def handle(*,
|
526
|
+
def handle(*, response)
|
527
527
|
# ...
|
528
|
-
|
528
|
+
response.cookies[:foo] = { value: "bar", max_age: 100 }
|
529
529
|
end
|
530
530
|
end
|
531
531
|
|
@@ -537,7 +537,7 @@ action.call({}) # => [200, {"Set-Cookie" => "foo=bar; max-age=100;"}, "..."]
|
|
537
537
|
|
538
538
|
Actions have builtin support for Rack sessions.
|
539
539
|
Similarly to cookies, you can read the session sent by the HTTP client via
|
540
|
-
`
|
540
|
+
`request.session`, and also manipulate it via `response.session`.
|
541
541
|
|
542
542
|
```ruby
|
543
543
|
require "hanami/controller"
|
@@ -546,9 +546,9 @@ require "hanami/action/session"
|
|
546
546
|
class ReadSessionFromRackEnv < Hanami::Action
|
547
547
|
include Hanami::Action::Session
|
548
548
|
|
549
|
-
def handle(
|
549
|
+
def handle(request, *)
|
550
550
|
# ...
|
551
|
-
|
551
|
+
request.session[:age] # => "35"
|
552
552
|
end
|
553
553
|
end
|
554
554
|
|
@@ -565,9 +565,9 @@ require "hanami/action/session"
|
|
565
565
|
class SetSession < Hanami::Action
|
566
566
|
include Hanami::Action::Session
|
567
567
|
|
568
|
-
def handle(*,
|
568
|
+
def handle(*, response)
|
569
569
|
# ...
|
570
|
-
|
570
|
+
response.session[:age] = 31
|
571
571
|
end
|
572
572
|
end
|
573
573
|
|
@@ -584,9 +584,9 @@ require "hanami/action/session"
|
|
584
584
|
class RemoveSession < Hanami::Action
|
585
585
|
include Hanami::Action::Session
|
586
586
|
|
587
|
-
def handle(*,
|
587
|
+
def handle(*, response)
|
588
588
|
# ...
|
589
|
-
|
589
|
+
response.session[:age] = nil
|
590
590
|
end
|
591
591
|
end
|
592
592
|
|
@@ -663,7 +663,7 @@ end
|
|
663
663
|
|
664
664
|
If `resource.cache_key` is equal to `IfNoneMatch` header, then hanami will `halt 304`.
|
665
665
|
|
666
|
-
An
|
666
|
+
An alternative to hashing based check, is the time based check:
|
667
667
|
|
668
668
|
```ruby
|
669
669
|
require "hanami/controller"
|
@@ -674,23 +674,23 @@ class ConditionalGetController < Hanami::Action
|
|
674
674
|
|
675
675
|
def handle(*)
|
676
676
|
# ...
|
677
|
-
fresh last_modified: resource.
|
678
|
-
# => halt 304 with header IfModifiedSince = resource.
|
677
|
+
fresh last_modified: resource.updated_at
|
678
|
+
# => halt 304 with header IfModifiedSince = resource.updated_at.httpdate
|
679
679
|
end
|
680
680
|
end
|
681
681
|
```
|
682
682
|
|
683
|
-
If `resource.
|
683
|
+
If `resource.updated_at` is equal to `IfModifiedSince` header, then hanami will `halt 304`.
|
684
684
|
|
685
685
|
### Redirect
|
686
686
|
|
687
|
-
If you need to redirect the client to another resource, use `
|
687
|
+
If you need to redirect the client to another resource, use `response.redirect_to`:
|
688
688
|
|
689
689
|
```ruby
|
690
690
|
class Create < Hanami::Action
|
691
|
-
def handle(*,
|
691
|
+
def handle(*, response)
|
692
692
|
# ...
|
693
|
-
|
693
|
+
response.redirect_to "http://example.com/articles/23"
|
694
694
|
end
|
695
695
|
end
|
696
696
|
|
@@ -702,9 +702,9 @@ You can also redirect with a custom status code:
|
|
702
702
|
|
703
703
|
```ruby
|
704
704
|
class Create < Hanami::Action
|
705
|
-
def handle(*,
|
705
|
+
def handle(*, response)
|
706
706
|
# ...
|
707
|
-
|
707
|
+
response.redirect_to "http://example.com/articles/23", status: 301
|
708
708
|
end
|
709
709
|
end
|
710
710
|
|
@@ -735,9 +735,9 @@ However, you can force this value:
|
|
735
735
|
|
736
736
|
```ruby
|
737
737
|
class Show < Hanami::Action
|
738
|
-
def handle(*,
|
738
|
+
def handle(*, response)
|
739
739
|
# ...
|
740
|
-
|
740
|
+
response.format = :json
|
741
741
|
end
|
742
742
|
end
|
743
743
|
|
@@ -761,7 +761,7 @@ class Show < Hanami::Action
|
|
761
761
|
end
|
762
762
|
end
|
763
763
|
|
764
|
-
# When called with "
|
764
|
+
# When called with "*/*" => 200
|
765
765
|
# When called with "text/html" => 200
|
766
766
|
# When called with "application/json" => 200
|
767
767
|
# When called with "application/xml" => 415
|
@@ -771,23 +771,22 @@ You can check if the requested MIME type is accepted by the client.
|
|
771
771
|
|
772
772
|
```ruby
|
773
773
|
class Show < Hanami::Action
|
774
|
-
def handle(
|
774
|
+
def handle(request, response)
|
775
775
|
# ...
|
776
776
|
# @_env["HTTP_ACCEPT"] # => "text/html,application/xhtml+xml,application/xml;q=0.9"
|
777
777
|
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
778
|
+
request.accept?("text/html") # => true
|
779
|
+
request.accept?("application/xml") # => true
|
780
|
+
request.accept?("application/json") # => false
|
781
|
+
response.format # :html
|
783
782
|
|
784
783
|
|
785
784
|
# @_env["HTTP_ACCEPT"] # => "*/*"
|
786
785
|
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
786
|
+
request.accept?("text/html") # => true
|
787
|
+
request.accept?("application/xml") # => true
|
788
|
+
request.accept?("application/json") # => true
|
789
|
+
response.format # :html
|
791
790
|
end
|
792
791
|
end
|
793
792
|
```
|
@@ -811,9 +810,9 @@ response = action.call({ "HTTP_ACCEPT" => "application/custom" }) # => Content-T
|
|
811
810
|
response.format # => :custom
|
812
811
|
|
813
812
|
class Show < Hanami::Action
|
814
|
-
def handle(*,
|
813
|
+
def handle(*, response)
|
815
814
|
# ...
|
816
|
-
|
815
|
+
response.format = :custom
|
817
816
|
end
|
818
817
|
end
|
819
818
|
|
@@ -833,9 +832,9 @@ configuration = Hanami::Controller::Configuration.new do |config|
|
|
833
832
|
end
|
834
833
|
|
835
834
|
class Csv < Hanami::Action
|
836
|
-
def handle(*,
|
837
|
-
|
838
|
-
|
835
|
+
def handle(*, response)
|
836
|
+
response.format = :csv
|
837
|
+
response.body = Enumerator.new do |yielder|
|
839
838
|
yielder << csv_header
|
840
839
|
|
841
840
|
# Expensive operation is streamed as each line becomes available
|
data/hanami-controller.gemspec
CHANGED
@@ -20,8 +20,8 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.metadata["rubygems_mfa_required"] = "true"
|
21
21
|
spec.required_ruby_version = ">= 3.1"
|
22
22
|
|
23
|
-
spec.add_dependency "rack", "
|
24
|
-
spec.add_dependency "hanami-utils", "~> 2.
|
23
|
+
spec.add_dependency "rack", ">= 2.1"
|
24
|
+
spec.add_dependency "hanami-utils", "~> 2.3.0.beta1"
|
25
25
|
spec.add_dependency "dry-configurable", "~> 1.0", "< 2"
|
26
26
|
spec.add_dependency "dry-core", "~> 1.0"
|
27
27
|
spec.add_dependency "zeitwerk", "~> 2.6"
|
data/lib/hanami/action/config.rb
CHANGED
@@ -117,6 +117,24 @@ module Hanami
|
|
117
117
|
#
|
118
118
|
# @since 0.4.0
|
119
119
|
|
120
|
+
# @!attribute [rw] default_tld_length
|
121
|
+
#
|
122
|
+
# Sets the default TLD length for host names. It is used to extract the
|
123
|
+
# subdomain(s) in `Request#subdomains`.
|
124
|
+
#
|
125
|
+
# Defaults to 1.
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# # For *.example.com
|
129
|
+
# config.default_tld_length = 1
|
130
|
+
#
|
131
|
+
# # Or for *.example.co.uk
|
132
|
+
# config.default_tld_length = 2
|
133
|
+
#
|
134
|
+
# @return [Integer] the number of subdomains
|
135
|
+
#
|
136
|
+
# @since 2.3.0
|
137
|
+
|
120
138
|
# @!attribute [rw] cookies
|
121
139
|
#
|
122
140
|
# Sets default cookie options for all responses.
|
@@ -50,14 +50,14 @@ module Hanami
|
|
50
50
|
# @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.5
|
51
51
|
# @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html
|
52
52
|
ENTITY_HEADERS = {
|
53
|
-
"
|
54
|
-
"
|
55
|
-
"
|
56
|
-
"
|
57
|
-
"
|
58
|
-
"
|
59
|
-
"
|
60
|
-
"
|
53
|
+
"allow" => true,
|
54
|
+
"content-encoding" => true,
|
55
|
+
"content-language" => true,
|
56
|
+
"content-location" => true,
|
57
|
+
"content-md5" => true,
|
58
|
+
"content-range" => true,
|
59
|
+
"expires" => true,
|
60
|
+
"last-modified" => true,
|
61
61
|
"extension-header" => true
|
62
62
|
}.freeze
|
63
63
|
|
@@ -245,5 +245,9 @@ module Hanami
|
|
245
245
|
# @since 2.0.0
|
246
246
|
# @api private
|
247
247
|
DEFAULT_CHARSET = "utf-8"
|
248
|
+
|
249
|
+
# @since 2.2.0
|
250
|
+
# @api private
|
251
|
+
ACTION_INSTANCE = "hanami.action_instance"
|
248
252
|
end
|
249
253
|
end
|
data/lib/hanami/action/errors.rb
CHANGED
data/lib/hanami/action/mime.rb
CHANGED
@@ -222,11 +222,13 @@ module Hanami
|
|
222
222
|
# @since 2.0.0
|
223
223
|
# @api private
|
224
224
|
def enforce_content_type(request, config)
|
225
|
-
|
225
|
+
# Compare media type (without parameters) instead of full Content-Type header
|
226
|
+
# to avoid false negatives (e.g., multipart/form-data; boundary=...)
|
227
|
+
media_type = request.media_type
|
226
228
|
|
227
|
-
return if
|
229
|
+
return if media_type.nil?
|
228
230
|
|
229
|
-
return if accepted_mime_type?(
|
231
|
+
return if accepted_mime_type?(media_type, config)
|
230
232
|
|
231
233
|
yield
|
232
234
|
end
|
data/lib/hanami/action/params.rb
CHANGED
@@ -362,13 +362,7 @@ module Hanami
|
|
362
362
|
# @since 0.7.0
|
363
363
|
# @api private
|
364
364
|
def _router_params(fallback = {})
|
365
|
-
env.fetch(ROUTER_PARAMS)
|
366
|
-
if session = fallback.delete(Action::RACK_SESSION)
|
367
|
-
fallback[Action::RACK_SESSION] = Utils::Hash.deep_symbolize(session)
|
368
|
-
end
|
369
|
-
|
370
|
-
fallback
|
371
|
-
end
|
365
|
+
env.fetch(ROUTER_PARAMS, fallback)
|
372
366
|
end
|
373
367
|
end
|
374
368
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "rack/
|
3
|
+
require "rack/files"
|
4
4
|
|
5
5
|
module Hanami
|
6
6
|
class Action
|
@@ -21,7 +21,7 @@ module Hanami
|
|
21
21
|
# @since 0.4.3
|
22
22
|
# @api private
|
23
23
|
def initialize(path, root)
|
24
|
-
@file = ::Rack::
|
24
|
+
@file = ::Rack::Files.new(root.to_s)
|
25
25
|
@path = path.to_s
|
26
26
|
end
|
27
27
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "forwardable"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
class Action
|
7
|
+
class Request < ::Rack::Request
|
8
|
+
# Wrapper for Rack-provided sessions, allowing access using symbol keys.
|
9
|
+
#
|
10
|
+
# @since 2.3.0
|
11
|
+
# @api public
|
12
|
+
class Session
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
def_delegators \
|
16
|
+
:@session,
|
17
|
+
:clear,
|
18
|
+
:delete,
|
19
|
+
:empty?,
|
20
|
+
:size,
|
21
|
+
:length,
|
22
|
+
:each,
|
23
|
+
:to_h,
|
24
|
+
:inspect,
|
25
|
+
:keys,
|
26
|
+
:values
|
27
|
+
|
28
|
+
def initialize(session)
|
29
|
+
@session = session
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](key)
|
33
|
+
@session[key.to_s]
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(key, value)
|
37
|
+
@session[key.to_s] = value
|
38
|
+
end
|
39
|
+
|
40
|
+
def key?(key)
|
41
|
+
@session.key?(key.to_s)
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :has_key?, :key?
|
45
|
+
alias_method :include?, :key?
|
46
|
+
|
47
|
+
def ==(other)
|
48
|
+
Utils::Hash.deep_symbolize(@session) == Utils::Hash.deep_symbolize(other)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Provides a fallback for any methods not handled by the def_delegators.
|
54
|
+
def method_missing(method_name, *args, &block)
|
55
|
+
if @session.respond_to?(method_name)
|
56
|
+
@session.send(method_name, *args, &block)
|
57
|
+
else
|
58
|
+
super
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def respond_to_missing?(method_name, include_private = false)
|
63
|
+
@session.respond_to?(method_name, include_private) || super
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -26,11 +26,12 @@ module Hanami
|
|
26
26
|
|
27
27
|
# @since 2.0.0
|
28
28
|
# @api private
|
29
|
-
def initialize(env:, params:, session_enabled: false)
|
29
|
+
def initialize(env:, params:, default_tld_length: 1, session_enabled: false)
|
30
30
|
super(env)
|
31
31
|
|
32
32
|
@params = params
|
33
33
|
@session_enabled = session_enabled
|
34
|
+
@default_tld_length = default_tld_length
|
34
35
|
end
|
35
36
|
|
36
37
|
# Returns the request's ID
|
@@ -56,7 +57,7 @@ module Hanami
|
|
56
57
|
|
57
58
|
# Returns the session for the request.
|
58
59
|
#
|
59
|
-
# @return [
|
60
|
+
# @return [Hanami::Request::Session] the session object
|
60
61
|
#
|
61
62
|
# @raise [MissingSessionError] if the session is not enabled
|
62
63
|
#
|
@@ -70,7 +71,7 @@ module Hanami
|
|
70
71
|
raise Hanami::Action::MissingSessionError.new("Hanami::Action::Request#session")
|
71
72
|
end
|
72
73
|
|
73
|
-
super
|
74
|
+
@session ||= Session.new(super)
|
74
75
|
end
|
75
76
|
|
76
77
|
# Returns the flash for the request.
|
@@ -91,6 +92,31 @@ module Hanami
|
|
91
92
|
@flash ||= Flash.new(session[Flash::KEY])
|
92
93
|
end
|
93
94
|
|
95
|
+
# Returns the subdomains for the current host.
|
96
|
+
#
|
97
|
+
# @return [Array<String>]
|
98
|
+
#
|
99
|
+
# @api public
|
100
|
+
# @since 2.3.0
|
101
|
+
def subdomains(tld_length = @default_tld_length)
|
102
|
+
return [] if IP_ADDRESS_HOST_REGEXP.match?(host)
|
103
|
+
|
104
|
+
host.split(".")[0..-(tld_length + 2)]
|
105
|
+
end
|
106
|
+
|
107
|
+
IP_ADDRESS_HOST_REGEXP = /\A\d+\.\d+\.\d+\.\d+\z/
|
108
|
+
private_constant :IP_ADDRESS_HOST_REGEXP
|
109
|
+
|
110
|
+
# Returns the subdomain for the current host.
|
111
|
+
#
|
112
|
+
# @return [String]
|
113
|
+
#
|
114
|
+
# @api public
|
115
|
+
# @since 2.3.0
|
116
|
+
def subdomain(tld_length = @default_tld_length)
|
117
|
+
subdomains(tld_length).join(".")
|
118
|
+
end
|
119
|
+
|
94
120
|
# @since 2.0.0
|
95
121
|
# @api private
|
96
122
|
def accept?(mime_type)
|
@@ -71,12 +71,15 @@ module Hanami
|
|
71
71
|
# @api public
|
72
72
|
def body=(str)
|
73
73
|
@length = 0
|
74
|
-
@body
|
74
|
+
@body = EMPTY_BODY.dup
|
75
|
+
|
76
|
+
return if str.nil? || str == EMPTY_BODY
|
75
77
|
|
76
78
|
if str.is_a?(::Rack::Files::BaseIterator)
|
77
79
|
@body = str
|
80
|
+
buffered_body! # Ensure appropriate content-length is set
|
78
81
|
else
|
79
|
-
write(str)
|
82
|
+
write(str)
|
80
83
|
end
|
81
84
|
end
|
82
85
|
|
data/lib/hanami/action.rb
CHANGED
@@ -7,6 +7,7 @@ require "hanami/utils/kernel"
|
|
7
7
|
require "hanami/utils/string"
|
8
8
|
require "rack"
|
9
9
|
require "rack/utils"
|
10
|
+
require "hanami/action/rack_utils"
|
10
11
|
require "zeitwerk"
|
11
12
|
|
12
13
|
require_relative "action/constants"
|
@@ -61,6 +62,7 @@ module Hanami
|
|
61
62
|
setting :formats, default: Config::Formats.new, mutable: true
|
62
63
|
setting :default_charset
|
63
64
|
setting :default_headers, default: {}, constructor: -> (headers) { headers.compact }
|
65
|
+
setting :default_tld_length, default: 1
|
64
66
|
setting :cookies, default: {}, constructor: -> (cookie_options) {
|
65
67
|
# Call `to_h` here to permit `ApplicationConfiguration::Cookies` object to be
|
66
68
|
# provided when application actions are configured
|
@@ -312,7 +314,8 @@ module Hanami
|
|
312
314
|
request = build_request(
|
313
315
|
env: env,
|
314
316
|
params: params,
|
315
|
-
session_enabled: session_enabled
|
317
|
+
session_enabled: session_enabled?,
|
318
|
+
default_tld_length: config.default_tld_length
|
316
319
|
)
|
317
320
|
response = build_response(
|
318
321
|
request: request,
|
@@ -332,6 +335,10 @@ module Hanami
|
|
332
335
|
_handle_exception(request, response, exception)
|
333
336
|
end
|
334
337
|
|
338
|
+
# Before finishing, put ourself into the Rack env for third-party instrumentation tools to
|
339
|
+
# integrate with actions
|
340
|
+
env[ACTION_INSTANCE] = self
|
341
|
+
|
335
342
|
finish(request, response, halted)
|
336
343
|
end
|
337
344
|
|
@@ -557,7 +564,7 @@ module Hanami
|
|
557
564
|
#
|
558
565
|
# # Both Content-Type and X-No-Pass are removed because they're not allowed
|
559
566
|
def keep_response_header?(header)
|
560
|
-
ENTITY_HEADERS.include?(header)
|
567
|
+
ENTITY_HEADERS.include?(header.downcase)
|
561
568
|
end
|
562
569
|
|
563
570
|
# @since 2.0.0
|
metadata
CHANGED
@@ -1,43 +1,42 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hanami-controller
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: rack
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
16
15
|
requirements:
|
17
|
-
- - "
|
16
|
+
- - ">="
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version: '2.
|
18
|
+
version: '2.1'
|
20
19
|
type: :runtime
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
|
-
- - "
|
23
|
+
- - ">="
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version: '2.
|
25
|
+
version: '2.1'
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
27
|
name: hanami-utils
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
30
|
- - "~>"
|
32
31
|
- !ruby/object:Gem::Version
|
33
|
-
version: 2.
|
32
|
+
version: 2.3.0.beta1
|
34
33
|
type: :runtime
|
35
34
|
prerelease: false
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
37
36
|
requirements:
|
38
37
|
- - "~>"
|
39
38
|
- !ruby/object:Gem::Version
|
40
|
-
version: 2.
|
39
|
+
version: 2.3.0.beta1
|
41
40
|
- !ruby/object:Gem::Dependency
|
42
41
|
name: dry-configurable
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -193,7 +192,9 @@ files:
|
|
193
192
|
- lib/hanami/action/mime/request_mime_weight.rb
|
194
193
|
- lib/hanami/action/params.rb
|
195
194
|
- lib/hanami/action/rack/file.rb
|
195
|
+
- lib/hanami/action/rack_utils.rb
|
196
196
|
- lib/hanami/action/request.rb
|
197
|
+
- lib/hanami/action/request/session.rb
|
197
198
|
- lib/hanami/action/response.rb
|
198
199
|
- lib/hanami/action/session.rb
|
199
200
|
- lib/hanami/action/validatable.rb
|
@@ -206,7 +207,6 @@ licenses:
|
|
206
207
|
- MIT
|
207
208
|
metadata:
|
208
209
|
rubygems_mfa_required: 'true'
|
209
|
-
post_install_message:
|
210
210
|
rdoc_options: []
|
211
211
|
require_paths:
|
212
212
|
- lib
|
@@ -221,8 +221,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
223
|
requirements: []
|
224
|
-
rubygems_version: 3.
|
225
|
-
signing_key:
|
224
|
+
rubygems_version: 3.6.9
|
226
225
|
specification_version: 4
|
227
226
|
summary: Complete, fast and testable actions for Rack and Hanami
|
228
227
|
test_files: []
|