roda 3.26.0 → 3.27.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 441b09ae88ce4427866fe7c773ce8476f60e837b7b4477bfc5106c162e8b0a06
4
- data.tar.gz: aca50917cc14429a77c16461352c997dff592e52cc9a44d1f4f64688b8b50693
3
+ metadata.gz: 1aaf8be3e1a6fa8d822436561e48f5a5978e94e3430c0287b32be3a0a292d351
4
+ data.tar.gz: 84ed6cd43e59574cc1264889578296d30c96c91d4b53a45bb2b839619f2d1786
5
5
  SHA512:
6
- metadata.gz: bb56cb6dbcb70fbc182d10bf161db009162d6887e9117823b9fab26554670644c3eec1c2e2977008310826fee799d1213ff8d7a85ed261e5f257ed949b9baa2b
7
- data.tar.gz: 8d7b4ee8b669e7a3a5f3f4ac79f7d51b879cf9700e9634bd5b52b9c219c1158d0f07f03ce5aeab983bbddd3038febd996738b45047fa1e8e49c6e1dc7e07687d
6
+ metadata.gz: 7e339e3062178e6e47bf5b7fb59b32076f5e48131ce14480e3c685f5eb4d0d59903fa635ac790410ef88a2d5cdc46a5e3792d98c511c21acee7931ec5d15d16a
7
+ data.tar.gz: 38f9c91a1b9356d07854c364a10a901c2a126162e42e25e112cdca59b9b065e587f17c2019f4c785ed6610fc0b98011f0ab7adb8cfab0da51acb13e1a0477c34
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ = 3.27.0 (2019-12-13)
2
+
3
+ * Allow json_parser return correct result for invalid JSON if the params_capturing plugin is used (jeremyevans) (#180)
4
+
5
+ * Add multibyte_string_matcher plugin for matching multibyte characters (jeremyevans)
6
+
7
+ * Split roda.rb into separate files (janko) (#177)
8
+
1
9
  = 3.26.0 (2019-11-18)
2
10
 
3
11
  * Combine multiple asset files with a newline when compiling them, avoiding corner cases with comments (ameuret) (#176)
@@ -0,0 +1,15 @@
1
+ = New Features
2
+
3
+ * A multibyte_string_matcher plugin has been added that supports
4
+ multibyte characters in strings used as matchers. It uses a slower
5
+ string matching implementation that supports multibyte characters.
6
+ As multibyte strings in paths must be escaped, this also loads the
7
+ unescape_path plugin.
8
+
9
+ = Other Improvements
10
+
11
+ * The json_parser plugin now returns expected results for invalid JSON
12
+ if the params_capturing plugin is used.
13
+
14
+ * lib/roda.rb has been split into multiple files for easier code
15
+ navigation.
@@ -1,7 +1,10 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require "rack"
4
3
  require "thread"
4
+ require_relative "roda/request"
5
+ require_relative "roda/response"
6
+ require_relative "roda/plugins"
7
+ require_relative "roda/cache"
5
8
  require_relative "roda/version"
6
9
 
7
10
  # The main class for Roda. Roda is built completely out of plugins, with the
@@ -11,51 +14,6 @@ class Roda
11
14
  # Error class raised by Roda
12
15
  class RodaError < StandardError; end
13
16
 
14
- # A thread safe cache class, offering only #[] and #[]= methods,
15
- # each protected by a mutex.
16
- class RodaCache
17
- # Create a new thread safe cache.
18
- def initialize
19
- @mutex = Mutex.new
20
- @hash = {}
21
- end
22
-
23
- # Make getting value from underlying hash thread safe.
24
- def [](key)
25
- @mutex.synchronize{@hash[key]}
26
- end
27
-
28
- # Make setting value in underlying hash thread safe.
29
- def []=(key, value)
30
- @mutex.synchronize{@hash[key] = value}
31
- end
32
-
33
- private
34
-
35
- # Create a copy of the cache with a separate mutex.
36
- def initialize_copy(other)
37
- @mutex = Mutex.new
38
- other.instance_variable_get(:@mutex).synchronize do
39
- @hash = other.instance_variable_get(:@hash).dup
40
- end
41
- end
42
- end
43
-
44
- # Base class used for Roda requests. The instance methods for this
45
- # class are added by Roda::RodaPlugins::Base::RequestMethods, the
46
- # class methods are added by Roda::RodaPlugins::Base::RequestClassMethods.
47
- class RodaRequest < ::Rack::Request
48
- @roda_class = ::Roda
49
- @match_pattern_cache = ::Roda::RodaCache.new
50
- end
51
-
52
- # Base class used for Roda responses. The instance methods for this
53
- # class are added by Roda::RodaPlugins::Base::ResponseMethods, the class
54
- # methods are added by Roda::RodaPlugins::Base::ResponseClassMethods.
55
- class RodaResponse
56
- @roda_class = ::Roda
57
- end
58
-
59
17
  @app = nil
60
18
  @inherit_middleware = true
61
19
  @middleware = []
@@ -64,53 +22,7 @@ class Roda
64
22
  @route_block = nil
65
23
  @rack_app_route_block = nil
66
24
 
67
- # Module in which all Roda plugins should be stored. Also contains logic for
68
- # registering and loading plugins.
69
25
  module RodaPlugins
70
- OPTS = {}.freeze
71
- EMPTY_ARRAY = [].freeze
72
-
73
- # Stores registered plugins
74
- @plugins = RodaCache.new
75
-
76
- class << self
77
- # Make warn a public method, as it is used for deprecation warnings.
78
- # Roda::RodaPlugins.warn can be overridden for custom handling of
79
- # deprecation warnings.
80
- public :warn
81
- end
82
-
83
- # If the registered plugin already exists, use it. Otherwise,
84
- # require it and return it. This raises a LoadError if such a
85
- # plugin doesn't exist, or a RodaError if it exists but it does
86
- # not register itself correctly.
87
- def self.load_plugin(name)
88
- h = @plugins
89
- unless plugin = h[name]
90
- require "roda/plugins/#{name}"
91
- raise RodaError, "Plugin #{name} did not register itself correctly in Roda::RodaPlugins" unless plugin = h[name]
92
- end
93
- plugin
94
- end
95
-
96
- # Register the given plugin with Roda, so that it can be loaded using #plugin
97
- # with a symbol. Should be used by plugin files. Example:
98
- #
99
- # Roda::RodaPlugins.register_plugin(:plugin_name, PluginModule)
100
- def self.register_plugin(name, mod)
101
- @plugins[name] = mod
102
- end
103
-
104
- # Deprecate the constant with the given name in the given module,
105
- # if the ruby version supports it.
106
- def self.deprecate_constant(mod, name)
107
- # :nocov:
108
- if RUBY_VERSION >= '2.3'
109
- mod.deprecate_constant(name)
110
- end
111
- # :nocov:
112
- end
113
-
114
26
  # The base plugin for Roda, implementing all default functionality.
115
27
  # Methods are put into a plugin so future plugins can easily override
116
28
  # them and call super to get the default behavior.
@@ -628,771 +540,6 @@ WARNING
628
540
  @_request.session
629
541
  end
630
542
  end
631
-
632
- # Class methods for RodaRequest
633
- module RequestClassMethods
634
- # Reference to the Roda class related to this request class.
635
- attr_accessor :roda_class
636
-
637
- # The cache to use for match patterns for this request class.
638
- attr_accessor :match_pattern_cache
639
-
640
- # Return the cached pattern for the given object. If the object is
641
- # not already cached, yield to get the basic pattern, and convert the
642
- # basic pattern to a pattern that does not partial segments.
643
- def cached_matcher(obj)
644
- cache = @match_pattern_cache
645
-
646
- unless pattern = cache[obj]
647
- pattern = cache[obj] = consume_pattern(yield)
648
- end
649
-
650
- pattern
651
- end
652
-
653
- # Since RodaRequest is anonymously subclassed when Roda is subclassed,
654
- # and then assigned to a constant of the Roda subclass, make inspect
655
- # reflect the likely name for the class.
656
- def inspect
657
- "#{roda_class.inspect}::RodaRequest"
658
- end
659
-
660
- private
661
-
662
- # The pattern to use for consuming, based on the given argument. The returned
663
- # pattern requires the path starts with a string and does not match partial
664
- # segments.
665
- def consume_pattern(pattern)
666
- /\A\/(?:#{pattern})(?=\/|\z)/
667
- end
668
- end
669
-
670
- # Instance methods for RodaRequest, mostly related to handling routing
671
- # for the request.
672
- module RequestMethods
673
- TERM = Object.new
674
- def TERM.inspect
675
- "TERM"
676
- end
677
- TERM.freeze
678
-
679
- # The current captures for the request. This gets modified as routing
680
- # occurs.
681
- attr_reader :captures
682
-
683
- # The Roda instance related to this request object. Useful if routing
684
- # methods need access to the scope of the Roda route block.
685
- attr_reader :scope
686
-
687
- # Store the roda instance and environment.
688
- def initialize(scope, env)
689
- @scope = scope
690
- @captures = []
691
- @remaining_path = _remaining_path(env)
692
- @env = env
693
- end
694
-
695
- # Handle match block return values. By default, if a string is given
696
- # and the response is empty, use the string as the response body.
697
- def block_result(result)
698
- res = response
699
- if res.empty? && (body = block_result_body(result))
700
- res.write(body)
701
- end
702
- end
703
-
704
- # Match GET requests. If no arguments are provided, matches all GET
705
- # requests, otherwise, matches only GET requests where the arguments
706
- # given fully consume the path.
707
- def get(*args, &block)
708
- _verb(args, &block) if is_get?
709
- end
710
-
711
- # Immediately stop execution of the route block and return the given
712
- # rack response array of status, headers, and body. If no argument
713
- # is given, uses the current response.
714
- #
715
- # r.halt [200, {'Content-Type'=>'text/html'}, ['Hello World!']]
716
- #
717
- # response.status = 200
718
- # response['Content-Type'] = 'text/html'
719
- # response.write 'Hello World!'
720
- # r.halt
721
- def halt(res=response.finish)
722
- throw :halt, res
723
- end
724
-
725
- # Show information about current request, including request class,
726
- # request method and full path.
727
- #
728
- # r.inspect
729
- # # => '#<Roda::RodaRequest GET /foo/bar>'
730
- def inspect
731
- "#<#{self.class.inspect} #{@env["REQUEST_METHOD"]} #{path}>"
732
- end
733
-
734
- # Does a terminal match on the current path, matching only if the arguments
735
- # have fully matched the path. If it matches, the match block is
736
- # executed, and when the match block returns, the rack response is
737
- # returned.
738
- #
739
- # r.remaining_path
740
- # # => "/foo/bar"
741
- #
742
- # r.is 'foo' do
743
- # # does not match, as path isn't fully matched (/bar remaining)
744
- # end
745
- #
746
- # r.is 'foo/bar' do
747
- # # matches as path is empty after matching
748
- # end
749
- #
750
- # If no arguments are given, matches if the path is already fully matched.
751
- #
752
- # r.on 'foo/bar' do
753
- # r.is do
754
- # # matches as path is already empty
755
- # end
756
- # end
757
- #
758
- # Note that this matches only if the path after matching the arguments
759
- # is empty, not if it still contains a trailing slash:
760
- #
761
- # r.remaining_path
762
- # # => "/foo/bar/"
763
- #
764
- # r.is 'foo/bar' do
765
- # # does not match, as path isn't fully matched (/ remaining)
766
- # end
767
- #
768
- # r.is 'foo/bar/' do
769
- # # matches as path is empty after matching
770
- # end
771
- #
772
- # r.on 'foo/bar' do
773
- # r.is "" do
774
- # # matches as path is empty after matching
775
- # end
776
- # end
777
- def is(*args, &block)
778
- if args.empty?
779
- if empty_path?
780
- always(&block)
781
- end
782
- else
783
- args << TERM
784
- if_match(args, &block)
785
- end
786
- end
787
-
788
- # Optimized method for whether this request is a +GET+ request.
789
- # Similar to the default Rack::Request get? method, but can be
790
- # overridden without changing rack's behavior.
791
- def is_get?
792
- @env["REQUEST_METHOD"] == 'GET'
793
- end
794
-
795
- # Does a match on the path, matching only if the arguments
796
- # have matched the path. Because this doesn't fully match the
797
- # path, this is usually used to setup branches of the routing tree,
798
- # not for final handling of the request.
799
- #
800
- # r.remaining_path
801
- # # => "/foo/bar"
802
- #
803
- # r.on 'foo' do
804
- # # matches, path is /bar after matching
805
- # end
806
- #
807
- # r.on 'bar' do
808
- # # does not match
809
- # end
810
- #
811
- # Like other routing methods, If it matches, the match block is
812
- # executed, and when the match block returns, the rack response is
813
- # returned. However, in general you will call another routing method
814
- # inside the match block that fully matches the path and does the
815
- # final handling for the request:
816
- #
817
- # r.on 'foo' do
818
- # r.is 'bar' do
819
- # # handle /foo/bar request
820
- # end
821
- # end
822
- def on(*args, &block)
823
- if args.empty?
824
- always(&block)
825
- else
826
- if_match(args, &block)
827
- end
828
- end
829
-
830
- # The already matched part of the path, including the original SCRIPT_NAME.
831
- def matched_path
832
- e = @env
833
- e["SCRIPT_NAME"] + e["PATH_INFO"].chomp(@remaining_path)
834
- end
835
-
836
- # This an an optimized version of Rack::Request#path.
837
- #
838
- # r.env['SCRIPT_NAME'] = '/foo'
839
- # r.env['PATH_INFO'] = '/bar'
840
- # r.path
841
- # # => '/foo/bar'
842
- def path
843
- e = @env
844
- "#{e["SCRIPT_NAME"]}#{e["PATH_INFO"]}"
845
- end
846
-
847
- # The current path to match requests against.
848
- attr_reader :remaining_path
849
-
850
- # An alias of remaining_path. If a plugin changes remaining_path then
851
- # it should override this method to return the untouched original.
852
- def real_remaining_path
853
- remaining_path
854
- end
855
-
856
- # Match POST requests. If no arguments are provided, matches all POST
857
- # requests, otherwise, matches only POST requests where the arguments
858
- # given fully consume the path.
859
- def post(*args, &block)
860
- _verb(args, &block) if post?
861
- end
862
-
863
- # Immediately redirect to the path using the status code. This ends
864
- # the processing of the request:
865
- #
866
- # r.redirect '/page1', 301 if r['param'] == 'value1'
867
- # r.redirect '/page2' # uses 302 status code
868
- # response.status = 404 # not reached
869
- #
870
- # If you do not provide a path, by default it will redirect to the same
871
- # path if the request is not a +GET+ request. This is designed to make
872
- # it easy to use where a +POST+ request to a URL changes state, +GET+
873
- # returns the current state, and you want to show the current state
874
- # after changing:
875
- #
876
- # r.is "foo" do
877
- # r.get do
878
- # # show state
879
- # end
880
- #
881
- # r.post do
882
- # # change state
883
- # r.redirect
884
- # end
885
- # end
886
- def redirect(path=default_redirect_path, status=default_redirect_status)
887
- response.redirect(path, status)
888
- throw :halt, response.finish
889
- end
890
-
891
- # The response related to the current request. See ResponseMethods for
892
- # instance methods for the response, but in general the most common usage
893
- # is to override the response status and headers:
894
- #
895
- # response.status = 200
896
- # response['Header-Name'] = 'Header value'
897
- def response
898
- @scope.response
899
- end
900
-
901
- # Return the Roda class related to this request.
902
- def roda_class
903
- self.class.roda_class
904
- end
905
-
906
- # Match method that only matches +GET+ requests where the current
907
- # path is +/+. If it matches, the match block is executed, and when
908
- # the match block returns, the rack response is returned.
909
- #
910
- # [r.request_method, r.remaining_path]
911
- # # => ['GET', '/']
912
- #
913
- # r.root do
914
- # # matches
915
- # end
916
- #
917
- # This is usuable inside other match blocks:
918
- #
919
- # [r.request_method, r.remaining_path]
920
- # # => ['GET', '/foo/']
921
- #
922
- # r.on 'foo' do
923
- # r.root do
924
- # # matches
925
- # end
926
- # end
927
- #
928
- # Note that this does not match non-+GET+ requests:
929
- #
930
- # [r.request_method, r.remaining_path]
931
- # # => ['POST', '/']
932
- #
933
- # r.root do
934
- # # does not match
935
- # end
936
- #
937
- # Use <tt>r.post ""</tt> for +POST+ requests where the current path
938
- # is +/+.
939
- #
940
- # Nor does it match empty paths:
941
- #
942
- # [r.request_method, r.remaining_path]
943
- # # => ['GET', '/foo']
944
- #
945
- # r.on 'foo' do
946
- # r.root do
947
- # # does not match
948
- # end
949
- # end
950
- #
951
- # Use <tt>r.get true</tt> to handle +GET+ requests where the current
952
- # path is empty.
953
- def root(&block)
954
- if remaining_path == "/" && is_get?
955
- always(&block)
956
- end
957
- end
958
-
959
- # Call the given rack app with the environment and return the response
960
- # from the rack app as the response for this request. This ends
961
- # the processing of the request:
962
- #
963
- # r.run(proc{[403, {}, []]}) unless r['letmein'] == '1'
964
- # r.run(proc{[404, {}, []]})
965
- # response.status = 404 # not reached
966
- #
967
- # This updates SCRIPT_NAME/PATH_INFO based on the current remaining_path
968
- # before dispatching to another rack app, so the app still works as
969
- # a URL mapper.
970
- def run(app)
971
- e = @env
972
- path = real_remaining_path
973
- sn = "SCRIPT_NAME"
974
- pi = "PATH_INFO"
975
- script_name = e[sn]
976
- path_info = e[pi]
977
- begin
978
- e[sn] += path_info.chomp(path)
979
- e[pi] = path
980
- throw :halt, app.call(e)
981
- ensure
982
- e[sn] = script_name
983
- e[pi] = path_info
984
- end
985
- end
986
-
987
- # The session for the current request. Raises a RodaError if
988
- # a session handler has not been loaded.
989
- def session
990
- @env['rack.session'] || raise(RodaError, "You're missing a session handler, try using the sessions plugin.")
991
- end
992
-
993
- private
994
-
995
- # Match any of the elements in the given array. Return at the
996
- # first match without evaluating future matches. Returns false
997
- # if no elements in the array match.
998
- def _match_array(matcher)
999
- matcher.any? do |m|
1000
- if matched = match(m)
1001
- if m.is_a?(String)
1002
- @captures.push(m)
1003
- end
1004
- end
1005
-
1006
- matched
1007
- end
1008
- end
1009
-
1010
- # Match the given class. Currently, the following classes
1011
- # are supported by default:
1012
- # Integer :: Match an integer segment, yielding result to block as an integer
1013
- # String :: Match any non-empty segment, yielding result to block as a string
1014
- def _match_class(klass)
1015
- meth = :"_match_class_#{klass}"
1016
- if respond_to?(meth, true)
1017
- # Allow calling private methods, as match methods are generally private
1018
- send(meth)
1019
- else
1020
- unsupported_matcher(klass)
1021
- end
1022
- end
1023
-
1024
- # Match the given hash if all hash matchers match.
1025
- def _match_hash(hash)
1026
- # Allow calling private methods, as match methods are generally private
1027
- hash.all?{|k,v| send("match_#{k}", v)}
1028
- end
1029
-
1030
- # Match integer segment, and yield resulting value as an
1031
- # integer.
1032
- def _match_class_Integer
1033
- consume(/\A\/(\d+)(?=\/|\z)/){|i| [i.to_i]}
1034
- end
1035
-
1036
- # Match only if all of the arguments in the given array match.
1037
- # Match the given regexp exactly if it matches a full segment.
1038
- def _match_regexp(re)
1039
- consume(self.class.cached_matcher(re){re})
1040
- end
1041
-
1042
- # Match the given string to the request path. Matches only if the
1043
- # request path ends with the string or if the next character in the
1044
- # request path is a slash (indicating a new segment).
1045
- def _match_string(str)
1046
- rp = @remaining_path
1047
- length = str.length
1048
-
1049
- match = case rp.rindex(str, length)
1050
- when nil
1051
- # segment does not match, most common case
1052
- return
1053
- when 1
1054
- # segment matches, check first character is /
1055
- rp.getbyte(0) == 47
1056
- else # must be 0
1057
- # segment matches at first character, only a match if
1058
- # empty string given and first character is /
1059
- length == 0 && rp.getbyte(0) == 47
1060
- end
1061
-
1062
- if match
1063
- length += 1
1064
- case rp.getbyte(length)
1065
- when 47
1066
- # next character is /, update remaining path to rest of string
1067
- @remaining_path = rp[length, 100000000]
1068
- when nil
1069
- # end of string, so remaining path is empty
1070
- @remaining_path = ""
1071
- # else
1072
- # Any other value means this was partial segment match,
1073
- # so we return nil in that case without updating the
1074
- # remaining_path. No need for explicit else clause.
1075
- end
1076
- end
1077
- end
1078
-
1079
- # Match the given symbol if any segment matches.
1080
- def _match_symbol(sym=nil)
1081
- rp = @remaining_path
1082
- if rp.getbyte(0) == 47
1083
- if last = rp.index('/', 1)
1084
- if last > 1
1085
- @captures << rp[1, last-1]
1086
- @remaining_path = rp[last, rp.length]
1087
- end
1088
- elsif rp.length > 1
1089
- @captures << rp[1,rp.length]
1090
- @remaining_path = ""
1091
- end
1092
- end
1093
- end
1094
-
1095
- # Match any nonempty segment. This should be called without an argument.
1096
- alias _match_class_String _match_symbol
1097
-
1098
- # The base remaining path to use.
1099
- def _remaining_path(env)
1100
- env["PATH_INFO"]
1101
- end
1102
-
1103
- # Backbone of the verb method support, using a terminal match if
1104
- # args is not empty, or a regular match if it is empty.
1105
- def _verb(args, &block)
1106
- if args.empty?
1107
- always(&block)
1108
- else
1109
- args << TERM
1110
- if_match(args, &block)
1111
- end
1112
- end
1113
-
1114
- # Yield to the match block and return rack response after the block returns.
1115
- def always
1116
- block_result(yield)
1117
- throw :halt, response.finish
1118
- end
1119
-
1120
- # The body to use for the response if the response does not already have
1121
- # a body. By default, a String is returned directly, and nil is
1122
- # returned otherwise.
1123
- def block_result_body(result)
1124
- case result
1125
- when String
1126
- result
1127
- when nil, false
1128
- # nothing
1129
- else
1130
- raise RodaError, "unsupported block result: #{result.inspect}"
1131
- end
1132
- end
1133
-
1134
- # Attempts to match the pattern to the current path. If there is no
1135
- # match, returns false without changes. Otherwise, modifies
1136
- # SCRIPT_NAME to include the matched path, removes the matched
1137
- # path from PATH_INFO, and updates captures with any regex captures.
1138
- def consume(pattern)
1139
- if matchdata = remaining_path.match(pattern)
1140
- @remaining_path = matchdata.post_match
1141
- captures = matchdata.captures
1142
- captures = yield(*captures) if block_given?
1143
- @captures.concat(captures)
1144
- end
1145
- end
1146
-
1147
- # The default path to use for redirects when a path is not given.
1148
- # For non-GET requests, redirects to the current path, which will
1149
- # trigger a GET request. This is to make the common case where
1150
- # a POST request will redirect to a GET request at the same location
1151
- # will work fine.
1152
- #
1153
- # If the current request is a GET request, raise an error, as otherwise
1154
- # it is easy to create an infinite redirect.
1155
- def default_redirect_path
1156
- raise RodaError, "must provide path argument to redirect for get requests" if is_get?
1157
- path
1158
- end
1159
-
1160
- # The default status to use for redirects if a status is not provided,
1161
- # 302 by default.
1162
- def default_redirect_status
1163
- 302
1164
- end
1165
-
1166
- # Whether the current path is considered empty.
1167
- def empty_path?
1168
- remaining_path.empty?
1169
- end
1170
-
1171
- # If all of the arguments match, yields to the match block and
1172
- # returns the rack response when the block returns. If any of
1173
- # the match arguments doesn't match, does nothing.
1174
- def if_match(args)
1175
- path = @remaining_path
1176
- # For every block, we make sure to reset captures so that
1177
- # nesting matchers won't mess with each other's captures.
1178
- captures = @captures.clear
1179
-
1180
- if match_all(args)
1181
- block_result(yield(*captures))
1182
- throw :halt, response.finish
1183
- else
1184
- @remaining_path = path
1185
- false
1186
- end
1187
- end
1188
-
1189
- # Attempt to match the argument to the given request, handling
1190
- # common ruby types.
1191
- def match(matcher)
1192
- case matcher
1193
- when String
1194
- _match_string(matcher)
1195
- when Class
1196
- _match_class(matcher)
1197
- when TERM
1198
- empty_path?
1199
- when Regexp
1200
- _match_regexp(matcher)
1201
- when true
1202
- matcher
1203
- when Array
1204
- _match_array(matcher)
1205
- when Hash
1206
- _match_hash(matcher)
1207
- when Symbol
1208
- _match_symbol(matcher)
1209
- when false, nil
1210
- matcher
1211
- when Proc
1212
- matcher.call
1213
- else
1214
- unsupported_matcher(matcher)
1215
- end
1216
- end
1217
-
1218
- # Match only if all of the arguments in the given array match.
1219
- def match_all(args)
1220
- args.all?{|arg| match(arg)}
1221
- end
1222
-
1223
- # Match by request method. This can be an array if you want
1224
- # to match on multiple methods.
1225
- def match_method(type)
1226
- if type.is_a?(Array)
1227
- type.any?{|t| match_method(t)}
1228
- else
1229
- type.to_s.upcase == @env["REQUEST_METHOD"]
1230
- end
1231
- end
1232
-
1233
- # Handle an unsupported matcher.
1234
- def unsupported_matcher(matcher)
1235
- raise RodaError, "unsupported matcher: #{matcher.inspect}"
1236
- end
1237
- end
1238
-
1239
- # Class methods for RodaResponse
1240
- module ResponseClassMethods
1241
- # Reference to the Roda class related to this response class.
1242
- attr_accessor :roda_class
1243
-
1244
- # Since RodaResponse is anonymously subclassed when Roda is subclassed,
1245
- # and then assigned to a constant of the Roda subclass, make inspect
1246
- # reflect the likely name for the class.
1247
- def inspect
1248
- "#{roda_class.inspect}::RodaResponse"
1249
- end
1250
- end
1251
-
1252
- # Instance methods for RodaResponse
1253
- module ResponseMethods
1254
- DEFAULT_HEADERS = {"Content-Type" => "text/html".freeze}.freeze
1255
-
1256
- # The body for the current response.
1257
- attr_reader :body
1258
-
1259
- # The hash of response headers for the current response.
1260
- attr_reader :headers
1261
-
1262
- # The status code to use for the response. If none is given, will use 200
1263
- # code for non-empty responses and a 404 code for empty responses.
1264
- attr_accessor :status
1265
-
1266
- # Set the default headers when creating a response.
1267
- def initialize
1268
- @headers = {}
1269
- @body = []
1270
- @length = 0
1271
- end
1272
-
1273
- # Return the response header with the given key. Example:
1274
- #
1275
- # response['Content-Type'] # => 'text/html'
1276
- def [](key)
1277
- @headers[key]
1278
- end
1279
-
1280
- # Set the response header with the given key to the given value.
1281
- #
1282
- # response['Content-Type'] = 'application/json'
1283
- def []=(key, value)
1284
- @headers[key] = value
1285
- end
1286
-
1287
- # The default headers to use for responses.
1288
- def default_headers
1289
- DEFAULT_HEADERS
1290
- end
1291
-
1292
- # Whether the response body has been written to yet. Note
1293
- # that writing an empty string to the response body marks
1294
- # the response as not empty. Example:
1295
- #
1296
- # response.empty? # => true
1297
- # response.write('a')
1298
- # response.empty? # => false
1299
- def empty?
1300
- @body.empty?
1301
- end
1302
-
1303
- # Return the rack response array of status, headers, and body
1304
- # for the current response. If the status has not been set,
1305
- # uses the return value of default_status if the body has
1306
- # been written to, otherwise uses a 404 status.
1307
- # Adds the Content-Length header to the size of the response body.
1308
- #
1309
- # Example:
1310
- #
1311
- # response.finish
1312
- # # => [200,
1313
- # # {'Content-Type'=>'text/html', 'Content-Length'=>'0'},
1314
- # # []]
1315
- def finish
1316
- b = @body
1317
- set_default_headers
1318
- h = @headers
1319
-
1320
- if b.empty?
1321
- s = @status || 404
1322
- if (s == 304 || s == 204 || (s >= 100 && s <= 199))
1323
- h.delete("Content-Type")
1324
- elsif s == 205
1325
- h.delete("Content-Type")
1326
- h["Content-Length"] = '0'
1327
- else
1328
- h["Content-Length"] ||= '0'
1329
- end
1330
- else
1331
- s = @status || default_status
1332
- h["Content-Length"] ||= @length.to_s
1333
- end
1334
-
1335
- [s, h, b]
1336
- end
1337
-
1338
- # Return the rack response array using a given body. Assumes a
1339
- # 200 response status unless status has been explicitly set,
1340
- # and doesn't add the Content-Length header or use the existing
1341
- # body.
1342
- def finish_with_body(body)
1343
- set_default_headers
1344
- [@status || default_status, @headers, body]
1345
- end
1346
-
1347
- # Return the default response status to be used when the body
1348
- # has been written to. This is split out to make overriding
1349
- # easier in plugins.
1350
- def default_status
1351
- 200
1352
- end
1353
-
1354
- # Show response class, status code, response headers, and response body
1355
- def inspect
1356
- "#<#{self.class.inspect} #{@status.inspect} #{@headers.inspect} #{@body.inspect}>"
1357
- end
1358
-
1359
- # Set the Location header to the given path, and the status
1360
- # to the given status. Example:
1361
- #
1362
- # response.redirect('foo', 301)
1363
- # response.redirect('bar')
1364
- def redirect(path, status = 302)
1365
- @headers["Location"] = path
1366
- @status = status
1367
- nil
1368
- end
1369
-
1370
- # Return the Roda class related to this response.
1371
- def roda_class
1372
- self.class.roda_class
1373
- end
1374
-
1375
- # Write to the response body. Returns nil.
1376
- #
1377
- # response.write('foo')
1378
- def write(str)
1379
- s = str.to_s
1380
- @length += s.bytesize
1381
- @body << s
1382
- nil
1383
- end
1384
-
1385
- private
1386
-
1387
- # For each default header, if a header has not already been set for the
1388
- # response, set the header in the response.
1389
- def set_default_headers
1390
- h = @headers
1391
- default_headers.each do |k,v|
1392
- h[k] ||= v
1393
- end
1394
- end
1395
- end
1396
543
  end
1397
544
  end
1398
545