webmachine 1.4.0 → 1.5.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
  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