webmachine 1.4.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 76f443d8e22ccb13b371b7aedfd2d02204653d89
4
- data.tar.gz: d2b04ca2adc316e74903f02696bd55ae6bc1a900
3
+ metadata.gz: 9309a2f44549ae1c912684fcdc7f60d31404bbf3
4
+ data.tar.gz: 9531f40a275a2bf5e2bda7ed4816ff2463e4c234
5
5
  SHA512:
6
- metadata.gz: ea9cdbfdf582062d7c5eea818816482f99bb75e915625b62263b62936c79b5126c5f046cab13bc72524caf97c1e0cf031551e445b0f10d9d0b0714377e556e7f
7
- data.tar.gz: 26575bcdc2c324d42c2af0cb1029654a4c4c92003a17738c12036f55908c97d94f8a461e79f24df3700040c480937568a03f55f9c2c21c1dd40ca1fae2cc980a
6
+ metadata.gz: 5f5e9419fadcf121f8e0e80ec41417bd5dd9f096933aaf06793dd18d9d6ac6f5e2d028f56e0bc105ff5a87f9b2aa7ebc72e1d61424cc8c88d76e7cb92b9655a0
7
+ data.tar.gz: d79442bba70911a7b704a26135a2fcadc4dbc1f0a0e12197d1ddbfb055498c54e164e972b03479d7428839ceef15bf32faa070b7ace58fc4b76704bdc5ec7bf8
data/.gitignore CHANGED
@@ -29,4 +29,3 @@ Gemfile.lock
29
29
  .SyncID
30
30
  .SyncIgnore
31
31
  vendor
32
-
@@ -1,5 +1,12 @@
1
1
  ### HEAD
2
2
 
3
+ ### 1.5.0 September 8, 2017
4
+ * Removed Fixnum/Integer deprecation warnings in Ruby 2.4
5
+ * Fixed multiple cookie setting code
6
+ * Added support for named captures
7
+ * Improved logic for determining which errors are 'rescuable' by Webmachine,
8
+ and which are 'unhandlable'.
9
+
3
10
  ### 1.4.0 March 20, 2015
4
11
 
5
12
  * Added RackMapped adapter which allows Webmachine apps to be mounted
data/Gemfile CHANGED
@@ -3,15 +3,15 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :development do
6
- gem "yard"
7
- gem "rake"
6
+ gem "yard", "~> 0.9"
7
+ gem "rake", "~> 12.0"
8
8
  end
9
9
 
10
10
  group :test do
11
- gem "rspec", '~> 3.0.0'
12
- gem "rspec-its"
13
- gem "rack"
14
- gem "rack-test"
11
+ gem "rspec", "~> 3.0", ">= 3.6.0"
12
+ gem "rspec-its", "~> 1.2"
13
+ gem "rack", "~> 2.0"
14
+ gem "rack-test", "~> 0.7"
15
15
  end
16
16
 
17
17
  group :webservers do
@@ -21,12 +21,12 @@ group :webservers do
21
21
  end
22
22
 
23
23
  group :guard do
24
- gem 'guard-rspec'
24
+ gem 'guard-rspec', '~> 4.7'
25
25
  case RbConfig::CONFIG['host_os']
26
26
  when /darwin/
27
- gem 'rb-fsevent'
27
+ gem 'rb-fsevent', '~> 0.10'
28
28
  # gem 'growl_notify'
29
- gem 'growl'
29
+ gem 'growl', '~> 1.0'
30
30
  when /linux/
31
31
  gem 'rb-inotify'
32
32
  gem 'libnotify'
@@ -35,7 +35,7 @@ end
35
35
 
36
36
  group :docs do
37
37
  platform :mri_19, :mri_20 do
38
- gem 'redcarpet'
38
+ gem 'redcarpet', '~> 3.4'
39
39
  end
40
40
  end
41
41
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # webmachine for Ruby [![Build Status](https://travis-ci.org/seancribbs/webmachine-ruby.svg)](https://travis-ci.org/seancribbs/webmachine-ruby)
1
+ # webmachine for Ruby [![Build Status](https://travis-ci.org/webmachine/webmachine-ruby.svg)](https://travis-ci.org/webmachine/webmachine-ruby)
2
2
 
3
3
  webmachine-ruby is a port of
4
4
  [Webmachine](https://github.com/basho/webmachine), which is written in
@@ -163,7 +163,7 @@ the "visual debugger". Learn how to configure it [here][visual-debugger].
163
163
 
164
164
  ## Related libraries
165
165
 
166
- * [irwebmachine](https://github.com/robgleeson/irwebmachine) - IRB/Pry debugging of Webmachine applications
166
+ * [irwebmachine](https://github.com/generalassembly/irwebmachine) - IRB/Pry debugging of Webmachine applications
167
167
  * [webmachine-test](https://github.com/bernd/webmachine-test) - Helpers for testing Webmachine applications
168
168
  * [webmachine-linking](https://github.com/petejohanson/webmachine-linking) - Helpers for linking between Resources, and Web Linking
169
169
  * [webmachine-sprockets](https://github.com/lgierth/webmachine-sprockets) - Integration with Sprockets assets packaging system
@@ -0,0 +1,21 @@
1
+ 1. Open `CHANGELOG.md` and summarize the changes made since the last release (hopefully better than the individual commit messages). The history can be grabbed with a simple git command (assuming the last version was 1.3.0:
2
+
3
+ $ git log --pretty=format:' * %s' v1.3.0..HEAD
4
+
5
+ 2. Edit the version in `lib/webmachine/version.rb` according to semantic versioning rules.
6
+ 3. Commit both files.
7
+
8
+ $ git add CHANGELOG.md lib/webmachine/version.rb
9
+ $ git commit -m "chore(release): version $(ruby -r ./lib/webmachine/version.rb -e "puts Webmachine::VERSION")" && git push
10
+
11
+ 4. Release the gem.
12
+
13
+ $ bundle exec rake release
14
+
15
+ 5. If this is a new major or minor release, push a new stable branch, otherwise merge the commit into the stable branch (or master, depending on where you made the commit).
16
+
17
+ $ git push origin HEAD:1.3-stable
18
+ # or
19
+ $ git checkout 1.3-stable; git merge master; git push origin; git checkout master
20
+
21
+ 6. YOU'RE DONE!
data/Rakefile CHANGED
@@ -1,5 +1,4 @@
1
- require 'rubygems'
2
- require 'rubygems/package_task'
1
+ require "bundler/gem_tasks"
3
2
 
4
3
  begin
5
4
  require 'yard'
@@ -11,26 +10,12 @@ begin
11
10
  rescue LoadError
12
11
  end
13
12
 
14
- def gemspec
15
- $webmachine_gemspec ||= Gem::Specification.load("webmachine.gemspec")
13
+ desc "Validate the gemspec file."
14
+ task :validate_gemspec do
15
+ Gem::Specification.load("webmachine.gemspec").validate
16
16
  end
17
17
 
18
- Gem::PackageTask.new(gemspec) do |pkg|
19
- pkg.need_zip = false
20
- pkg.need_tar = false
21
- end
22
-
23
- task :gem => :gemspec
24
-
25
- desc %{Validate the gemspec file.}
26
- task :gemspec do
27
- gemspec.validate
28
- end
29
-
30
- desc %{Release the gem to RubyGems.org}
31
- task :release => :gem do
32
- system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
33
- end
18
+ task :build => :validate_gemspec
34
19
 
35
20
  desc "Cleans up white space in source files"
36
21
  task :clean_whitespace do
@@ -0,0 +1,3 @@
1
+ * use Integer instead of Fixnum (removes deprecation warnings in Ruby 2.4)
2
+ * Fixing multiple cookie setting code
3
+ * Added support for named captures
@@ -137,6 +137,8 @@ class OrderResource < Webmachine::Resource
137
137
  [["application/json", :from_json]]
138
138
  end
139
139
 
140
+ # Note that returning falsey will NOT result in a 404 for PUT requests.
141
+ # See note below.
140
142
  def resource_exists?
141
143
  order
142
144
  end
@@ -166,8 +168,10 @@ class OrderResource < Webmachine::Resource
166
168
  end
167
169
  ```
168
170
 
171
+ If you wish to disallow PUT to a non-existent resource, read more [here](https://github.com/webmachine/webmachine-ruby/issues/207#issuecomment-132604379).
172
+
169
173
  # PATCH
170
- * Webmachine does not currently support PATCH requests. See https://github.com/seancribbs/webmachine-ruby/issues/109 for more information and https://github.com/bethesque/pact_broker/blob/2918814e70bbda14df68598a6a41502a5eac4308/lib/pact_broker/api/resources/pacticipant.rb for a dirty hack to make it work if you need to.
174
+ * Webmachine does not currently support PATCH requests. See https://github.com/webmachine/webmachine-ruby/issues/109 for more information and https://github.com/bethesque/pact_broker/blob/2918814e70bbda14df68598a6a41502a5eac4308/lib/pact_broker/api/resources/pacticipant.rb for a dirty hack to make it work if you need to.
171
175
 
172
176
  # DELETE
173
177
  * Override `resource_exists?` and `delete_resource`
@@ -208,8 +212,7 @@ Thanks to [oestrich][oestrich] for putting together the original example. You ca
208
212
  [oestrich]: https://github.com/oestrich
209
213
  [source]: https://gist.github.com/oestrich/3638605
210
214
 
211
- <a name="callback-order">
215
+ <a name="callback-order"></a>
212
216
  ## What order are the callbacks invoked in?
213
- </a>
214
217
 
215
218
  This question is actually irrelevant if you write your code in a "stateless" way using lazy initialization as the examples do above. As much as possible, think about exposing "facts" about your resource, not writing procedural code that needs to be called in a certain order. See [How it works](/documentation/how-it-works.md) for more information on how the Webmachine state machine works.
@@ -71,6 +71,6 @@ end
71
71
  * A collection resource (eg. /orders) should be implemented as a separate class to a single object resource (eg. /orders/1), as the routes represent different underlying objects with different "facts". For example, the orders _collection_ resource probably always exists (but may be empty), however the order with ID 1 may or may not exist.
72
72
 
73
73
  [callbacks]: https://github.com/seancribbs/webmachine-ruby/blob/master/lib/webmachine/resource/callbacks.rb
74
- [diagram]: http://webmachine.basho.com/images/http-headers-status-v3.png
74
+ [diagram]: https://webmachine.github.io/images/http-headers-status-v3.png
75
75
  [flow]: https://github.com/seancribbs/webmachine-ruby/blob/master/lib/webmachine/decision/flow.rb
76
76
  [examples]: /documentation/examples.md
@@ -20,6 +20,21 @@ App = Webmachine::Application.new do |app|
20
20
  # but will not provide any path_info
21
21
  add ["orders", :*], OrderResource
22
22
 
23
+ # Will map to any path that matches the given components and regular expression
24
+ # Any capture groups specified in the regex will be made available in
25
+ # request.path_info[:captures. In this case, you would get one or two
26
+ # values in :captures depending on whether your request looked like:
27
+ # /orders/1/cancel
28
+ # or
29
+ # /orders/1/cancel.json
30
+ add ["orders", :id, /([^.]*)\.?(.*)?/], OrderResource
31
+
32
+ # You can even use named captures with regular expressions. This will
33
+ # automatically put the captures into path_info. In the below example,
34
+ # you would get :id from the symbol, along with :action and :format
35
+ # from the regex. :format in this case would be optional.
36
+ add ["orders", :id, /(?<action>)[^.]*)\.?(?<format>.*)?/], OrderResource
37
+
23
38
  # will map to any path
24
39
  add [:*], DefaultResource
25
40
  end
@@ -94,4 +109,4 @@ end
94
109
  request.path_info[:foo]
95
110
  => "bar"
96
111
 
97
- ```
112
+ ```
@@ -1,6 +1,7 @@
1
1
  require 'webmachine/adapter'
2
2
  require 'webmachine/constants'
3
3
  require 'set'
4
+ require 'celluloid/current'
4
5
  require 'reel'
5
6
  require 'webmachine/headers'
6
7
  require 'webmachine/request'
@@ -5,7 +5,7 @@ module Webmachine
5
5
  # defaults will be filled in when {Webmachine::run} is called.
6
6
  # @attr [String] ip the interface to bind to, defaults to "0.0.0.0"
7
7
  # (all interfaces)
8
- # @attr [Fixnum] port the port to bind to, defaults to 8080
8
+ # @attr [Integer] port the port to bind to, defaults to 8080
9
9
  # @attr [Symbol] adapter the adapter to use, defaults to :WEBrick
10
10
  # @attr [Hash] adapter_options adapter-specific options, defaults to {}
11
11
  Configuration = Struct.new(:ip, :port, :adapter, :adapter_options)
@@ -35,7 +35,7 @@ module Webmachine
35
35
  # Handles standard decisions where halting is allowed
36
36
  def decision_test(test, iftrue, iffalse)
37
37
  case test
38
- when Fixnum # Allows callbacks to "halt" with a given response code
38
+ when Integer # Allows callbacks to "halt" with a given response code
39
39
  test
40
40
  when Falsey
41
41
  iffalse
@@ -77,7 +77,7 @@ module Webmachine
77
77
  # Content-MD5 valid?
78
78
  def b9a
79
79
  case valid = resource.validate_content_checksum
80
- when Fixnum
80
+ when Integer
81
81
  valid
82
82
  when true
83
83
  :b9b
@@ -105,7 +105,7 @@ module Webmachine
105
105
  case result
106
106
  when true
107
107
  :b7
108
- when Fixnum
108
+ when Integer
109
109
  result
110
110
  when String
111
111
  response.headers['WWW-Authenticate'] = result
@@ -280,7 +280,7 @@ module Webmachine
280
280
  when String, URI
281
281
  response.headers[LOCATION] = uri.to_s
282
282
  301
283
- when Fixnum
283
+ when Integer
284
284
  uri
285
285
  else
286
286
  :p3
@@ -313,7 +313,7 @@ module Webmachine
313
313
  when String, URI
314
314
  response.headers[LOCATION] = uri.to_s
315
315
  301
316
- when Fixnum
316
+ when Integer
317
317
  uri
318
318
  else
319
319
  :l5
@@ -342,7 +342,7 @@ module Webmachine
342
342
  when String, URI
343
343
  response.headers[LOCATION] = uri.to_s
344
344
  307
345
- when Fixnum
345
+ when Integer
346
346
  uri
347
347
  else
348
348
  :m5
@@ -422,13 +422,13 @@ module Webmachine
422
422
  request.disp_path = new_uri.path
423
423
  response.headers[LOCATION] = new_uri.to_s
424
424
  result = accept_helper
425
- return result if Fixnum === result
425
+ return result if Integer === result
426
426
  end
427
427
  else
428
428
  case result = resource.process_post
429
429
  when true
430
430
  encode_body_if_set
431
- when Fixnum
431
+ when Integer
432
432
  return result
433
433
  else
434
434
  raise InvalidResource, t('process_post_invalid', :result => result.inspect)
@@ -456,7 +456,7 @@ module Webmachine
456
456
  409
457
457
  else
458
458
  res = accept_helper
459
- (Fixnum === res) ? res : :p11
459
+ (Integer === res) ? res : :p11
460
460
  end
461
461
  end
462
462
 
@@ -473,7 +473,7 @@ module Webmachine
473
473
  content_type = metadata[CONTENT_TYPE]
474
474
  handler = resource.content_types_provided.find {|ct, _| content_type.type_matches?(MediaType.parse(ct)) }.last
475
475
  result = resource.send(handler)
476
- if Fixnum === result
476
+ if Integer === result
477
477
  result
478
478
  else
479
479
  response.body = result
@@ -501,7 +501,7 @@ module Webmachine
501
501
  409
502
502
  else
503
503
  res = accept_helper
504
- (Fixnum === res) ? res : :p11
504
+ (Integer === res) ? res : :p11
505
505
  end
506
506
  end
507
507
 
@@ -2,6 +2,7 @@
2
2
  require 'webmachine/trace'
3
3
  require 'webmachine/translation'
4
4
  require 'webmachine/constants'
5
+ require 'webmachine/rescueable_exception'
5
6
 
6
7
  module Webmachine
7
8
  module Decision
@@ -29,7 +30,7 @@ module Webmachine
29
30
  trace_decision(state)
30
31
  result = handle_exceptions { send(state) }
31
32
  case result
32
- when Fixnum # Response code
33
+ when Integer # Response code
33
34
  respond(result)
34
35
  break
35
36
  when Symbol # Next state
@@ -48,12 +49,12 @@ module Webmachine
48
49
 
49
50
  def handle_exceptions
50
51
  yield
52
+ rescue Webmachine::RescuableException => e
53
+ resource.handle_exception(e)
54
+ 500
51
55
  rescue MalformedRequest => e
52
56
  Webmachine.render_error(400, request, response, :message => e.message)
53
57
  400
54
- rescue => e
55
- resource.handle_exception(e)
56
- 500
57
58
  end
58
59
 
59
60
  def respond(code, headers={})
@@ -95,7 +95,7 @@ module Webmachine
95
95
  # is a String or IO with known size.
96
96
  def body_is_fixed_length?
97
97
  response.body.respond_to?(:bytesize) &&
98
- Fixnum === response.body.bytesize
98
+ Integer === response.body.bytesize
99
99
  end
100
100
  end # module Helpers
101
101
  end # module Decision
@@ -103,7 +103,7 @@ module Webmachine
103
103
  # accumulating variable bindings.
104
104
  # @param [Array<String>] tokens the list of path segments
105
105
  # @param [Hash] bindings where path bindings will be stored
106
- # @return [Fixnum, Array<Fixnum, Array>, false] either the depth
106
+ # @return [Integer, Array<Integer, Array>, false] either the depth
107
107
  # that the path matched at, the depth and tokens matched by
108
108
  # {MATCH_ALL}, or false if it didn't match.
109
109
  def bind(tokens, bindings)
@@ -119,6 +119,20 @@ module Webmachine
119
119
  return [depth, tokens]
120
120
  when tokens.empty?
121
121
  return false
122
+ when Regexp === spec.first
123
+ matches = spec.first.match URI.decode(tokens.first)
124
+ if matches
125
+ if spec.first.named_captures.empty?
126
+ bindings[:captures] = (bindings[:captures] || []) + matches.captures
127
+ else
128
+ spec.first.named_captures.reduce(bindings) do |bindings, (name, idxs)|
129
+ bindings[name.to_sym] = matches.captures[idxs.first-1]
130
+ bindings
131
+ end
132
+ end
133
+ else
134
+ return false
135
+ end
122
136
  when Symbol === spec.first
123
137
  bindings[spec.first] = URI.decode(tokens.first)
124
138
  when spec.first == tokens.first
@@ -9,7 +9,7 @@ module Webmachine
9
9
 
10
10
  # Renders a standard error message body for the response. The
11
11
  # standard messages are defined in localization files.
12
- # @param [Fixnum] code the response status code
12
+ # @param [Integer] code the response status code
13
13
  # @param [Request] req the request object
14
14
  # @param [Response] req the response object
15
15
  # @param [Hash] options keys to override the defaults when rendering
@@ -39,6 +39,6 @@ module Webmachine
39
39
 
40
40
  # Raised when the client has submitted an invalid request, e.g. in
41
41
  # the case where a request header is improperly formed. Raising this
42
- # exception will result in a 400 response.
42
+ # error will result in a 400 response.
43
43
  class MalformedRequest < Error; end
44
44
  end # module Webmachine
@@ -55,9 +55,9 @@ module Webmachine
55
55
  # and publish it. Notice that events get sent even if an error occurs
56
56
  # in the passed-in block.
57
57
  #
58
- # If an exception happens during an instrumentation the payload will
58
+ # If an error happens during an instrumentation the payload will
59
59
  # have a key `:exception` with an array of two elements as value:
60
- # a string with the name of the exception class, and the exception
60
+ # a string with the name of the error class, and the error
61
61
  # message. (when using the default
62
62
  # [AS::Notifications](http://rubydoc.info/gems/as-notifications/AS/Notifications)
63
63
  # backend)
@@ -48,7 +48,7 @@ module Webmachine
48
48
 
49
49
  # Returns the value for the given key. If the key can't be found,
50
50
  # there are several options:
51
- # With no other arguments, it will raise a KeyError exception;
51
+ # With no other arguments, it will raise a KeyError error;
52
52
  # if default is given, then that will be returned;
53
53
  # if the optional code block is specified, then that will be run and its
54
54
  # result returned.
@@ -0,0 +1,62 @@
1
+ module Webmachine::RescuableException
2
+ require_relative 'errors'
3
+ require 'set'
4
+
5
+ UNRESCUABLE_DEFAULTS = [
6
+ Webmachine::MalformedRequest,
7
+ NoMemoryError, SystemExit, SignalException
8
+ ].freeze
9
+
10
+ UNRESCUABLE = Set.new UNRESCUABLE_DEFAULTS.dup
11
+ private_constant :UNRESCUABLE
12
+
13
+ def self.===(e)
14
+ case e
15
+ when *UNRESCUABLE then false
16
+ else true
17
+ end
18
+ end
19
+
20
+ #
21
+ # Remove modifications to Webmachine::RescuableException.
22
+ # Restores default list of unrescue-able exceptions.
23
+ #
24
+ # @return [nil]
25
+ #
26
+ def self.default!
27
+ UNRESCUABLE.replace Set.new(UNRESCUABLE_DEFAULTS.dup)
28
+ nil
29
+ end
30
+
31
+ #
32
+ # @return [Array<Exception>]
33
+ # Returns an Array of exceptions that will not be
34
+ # rescued by {Webmachine::Resource#handle_exception}.
35
+ #
36
+ def self.UNRESCUABLEs
37
+ UNRESCUABLE.to_a
38
+ end
39
+
40
+ #
41
+ # Add a variable number of exceptions that should be rescued by
42
+ # {Webmachine::Resource#handle_exception}. See {UNRESCUABLE_DEFAULTS}
43
+ # for a list of exceptions that are not caught by default.
44
+ #
45
+ # @param (see #remove)
46
+ #
47
+ def self.add(*exceptions)
48
+ exceptions.each{|e| UNRESCUABLE.delete(e)}
49
+ end
50
+
51
+ #
52
+ # Remove a variable number of exceptions from being rescued by
53
+ # {Webmachine::Resource#handle_exception}. See {UNRESCUABLE_DEFAULTS}
54
+ # for a list of exceptions that are not caught by default.
55
+ #
56
+ # @param [Exception] *exceptions
57
+ # A subclass of Exception.
58
+ #
59
+ def self.remove(*exceptions)
60
+ exceptions.each{|e| UNRESCUABLE.add(e)}
61
+ end
62
+ end
@@ -103,7 +103,7 @@ module Webmachine
103
103
  # If the entity length on PUT or POST is invalid, this should
104
104
  # return false, which will result in a '413 Request Entity Too
105
105
  # Large' response. Defaults to true.
106
- # @param [Fixnum] length the size of the request body (entity)
106
+ # @param [Integer] length the size of the request body (entity)
107
107
  # @return [true,false] Whether the body is a valid length (not too
108
108
  # large)
109
109
  # @api callback
@@ -192,7 +192,7 @@ module Webmachine
192
192
 
193
193
  # If post_is_create? returns false, then this will be called to
194
194
  # process any POST request. If it succeeds, it should return true.
195
- # @return [true,false,Fixnum] Whether the POST was successfully
195
+ # @return [true,false,Integer] Whether the POST was successfully
196
196
  # processed, or an alternate response code
197
197
  # @api callback
198
198
  def process_post
@@ -363,11 +363,11 @@ module Webmachine
363
363
  def finish_request; end
364
364
 
365
365
  #
366
- # This method is called when an exception is raised within a subclass of
366
+ # This method is called when an error is raised within a subclass of
367
367
  # {Webmachine::Resource}.
368
368
  #
369
- # @param [Exception] e
370
- # The exception.
369
+ # @param [StandardError] e
370
+ # The error.
371
371
  #
372
372
  # @return [void]
373
373
  #
@@ -4,7 +4,7 @@ module Webmachine
4
4
  # @return [HeaderHash] Response headers that will be sent to the client
5
5
  attr_reader :headers
6
6
 
7
- # @return [Fixnum] The HTTP status code of the response
7
+ # @return [Integer] The HTTP status code of the response
8
8
  attr_accessor :code
9
9
 
10
10
  # @return [String, #each] The response body
@@ -48,11 +48,9 @@ module Webmachine
48
48
  cookie = Webmachine::Cookie.new(name, value, attributes).to_s
49
49
  case headers['Set-Cookie']
50
50
  when nil
51
- headers['Set-Cookie'] = cookie
52
- when String
53
- headers['Set-Cookie'] = [headers['Set-Cookie'], cookie]
51
+ headers['Set-Cookie'] = [cookie]
54
52
  when Array
55
- headers['Set-Cookie'] = headers['Set-Cookie'] + cookie
53
+ headers['Set-Cookie'] << cookie
56
54
  end
57
55
  end
58
56
 
@@ -1,28 +1,42 @@
1
1
  require "webmachine/spec/test_resource"
2
2
  require "net/http"
3
3
 
4
+ ADDRESS = "127.0.0.1"
5
+
4
6
  shared_examples_for :adapter_lint do
5
- attr_accessor :client
7
+ attr_reader :client
8
+
9
+ class TestApplicationNotResponsive < Timeout::Error; end
6
10
 
7
- let(:address) { "127.0.0.1" }
8
- let(:port) { s = TCPServer.new(address, 0); p = s.addr[1]; s.close; p }
11
+ def find_free_port
12
+ temp_server = TCPServer.new(ADDRESS, 0)
13
+ port = temp_server.addr[1]
14
+ temp_server.close # only frees Ruby resource, socket is in TIME_WAIT at OS level
15
+ # so we can't have our adapter use it too quickly
9
16
 
10
- let(:application) do
11
- application = Webmachine::Application.new
12
- application.dispatcher.add_route ["test"], Test::Resource
17
+ sleep(0.1) # 'Wait' for temp_server to *really* close, not just TIME_WAIT
18
+ port
19
+ end
13
20
 
14
- application.configure do |c|
15
- c.ip = address
16
- c.port = port
21
+ def create_test_application(port)
22
+ Webmachine::Application.new.tap do |application|
23
+ application.dispatcher.add_route ["test"], Test::Resource
24
+
25
+ application.configure do |c|
26
+ c.ip = ADDRESS
27
+ c.port = port
28
+ end
17
29
  end
30
+ end
18
31
 
19
- application
32
+ def run_application(adapter_class, application)
33
+ adapter = adapter_class.new(application)
34
+ Thread.abort_on_exception = true
35
+ Thread.new { adapter.run }
20
36
  end
21
37
 
22
- let(:client) do
23
- client = Net::HTTP.new(application.configuration.ip, port)
24
- # Wait until the server is responsive
25
- timeout(5) do
38
+ def wait_until_server_responds_to(client)
39
+ Timeout.timeout(5, TestApplicationNotResponsive) do
26
40
  begin
27
41
  client.start
28
42
  rescue Errno::ECONNREFUSED
@@ -30,19 +44,21 @@ shared_examples_for :adapter_lint do
30
44
  retry
31
45
  end
32
46
  end
33
- client
34
47
  end
35
48
 
36
- before do
37
- @adapter = described_class.new(application)
49
+ before(:all) do
50
+ @port = find_free_port
51
+ application = create_test_application(@port)
38
52
 
39
- Thread.abort_on_exception = true
40
- @server_thread = Thread.new { @adapter.run }
41
- sleep(0.01)
53
+ adapter_class = described_class
54
+ @server_thread = run_application(adapter_class, application)
55
+
56
+ @client = Net::HTTP.new(application.configuration.ip, @port)
57
+ wait_until_server_responds_to(client)
42
58
  end
43
59
 
44
- after do
45
- client.finish
60
+ after(:all) do
61
+ @client.finish
46
62
  @server_thread.kill
47
63
  end
48
64
 
@@ -50,7 +66,7 @@ shared_examples_for :adapter_lint do
50
66
  request = Net::HTTP::Get.new("/test")
51
67
  request["Accept"] = "test/response.request_uri"
52
68
  response = client.request(request)
53
- expect(response.body).to eq("http://#{address}:#{port}/test")
69
+ expect(response.body).to eq("http://#{ADDRESS}:#{@port}/test")
54
70
  end
55
71
 
56
72
  # context do
@@ -39,7 +39,7 @@ module Webmachine
39
39
  # Returns the length of the IO stream, if known. Returns nil if
40
40
  # the stream uses an encoder or charsetter that might modify the
41
41
  # length of the stream, or the stream size is unknown.
42
- # @return [Fixnum] the size, in bytes, of the underlying IO, or
42
+ # @return [Integer] the size, in bytes, of the underlying IO, or
43
43
  # nil if unsupported
44
44
  def size
45
45
  if is_unencoded?
@@ -1,6 +1,6 @@
1
1
  module Webmachine
2
2
  # Library version
3
- VERSION = "1.4.0".freeze
3
+ VERSION = "1.5.0".freeze
4
4
 
5
5
  # String for use in "Server" HTTP response header, which includes
6
6
  # the {VERSION}.
@@ -18,6 +18,14 @@ RSpec.configure do |config|
18
18
  config.order = :random
19
19
  end
20
20
 
21
+ config.before :each do
22
+ Webmachine::RescuableException.remove(RSpec::Mocks::MockExpectationError)
23
+ end
24
+
25
+ config.after :each do
26
+ Webmachine::RescuableException.default!
27
+ end
28
+
21
29
  config.before(:suite) do
22
30
  options = {
23
31
  :Logger => NullLogger.new(STDERR),
@@ -56,7 +56,7 @@ describe Webmachine::Adapters::Reel do
56
56
  def reel_server(adptr = adapter)
57
57
  thread = Thread.new { adptr.run }
58
58
  begin
59
- timeout(5) do
59
+ Timeout.timeout(5) do
60
60
  begin
61
61
  sock = TCPSocket.new(adptr.application.configuration.ip, adptr.application.configuration.port)
62
62
  begin
@@ -41,7 +41,7 @@ describe Webmachine::Decision::Conneg do
41
41
 
42
42
  end
43
43
 
44
- it "should raise an exception when a media-type is improperly formatted" do
44
+ it "should raise an error when a media-type is improperly formatted" do
45
45
  expect {
46
46
  subject.choose_media_type(["text/html", "application/xml"],
47
47
  "bah;")
@@ -1100,7 +1100,7 @@ describe Webmachine::Decision::Flow do
1100
1100
  end
1101
1101
  end
1102
1102
 
1103
- describe "On exception" do
1103
+ describe "On error" do
1104
1104
  context "handle_exception is inherited." do
1105
1105
  let :resource do
1106
1106
  resource_with do
@@ -5,15 +5,58 @@ describe Webmachine::Decision::FSM do
5
5
 
6
6
  subject { described_class.new(resource, request, response) }
7
7
 
8
+ let(:run_with_exception) do
9
+ begin
10
+ subject.run
11
+ rescue Exception
12
+ end
13
+ end
14
+
8
15
  describe 'handling of exceptions from decision methods' do
9
- let(:exception) { RuntimeError.new }
16
+ let(:UNRESCUABLE_exceptions) do
17
+ Webmachine::RescuableException::UNRESCUABLE
18
+ end
19
+
20
+ describe "rescueable exceptions" do
21
+ it 'does rescue Exception' do
22
+ allow(subject).to receive(Webmachine::Decision::Flow::START) { raise(Exception) }
23
+ expect(resource).to receive(:handle_exception).with instance_of(Exception)
24
+ expect { subject.run }.to_not raise_error
25
+ end
26
+
27
+ it 'does rescue a failed require' do
28
+ allow(subject).to receive(Webmachine::Decision::Flow::START) { require 'laterequire' }
29
+ expect(resource).to receive(:handle_exception).with instance_of(LoadError)
30
+ expect { subject.run }.to_not raise_error
31
+ end
32
+ end
33
+
34
+ describe "UNRESCUABLE exceptions" do
35
+ shared_examples "UNRESCUABLE" do |e|
36
+ specify "#{e} is not rescued" do
37
+ allow(subject).to receive(Webmachine::Decision::Flow::START) {raise(e)}
38
+ expect(resource).to_not receive(:handle_exception).with instance_of(e)
39
+ expect { subject.run }.to raise_error(e)
40
+ end
41
+ end
42
+ eary = Webmachine::RescuableException::UNRESCUABLE_DEFAULTS - [
43
+ Webmachine::MalformedRequest, # Webmachine rescues by default, so it won't re-raise.
44
+ SignalException # Requires raise in form 'raise SignalException, "SIGSOMESIGNAL"'.
45
+ # Haven't found a good no-op signal to use here.
46
+ ]
47
+ eary.each{|e| include_examples "UNRESCUABLE", e}
48
+ end
49
+ end
50
+
51
+ describe 'handling of errors from decision methods' do
52
+ let(:error) { RuntimeError.new }
10
53
 
11
54
  before do
12
- allow(subject).to receive(Webmachine::Decision::Flow::START) { raise exception }
55
+ allow(subject).to receive(Webmachine::Decision::Flow::START) { raise error }
13
56
  end
14
57
 
15
58
  it 'calls resource.handle_exception' do
16
- expect(resource).to receive(:handle_exception).with(exception)
59
+ expect(resource).to receive(:handle_exception).with(error)
17
60
  subject.run
18
61
  end
19
62
 
@@ -23,12 +66,12 @@ describe Webmachine::Decision::FSM do
23
66
  end
24
67
  end
25
68
 
26
- describe 'handling of exceptions from resource.handle_exception' do
27
- let(:exception) { RuntimeError.new('an error message') }
69
+ describe 'handling of errors from resource.handle_exception' do
70
+ let(:error) { RuntimeError.new('an error message') }
28
71
 
29
72
  before do
30
73
  allow(subject).to receive(Webmachine::Decision::Flow::START) { raise }
31
- allow(resource).to receive(:handle_exception) { raise exception }
74
+ allow(resource).to receive(:handle_exception) { raise error }
32
75
  end
33
76
 
34
77
  it 'does not call resource.handle_exception again' do
@@ -44,20 +87,39 @@ describe Webmachine::Decision::FSM do
44
87
  it 'renders an error' do
45
88
  expect(Webmachine).
46
89
  to receive(:render_error).
47
- with(500, request, response, { :message => exception.message })
90
+ with(500, request, response, { :message => error.message })
48
91
  subject.run
49
92
  end
50
93
  end
51
94
 
52
95
  describe 'handling of exceptions from resource.finish_request' do
53
- let(:exception) { RuntimeError.new }
96
+ let(:exception) { Class.new(Exception).new }
54
97
 
55
98
  before do
99
+ Webmachine::RescuableException.remove(exception)
56
100
  allow(resource).to receive(:finish_request) { raise exception }
57
101
  end
58
102
 
103
+ it 'does not call resource.handle_exception' do
104
+ expect(resource).to_not receive(:handle_exception)
105
+ run_with_exception
106
+ end
107
+
108
+ it 'does not call resource.finish_request again' do
109
+ expect(resource).to_not receive(:finish_request).once { raise }
110
+ run_with_exception
111
+ end
112
+ end
113
+
114
+ describe 'handling of errors from resource.finish_request' do
115
+ let(:error) { RuntimeError.new }
116
+
117
+ before do
118
+ allow(resource).to receive(:finish_request) { raise error }
119
+ end
120
+
59
121
  it 'calls resource.handle_exception' do
60
- expect(resource).to receive(:handle_exception).with(exception)
122
+ expect(resource).to receive(:handle_exception).with(error)
61
123
  subject.run
62
124
  end
63
125
 
@@ -180,7 +180,6 @@ describe Webmachine::Dispatcher::Route do
180
180
  end
181
181
  end
182
182
  end
183
-
184
183
  context "on a deep path" do
185
184
  subject { described_class.new(%w{foo bar baz}, resource) }
186
185
  let(:uri) { URI.parse("http://localhost:8080/foo/bar/baz") }
@@ -205,6 +204,29 @@ describe Webmachine::Dispatcher::Route do
205
204
  expect(request.path_info).to eq({:id => "bar"})
206
205
  end
207
206
  end
207
+ context "with regex" do
208
+ subject { described_class.new([/foo/, /(.*)/, 'baz'], resource) }
209
+
210
+ it "should assign the captures path variables" do
211
+ expect(request.path_info).to eq({:captures => ["bar"]})
212
+ end
213
+ end
214
+ context "with multi-capture regex" do
215
+ subject { described_class.new([/foo/, /(.*)/, /baz\.(.*)/], resource) }
216
+ let(:uri) { URI.parse("http://localhost:8080/foo/bar/baz.json") }
217
+
218
+ it "should assign the captures path variables" do
219
+ expect(request.path_info).to eq({:captures => ["bar", "json"]})
220
+ end
221
+ end
222
+ context "with named capture regex" do
223
+ subject { described_class.new(['foo', :bar, /(?<baz>[^.]+)\.(?<format>.*)/], resource) }
224
+ let(:uri) { URI.parse("http://localhost:8080/foo/bar/baz.json") }
225
+
226
+ it "should assign the captures path variables" do
227
+ expect(request.path_info).to eq({bar: 'bar', baz: 'baz', format: "json"})
228
+ end
229
+ end
208
230
 
209
231
  context "with a splat" do
210
232
  subject { described_class.new(['foo', :*], resource) }
@@ -3,6 +3,7 @@ require 'spec_helper'
3
3
  describe Webmachine::Dispatcher do
4
4
  let(:dispatcher) { Webmachine.application.dispatcher }
5
5
  let(:request) { Webmachine::Request.new("GET", URI.parse("http://localhost:8080/"), Webmachine::Headers["accept" => "*/*"], "") }
6
+ let(:request2) { Webmachine::Request.new("GET", URI.parse("http://localhost:8080/hello/bob.html"), Webmachine::Headers["accept" => "*/*"], "") }
6
7
  let(:response) { Webmachine::Response.new }
7
8
  let(:resource) do
8
9
  Class.new(Webmachine::Resource) do
@@ -14,6 +15,14 @@ describe Webmachine::Dispatcher do
14
15
  def to_html; "goodbye, cruel world"; end
15
16
  end
16
17
  end
18
+ let(:resource3) do
19
+ Class.new(Webmachine::Resource) do
20
+ def to_html
21
+ name, format = request.path_info[:captures]
22
+ "Hello #{name} with #{format}"
23
+ end
24
+ end
25
+ end
17
26
  let(:fsm){ double }
18
27
 
19
28
  before { dispatcher.reset }
@@ -44,6 +53,12 @@ describe Webmachine::Dispatcher do
44
53
  expect(fsm).to receive(:run)
45
54
  dispatcher.dispatch(request, response)
46
55
  end
56
+ it "should handle regex path segments in route definition" do
57
+ dispatcher.add_route ["hello", /(.*)\.(.*)/], resource3
58
+ expect(Webmachine::Decision::FSM).to receive(:new).with(instance_of(resource3), request2, response).and_return(fsm)
59
+ expect(fsm).to receive(:run)
60
+ dispatcher.dispatch(request2, response)
61
+ end
47
62
 
48
63
  it "should apply route to request before creating the resource" do
49
64
  route = dispatcher.add_route [:*], resource
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+ RSpec.describe Webmachine::RescuableException do
3
+ before { described_class.default! }
4
+
5
+ describe ".UNRESCUABLEs" do
6
+ specify "returns an array of UNRESCUABLE exceptions" do
7
+ expect(described_class.UNRESCUABLEs).to eq(described_class::UNRESCUABLE_DEFAULTS)
8
+ end
9
+
10
+ specify "returns an array of UNRESCUABLE exceptions, with custom exceptions added" do
11
+ described_class.remove(Exception)
12
+ expect(described_class.UNRESCUABLEs).to eq(described_class::UNRESCUABLE_DEFAULTS.dup.concat([Exception]))
13
+ end
14
+ end
15
+ end
@@ -33,12 +33,18 @@ describe Webmachine::Response do
33
33
  describe "setting multiple cookies" do
34
34
  let(:cookie2) { "rodeo" }
35
35
  let(:cookie2_value) { "clown" }
36
- before(:each) { subject.set_cookie(cookie2, cookie2_value) }
36
+ let(:cookie3) {"color"}
37
+ let(:cookie3_value) {"blue"}
38
+ before(:each) do
39
+ subject.set_cookie(cookie2, cookie2_value)
40
+ subject.set_cookie(cookie3, cookie3_value)
41
+ end
37
42
 
38
43
  it "should have a proper Set-Cookie header" do
39
44
  expect(subject.headers["Set-Cookie"]).to be_a Array
40
45
  expect(subject.headers["Set-Cookie"]).to include "rodeo=clown"
41
46
  expect(subject.headers["Set-Cookie"]).to include "monster=mash"
47
+ expect(subject.headers["Set-Cookie"]).to include "color=blue"
42
48
  end
43
49
  end
44
50
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webmachine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Cribbs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-20 00:00:00.000000000 Z
11
+ date: 2017-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -68,7 +68,9 @@ files:
68
68
  - Guardfile
69
69
  - LICENSE
70
70
  - README.md
71
+ - RELEASING.md
71
72
  - Rakefile
73
+ - bethtemp
72
74
  - documentation/adapters.md
73
75
  - documentation/authentication-and-authorization.md
74
76
  - documentation/configurator.md
@@ -115,6 +117,7 @@ files:
115
117
  - lib/webmachine/media_type.rb
116
118
  - lib/webmachine/quoted_string.rb
117
119
  - lib/webmachine/request.rb
120
+ - lib/webmachine/rescueable_exception.rb
118
121
  - lib/webmachine/resource.rb
119
122
  - lib/webmachine/resource/authentication.rb
120
123
  - lib/webmachine/resource/callbacks.rb
@@ -145,6 +148,7 @@ files:
145
148
  - lib/webmachine/translation.rb
146
149
  - lib/webmachine/version.rb
147
150
  - memory_test.rb
151
+ - pkg/webmachine-1.5.0.gem
148
152
  - spec/spec_helper.rb
149
153
  - spec/webmachine/adapter_spec.rb
150
154
  - spec/webmachine/adapters/httpkit_spec.rb
@@ -169,6 +173,7 @@ files:
169
173
  - spec/webmachine/headers_spec.rb
170
174
  - spec/webmachine/media_type_spec.rb
171
175
  - spec/webmachine/request_spec.rb
176
+ - spec/webmachine/rescueable_exception_spec.rb
172
177
  - spec/webmachine/resource/authentication_spec.rb
173
178
  - spec/webmachine/response_spec.rb
174
179
  - spec/webmachine/trace/fsm_spec.rb
@@ -196,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
196
201
  version: '0'
197
202
  requirements: []
198
203
  rubyforge_project:
199
- rubygems_version: 2.2.2
204
+ rubygems_version: 2.6.11
200
205
  signing_key:
201
206
  specification_version: 4
202
207
  summary: webmachine is a toolkit for building HTTP applications,
@@ -225,6 +230,7 @@ test_files:
225
230
  - spec/webmachine/headers_spec.rb
226
231
  - spec/webmachine/media_type_spec.rb
227
232
  - spec/webmachine/request_spec.rb
233
+ - spec/webmachine/rescueable_exception_spec.rb
228
234
  - spec/webmachine/resource/authentication_spec.rb
229
235
  - spec/webmachine/response_spec.rb
230
236
  - spec/webmachine/trace/fsm_spec.rb