diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 00000000..be954557
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,25 @@
+name: Test
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: "ubuntu-latest"
+ continue-on-error: ${{ matrix.experimental }}
+ strategy:
+ fail-fast: false
+ matrix:
+ ruby_version: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3"]
+ experimental: [false]
+ include:
+ - ruby_version: "ruby-head"
+ experimental: true
+
+ steps:
+ - uses: actions/checkout@v4
+ - uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby_version }}
+ bundler-cache: true
+ - run: "bundle exec rubocop lib/ spec/"
+ - run: "bundle exec rspec spec/ -b"
diff --git a/.gitignore b/.gitignore
index c84df18a..da6e7dd4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,3 +28,4 @@ Gemfile.lock
.rvmrc
.SyncID
.SyncIgnore
+vendor
diff --git a/.rubocop.yml b/.rubocop.yml
new file mode 100644
index 00000000..2f434748
--- /dev/null
+++ b/.rubocop.yml
@@ -0,0 +1,56 @@
+##
+# Plugins
+require:
+ - standard
+
+##
+# Defaults: standardrb
+inherit_gem:
+ standard: config/base.yml
+
+##
+# Rules that break from standardrb defaults
+Style/StringLiterals:
+ EnforcedStyle: single_quotes
+
+##
+# Disabled rules
+Lint/AssignmentInCondition:
+ Enabled: false
+Lint/FloatComparison:
+ Enabled: false
+Lint/ConstantDefinitionInBlock:
+ Enabled: false
+Lint/EmptyWhen:
+ Exclude:
+ - "lib/webmachine/dispatcher/route.rb"
+Lint/DuplicateMethods:
+ Exclude:
+ - "lib/webmachine/application.rb"
+Lint/UnderscorePrefixedVariableName:
+ Exclude:
+ - "lib/webmachine/trace/resource_proxy.rb"
+ - "spec/webmachine/dispatcher_spec.rb"
+Lint/NestedMethodDefinition:
+ Exclude:
+ - "spec/webmachine/decision/flow_spec.rb"
+Lint/RescueException:
+ Exclude:
+ - "spec/webmachine/decision/fsm_spec.rb"
+Lint/RaiseException:
+ Exclude:
+ - "spec/webmachine/decision/fsm_spec.rb"
+Style/MissingRespondToMissing:
+ Exclude:
+ - "lib/webmachine/request.rb"
+Style/NilComparison:
+ Exclude:
+ - "spec/webmachine/decision/falsey_spec.rb"
+Style/GlobalVars:
+ Exclude:
+ - "lib/webmachine/decision/conneg.rb"
+
+AllCops:
+ NewCops: disable
+ SuggestExtensions: false
+ TargetRubyVersion: 2.6
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f3dad088..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-rvm:
- - 1.9.3
- - 2.0.0
- - 2.1.2
- - ruby-head
- - jruby
- - rbx-2
-
-matrix:
- allow_failures:
- - rvm: ruby-head
- - rvm: jruby
-
-bundler_args: --without guard docs
-
-script: bundle exec rspec spec/ -b
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f0565a2..34220692 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,74 @@
### HEAD
-* decode the value of the header 'Content-MD5' as base64-encoded string.
+### 2.0.1 Feb 27, 2024
+
+* Don't include the `doc/`, `pkg/`, or `vendor/` directory in the gem package
+* Add `base64` as a runtime dependency
+* Add support for Ruby 3.3
+* Fix Rubocop issues
+
+### 2.0.0 Mar 31, 2023
+
+* Add support for ruby 3.0, 3.1 and 3.2
+* Drop support for ruby 2.3, 2.4 and 2.5
+* Remove the HTTPkit adapter
+* Remove the Reel adapter
+* Format cookie 'Expires' timestamps as per RFC 2616
+
+### 1.6.0 June 22, 2021
+
+* fix: replace missed URI.decode with new Route.rfc3986_percent_decode (#261)
+* fix: implement rfc3986 Percent-Encoding decoder
+* feat: make rack env available on the webmachine request when using a rack adapter
+
+### 1.5.0 September 8, 2017
+
+* Removed Fixnum/Integer deprecation warnings in Ruby 2.4
+* Fixed multiple cookie setting code
+* Added support for named captures
+* Improved logic for determining which errors are 'rescuable' by Webmachine,
+ and which are 'unhandlable'.
+
+### 1.4.0 March 20, 2015
+
+* Added RackMapped adapter which allows Webmachine apps to be mounted
+ at a non-root path using Rack::Map.
+ Thanks to [Julian Doherty](https://github.com/madlep) for writing this.
+
+### 1.3.1 January 15, 2015
+
+* Fixed URI construction, including handling IPv6 addresses, when the
+ adapter did not supply a complete hostname.
+* Removed dependency of Rack Adapter on REQUEST_INFO rack environment
+ variable which was not always present.
+* Use IPAddr instead of Addrinfo as rbx does not support Addrinfo.
+
+### 1.3.0 January 3, 2015
+
+1.3.0 is a feature and bugfix release that removes two adapters,
+reduces memory usage, fixes bugs, and includes a bunch of new
+documentation. Thank you to our new contributor @rpag!
+
+* Greatly reduced per-request garbage by freezing commonly used
+ Strings and Regexps into constants.
+* Tutorial/example documentation was extracted from the README and
+ extended in the `documentation` directory.
+* HTTPkit adapter was added.
+* Hatetepe and Mongrel adapters were removed and adapters no longer
+ install interrupt handlers.
+* The "splat" matcher in path specifications is now a Symbol `:*`
+ rather than a String `"*"`. Using the String version will result in
+ a deprecation warning.
+* Requests with If-None-Match where the resource does not supply an
+ ETag will no longer respond with 412 or 500, but follow the success
+ path.
+* Path fragments are now decoded.
+* Simplified the interaction between the decision FSM and tracing.
+* Updated specs to use RSpec 3.
+* Improved handling of IO.copy_stream on Rack servers.
+* Updated the Reel adapter.
+* Exposed Application instance to the Adapter.
+* Decode the value of the header 'Content-MD5' as base64-encoded string.
### 1.2.2 January 8, 2014
diff --git a/Gemfile b/Gemfile
index a966da12..3479e339 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,43 +1,35 @@
-require 'rbconfig'
source 'https://rubygems.org'
gemspec
group :development do
- gem "yard"
- gem "rake"
+ gem 'rake', '~> 12.0'
+ gem 'standard', '~> 1.21'
+ gem 'webrick', '~> 1.7'
end
group :test do
- gem "rspec", '~> 3.0.0'
- gem "rspec-its"
- gem "rack"
+ gem 'rack', '~> 2.0'
+ gem 'rack-test', '~> 0.7'
+ gem 'rspec', '~> 3.0', '>= 3.6.0'
+ gem 'rspec-its', '~> 1.2'
+ gem 'websocket_parser', '~>1.0'
end
-group :webservers do
- gem 'reel', '~> 0.5.0'
- gem 'http', '~> 0.6.0'
- gem 'httpkit', :platform => [:mri, :rbx]
+group :docs do
+ gem 'redcarpet', '~> 3.4', platform: :ruby
+ gem 'yard', '~> 0.9'
end
-group :guard do
- gem 'guard-rspec'
- case RbConfig::CONFIG['host_os']
- when /darwin/
- gem 'rb-fsevent'
- # gem 'growl_notify'
- gem 'growl'
- when /linux/
- gem 'rb-inotify'
- gem 'libnotify'
- end
+platforms :jruby do
+ gem 'jruby-openssl'
end
-group :docs do
- platform :mri_19, :mri_20 do
- gem 'redcarpet'
- end
+if RUBY_VERSION >= '3.4'
+ gem 'mutex_m' # TODO: remove this once as-notifications has such a dependency
end
-platforms :jruby do
- gem 'jruby-openssl'
+if RUBY_VERSION >= '4.0'
+ gem 'pstore'
+ gem 'logger'
+ gem 'ostruct'
end
diff --git a/Guardfile b/Guardfile
deleted file mode 100644
index 357f38e2..00000000
--- a/Guardfile
+++ /dev/null
@@ -1,11 +0,0 @@
-gemset = ENV['RVM_GEMSET'] || 'webmachine'
-gemset = "@#{gemset}" unless gemset.to_s == ''
-
-rvms = %W[ 1.8.7 1.9.2 1.9.3 jruby rbx ].map {|v| "#{v}#{gemset}" }
-
-guard 'rspec', :cli => "--color --profile", :growl => true, :rvm => rvms do
- watch(%r{^lib/webmachine/locale/.+$}) { "spec" }
- watch(%r{^spec/.+_spec\.rb$})
- watch(%r{^lib/(.+)\.rb$}){ |m| "spec/#{m[1]}_spec.rb" }
- watch('spec/spec_helper.rb') { "spec" }
-end
diff --git a/README.md b/README.md
index eefa9946..d60863f4 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-# webmachine for Ruby [](http://travis-ci.org/seancribbs/webmachine-ruby)
+# webmachine for Ruby
+ [](https://badge.fury.io/rb/webmachine)
+ [](https://github.com/webmachine/webmachine-ruby/actions/workflows/test.yml)
webmachine-ruby is a port of
[Webmachine](https://github.com/basho/webmachine), which is written in
@@ -18,8 +20,7 @@ are up to you.
requests, and response codes for you.
* Provides a base resource with points of extension to let you
describe what is relevant about your particular resource.
-* Supports WEBrick, Reel, HTTPkit, and a Rack shim. Other host
- servers are being investigated.
+* Supports WEBrick and a Rack shim. Other host servers are being investigated.
* Streaming/chunked response bodies are permitted as Enumerables,
Procs, or Fibers!
* Unlike the Erlang original, it does real Language negotiation.
@@ -163,10 +164,8 @@ the "visual debugger". Learn how to configure it [here][visual-debugger].
## Related libraries
-* [irwebmachine](https://github.com/robgleeson/irwebmachine) - IRB/Pry debugging of Webmachine applications
* [webmachine-test](https://github.com/bernd/webmachine-test) - Helpers for testing Webmachine applications
* [webmachine-linking](https://github.com/petejohanson/webmachine-linking) - Helpers for linking between Resources, and Web Linking
-* [webmachine-sprockets](https://github.com/lgierth/webmachine-sprockets) - Integration with Sprockets assets packaging system
* [webmachine-actionview](https://github.com/rgarner/webmachine-actionview) - Integration of some Rails-style view conventions into Webmachine
* [jruby-http-kit](https://github.com/nLight/jruby-http-kit) - Includes an adapter for the Clojure-based Ring library/server
* [newrelic-webmachine](https://github.com/mdub/newrelic-webmachine) - NewRelic instrumentation
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 00000000..4642cb2d
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,21 @@
+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:
+
+ $ git log --pretty=format:' * %s' v1.3.0..HEAD
+
+2. Edit the version in `lib/webmachine/version.rb` according to semantic versioning rules.
+3. Commit both files.
+
+ $ git add CHANGELOG.md lib/webmachine/version.rb
+ $ git commit -m "chore(release): version $(ruby -r ./lib/webmachine/version.rb -e "puts Webmachine::VERSION")" && git push
+
+4. Release the gem.
+
+ $ bundle exec rake release
+
+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).
+
+ $ git push origin HEAD:1.3-stable
+ # or
+ $ git checkout 1.3-stable; git merge master; git push origin; git checkout master
+
+6. YOU'RE DONE!
diff --git a/Rakefile b/Rakefile
index f88cf434..c9f54e96 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,60 +1,44 @@
-require 'rubygems'
-require 'rubygems/package_task'
+require 'bundler/gem_tasks'
begin
require 'yard'
require 'yard/rake/yardoc_task'
YARD::Rake::YardocTask.new do |doc|
- doc.files = Dir["lib/**/*.rb"] + ['README.md']
- doc.options = ["-m", "markdown"]
+ doc.files = Dir['lib/**/*.rb'] + ['README.md']
+ doc.options = ['-m', 'markdown']
end
rescue LoadError
end
-def gemspec
- $webmachine_gemspec ||= Gem::Specification.load("webmachine.gemspec")
+desc 'Validate the gemspec file.'
+task :validate_gemspec do
+ Gem::Specification.load('webmachine.gemspec').validate
end
-Gem::PackageTask.new(gemspec) do |pkg|
- pkg.need_zip = false
- pkg.need_tar = false
-end
-
-task :gem => :gemspec
-
-desc %{Validate the gemspec file.}
-task :gemspec do
- gemspec.validate
-end
-
-desc %{Release the gem to RubyGems.org}
-task :release => :gem do
- system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
-end
+task build: :validate_gemspec
-desc "Cleans up white space in source files"
+desc 'Cleans up white space in source files'
task :clean_whitespace do
no_file_cleaned = true
- Dir["**/*.rb"].each do |file|
+ Dir['**/*.rb'].each do |file|
contents = File.read(file)
cleaned_contents = contents.gsub(/([ \t]+)$/, '')
unless cleaned_contents == contents
no_file_cleaned = false
puts " - Cleaned #{file}"
- File.open(file, 'w') { |f| f.write(cleaned_contents) }
+ File.write(file, cleaned_contents)
end
end
if no_file_cleaned
- puts "No files with trailing whitespace found"
+ puts 'No files with trailing whitespace found'
end
end
-require 'rspec/core'
require 'rspec/core/rake_task'
-desc "Run specs"
+desc 'Run specs'
RSpec::Core::RakeTask.new(:spec)
-task :default => :spec
+task default: :spec
diff --git a/documentation/adapters.md b/documentation/adapters.md
index 78b83b57..3bba6911 100644
--- a/documentation/adapters.md
+++ b/documentation/adapters.md
@@ -1,7 +1,7 @@
### Adapters
-Webmachine includes adapters for [WEBrick][webrick], [Reel][reel], and
-[HTTPkit][httpkit]. Additionally, the [Rack][rack] adapter lets it
+Webmachine includes an adapter for [WEBrick][webrick].
+Additionally, the [Rack][rack] adapter lets it
run on any webserver that provides a Rack interface. It also lets it run on
[Shotgun][shotgun] ([example][shotgun_example]).
@@ -10,28 +10,21 @@ run on any webserver that provides a Rack interface. It also lets it run on
In order to be compatible with popular deployment stacks,
Webmachine has a [Rack](https://github.com/rack/rack) adapter (thanks to Jamis Buck).
-Webmachine can be used with Rack middlware features such as Rack::Map and Rack::Cascade as long as:
-
-1. The Webmachine app is mounted at the root directory.
-2. Any requests/responses that are handled by the Webmachine app are not modified by the middleware. The behaviours that are encapsulated in Webmachine assume that no modifications
+Webmachine can be used with Rack middlware features such as Rack::Map and Rack::Cascade as long as any requests/responses that are handled by the Webmachine app are **not** modified by the middleware. The behaviours that are encapsulated in Webmachine assume that no modifications
are done to requests or response outside of Webmachine.
Keep in mind that Webmachine already supports many things that Rack middleware is used for with other HTTP frameworks (eg. etags, specifying supported/preferred Accept and Content-Types).
+The base `Webmachine::Adapters::Rack` class assumes the Webmachine application
+is mounted at the route path `/` (i.e. not using `Rack::Builder#map` or Rails
+`ActionDispatch::Routing::Mapper::Base#mount`). In order to
+map to a subpath, use the `Webmachine::Adapters::RackMapped` adapter instead.
+
For an example of using Webmachine with Rack middleware, see the [Pact Broker][middleware-example].
See the [Rack Adapter API docs][rack-adapter-api-docs] for more information.
-#### A Note about MRI 1.9
-
-The [Reel][reel] and [HTTPkit][httpkit]
-adapters might crash with a `SystemStackError` on MRI 1.9 due to its
-limited fiber stack size. If your application is affected by this, the
-only known solution is to switch to JRuby, Rubinius or MRI 2.0.
-
[webrick]: http://rubydoc.info/stdlib/webrick
-[reel]: https://github.com/celluloid/reel
-[httpkit]: https://github.com/lgierth/httpkit
[rack]: https://github.com/rack/rack
[shotgun]: https://github.com/rtomayko/shotgun
[shotgun_example]: https://gist.github.com/4389220
diff --git a/documentation/examples.md b/documentation/examples.md
index 08f25538..3891cc7d 100644
--- a/documentation/examples.md
+++ b/documentation/examples.md
@@ -92,12 +92,15 @@ end
```
# POST to perform a task
-* Override `allowed_methods` and `process_post`. Put all the code to be executed in `process_post`.
-* `process_post` must return true, or the HTTP response code
-* Response headers like Content-Type will need to be set manually.
+* Override `allowed_methods`, `process_post`, and `content_types_provided` (if the response has a content type).
+* Rather than providing a method handler in the `content_type_provided` mappings, put all the code to be executed in `process_post`.
+* `process_post` must return true, or the HTTP response code.
```ruby
class DispatchOrderResource < Webmachine::Resource
+ def content_types_provided
+ [["application/json"]]
+ end
def allowed_methods
["POST"]
@@ -108,9 +111,8 @@ class DispatchOrderResource < Webmachine::Resource
end
def process_post
- @order.dispatch
- response.headers['Content-Type'] = 'text/plain'
- response.body = "Successfully dispatched order #{id}"
+ @order.dispatch(params['some_param'])
+ response.body = { message: "Successfully dispatched order #{id}" }.to_json
true
end
@@ -119,6 +121,10 @@ class DispatchOrderResource < Webmachine::Resource
def id
request.path_info[:id]
end
+
+ def params
+ JSON.parse(request.body.to_s)
+ end
end
```
@@ -137,6 +143,8 @@ class OrderResource < Webmachine::Resource
[["application/json", :from_json]]
end
+ # Note that returning falsey will NOT result in a 404 for PUT requests.
+ # See note below.
def resource_exists?
order
end
@@ -166,8 +174,10 @@ class OrderResource < Webmachine::Resource
end
```
+If you wish to disallow PUT to a non-existent resource, read more [here](https://github.com/webmachine/webmachine-ruby/issues/207#issuecomment-132604379).
+
# PATCH
-* 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.
+* 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.
# DELETE
* Override `resource_exists?` and `delete_resource`
@@ -208,8 +218,7 @@ Thanks to [oestrich][oestrich] for putting together the original example. You ca
[oestrich]: https://github.com/oestrich
[source]: https://gist.github.com/oestrich/3638605
-
+
## What order are the callbacks invoked in?
-
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.
diff --git a/documentation/how-it-works.md b/documentation/how-it-works.md
index 70d49087..323dfd1a 100644
--- a/documentation/how-it-works.md
+++ b/documentation/how-it-works.md
@@ -71,6 +71,6 @@ end
* 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.
[callbacks]: https://github.com/seancribbs/webmachine-ruby/blob/master/lib/webmachine/resource/callbacks.rb
-[diagram]: http://webmachine.basho.com/images/http-headers-status-v3.png
+[diagram]: https://webmachine.github.io/images/http-headers-status-v3.png
[flow]: https://github.com/seancribbs/webmachine-ruby/blob/master/lib/webmachine/decision/flow.rb
[examples]: /documentation/examples.md
diff --git a/documentation/routes.md b/documentation/routes.md
index bdbd27f0..9dcfd307 100644
--- a/documentation/routes.md
+++ b/documentation/routes.md
@@ -20,6 +20,21 @@ App = Webmachine::Application.new do |app|
# but will not provide any path_info
add ["orders", :*], OrderResource
+ # Will map to any path that matches the given components and regular expression
+ # Any capture groups specified in the regex will be made available in
+ # request.path_info[:captures. In this case, you would get one or two
+ # values in :captures depending on whether your request looked like:
+ # /orders/1/cancel
+ # or
+ # /orders/1/cancel.json
+ add ["orders", :id, /([^.]*)\.?(.*)?/], OrderResource
+
+ # You can even use named captures with regular expressions. This will
+ # automatically put the captures into path_info. In the below example,
+ # you would get :id from the symbol, along with :action and :format
+ # from the regex. :format in this case would be optional.
+ add ["orders", :id, /(?)[^.]*)\.?(?.*)?/], OrderResource
+
# will map to any path
add [:*], DefaultResource
end
@@ -94,4 +109,4 @@ end
request.path_info[:foo]
=> "bar"
-```
\ No newline at end of file
+```
diff --git a/examples/debugger.rb b/examples/debugger.rb
index cff11c90..ec4bffa5 100644
--- a/examples/debugger.rb
+++ b/examples/debugger.rb
@@ -2,21 +2,23 @@
require 'webmachine/trace'
class MyTracedResource < Webmachine::Resource
- def trace?; true; end
+ def trace?
+ true
+ end
def resource_exists?
case request.query['e']
when 'true'
true
when 'fail'
- raise "BOOM"
+ raise 'BOOM'
else
false
end
end
def to_html
- "You found me."
+ 'You found me.'
end
end
diff --git a/examples/logging.rb b/examples/logging.rb
index 24ceac7f..cda597ee 100644
--- a/examples/logging.rb
+++ b/examples/logging.rb
@@ -18,7 +18,7 @@ def handle_event(event)
resource = event.payload[:resource]
code = event.payload[:code]
- puts "[%s] method=%s uri=%s code=%d resource=%s time=%.4f" % [
+ puts '[%s] method=%s uri=%s code=%d resource=%s time=%.4f' % [
Time.now.iso8601, request.method, request.uri.to_s, code, resource,
event.duration
]
@@ -34,7 +34,7 @@ def handle_event(event)
app.configure do |config|
config.adapter = :WEBrick
- config.adapter_options = {:AccessLog => [], :Logger => Logger.new('/dev/null')}
+ config.adapter_options = {AccessLog: [], Logger: Logger.new(File::NULL)}
end
end
diff --git a/examples/webrick.rb b/examples/webrick.rb
index 3c899ca8..a6a81268 100644
--- a/examples/webrick.rb
+++ b/examples/webrick.rb
@@ -6,11 +6,11 @@ def last_modified
end
def encodings_provided
- { "gzip" => :encode_gzip, "identity" => :encode_identity }
+ {'gzip' => :encode_gzip, 'identity' => :encode_identity}
end
def to_html
- "Hello from WebmachineHello, world!"
+ 'Hello from WebmachineHello, world!'
end
end
diff --git a/lib/webmachine.rb b/lib/webmachine.rb
index 3af9a461..0860ac32 100644
--- a/lib/webmachine.rb
+++ b/lib/webmachine.rb
@@ -1,4 +1,5 @@
-require 'webmachine/configuration'
+require 'webmachine/configuration'
+require 'webmachine/constants'
require 'webmachine/cookie'
require 'webmachine/headers'
require 'webmachine/request'
diff --git a/lib/webmachine/adapter.rb b/lib/webmachine/adapter.rb
index c11aad52..2896a8cc 100644
--- a/lib/webmachine/adapter.rb
+++ b/lib/webmachine/adapter.rb
@@ -1,10 +1,8 @@
module Webmachine
-
# The abstract class for definining a Webmachine adapter.
#
# @abstract Subclass and override {#run} to implement a custom adapter.
class Adapter
-
# @return [Webmachine::Application] returns the application
attr_reader :application
@@ -25,6 +23,5 @@ def self.run(application)
def run
raise NotImplementedError
end
-
end
end
diff --git a/lib/webmachine/adapters.rb b/lib/webmachine/adapters.rb
index 37c8e9d3..ab6dc231 100644
--- a/lib/webmachine/adapters.rb
+++ b/lib/webmachine/adapters.rb
@@ -5,7 +5,5 @@ module Webmachine
# Contains classes and modules that connect Webmachine to Ruby
# application servers.
module Adapters
- autoload :Reel, 'webmachine/adapters/reel'
- autoload :HTTPkit, 'webmachine/adapters/httpkit'
end
end
diff --git a/lib/webmachine/adapters/httpkit.rb b/lib/webmachine/adapters/httpkit.rb
deleted file mode 100644
index 9961f8ff..00000000
--- a/lib/webmachine/adapters/httpkit.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-require 'httpkit'
-
-require 'webmachine/version'
-require 'webmachine/headers'
-require 'webmachine/request'
-require 'webmachine/response'
-require 'webmachine/dispatcher'
-require 'webmachine/chunked_body'
-
-module Webmachine
- module Adapters
- class HTTPkit < Adapter
- def options
- @options ||= {
- :address => application.configuration.ip,
- :port => application.configuration.port,
- :handlers => [
- ::HTTPkit::Server::TimeoutsHandler.new,
- ::HTTPkit::Server::KeepAliveHandler.new,
- self
- ]
- }
- end
-
- def run
- ::HTTPkit.start do
- ::HTTPkit::Server.start(options)
- end
- end
-
- # Called by HTTPkit::Server for every request
- def serve(request, served)
- response = Webmachine::Response.new
- application.dispatcher.dispatch(convert_request(request), response)
-
- served.fulfill(convert_response(response))
- end
-
- private
-
- # Converts HTTPkit::Request to Webmachine::Request
- def convert_request(request)
- Webmachine::Request.new(
- request.http_method.to_s.upcase,
- request.uri,
- Webmachine::Headers[request.headers.dup],
- request.body)
- end
-
- # Converts Webmachine::Response to HTTPkit::Response
- def convert_response(response)
- response.headers["Server"] =
- Webmachine::SERVER_STRING + ' HTTPkit/' + ::HTTPkit::VERSION
-
- ::HTTPkit::Response.new(
- response.code.to_i,
- response.headers,
- convert_body(response.body))
- end
-
- # HTTPkit::Body accepts strings and enumerables, i.e. Webmachine's
- # Callable, Enumerable, IO, and Fiber encoders are supported.
- def convert_body(body)
- if body.respond_to?(:call)
- [body.call]
- else
- body || ''
- end
- end
- end
- end
-end
diff --git a/lib/webmachine/adapters/lazy_request_body.rb b/lib/webmachine/adapters/lazy_request_body.rb
index 08e6dbed..8fd2c069 100644
--- a/lib/webmachine/adapters/lazy_request_body.rb
+++ b/lib/webmachine/adapters/lazy_request_body.rb
@@ -1,5 +1,4 @@
-
-module Webmachine
+module Webmachine
module Adapters
# Wraps a request body so that it can be passed to
# {Request} while still lazily evaluating the body.
@@ -25,7 +24,7 @@ def empty?
# @yield [chunk]
# @yieldparam [String] chunk a chunk of the request body
def each
- @request.body {|chunk| yield chunk }
+ @request.body { |chunk| yield chunk }
end
end # class RequestBody
end # module Adapters
diff --git a/lib/webmachine/adapters/rack.rb b/lib/webmachine/adapters/rack.rb
index d1d34fee..b4484d5b 100644
--- a/lib/webmachine/adapters/rack.rb
+++ b/lib/webmachine/adapters/rack.rb
@@ -1,9 +1,10 @@
+require 'webmachine/adapter'
require 'rack'
-require 'webmachine/version'
+require 'webmachine/constants'
require 'webmachine/headers'
require 'webmachine/request'
require 'webmachine/response'
-require 'webmachine/dispatcher'
+require 'webmachine/version'
require 'webmachine/chunked_body'
module Webmachine
@@ -11,9 +12,12 @@ module Adapters
# A minimal "shim" adapter to allow Webmachine to interface with Rack. The
# intention here is to allow Webmachine to run under Rack-compatible
# web-servers, like unicorn and pow.
+ #
# The adapter expects your Webmachine application to be mounted at the root path -
# it will NOT allow you to nest your Webmachine application at an arbitrary path
# eg. map "/api" { run MyWebmachineAPI }
+ # To use map your Webmachine application at an arbitrary path, use the
+ # `Webmachine::Adapters::RackMapped` subclass instead.
#
# To use this adapter, create a config.ru file and populate it like so:
#
@@ -36,12 +40,16 @@ class Rack < Adapter
# Used to override default Rack server options (useful in testing)
DEFAULT_OPTIONS = {}
+ REQUEST_URI = 'REQUEST_URI'.freeze
+ VERSION_STRING = "#{Webmachine::SERVER_STRING} Rack/#{::Rack.release}".freeze
+ NEWLINE = "\n".freeze
+
# Start the Rack adapter
def run
options = DEFAULT_OPTIONS.merge({
- :app => self,
- :Port => application.configuration.port,
- :Host => application.configuration.ip
+ app: self,
+ Port: application.configuration.port,
+ Host: application.configuration.ip
}).merge(application.configuration.adapter_options)
@server = ::Rack::Server.new(options)
@@ -54,52 +62,82 @@ def call(env)
headers = Webmachine::Headers.from_cgi(env)
rack_req = ::Rack::Request.new env
- request = Webmachine::Request.new(rack_req.request_method,
- env['REQUEST_URI'],
- headers,
- RequestBody.new(rack_req))
+ request = build_webmachine_request(rack_req, headers)
response = Webmachine::Response.new
application.dispatcher.dispatch(request, response)
- response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ")
+ response.headers[SERVER] = VERSION_STRING
- rack_status = response.code
- rack_headers = response.headers.flattened("\n")
+ rack_status = response.code
+ rack_headers = response.headers.flattened(NEWLINE)
rack_body = case response.body
- when String # Strings are enumerable in ruby 1.8
- [response.body]
- else
- if (io_body = IO.try_convert(response.body))
- io_body
- elsif response.body.respond_to?(:call)
- Webmachine::ChunkedBody.new(Array(response.body.call))
- elsif response.body.respond_to?(:each)
- # This might be an IOEncoder with a Content-Length, which shouldn't be chunked.
- if response.headers["Transfer-Encoding"] == "chunked"
- Webmachine::ChunkedBody.new(response.body)
- else
- response.body
- end
- else
- [response.body.to_s]
- end
- end
+ when String # Strings are enumerable in ruby 1.8
+ [response.body]
+ else
+ if (io_body = IO.try_convert(response.body))
+ io_body
+ elsif response.body.respond_to?(:call)
+ Webmachine::ChunkedBody.new(Array(response.body.call))
+ elsif response.body.respond_to?(:each)
+ # This might be an IOEncoder with a Content-Length, which shouldn't be chunked.
+ if response.headers[TRANSFER_ENCODING] == 'chunked'
+ Webmachine::ChunkedBody.new(response.body)
+ else
+ response.body
+ end
+ else
+ [response.body.to_s]
+ end
+ end
rack_res = RackResponse.new(rack_body, rack_status, rack_headers)
rack_res.finish
end
+ protected
+
+ def routing_tokens(rack_req)
+ nil # no-op for default, un-mapped rack adapter
+ end
+
+ def base_uri(rack_req)
+ nil # no-op for default, un-mapped rack adapter
+ end
+
+ private
+
+ def build_webmachine_request(rack_req, headers)
+ RackRequest.new(rack_req.request_method,
+ rack_req.url,
+ headers,
+ RequestBody.new(rack_req),
+ routing_tokens(rack_req),
+ base_uri(rack_req),
+ rack_req.env)
+ end
+
+ class RackRequest < Webmachine::Request
+ attr_reader :env
+
+ def initialize(method, uri, headers, body, routing_tokens, base_uri, env)
+ super(method, uri, headers, body, routing_tokens, base_uri)
+ @env = env
+ end
+ end
+
class RackResponse
+ ONE_FIVE = '1.5'.freeze
+
def initialize(body, status, headers)
- @body = body
- @status = status
+ @body = body
+ @status = status
@headers = headers
end
def finish
- @headers['Content-Type'] ||= 'text/html' if rack_release_enforcing_content_type
- @headers.delete('Content-Type') if response_without_body
+ @headers[CONTENT_TYPE] ||= TEXT_HTML if rack_release_enforcing_content_type
+ @headers.delete(CONTENT_TYPE) if response_without_body
[@status, @headers, @body]
end
@@ -110,7 +148,7 @@ def response_without_body
end
def rack_release_enforcing_content_type
- ::Rack.release < '1.5'
+ ::Rack.release < ONE_FIVE
end
end
@@ -151,14 +189,16 @@ def to_s
# @yieldparam [String] chunk a chunk of the request body
def each
if @value
- @value.each {|chunk| yield chunk }
+ @value.each { |chunk| yield chunk }
else
@value = []
- @request.body.each {|chunk| @value << chunk; yield chunk }
+ @request.body.each { |chunk|
+ @value << chunk
+ yield chunk
+ }
end
end
end # class RequestBody
end # class Rack
-
end # module Adapters
end # module Webmachine
diff --git a/lib/webmachine/adapters/rack_mapped.rb b/lib/webmachine/adapters/rack_mapped.rb
new file mode 100644
index 00000000..dc87a732
--- /dev/null
+++ b/lib/webmachine/adapters/rack_mapped.rb
@@ -0,0 +1,41 @@
+require 'webmachine/adapters/rack'
+
+module Webmachine
+ module Adapters
+ # Provides the same functionality as the parent Webmachine::Adapters::Rack
+ # adapter, but allows the Webmachine application to be hosted at an
+ # arbitrary path in a parent Rack application (as in Rack `map` or Rails
+ # routing `mount`)
+ #
+ # This functionality is separated out from the parent class to preserve
+ # backward compatibility in the behaviour of the parent Rack adpater.
+ #
+ # To use the adapter in a parent Rack application, map the Webmachine
+ # application as follows in a rackup file or Rack::Builder:
+ #
+ # map '/foo' do
+ # run SomeotherRackApp
+ #
+ # map '/bar' do
+ # run MyWebmachineApp.adapter
+ # end
+ # end
+ class RackMapped < Rack
+ protected
+
+ def routing_tokens(rack_req)
+ routing_match = rack_req.path_info.match(Webmachine::Request::ROUTING_PATH_MATCH)
+ routing_path = routing_match ? routing_match[1] : ''
+ routing_path.split(SLASH)
+ end
+
+ def base_uri(rack_req)
+ # rack SCRIPT_NAME env var doesn't end with "/". This causes weird
+ # behavour when URI.join concatenates URI components in
+ # Webmachine::Decision::Flow#n11
+ script_name = rack_req.script_name + SLASH
+ URI.join(rack_req.base_url, script_name)
+ end
+ end # class RackMapped
+ end # module Adapters
+end # module Webmachine
diff --git a/lib/webmachine/adapters/reel.rb b/lib/webmachine/adapters/reel.rb
deleted file mode 100644
index d76b31d5..00000000
--- a/lib/webmachine/adapters/reel.rb
+++ /dev/null
@@ -1,112 +0,0 @@
-require 'reel'
-require 'webmachine/version'
-require 'webmachine/headers'
-require 'webmachine/request'
-require 'webmachine/response'
-require 'webmachine/dispatcher'
-require 'set'
-
-module Webmachine
- module Adapters
- class Reel < Adapter
- # Used to override default Reel server options (useful in testing)
- DEFAULT_OPTIONS = {}
-
- def run
- @options = DEFAULT_OPTIONS.merge({
- :port => application.configuration.port,
- :host => application.configuration.ip
- }).merge(application.configuration.adapter_options)
-
- if extra_verbs = application.configuration.adapter_options[:extra_verbs]
- @extra_verbs = Set.new(extra_verbs.map(&:to_s).map(&:upcase))
- else
- @extra_verbs = Set.new
- end
-
- if @options[:ssl]
- unless @options[:ssl][:cert] && @options[:ssl][:key]
- raise ArgumentError, 'Certificate or Private key missing for HTTPS Server'
- end
- @server = ::Reel::Server::HTTPS.supervise(@options[:host], @options[:port], @options[:ssl], &method(:process))
- else
- @server = ::Reel::Server::HTTP.supervise(@options[:host], @options[:port], &method(:process))
- end
-
- Celluloid::Actor.join(@server)
- end
-
- def process(connection)
- connection.each_request do |request|
- # Users of the adapter can configure a custom WebSocket handler
- if request.websocket?
- if handler = @options[:websocket_handler]
- handler.call(request.websocket)
- else
- # Pretend we don't know anything about the WebSocket protocol
- # FIXME: This isn't strictly what RFC 6455 would have us do
- request.respond :bad_request, "WebSockets not supported"
- end
-
- next
- end
-
- # Optional support for e.g. WebDAV verbs not included in Webmachine's
- # state machine. Do the "Railsy" thing and handle them like POSTs
- # with a magical parameter
- if @extra_verbs.include?(request.method)
- method = "POST"
- param = "_method=#{request.method}"
- uri = request_uri(request.url, request.headers, param)
- else
- method = request.method
- uri = request_uri(request.url, request.headers)
- end
-
- wm_headers = Webmachine::Headers[request.headers.dup]
- wm_request = Webmachine::Request.new(method, uri, wm_headers, request.body)
-
- wm_response = Webmachine::Response.new
- application.dispatcher.dispatch(wm_request, wm_response)
-
- fixup_headers(wm_response)
- fixup_callable_encoder(wm_response)
-
- request.respond ::Reel::Response.new(wm_response.code,
- wm_response.headers,
- wm_response.body)
- end
- end
-
- def request_uri(path, headers, extra_query_params = nil)
- path_parts = path.split('?')
- uri_hash = {path: path_parts.first}
- uri_hash[:query] = path_parts.last if path_parts.length == 2
-
- if extra_query_params
- if uri_hash[:query]
- uri_hash[:query] << "{extra_query_params}"
- else
- uri_hash[:query] = extra_query_params
- end
- end
-
- URI::HTTP.build(uri_hash)
- end
-
- def fixup_headers(response)
- response.headers.each do |key, value|
- if value.is_a?(Array)
- response.headers[key] = value.join(", ")
- end
- end
- end
-
- def fixup_callable_encoder(response)
- if response.body.is_a?(Streaming::CallableEncoder)
- response.body = [response.body.call]
- end
- end
- end
- end
-end
diff --git a/lib/webmachine/adapters/webrick.rb b/lib/webmachine/adapters/webrick.rb
index 18644521..52c6ce78 100644
--- a/lib/webmachine/adapters/webrick.rb
+++ b/lib/webmachine/adapters/webrick.rb
@@ -1,9 +1,11 @@
+require 'webmachine/adapter'
require 'webrick'
-require 'webmachine/version'
+require 'webmachine/constants'
require 'webmachine/headers'
+require 'webmachine/adapters/lazy_request_body'
require 'webmachine/request'
require 'webmachine/response'
-require 'webmachine/dispatcher'
+require 'webmachine/version'
module Webmachine
module Adapters
@@ -15,9 +17,9 @@ class WEBrick < Adapter
# Starts the WEBrick adapter
def run
options = DEFAULT_OPTIONS.merge({
- :Port => application.configuration.port,
- :BindAddress => application.configuration.ip,
- :application => application
+ Port: application.configuration.port,
+ BindAddress: application.configuration.ip,
+ application: application
}).merge(application.configuration.adapter_options)
@server = Server.new(options)
@server.start
@@ -27,35 +29,35 @@ def run
class Server < ::WEBrick::HTTPServer
def initialize(options)
@application = options[:application]
- super(options)
+ super
end
# Handles a request
def service(wreq, wres)
header = Webmachine::Headers.new
- wreq.each {|k,v| header[k] = v }
+ wreq.each { |k, v| header[k] = v }
request = Webmachine::Request.new(wreq.request_method,
- wreq.request_uri,
- header,
- LazyRequestBody.new(wreq))
+ wreq.request_uri,
+ header,
+ LazyRequestBody.new(wreq))
response = Webmachine::Response.new
@application.dispatcher.dispatch(request, response)
wres.status = response.code.to_i
- headers = response.headers.flattened.reject { |k,v| k == 'Set-Cookie' }
- headers.each { |k,v| wres[k] = v }
+ headers = response.headers.flattened.reject { |k, v| k == 'Set-Cookie' }
+ headers.each { |k, v| wres[k] = v }
cookies = [response.headers['Set-Cookie'] || []].flatten
cookies.each { |c| wres.cookies << c }
- wres['Server'] = [Webmachine::SERVER_STRING, wres.config[:ServerSoftware]].join(" ")
+ wres[SERVER] = [Webmachine::SERVER_STRING, wres.config[:ServerSoftware]].join(' ')
case response.body
when String
wres.body << response.body
when Enumerable
- wres.chunked = response.headers['Transfer-Encoding'] == 'chunked'
- response.body.each {|part| wres.body << part }
+ wres.chunked = response.headers[TRANSFER_ENCODING] == 'chunked'
+ response.body.each { |part| wres.body << part }
else
if response.body.respond_to?(:call)
wres.chunked = true
diff --git a/lib/webmachine/application.rb b/lib/webmachine/application.rb
index 9cc3a5b7..314f2e8b 100644
--- a/lib/webmachine/application.rb
+++ b/lib/webmachine/application.rb
@@ -43,7 +43,7 @@ class Application
# the Application instance being initialized
def initialize(configuration = Configuration.default, dispatcher = Dispatcher.new)
@configuration = configuration
- @dispatcher = dispatcher
+ @dispatcher = dispatcher
yield self if block_given?
end
@@ -73,7 +73,7 @@ def adapter_class
#
# @see Webmachine::Dispatcher#add_route
def routes(&block)
- if block_given?
+ if block
dispatcher.instance_eval(&block)
self
else
diff --git a/lib/webmachine/chunked_body.rb b/lib/webmachine/chunked_body.rb
index a1819ea9..8942ac7f 100644
--- a/lib/webmachine/chunked_body.rb
+++ b/lib/webmachine/chunked_body.rb
@@ -1,3 +1,5 @@
+require 'webmachine/constants'
+
module Webmachine
# {ChunkedBody} is used to wrap an {Enumerable} object (like an enumerable
# {Response#body}) so it yields proper chunks for chunked transfer encoding.
@@ -13,11 +15,8 @@ module Webmachine
#
# This is needed for Ruby webservers which don't do the chunking themselves.
class ChunkedBody
- # Delimiter for chunked encoding
- CRLF = "\r\n"
-
# Final chunk in any chunked-encoding response
- FINAL_CHUNK = "0#{CRLF}#{CRLF}"
+ FINAL_CHUNK = "0#{CRLF}#{CRLF}".freeze
# Creates a new {ChunkedBody} from the given {Enumerable}.
# @param [Enumerable] body the enumerable response body
@@ -30,7 +29,7 @@ def initialize(body)
# parameter.
# Returns an {Enumerator} if no block is given.
def each
- return self.to_enum unless block_given?
+ return to_enum unless block_given?
@body.each do |chunk|
size = chunk.bytesize
diff --git a/lib/webmachine/configuration.rb b/lib/webmachine/configuration.rb
index d6b66ef2..5555a92c 100644
--- a/lib/webmachine/configuration.rb
+++ b/lib/webmachine/configuration.rb
@@ -5,14 +5,14 @@ module Webmachine
# defaults will be filled in when {Webmachine::run} is called.
# @attr [String] ip the interface to bind to, defaults to "0.0.0.0"
# (all interfaces)
- # @attr [Fixnum] port the port to bind to, defaults to 8080
+ # @attr [Integer] port the port to bind to, defaults to 8080
# @attr [Symbol] adapter the adapter to use, defaults to :WEBrick
# @attr [Hash] adapter_options adapter-specific options, defaults to {}
Configuration = Struct.new(:ip, :port, :adapter, :adapter_options)
# @return [Configuration] the default configuration
def Configuration.default
- new("0.0.0.0", 8080, :WEBrick, {})
+ new('0.0.0.0', 8080, :WEBrick, {})
end
# Yields the current configuration to the passed block.
diff --git a/lib/webmachine/constants.rb b/lib/webmachine/constants.rb
new file mode 100644
index 00000000..444f4eb8
--- /dev/null
+++ b/lib/webmachine/constants.rb
@@ -0,0 +1,75 @@
+module Webmachine
+ # Universal HTTP delimiter
+ CRLF = "\r\n".freeze
+
+ # HTTP Content-Type
+ CONTENT_TYPE = 'Content-Type'.freeze
+
+ # Default Content-Type
+ TEXT_HTML = 'text/html'.freeze
+
+ # HTTP Date
+ DATE = 'Date'.freeze
+
+ # HTTP Transfer-Encoding
+ TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
+
+ # HTTP Content-Length
+ CONTENT_LENGTH = 'Content-Length'.freeze
+
+ # A underscore
+ UNDERSCORE = '_'.freeze
+
+ # A dash
+ DASH = '-'.freeze
+
+ # A Slash
+ SLASH = '/'.freeze
+
+ MATCHES_ALL = '*/*'.freeze
+
+ GET_METHOD = 'GET'
+ HEAD_METHOD = 'HEAD'
+ POST_METHOD = 'POST'
+ PUT_METHOD = 'PUT'
+ DELETE_METHOD = 'DELETE'
+ OPTIONS_METHOD = 'OPTIONS'
+ TRACE_METHOD = 'TRACE'
+ CONNECT_METHOD = 'CONNECT'
+
+ STANDARD_HTTP_METHODS = [
+ GET_METHOD, HEAD_METHOD, POST_METHOD,
+ PUT_METHOD, DELETE_METHOD, TRACE_METHOD,
+ CONNECT_METHOD, OPTIONS_METHOD
+ ].map!(&:freeze)
+ STANDARD_HTTP_METHODS.freeze
+
+ # A colon
+ COLON = ':'.freeze
+
+ # http string
+ HTTP = 'http'.freeze
+
+ # Host string
+ HOST = 'Host'.freeze
+
+ # HTTP Content-Encoding
+ CONTENT_ENCODING = 'Content-Encoding'.freeze
+
+ # Charset string
+ CHARSET = 'Charset'.freeze
+
+ # Comma split match
+ SPLIT_COMMA = /\s*,\s*/.freeze
+
+ # Star Character
+ STAR = '*'.freeze
+
+ # HTTP Location
+ LOCATION = 'Location'.freeze
+
+ # identity Encoding
+ IDENTITY = 'identity'.freeze
+
+ SERVER = 'Server'.freeze
+end
diff --git a/lib/webmachine/cookie.rb b/lib/webmachine/cookie.rb
index 9d80e26e..dfb07990 100644
--- a/lib/webmachine/cookie.rb
+++ b/lib/webmachine/cookie.rb
@@ -11,7 +11,7 @@ class Cookie
def self.parse(cstr, include_dups = false)
cookies = {}
(cstr || '').split(/\s*[;,]\s*/n).each { |c|
- k,v = c.split(/\s*=\s*/, 2).map { |s| unescape(s) }
+ k, v = c.split(/\s*=\s*/, 2).map { |s| unescape(s) }
case cookies[k]
when nil
@@ -31,7 +31,7 @@ def self.parse(cstr, include_dups = false)
# Allowed keys for the attributes parameter of
# {Webmachine::Cookie#initialize}
ALLOWED_ATTRIBUTES = [:secure, :httponly, :path, :domain,
- :comment, :maxage, :expires, :version]
+ :comment, :maxage, :expires, :version]
# If the cookie is HTTP only
def http_only?
@@ -83,32 +83,34 @@ def to_s
attributes = ALLOWED_ATTRIBUTES.select { |a| @attributes[a] }.map do |a|
case a
when :httponly
- "HttpOnly" if @attributes[a]
+ 'HttpOnly' if @attributes[a]
when :secure
- "Secure" if @attributes[a]
+ 'Secure' if @attributes[a]
when :maxage
- "Max-Age=" + @attributes[a].to_s
+ 'Max-Age=' + @attributes[a].to_s
when :expires
- "Expires=" + rfc2822(@attributes[a])
+ 'Expires=' + rfc2822(@attributes[a])
when :comment
- "Comment=" + escape(@attributes[a].to_s)
+ 'Comment=' + escape(@attributes[a].to_s)
else
- a.to_s.sub(/^\w/) { $&.capitalize } + "=" + @attributes[a].to_s
+ a.to_s.sub(/^\w/) { $&.capitalize } + '=' + @attributes[a].to_s
end
end
- ([escape(name) + "=" + escape(value)] + attributes).join("; ")
+ ([escape(name) + '=' + escape(value)] + attributes).join('; ')
end
private
+ # Format timestamps for the 'Expires' portion of the cookie string, as per RFC 2822 and 2616.
+ #
+ # @see https://www.rfc-editor.org/rfc/rfc2616#section-3.3.1
+ # @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expires
def rfc2822(time)
- wday = Time::RFC2822_DAY_NAME[time.wday]
- mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
- time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
+ time.strftime('%a, %d %b %Y %T GMT')
end
- if URI.respond_to?(:decode_www_form_component) and defined?(::Encoding)
+ if URI.respond_to?(:decode_www_form_component) && defined?(::Encoding)
# Escape a cookie
def escape(s)
URI.encode_www_form_component(s)
@@ -132,7 +134,7 @@ def self.unescape(s, encoding = Encoding::UTF_8)
# @private
TBLDECWWWCOMP_ = {}
256.times do |i|
- h, l = i>>4, i&15
+ h, l = i >> 4, i & 15
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
@@ -146,9 +148,9 @@ def self.unescape(s, encoding = Encoding::UTF_8)
# This decodes + to SP.
#
# @private
- def self.unescape(str, enc=nil)
- raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%\h\h|[^%]+)*\z/ =~ str
- str.gsub(/\+|%\h\h/){|c| TBLDECWWWCOMP_[c] }
+ def self.unescape(str, enc = nil)
+ raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%\h\h|[^%]+)*\z/.match?(str)
+ str.gsub(/\+|%\h\h/) { |c| TBLDECWWWCOMP_[c] }
end
# Encode given +str+ to URL-encoded form data.
@@ -161,7 +163,7 @@ def self.unescape(str, enc=nil)
#
# @private
def escape(str)
- str.to_s.gsub(/[^*\-.0-9A-Z_a-z]/){|c| TBLENCWWWCOMP_[c] }
+ str.to_s.gsub(/[^*\-.0-9A-Z_a-z]/) { |c| TBLENCWWWCOMP_[c] }
end
end
end
diff --git a/lib/webmachine/decision/conneg.rb b/lib/webmachine/decision/conneg.rb
index e89d2d9e..204634de 100644
--- a/lib/webmachine/decision/conneg.rb
+++ b/lib/webmachine/decision/conneg.rb
@@ -1,3 +1,4 @@
+require 'webmachine/constants'
require 'webmachine/translation'
require 'webmachine/media_type'
@@ -13,7 +14,7 @@ module Conneg
# appropriate media type.
# @api private
def choose_media_type(provided, header)
- types = Array(header).map{|h| h.split(/\s*,\s*/) }.flatten
+ types = Array(header).map { |h| h.split(SPLIT_COMMA) }.flatten
requested = MediaTypeList.build(types)
provided = provided.map do |p| # normalize_provided
MediaType.parse(p)
@@ -31,9 +32,9 @@ def choose_media_type(provided, header)
# @api private
def choose_encoding(provided, header)
encodings = provided.keys
- if encoding = do_choose(encodings, header, "identity")
- response.headers['Content-Encoding'] = encoding unless encoding == 'identity'
- metadata['Content-Encoding'] = encoding
+ if encoding = do_choose(encodings, header, IDENTITY)
+ response.headers[CONTENT_ENCODING] = encoding unless encoding == IDENTITY
+ metadata[CONTENT_ENCODING] = encoding
end
end
@@ -42,9 +43,9 @@ def choose_encoding(provided, header)
# @api private
def choose_charset(provided, header)
if provided && !provided.empty?
- charsets = provided.map {|c| c.first }
+ charsets = provided.map { |c| c.first }
if charset = do_choose(charsets, header, HAS_ENCODING ? Encoding.default_external.name : kcode_charset)
- metadata['Charset'] = charset
+ metadata[CHARSET] = charset
end
else
true
@@ -56,22 +57,22 @@ def choose_charset(provided, header)
# @api private
def choose_language(provided, header)
if provided && !provided.empty?
- requested = PriorityList.build(header.split(/\s*,\s*/))
- star_priority = requested.priority_of("*")
+ requested = PriorityList.build(header.split(SPLIT_COMMA))
+ star_priority = requested.priority_of(STAR)
any_ok = star_priority && star_priority > 0.0
accepted = requested.find do |priority, range|
if priority == 0.0
- provided.delete_if {|tag| language_match(range, tag) }
+ provided.delete_if { |tag| language_match(range, tag) }
false
else
- provided.any? {|tag| language_match(range, tag) }
+ provided.any? { |tag| language_match(range, tag) }
end
end
chosen = if accepted
- provided.find {|tag| language_match(accepted.last, tag) }
- elsif any_ok
- provided.first
- end
+ provided.find { |tag| language_match(accepted.last, tag) }
+ elsif any_ok
+ provided.first
+ end
if chosen
metadata['Language'] = chosen
response.headers['Content-Language'] = chosen
@@ -90,17 +91,17 @@ def choose_language(provided, header)
# is "-".
# @api private
def language_match(range, tag)
- range.downcase == tag.downcase || tag =~ /^#{Regexp.escape(range)}\-/i
+ range.downcase == tag.downcase || tag =~ /^#{Regexp.escape(range)}-/i
end
# Makes an conneg choice based what is accepted and what is
# provided.
# @api private
def do_choose(choices, header, default)
- choices = choices.dup.map {|s| s.downcase }
- accepted = PriorityList.build(header.split(/\s*,\s*/))
+ choices = choices.dup.map { |s| s.downcase }
+ accepted = PriorityList.build(header.split(SPLIT_COMMA))
default_priority = accepted.priority_of(default)
- star_priority = accepted.priority_of("*")
+ star_priority = accepted.priority_of(STAR)
default_ok = (default_priority.nil? && star_priority != 0.0) || default_priority
any_ok = star_priority && star_priority > 0.0
chosen = accepted.find do |priority, acceptable|
@@ -111,14 +112,15 @@ def do_choose(choices, header, default)
choices.include?(acceptable.downcase)
end
end
- (chosen && chosen.last) || # Use the matching one
+ chosen&.last || # Use the matching one
(any_ok && choices.first) || # Or first if "*"
(default_ok && choices.include?(default) && default) # Or default
end
private
+
# Matches acceptable items that include 'q' values
- CONNEG_REGEX = /^\s*(\S+);\s*q=(\S*)\s*$/
+ CONNEG_REGEX = /^\s*(\S+);\s*q=(\S*)\s*$/.freeze
# Matches the requested media type (with potential modifiers)
# against the provided types (with potential modifiers).
@@ -137,13 +139,13 @@ def media_match(requested, provided)
def kcode_charset
case $KCODE
when /^U/i
- "UTF-8"
+ 'UTF-8'
when /^S/i
- "Shift-JIS"
+ 'Shift-JIS'
when /^B/i
- "Big5"
- else #when /^A/i, nil
- "ASCII"
+ 'Big5'
+ else # when /^A/i, nil
+ 'ASCII'
end
end
@@ -156,7 +158,7 @@ class PriorityList
# Given an acceptance list, create a PriorityList from them.
def self.build(list)
new.tap do |plist|
- list.each {|item| plist.add_header_val(item) }
+ list.each { |item| plist.add_header_val(item) }
end
end
@@ -165,7 +167,7 @@ def self.build(list)
# Creates a {PriorityList}.
# @see PriorityList::build
def initialize
- @hash = Hash.new {|h,k| h[k] = [] }
+ @hash = Hash.new { |h, k| h[k] = [] }
@index = {}
end
@@ -184,8 +186,8 @@ def add(q, choice)
def add_header_val(c)
if c =~ CONNEG_REGEX
choice, q = $1, $2
- q = "0" << q if q =~ /^\./ # handle strange FeedBurner Accept
- add(q.to_f,choice)
+ q = '0' << q if /^\./.match?(q) # handle strange FeedBurner Accept
+ add(q.to_f, choice)
else
add(1.0, c)
end
@@ -211,8 +213,8 @@ def priority_of(choice)
# @yieldparam [Float] q the acceptable item's priority
# @yieldparam [String] v the acceptable item
def each
- @hash.to_a.sort.reverse_each do |q,l|
- l.each {|v| yield q, v }
+ @hash.to_a.sort.reverse_each do |q, l|
+ l.each { |v| yield q, v }
end
end
end
@@ -231,10 +233,9 @@ def add_header_val(c)
q = mt.params.delete('q') || 1.0
add(q.to_f, mt)
rescue ArgumentError
- raise MalformedRequest, t('invalid_media_type', :type => c)
+ raise MalformedRequest, t('invalid_media_type', type: c)
end
end
-
end # module Conneg
end # module Decision
end # module Webmachine
diff --git a/lib/webmachine/decision/falsey.rb b/lib/webmachine/decision/falsey.rb
index 30a15f56..06d5fe75 100644
--- a/lib/webmachine/decision/falsey.rb
+++ b/lib/webmachine/decision/falsey.rb
@@ -7,4 +7,3 @@ def Falsey.===(other)
end
end
end
-
diff --git a/lib/webmachine/decision/flow.rb b/lib/webmachine/decision/flow.rb
index a3f2c16f..5ecc8f54 100644
--- a/lib/webmachine/decision/flow.rb
+++ b/lib/webmachine/decision/flow.rb
@@ -1,6 +1,7 @@
-require 'time'
+require 'time'
require 'digest/md5'
require 'base64'
+require 'webmachine/constants'
require 'webmachine/decision/conneg'
require 'webmachine/decision/falsey'
require 'webmachine/translation'
@@ -34,7 +35,7 @@ module Flow
# Handles standard decisions where halting is allowed
def decision_test(test, iftrue, iffalse)
case test
- when Fixnum # Allows callbacks to "halt" with a given response code
+ when Integer # Allows callbacks to "halt" with a given response code
test
when Falsey
iffalse
@@ -63,7 +64,7 @@ def b10
if resource.allowed_methods.include?(request.method)
:b9
else
- response.headers["Allow"] = resource.allowed_methods.join(", ")
+ response.headers['Allow'] = resource.allowed_methods.join(', ')
405
end
end
@@ -76,18 +77,18 @@ def b9
# Content-MD5 valid?
def b9a
case valid = resource.validate_content_checksum
- when Fixnum
+ when Integer
valid
when true
:b9b
when false
- response.body = "Content-MD5 header does not match request body."
+ response.body = 'Content-MD5 header does not match request body.'
400
else # not_validated
if decode64(request.content_md5) == Digest::MD5.hexdigest(request.body)
:b9b
else
- response.body = "Content-MD5 header does not match request body."
+ response.body = 'Content-MD5 header does not match request body.'
400
end
end
@@ -104,7 +105,7 @@ def b8
case result
when true
:b7
- when Fixnum
+ when Integer
result
when String
response.headers['WWW-Authenticate'] = result
@@ -119,9 +120,10 @@ def b7
decision_test(resource.forbidden?, 403, :b6)
end
+ CONTENT = /content-/.freeze
# Okay Content-* Headers?
def b6
- decision_test(resource.valid_content_headers?(request.headers.grep(/content-/)), :b5, 501)
+ decision_test(resource.valid_content_headers?(request.headers.grep(CONTENT)), :b5, 501)
end
# Known Content-Type?
@@ -147,7 +149,7 @@ def b3
# Accept exists?
def c3
if !request.accept
- metadata['Content-Type'] = MediaType.parse(resource.content_types_provided.first.first)
+ metadata[CONTENT_TYPE] = MediaType.parse(resource.content_types_provided.first.first)
:d4
else
:c4
@@ -156,12 +158,12 @@ def c3
# Acceptable media type available?
def c4
- types = resource.content_types_provided.map {|pair| pair.first }
+ types = resource.content_types_provided.map { |pair| pair.first }
chosen_type = choose_media_type(types, request.accept)
if !chosen_type
406
else
- metadata['Content-Type'] = chosen_type
+ metadata[CONTENT_TYPE] = chosen_type
:d4
end
end
@@ -169,7 +171,7 @@ def c4
# Accept-Language exists?
def d4
if !request.accept_language
- if language = choose_language(resource.languages_provided, "*")
+ if language = choose_language(resource.languages_provided, STAR)
resource.language_chosen(language)
:e5
else
@@ -193,7 +195,7 @@ def d5
# Accept-Charset exists?
def e5
if !request.accept_charset
- choose_charset(resource.charsets_provided, "*") ? :f6 : 406
+ choose_charset(resource.charsets_provided, STAR) ? :f6 : 406
else
:e6
end
@@ -207,13 +209,13 @@ def e6
# Accept-Encoding exists?
# (also, set content-type header here, now that charset is chosen)
def f6
- chosen_type = metadata['Content-Type']
- if chosen_charset = metadata['Charset']
+ chosen_type = metadata[CONTENT_TYPE]
+ if chosen_charset = metadata[CHARSET]
chosen_type.params['charset'] = chosen_charset
end
- response.headers['Content-Type'] = chosen_type.to_s
+ response.headers[CONTENT_TYPE] = chosen_type.to_s
if !request.accept_encoding
- choose_encoding(resource.encodings_provided, "identity;q=1.0,*;q=0.5") ? :g7 : 406
+ choose_encoding(resource.encodings_provided, 'identity;q=1.0,*;q=0.5') ? :g7 : 406
else
:f7
end
@@ -227,7 +229,7 @@ def f7
# Resource exists?
def g7
# This is the first place after all conneg, so set Vary here
- response.headers['Vary'] = variances.join(", ") if variances.any?
+ response.headers['Vary'] = variances.join(', ') if variances.any?
decision_test(resource.resource_exists?, :g8, :h7)
end
@@ -238,18 +240,18 @@ def g8
# If-Match: * exists?
def g9
- quote(request.if_match) == '"*"' ? :h10 : :g11
+ (quote(request.if_match) == '"*"') ? :h10 : :g11
end
# ETag in If-Match
def g11
- request_etags = request.if_match.split(/\s*,\s*/).map {|etag| ETag.new(etag) }
+ request_etags = request.if_match.split(SPLIT_COMMA).map { |etag| ETag.new(etag) }
request_etags.include?(ETag.new(resource.generate_etag)) ? :h10 : 412
end
# If-Match exists?
def h7
- (request.if_match && unquote(request.if_match) == '*') ? 412 : :i7
+ (request.if_match && unquote(request.if_match) == STAR) ? 412 : :i7
end
# If-Unmodified-Since exists?
@@ -269,16 +271,16 @@ def h11
# Last-Modified > I-UM-S?
def h12
- resource.last_modified > metadata['If-Unmodified-Since'] ? 412 : :i12
+ (resource.last_modified > metadata['If-Unmodified-Since']) ? 412 : :i12
end
# Moved permanently? (apply PUT to different URI)
def i4
case uri = resource.moved_permanently?
when String, URI
- response.headers["Location"] = uri.to_s
+ response.headers[LOCATION] = uri.to_s
301
- when Fixnum
+ when Integer
uri
else
:p3
@@ -297,7 +299,7 @@ def i12
# If-none-match: * exists?
def i13
- quote(request.if_none_match) == '"*"' ? :j18 : :k13
+ (quote(request.if_none_match) == '"*"') ? :j18 : :k13
end
# GET or HEAD?
@@ -309,9 +311,9 @@ def j18
def k5
case uri = resource.moved_permanently?
when String, URI
- response.headers["Location"] = uri.to_s
+ response.headers[LOCATION] = uri.to_s
301
- when Fixnum
+ when Integer
uri
else
:l5
@@ -325,10 +327,10 @@ def k7
# Etag in if-none-match?
def k13
- request_etags = request.if_none_match.split(/\s*,\s*/).map {|etag| ETag.new(etag) }
+ request_etags = request.if_none_match.split(SPLIT_COMMA).map { |etag| ETag.new(etag) }
resource_etag = resource.generate_etag
if resource_etag && request_etags.include?(ETag.new(resource_etag))
- :j18
+ :j18
else
:l13
end
@@ -338,9 +340,9 @@ def k13
def l5
case uri = resource.moved_temporarily?
when String, URI
- response.headers["Location"] = uri.to_s
+ response.headers[LOCATION] = uri.to_s
307
- when Fixnum
+ when Integer
uri
else
:m5
@@ -369,12 +371,12 @@ def l14
# IMS > Now?
def l15
- metadata['If-Modified-Since'] > Time.now ? :m16 : :l17
+ (metadata['If-Modified-Since'] > Time.now) ? :m16 : :l17
end
# Last-Modified > IMS?
def l17
- resource.last_modified.nil? || resource.last_modified > metadata['If-Modified-Since'] ? :m16 : 304
+ (resource.last_modified.nil? || resource.last_modified > metadata['If-Modified-Since']) ? :m16 : 304
end
# POST?
@@ -413,27 +415,27 @@ def n11
if resource.post_is_create?
case uri = resource.create_path
when nil
- raise InvalidResource, t('create_path_nil', :class => resource.class)
+ raise InvalidResource, t('create_path_nil', class: resource.class)
when URI, String
base_uri = resource.base_uri || request.base_uri
new_uri = URI.join(base_uri.to_s, uri)
request.disp_path = new_uri.path
- response.headers['Location'] = new_uri.to_s
+ response.headers[LOCATION] = new_uri.to_s
result = accept_helper
- return result if Fixnum === result
+ return result if Integer === result
end
else
case result = resource.process_post
when true
encode_body_if_set
- when Fixnum
+ when Integer
return result
else
- raise InvalidResource, t('process_post_invalid', :result => result.inspect)
+ raise InvalidResource, t('process_post_invalid', result: result.inspect)
end
end
if response.is_redirect?
- if response.headers['Location']
+ if response.headers[LOCATION]
303
else
raise InvalidResource, t('do_redirect')
@@ -454,7 +456,7 @@ def o14
409
else
res = accept_helper
- (Fixnum === res) ? res : :p11
+ (Integer === res) ? res : :p11
end
end
@@ -468,10 +470,10 @@ def o16
def o18
if request.get? || request.head?
add_caching_headers
- content_type = metadata['Content-Type']
- handler = resource.content_types_provided.find {|ct, _| content_type.type_matches?(MediaType.parse(ct)) }.last
+ content_type = metadata[CONTENT_TYPE]
+ handler = resource.content_types_provided.find { |ct, _| content_type.type_matches?(MediaType.parse(ct)) }.last
result = resource.send(handler)
- if Fixnum === result
+ if Integer === result
result
else
response.body = result
@@ -499,15 +501,14 @@ def p3
409
else
res = accept_helper
- (Fixnum === res) ? res : :p11
+ (Integer === res) ? res : :p11
end
end
# New resource?
def p11
- !response.headers["Location"] ? :o20 : 201
+ (!response.headers[LOCATION]) ? :o20 : 201
end
-
end # module Flow
end # module Decision
end # module Webmachine
diff --git a/lib/webmachine/decision/fsm.rb b/lib/webmachine/decision/fsm.rb
index d641a7f2..44ec2484 100644
--- a/lib/webmachine/decision/fsm.rb
+++ b/lib/webmachine/decision/fsm.rb
@@ -1,6 +1,8 @@
-require 'webmachine/decision/helpers'
+require 'webmachine/decision/helpers'
require 'webmachine/trace'
require 'webmachine/translation'
+require 'webmachine/constants'
+require 'webmachine/rescueable_exception'
module Webmachine
module Decision
@@ -28,17 +30,17 @@ def run
trace_decision(state)
result = handle_exceptions { send(state) }
case result
- when Fixnum # Response code
+ when Integer # Response code
respond(result)
break
when Symbol # Next state
state = result
else # You bwoke it
- raise InvalidResource, t('fsm_broke', :state => state, :result => result.inspect)
+ raise InvalidResource, t('fsm_broke', state: state, result: result.inspect)
end
end
- rescue Exception => e
- Webmachine.render_error(500, request, response, :message => e.message)
+ rescue => e
+ Webmachine.render_error(500, request, response, message: e.message)
ensure
trace_response(response)
end
@@ -47,22 +49,22 @@ def run
def handle_exceptions
yield
- rescue MalformedRequest => e
- Webmachine.render_error(400, request, response, :message => e.message)
- 400
- rescue Exception => e
+ rescue Webmachine::RescuableException => e
resource.handle_exception(e)
500
+ rescue MalformedRequest => e
+ Webmachine.render_error(400, request, response, message: e.message)
+ 400
end
- def respond(code, headers={})
+ def respond(code, headers = {})
response.code = code
response.headers.merge!(headers)
case code
when 404
Webmachine.render_error(code, request, response)
when 304
- response.headers.delete('Content-Type')
+ response.headers.delete(CONTENT_TYPE)
add_caching_headers
end
diff --git a/lib/webmachine/decision/helpers.rb b/lib/webmachine/decision/helpers.rb
index 6625473c..892ad063 100644
--- a/lib/webmachine/decision/helpers.rb
+++ b/lib/webmachine/decision/helpers.rb
@@ -1,10 +1,11 @@
-require 'stringio'
+require 'stringio'
require 'time'
require 'webmachine/streaming'
require 'webmachine/media_type'
require 'webmachine/quoted_string'
require 'webmachine/etags'
require 'webmachine/header_negotiation'
+require 'webmachine/constants'
module Webmachine
module Decision
@@ -28,38 +29,38 @@ def encode_body_if_set
# Encodes the body in the selected charset and encoding.
def encode_body
body = response.body
- chosen_charset = metadata['Charset']
- chosen_encoding = metadata['Content-Encoding']
- charsetter = resource.charsets_provided && resource.charsets_provided.find {|c,_| c == chosen_charset }.last || :charset_nop
+ chosen_charset = metadata[CHARSET]
+ chosen_encoding = metadata[CONTENT_ENCODING]
+ charsetter = resource.charsets_provided&.find { |c, _| c == chosen_charset }&.last || :charset_nop
encoder = resource.encodings_provided[chosen_encoding]
response.body = case body
- when String # 1.8 treats Strings as Enumerable
- resource.send(encoder, resource.send(charsetter, body))
- when IO, StringIO
- IOEncoder.new(resource, encoder, charsetter, body)
- when Fiber
- FiberEncoder.new(resource, encoder, charsetter, body)
- when Enumerable
- EnumerableEncoder.new(resource, encoder, charsetter, body)
- else
- if body.respond_to?(:call)
- CallableEncoder.new(resource, encoder, charsetter, body)
- else
- resource.send(encoder, resource.send(charsetter, body))
- end
- end
+ when String # 1.8 treats Strings as Enumerable
+ resource.send(encoder, resource.send(charsetter, body))
+ when IO, StringIO
+ IOEncoder.new(resource, encoder, charsetter, body)
+ when Fiber
+ FiberEncoder.new(resource, encoder, charsetter, body)
+ when Enumerable
+ EnumerableEncoder.new(resource, encoder, charsetter, body)
+ else
+ if body.respond_to?(:call)
+ CallableEncoder.new(resource, encoder, charsetter, body)
+ else
+ resource.send(encoder, resource.send(charsetter, body))
+ end
+ end
if body_is_fixed_length?
ensure_content_length(response)
else
- response.headers.delete 'Content-Length'
- response.headers['Transfer-Encoding'] = 'chunked'
+ response.headers.delete CONTENT_LENGTH
+ response.headers[TRANSFER_ENCODING] = 'chunked'
end
end
# Assists in receiving request bodies
def accept_helper
content_type = MediaType.parse(request.content_type || 'application/octet-stream')
- acceptable = resource.content_types_accepted.find {|ct, _| content_type.match?(ct) }
+ acceptable = resource.content_types_accepted.find { |ct, _| content_type.match?(ct) }
if acceptable
resource.send(acceptable.last)
else
@@ -70,10 +71,10 @@ def accept_helper
# Computes the entries for the 'Vary' response header
def variances
resource.variances.tap do |v|
- v.unshift "Accept-Language" if resource.languages_provided.size > 1
- v.unshift "Accept-Charset" if resource.charsets_provided && resource.charsets_provided.size > 1
- v.unshift "Accept-Encoding" if resource.encodings_provided.size > 1
- v.unshift "Accept" if resource.content_types_provided.size > 1
+ v.unshift 'Accept-Language' if resource.languages_provided.size > 1
+ v.unshift 'Accept-Charset' if resource.charsets_provided && resource.charsets_provided.size > 1
+ v.unshift 'Accept-Encoding' if resource.encodings_provided.size > 1
+ v.unshift 'Accept' if resource.content_types_provided.size > 1
end
end
@@ -94,7 +95,7 @@ def add_caching_headers
# is a String or IO with known size.
def body_is_fixed_length?
response.body.respond_to?(:bytesize) &&
- Fixnum === response.body.bytesize
+ Integer === response.body.bytesize
end
end # module Helpers
end # module Decision
diff --git a/lib/webmachine/dispatcher.rb b/lib/webmachine/dispatcher.rb
index def8961e..c405369e 100644
--- a/lib/webmachine/dispatcher.rb
+++ b/lib/webmachine/dispatcher.rb
@@ -1,4 +1,4 @@
-require 'forwardable'
+require 'forwardable'
require 'webmachine/decision'
require 'webmachine/dispatcher/route'
@@ -6,6 +6,8 @@ module Webmachine
# Handles dispatching incoming requests to the proper registered
# resources and initializing the decision logic.
class Dispatcher
+ WM_DISPATCH = 'wm.dispatch'.freeze
+
# @return [Array] the list of routes that will be
# dispatched to
# @see #add_route
@@ -31,7 +33,7 @@ def add_route(*args, &block)
@routes << route
route
end
- alias :add :add_route
+ alias_method :add, :add_route
# Dispatches a request to the appropriate {Resource} in the
# dispatch list. If a matching resource is not found, a "404 Not
@@ -40,7 +42,7 @@ def add_route(*args, &block)
# @param [Response] response the response object
def dispatch(request, response)
if resource = find_resource(request, response)
- Webmachine::Events.instrument('wm.dispatch') do |payload|
+ Webmachine::Events.instrument(WM_DISPATCH) do |payload|
Webmachine::Decision::FSM.new(resource, request, response).run
payload[:resource] = resource.class.name
@@ -70,10 +72,11 @@ def find_resource(request, response)
# Find the first route that matches an incoming request
# @param [Request] request the request to match
def find_route(request)
- @routes.find {|r| r.match?(request) }
+ @routes.find { |r| r.match?(request) }
end
private
+
def prepare_resource(route, request, response)
route.apply(request)
@resource_creator.call(route, request, response)
diff --git a/lib/webmachine/dispatcher/route.rb b/lib/webmachine/dispatcher/route.rb
index e36d3b8a..17fca693 100644
--- a/lib/webmachine/dispatcher/route.rb
+++ b/lib/webmachine/dispatcher/route.rb
@@ -1,12 +1,13 @@
-require 'webmachine/resource'
+require 'webmachine/resource'
require 'webmachine/translation'
+require 'webmachine/constants'
module Webmachine
class Dispatcher
# Pairs URIs with {Resource} classes in the {Dispatcher}. To
# create routes, use {Dispatcher#add_route}.
class Route
- include Webmachine::Translation
+ include Translation
# @return [Class] the resource this route will dispatch to, a
# subclass of {Resource}
@@ -27,6 +28,21 @@ class Route
# String version of MATCH_ALL, deprecated. Use the symbol instead.
MATCH_ALL_STR = '*'.freeze
+ # Decode a string using the scheme described in RFC 3986 2.1. Percent-Encoding (https://www.ietf.org/rfc/rfc3986.txt)
+ def self.rfc3986_percent_decode(value)
+ s = StringScanner.new(value)
+ result = ''
+ until s.eos?
+ encoded_val = s.scan(/%([0-9a-fA-F]){2}/)
+ result << if encoded_val.nil?
+ s.getch
+ else
+ [encoded_val[1..]].pack('H*')
+ end
+ end
+ result
+ end
+
# Creates a new Route that will associate a pattern to a
# {Resource}.
#
@@ -57,32 +73,32 @@ class Route
# @yield [req] an optional guard block
# @yieldparam [Request] req the request object
# @see Dispatcher#add_route
- def initialize(path_spec, *args)
- if args.last.is_a? Hash
- bindings = args.pop
+ def initialize(path_spec, *args, &block)
+ bindings = if args.last.is_a? Hash
+ args.pop
else
- bindings = {}
+ {}
end
resource = args.pop
guards = args
- guards << Proc.new if block_given?
+ guards << block if block
warn t('match_all_symbol') if path_spec.include? MATCH_ALL_STR
@path_spec = path_spec
- @guards = guards
- @resource = resource
- @bindings = bindings
+ @guards = guards
+ @resource = resource
+ @bindings = bindings
- raise ArgumentError, t('not_resource_class', :class => resource.name) unless resource < Resource
+ raise ArgumentError, t('not_resource_class', class: resource.name) unless resource < Resource
end
# Determines whether the given request matches this route and
# should be dispatched to the {#resource}.
# @param [Reqeust] request the request object
def match?(request)
- tokens = request.uri.path.match(/^\/(.*)/)[1].split('/')
+ tokens = request.routing_tokens
bind(tokens, {}) && guards.all? { |guard| guard.call(request) }
end
@@ -90,19 +106,20 @@ def match?(request)
# route, including path bindings.
# @param [Request] request the request object
def apply(request)
- request.disp_path = request.uri.path.match(/^\/(.*)/)[1]
+ request.disp_path = request.routing_tokens.join(SLASH)
request.path_info = @bindings.dup
- tokens = request.disp_path.split('/')
- depth, trailing = bind(tokens, request.path_info)
+ tokens = request.routing_tokens
+ _depth, trailing = bind(tokens, request.path_info)
request.path_tokens = trailing || []
end
private
+
# Attempts to match the path spec against the path tokens, while
# accumulating variable bindings.
# @param [Array] tokens the list of path segments
# @param [Hash] bindings where path bindings will be stored
- # @return [Fixnum, Array, false] either the depth
+ # @return [Integer, Array, false] either the depth
# that the path matched at, the depth and tokens matched by
# {MATCH_ALL}, or false if it didn't match.
def bind(tokens, bindings)
@@ -118,18 +135,30 @@ def bind(tokens, bindings)
return [depth, tokens]
when tokens.empty?
return false
+ when Regexp === spec.first
+ matches = spec.first.match Route.rfc3986_percent_decode(tokens.first)
+ if matches
+ if spec.first.named_captures.empty?
+ bindings[:captures] = (bindings[:captures] || []) + matches.captures
+ else
+ spec.first.named_captures.each_with_object(bindings) do |(name, idxs), bindings|
+ bindings[name.to_sym] = matches.captures[idxs.first - 1]
+ end
+ end
+ else
+ return false
+ end
when Symbol === spec.first
- bindings[spec.first] = URI.decode(tokens.first)
+ bindings[spec.first] = Route.rfc3986_percent_decode(tokens.first)
when spec.first == tokens.first
else
return false
end
- spec = spec[1..-1]
- tokens = tokens[1..-1]
+ spec = spec[1..]
+ tokens = tokens[1..]
depth += 1
end
end
-
end # class Route
end # module Dispatcher
end # module Webmachine
diff --git a/lib/webmachine/errors.rb b/lib/webmachine/errors.rb
index bc93325b..8ebb1c9f 100644
--- a/lib/webmachine/errors.rb
+++ b/lib/webmachine/errors.rb
@@ -1,5 +1,6 @@
-require 'webmachine/header_negotiation'
+require 'webmachine/header_negotiation'
require 'webmachine/translation'
+require 'webmachine/constants'
require 'webmachine/version'
module Webmachine
@@ -8,28 +9,27 @@ module Webmachine
# Renders a standard error message body for the response. The
# standard messages are defined in localization files.
- # @param [Fixnum] code the response status code
+ # @param [Integer] code the response status code
# @param [Request] req the request object
# @param [Response] req the response object
# @param [Hash] options keys to override the defaults when rendering
# the response body
- def self.render_error(code, req, res, options={})
+ def self.render_error(code, req, res, options = {})
res.code = code
unless res.body
title, message = t(["errors.#{code}.title", "errors.#{code}.message"],
- { :method => req.method,
- :error => res.error}.merge(options))
- res.body = t("errors.standard_body",
- {:title => title,
- :message => message,
- :version => Webmachine::SERVER_STRING}.merge(options))
- res.headers['Content-Type'] = "text/html"
+ {method: req.method,
+ error: res.error}.merge(options))
+ res.body = t('errors.standard_body',
+ {title: title,
+ message: message,
+ version: Webmachine::SERVER_STRING}.merge(options))
+ res.headers[CONTENT_TYPE] = TEXT_HTML
end
ensure_content_length(res)
ensure_date_header(res)
end
-
# Superclass of all errors generated by Webmachine.
class Error < ::StandardError; end
@@ -38,6 +38,6 @@ class InvalidResource < Error; end
# Raised when the client has submitted an invalid request, e.g. in
# the case where a request header is improperly formed. Raising this
- # exception will result in a 400 response.
+ # error will result in a 400 response.
class MalformedRequest < Error; end
end # module Webmachine
diff --git a/lib/webmachine/etags.rb b/lib/webmachine/etags.rb
index b5ae2b72..4fe28a4a 100644
--- a/lib/webmachine/etags.rb
+++ b/lib/webmachine/etags.rb
@@ -5,12 +5,13 @@ module Webmachine
# This class by itself represents a "strong" entity tag.
class ETag
include QuotedString
+
# The pattern for a weak entity tag
WEAK_ETAG = /^W\/#{QUOTED_STRING}$/.freeze
def self.new(etag)
return etag if ETag === etag
- klass = etag =~ WEAK_ETAG ? WeakETag : self
+ klass = WEAK_ETAG.match?(etag) ? WeakETag : self
klass.send(:allocate).tap do |obj|
obj.send(:initialize, etag)
end
@@ -53,6 +54,7 @@ def to_s
end
private
+
def unquote(str)
if str =~ WEAK_ETAG
unescape_quotes $1
diff --git a/lib/webmachine/events.rb b/lib/webmachine/events.rb
index 1fe73d96..5040bcdc 100644
--- a/lib/webmachine/events.rb
+++ b/lib/webmachine/events.rb
@@ -55,9 +55,9 @@ def publish(name, *args)
# and publish it. Notice that events get sent even if an error occurs
# in the passed-in block.
#
- # If an exception happens during an instrumentation the payload will
+ # If an error happens during an instrumentation the payload will
# have a key `:exception` with an array of two elements as value:
- # a string with the name of the exception class, and the exception
+ # a string with the name of the error class, and the error
# message. (when using the default
# [AS::Notifications](http://rubydoc.info/gems/as-notifications/AS/Notifications)
# backend)
diff --git a/lib/webmachine/header_negotiation.rb b/lib/webmachine/header_negotiation.rb
index 4dc292db..6243e199 100644
--- a/lib/webmachine/header_negotiation.rb
+++ b/lib/webmachine/header_negotiation.rb
@@ -1,22 +1,23 @@
+require 'webmachine/constants'
+
module Webmachine
module HeaderNegotiation
def ensure_date_header(res)
- if (200..499).include?(res.code)
- res.headers['Date'] ||= Time.now.httpdate
+ if (200..499).cover?(res.code)
+ res.headers[DATE] ||= Time.now.httpdate
end
end
def ensure_content_length(res)
body = res.body
- case
- when res.headers['Transfer-Encoding']
- return
- when [204, 205, 304].include?(res.code)
- res.headers.delete 'Content-Length'
- when body != nil
- res.headers['Content-Length'] = body.respond_to?(:bytesize) ? body.bytesize.to_s : body.length.to_s
+ if res.headers[TRANSFER_ENCODING]
+ nil
+ elsif [204, 205, 304].include?(res.code)
+ res.headers.delete CONTENT_LENGTH
+ elsif !body.nil?
+ res.headers[CONTENT_LENGTH] = body.respond_to?(:bytesize) ? body.bytesize.to_s : body.length.to_s
else
- res.headers['Content-Length'] = '0'
+ res.headers[CONTENT_LENGTH] = '0'
end
end
end
diff --git a/lib/webmachine/headers.rb b/lib/webmachine/headers.rb
index a441b7aa..2de99112 100644
--- a/lib/webmachine/headers.rb
+++ b/lib/webmachine/headers.rb
@@ -1,15 +1,19 @@
+require 'webmachine/constants'
+
module Webmachine
# Case-insensitive Hash of Request headers
class Headers < ::Hash
+ CGI_HTTP_MATCH = /^HTTP_(\w+)$/.freeze
+ CONTENT_TYPE_LENGTH_MATCH = /^(CONTENT_(?:TYPE|LENGTH))$/.freeze
+
# Convert CGI-style Hash into Request headers
# @param [Hash] env a hash of CGI-style env/headers
# @return [Webmachine::Headers]
def self.from_cgi(env)
- env.inject(new) do |h,(k,v)|
- if k =~ /^HTTP_(\w+)$/ || k =~ /^(CONTENT_(?:TYPE|LENGTH))$/
- h[$1.tr("_", "-")] = v
+ env.each_with_object(new) do |(k, v), h|
+ if k =~ CGI_HTTP_MATCH || k =~ CONTENT_TYPE_LENGTH_MATCH
+ h[$1.tr(UNDERSCORE, DASH)] = v
end
- h
end
end
@@ -28,22 +32,22 @@ def self.from_cgi(env)
# @param [Object]
# @return [Webmachine::Headers]
def self.[](*args)
- super(super(*args).map {|k, v| [k.to_s.downcase, v]})
+ super(super.map { |k, v| [k.to_s.downcase, v] })
end
# Fetch a header
def [](key)
- super transform_key(key)
+ super(transform_key(key))
end
# Set a header
- def []=(key,value)
- super transform_key(key), value
+ def []=(key, value)
+ super(transform_key(key), value)
end
# Returns the value for the given key. If the key can't be found,
# there are several options:
- # With no other arguments, it will raise a KeyError exception;
+ # With no other arguments, it will raise a KeyError error;
# if default is given, then that will be returned;
# if the optional code block is specified, then that will be run and its
# result returned.
@@ -66,15 +70,16 @@ def fetch(*args, &block)
# Delete a header
def delete(key)
- super transform_key(key)
+ super(transform_key(key))
end
# Select matching headers
def grep(pattern)
- self.class[select { |k,_| pattern === k }]
+ self.class[select { |k, _| pattern === k }]
end
private
+
def transform_key(key)
key.to_s.downcase
end
diff --git a/lib/webmachine/media_type.rb b/lib/webmachine/media_type.rb
index 128ee801..53ee0746 100644
--- a/lib/webmachine/media_type.rb
+++ b/lib/webmachine/media_type.rb
@@ -1,14 +1,17 @@
-require 'webmachine/translation'
+require 'webmachine/translation'
+require 'webmachine/constants'
+require 'webmachine/dispatcher/route'
module Webmachine
# Encapsulates a MIME media type, with logic for matching types.
class MediaType
extend Translation
+
# Matches valid media types
- MEDIA_TYPE_REGEX = /^\s*([^;\s]+)\s*((?:;\s*\S+\s*)*)\s*$/
+ MEDIA_TYPE_REGEX = /^\s*([^;\s]+)\s*((?:;\s*\S+\s*)*)\s*$/.freeze
# Matches sub-type parameters
- PARAMS_REGEX = /;\s*([^=]+)(=([^;=\s]*))?/
+ PARAMS_REGEX = /;\s*([^=]+)(=([^;=\s]*))?/.freeze
# Creates a new MediaType by parsing an alternate representation.
# @param [MediaType, String, Array] obj the raw type
@@ -21,11 +24,11 @@ def self.parse(obj)
obj
when MEDIA_TYPE_REGEX
type, raw_params = $1, $2
- params = Hash[raw_params.scan(PARAMS_REGEX).map { |m| [m[0], m[2].to_s] }]
+ params = raw_params.scan(PARAMS_REGEX).map { |m| [m[0], m[2].to_s] }.to_h
new(type, params)
else
unless Array === obj && String === obj[0] && Hash === obj[1]
- raise ArgumentError, t('invalid_media_type', :type => obj.inspect)
+ raise ArgumentError, t('invalid_media_type', type: obj.inspect)
end
type = parse(obj[0])
type.params.merge!(obj[1])
@@ -41,14 +44,14 @@ def self.parse(obj)
# @param [String] type the main media type, e.g. application/json
# @param [Hash] params the media type parameters
- def initialize(type, params={})
+ def initialize(type, params = {})
@type, @params = type, params
end
# Detects whether the {MediaType} represents an open wildcard
# type, that is, "*/*" without any {#params}.
def matches_all?
- @type == "*/*" && @params.empty?
+ @type == MATCHES_ALL && @params.empty?
end
# @return [true,false] Are these two types strictly equal?
@@ -86,23 +89,23 @@ def match?(other)
# @param [Hash] params the requested params
# @return [true,false] whether it is an acceptable match
def params_match?(other)
- other.all? {|k,v| params[k] == v }
+ other.all? { |k, v| params[k] == v }
end
# Reconstitutes the type into a String
# @return [String] the type as a String
def to_s
- [type, *params.map {|k,v| "#{k}=#{v}" }].join(";")
+ [type, *params.map { |k, v| "#{k}=#{v}" }].join(';')
end
# @return [String] The major type, e.g. "application", "text", "image"
def major
- type.split("/").first
+ type.split(SLASH).first
end
# @return [String] the minor or sub-type, e.g. "json", "html", "jpeg"
def minor
- type.split("/").last
+ type.split(SLASH).last
end
# @param [MediaType] other the other type
@@ -110,11 +113,8 @@ def minor
# ignoring params and taking into account wildcards
def type_matches?(other)
other = self.class.parse(other)
- if ["*", "*/*", type].include?(other.type)
- true
- else
- other.major == major && other.minor == "*"
- end
+ [Dispatcher::Route::MATCH_ALL_STR, MATCHES_ALL, type].include?(other.type) ||
+ (other.major == major && other.minor == Dispatcher::Route::MATCH_ALL_STR)
end
end # class MediaType
end # module Webmachine
diff --git a/lib/webmachine/quoted_string.rb b/lib/webmachine/quoted_string.rb
index 7c09a91c..bb039f8e 100644
--- a/lib/webmachine/quoted_string.rb
+++ b/lib/webmachine/quoted_string.rb
@@ -19,21 +19,21 @@ def unquote(str)
# Ensures that quotes exist around a quoted-string
def quote(str)
- if str =~ QS_ANCHORED
+ if QS_ANCHORED.match?(str)
str
else
- %Q{"#{escape_quotes str}"}
+ %("#{escape_quotes str}")
end
end
# Escapes quotes within a quoted string.
def escape_quotes(str)
- str.gsub(/"/, '\\"')
+ str.gsub('"', '\\"')
end
# Unescapes quotes within a quoted string
def unescape_quotes(str)
- str.gsub(%r{\\}, '')
+ str.delete('\\')
end
end
end
diff --git a/lib/webmachine/request.rb b/lib/webmachine/request.rb
index 3ff7d8fd..f8f3b771 100644
--- a/lib/webmachine/request.rb
+++ b/lib/webmachine/request.rb
@@ -1,28 +1,19 @@
-require 'cgi'
+require 'cgi'
require 'forwardable'
+require 'webmachine/constants'
+require 'ipaddr'
module Webmachine
# Request represents a single HTTP request sent from a client. It
# should be instantiated by {Adapters} when a request is received
class Request
+ HTTP_HEADERS_MATCH = /^(?:[a-z0-9])+(?:_[a-z0-9]+)*$/i.freeze
+ ROUTING_PATH_MATCH = /^\/(.*)/.freeze
+
extend Forwardable
- attr_reader :method, :uri, :headers, :body
- attr_accessor :disp_path, :path_info, :path_tokens
- GET_METHOD = "GET"
- HEAD_METHOD = "HEAD"
- POST_METHOD = "POST"
- PUT_METHOD = "PUT"
- DELETE_METHOD = "DELETE"
- OPTIONS_METHOD = "OPTIONS"
- TRACE_METHOD = "TRACE"
- CONNECT_METHOD = "CONNECT"
-
- STANDARD_HTTP_METHODS = [
- GET_METHOD, HEAD_METHOD, POST_METHOD,
- PUT_METHOD, DELETE_METHOD, TRACE_METHOD,
- CONNECT_METHOD, OPTIONS_METHOD
- ].map!(&:freeze)
+ attr_reader :method, :uri, :headers, :body, :routing_tokens, :base_uri
+ attr_accessor :disp_path, :path_info, :path_tokens
# @param [String] method the HTTP request method
# @param [URI] uri the requested URI, including host, scheme and
@@ -30,9 +21,14 @@ class Request
# @param [Headers] headers the HTTP request headers
# @param [String,#to_s,#each,nil] body the entity included in the
# request, if present
- def initialize(method, uri, headers, body)
+ def initialize(method, uri, headers, body, routing_tokens = nil, base_uri = nil)
@method, @headers, @body = method, headers, body
@uri = build_uri(uri, headers)
+ @routing_tokens = routing_tokens || @uri.path.match(ROUTING_PATH_MATCH)[1].split(SLASH)
+ @base_uri = base_uri || @uri.dup.tap do |u|
+ u.path = SLASH
+ u.query = nil
+ end
end
def_delegators :headers, :[]
@@ -41,9 +37,18 @@ def initialize(method, uri, headers, body)
# lowercased-underscored version of the header name, e.g.
# `if_unmodified_since`.
def method_missing(m, *args, &block)
- if m.to_s =~ /^(?:[a-z0-9])+(?:_[a-z0-9]+)*$/i
+ if HTTP_HEADERS_MATCH.match?(m)
# Access headers more easily as underscored methods.
- self[m.to_s.tr('_', '-')]
+ header_name = m.to_s.tr(UNDERSCORE, DASH)
+ if (header_value = @headers[header_name])
+ # Make future lookups faster.
+ self.class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def #{m}
+ @headers["#{header_name}"]
+ end
+ RUBY
+ end
+ header_value
else
super
end
@@ -54,16 +59,6 @@ def has_body?
!(body.nil? || body.empty?)
end
- # The root URI for the request, ignoring path and query. This is
- # useful for calculating relative paths to resources.
- # @return [URI]
- def base_uri
- @base_uri ||= uri.dup.tap do |u|
- u.path = "/"
- u.query = nil
- end
- end
-
# Returns a hash of query parameters (they come after the ? in the
# URI). Note that this does NOT work in the same way as Rails,
# i.e. it does not support nested arrays and hashes.
@@ -71,8 +66,8 @@ def base_uri
def query
unless @query
@query = {}
- (uri.query || '').split(/&/).each do |kv|
- key, value = kv.split(/=/)
+ (uri.query || '').split('&').each do |kv|
+ key, value = kv.split('=')
if key && value
key, value = CGI.unescape(key), CGI.unescape(value)
@query[key] = value
@@ -87,9 +82,7 @@ def query
# @return [Hash]
# {} if no Cookies header set
def cookies
- unless @cookies
- @cookies = Webmachine::Cookie.parse(headers['Cookie'])
- end
+ @cookies ||= Webmachine::Cookie.parse(headers['Cookie'])
@cookies
end
@@ -98,7 +91,7 @@ def cookies
# @return [Boolean]
# true if this request was made via HTTPS
def https?
- uri.scheme == "https"
+ uri.scheme == 'https'
end
# Is this a GET request?
@@ -167,20 +160,34 @@ def options?
private
- def build_uri(uri, headers)
- uri = URI(uri)
-
- host, _, port = headers.fetch("Host", "").rpartition(":")
- return uri if host.empty?
+ IPV6_MATCH = /\A\[(? .* )\]:(? \d+ )\z/x.freeze # string like "[::1]:80"
+ HOST_MATCH = /\A(? [^:]+ ):(? \d+ )\z/x.freeze # string like "www.example.com:80"
+
+ def parse_host(uri, host_string)
+ # Split host and port number from string.
+ case host_string
+ when IPV6_MATCH
+ uri.host = IPAddr.new($~[:address], Socket::AF_INET6).to_s
+ uri.port = $~[:port].to_i
+ when HOST_MATCH
+ uri.host = $~[:host]
+ uri.port = $~[:port].to_i
+ else # string with no port number
+ uri.host = host_string
+ end
- host = "[#{host}]" if host.include?(":")
- port = 80 if port.empty?
+ uri
+ end
- uri.scheme = "http"
- uri.host, uri.port = host, port.to_i
+ def build_uri(uri, headers)
+ uri = URI(uri)
+ uri.port ||= 80
+ uri.scheme ||= HTTP
+ if uri.host
+ return uri
+ end
- URI.parse(uri.to_s)
+ parse_host(uri, headers.fetch(HOST))
end
-
end # class Request
end # module Webmachine
diff --git a/lib/webmachine/rescueable_exception.rb b/lib/webmachine/rescueable_exception.rb
new file mode 100644
index 00000000..0e7d9907
--- /dev/null
+++ b/lib/webmachine/rescueable_exception.rb
@@ -0,0 +1,62 @@
+module Webmachine::RescuableException
+ require_relative 'errors'
+ require 'set'
+
+ UNRESCUABLE_DEFAULTS = [
+ Webmachine::MalformedRequest,
+ NoMemoryError, SystemExit, SignalException
+ ].freeze
+
+ UNRESCUABLE = Set.new UNRESCUABLE_DEFAULTS.dup
+ private_constant :UNRESCUABLE
+
+ def self.===(e)
+ case e
+ when *UNRESCUABLE then false
+ else true
+ end
+ end
+
+ #
+ # Remove modifications to Webmachine::RescuableException.
+ # Restores default list of unrescue-able exceptions.
+ #
+ # @return [nil]
+ #
+ def self.default!
+ UNRESCUABLE.replace Set.new(UNRESCUABLE_DEFAULTS.dup)
+ nil
+ end
+
+ #
+ # @return [Array]
+ # Returns an Array of exceptions that will not be
+ # rescued by {Webmachine::Resource#handle_exception}.
+ #
+ def self.UNRESCUABLEs
+ UNRESCUABLE.to_a
+ end
+
+ #
+ # Add a variable number of exceptions that should be rescued by
+ # {Webmachine::Resource#handle_exception}. See {UNRESCUABLE_DEFAULTS}
+ # for a list of exceptions that are not caught by default.
+ #
+ # @param (see #remove)
+ #
+ def self.add(*exceptions)
+ exceptions.each { |e| UNRESCUABLE.delete(e) }
+ end
+
+ #
+ # Remove a variable number of exceptions from being rescued by
+ # {Webmachine::Resource#handle_exception}. See {UNRESCUABLE_DEFAULTS}
+ # for a list of exceptions that are not caught by default.
+ #
+ # @param [Exception] *exceptions
+ # A subclass of Exception.
+ #
+ def self.remove(*exceptions)
+ exceptions.each { |e| UNRESCUABLE.add(e) }
+ end
+end
diff --git a/lib/webmachine/resource.rb b/lib/webmachine/resource.rb
index 1f4b3cb1..c8fdbd22 100644
--- a/lib/webmachine/resource.rb
+++ b/lib/webmachine/resource.rb
@@ -59,11 +59,11 @@ def self.run
end
private
+
# When no specific charsets are provided, this acts as an identity
# on the response body. Probably deserves some refactoring.
def charset_nop(x)
x
end
-
end # class Resource
end # module Webmachine
diff --git a/lib/webmachine/resource/authentication.rb b/lib/webmachine/resource/authentication.rb
index d3a3e976..6cce84e6 100644
--- a/lib/webmachine/resource/authentication.rb
+++ b/lib/webmachine/resource/authentication.rb
@@ -23,14 +23,13 @@ module Authentication
# @yieldparam [String] user the passed username
# @yieldparam [String] password the passed password
# @yieldreturn [true,false] whether the username/password is correct
- def basic_auth(header, realm="Webmachine")
- if header =~ BASIC_HEADER && (yield *$1.unpack('m*').first.split(/:/,2))
+ def basic_auth(header, realm = 'Webmachine')
+ if header =~ BASIC_HEADER && yield(*$1.unpack1('m*').split(':', 2))
true
else
- %Q[Basic realm="#{realm}"]
+ %(Basic realm="#{realm}")
end
end
-
end # module Authentication
end # class Resource
end # module Webmachine
diff --git a/lib/webmachine/resource/callbacks.rb b/lib/webmachine/resource/callbacks.rb
index ecc607cc..6780de42 100644
--- a/lib/webmachine/resource/callbacks.rb
+++ b/lib/webmachine/resource/callbacks.rb
@@ -1,3 +1,5 @@
+require 'webmachine/constants'
+
module Webmachine
class Resource
# These methods are the primary way your {Webmachine::Resource}
@@ -101,7 +103,7 @@ def valid_content_headers?(content_headers = nil)
# If the entity length on PUT or POST is invalid, this should
# return false, which will result in a '413 Request Entity Too
# Large' response. Defaults to true.
- # @param [Fixnum] length the size of the request body (entity)
+ # @param [Integer] length the size of the request body (entity)
# @return [true,false] Whether the body is a valid length (not too
# large)
# @api callback
@@ -123,7 +125,7 @@ def options
# @return [Array] allowed methods on this resource
# @api callback
def allowed_methods
- ['GET', 'HEAD']
+ [GET_METHOD, HEAD_METHOD]
end
# HTTP methods that are known to the resource. Like
@@ -134,7 +136,7 @@ def allowed_methods
# @return [Array] known methods
# @api callback
def known_methods
- ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT', 'OPTIONS']
+ STANDARD_HTTP_METHODS
end
# This method is called when a DELETE request should be enacted,
@@ -190,7 +192,7 @@ def base_uri
# If post_is_create? returns false, then this will be called to
# process any POST request. If it succeeds, it should return true.
- # @return [true,false,Fixnum] Whether the POST was successfully
+ # @return [true,false,Integer] Whether the POST was successfully
# processed, or an alternate response code
# @api callback
def process_post
@@ -209,7 +211,7 @@ def process_post
# @return an array of mediatype/handler pairs
# @api callback
def content_types_provided
- [['text/html', :to_html]]
+ [[TEXT_HTML, :to_html]]
end
# Similarly to content_types_provided, this should return an array
@@ -263,7 +265,7 @@ def language_chosen(lang)
# @api callback
# @see Encodings
def encodings_provided
- {"identity" => :encode_identity }
+ {IDENTITY => :encode_identity}
end
# If this method is implemented, it should return a list of
@@ -358,14 +360,15 @@ def generate_etag
# constructed and sent. The return value is ignored, so any effect
# of this method must be by modifying the response.
# @api callback
- def finish_request; end
+ def finish_request
+ end
#
- # This method is called when an exception is raised within a subclass of
+ # This method is called when an error is raised within a subclass of
# {Webmachine::Resource}.
#
- # @param [Exception] e
- # The exception.
+ # @param [StandardError] e
+ # The error.
#
# @return [void]
#
@@ -387,7 +390,6 @@ def handle_exception(e)
def validate_content_checksum
nil
end
-
end # module Callbacks
end # class Resource
end # module Webmachine
diff --git a/lib/webmachine/resource/encodings.rb b/lib/webmachine/resource/encodings.rb
index 40987f1e..3e6c0cad 100644
--- a/lib/webmachine/resource/encodings.rb
+++ b/lib/webmachine/resource/encodings.rb
@@ -15,23 +15,17 @@ def encode_identity(data)
# The 'deflate' encoding, which uses libz's DEFLATE compression.
def encode_deflate(data)
# The deflate options were borrowed from Rack and Mongrel1.
- Zlib::Deflate.deflate(data, *[Zlib::DEFAULT_COMPRESSION,
- # drop the zlib header which causes both Safari and IE to choke
- -Zlib::MAX_WBITS,
- Zlib::DEF_MEM_LEVEL,
- Zlib::DEFAULT_STRATEGY
- ])
+ Zlib::Deflate.deflate(data, Zlib::DEFAULT_COMPRESSION, -Zlib::MAX_WBITS, Zlib::DEF_MEM_LEVEL, Zlib::DEFAULT_STRATEGY)
end
# The 'gzip' encoding, which uses GNU Zip (via libz).
# @note Because of the header/checksum requirements, gzip cannot
# be used on streamed responses.
def encode_gzip(data)
- "".tap do |out|
- Zlib::GzipWriter.wrap(StringIO.new(out)){|gz| gz << data }
+ ''.tap do |out|
+ Zlib::GzipWriter.wrap(StringIO.new(out)) { |gz| gz << data }
end
end
-
end # module Encodings
end # class Resource
end # module Webmachine
diff --git a/lib/webmachine/response.rb b/lib/webmachine/response.rb
index 8c45a8b8..73fee48e 100644
--- a/lib/webmachine/response.rb
+++ b/lib/webmachine/response.rb
@@ -4,7 +4,7 @@ class Response
# @return [HeaderHash] Response headers that will be sent to the client
attr_reader :headers
- # @return [Fixnum] The HTTP status code of the response
+ # @return [Integer] The HTTP status code of the response
attr_accessor :code
# @return [String, #each] The response body
@@ -35,7 +35,7 @@ def initialize
# of the target resource, or manually set the Location header
# using {#headers}.
# @param [String, URI] location the target of the redirection
- def do_redirect(location=nil)
+ def do_redirect(location = nil)
headers['Location'] = location.to_s if location
self.redirect = true
end
@@ -48,16 +48,14 @@ def set_cookie(name, value, attributes = {})
cookie = Webmachine::Cookie.new(name, value, attributes).to_s
case headers['Set-Cookie']
when nil
- headers['Set-Cookie'] = cookie
- when String
- headers['Set-Cookie'] = [headers['Set-Cookie'], cookie]
+ headers['Set-Cookie'] = [cookie]
when Array
- headers['Set-Cookie'] = headers['Set-Cookie'] + cookie
+ headers['Set-Cookie'] << cookie
end
end
- alias :is_redirect? :redirect
- alias :redirect_to :do_redirect
+ alias_method :is_redirect?, :redirect
+ alias_method :redirect_to, :do_redirect
# A {Hash} that can flatten array values into single values with a separator
class HeaderHash < ::Hash
@@ -65,17 +63,15 @@ class HeaderHash < ::Hash
# @param [String] The separator used to join Array values
# @return [HeaderHash] A new {HeaderHash} with Array values flattened
def flattened(separator = ',')
- Hash[self.collect { |k,v|
+ collect { |k, v|
case v
when Array
- [k,v.join(separator)]
+ [k, v.join(separator)]
else
- [k,v]
+ [k, v]
end
- }]
-
+ }.to_h
end
end
-
end # class Response
end # module Webmachine
diff --git a/lib/webmachine/spec/adapter_lint.rb b/lib/webmachine/spec/adapter_lint.rb
index 74244db8..2cbed74f 100644
--- a/lib/webmachine/spec/adapter_lint.rb
+++ b/lib/webmachine/spec/adapter_lint.rb
@@ -1,159 +1,169 @@
-require "webmachine/spec/test_resource"
-require "net/http"
+require 'webmachine/spec/test_resource'
+require 'net/http'
+
+ADDRESS = '127.0.0.1'
shared_examples_for :adapter_lint do
- attr_accessor :client
+ attr_reader :client
+
+ class TestApplicationNotResponsive < Timeout::Error; end
- let(:address) { "127.0.0.1" }
- let(:port) { s = TCPServer.new(address, 0); p = s.addr[1]; s.close; p }
+ def find_free_port
+ temp_server = TCPServer.new(ADDRESS, 0)
+ port = temp_server.addr[1]
+ temp_server.close # only frees Ruby resource, socket is in TIME_WAIT at OS level
+ # so we can't have our adapter use it too quickly
+
+ sleep(0.1) # 'Wait' for temp_server to *really* close, not just TIME_WAIT
+ port
+ end
- let(:application) do
- application = Webmachine::Application.new
- application.dispatcher.add_route ["test"], Test::Resource
+ def create_test_application(port)
+ Webmachine::Application.new.tap do |application|
+ application.dispatcher.add_route ['test'], Test::Resource
- application.configure do |c|
- c.ip = address
- c.port = port
+ application.configure do |c|
+ c.ip = ADDRESS
+ c.port = port
+ end
end
+ end
- application
+ def run_application(adapter_class, application)
+ adapter = adapter_class.new(application)
+ Thread.abort_on_exception = true
+ Thread.new { adapter.run }
end
- let(:client) do
- client = Net::HTTP.new(application.configuration.ip, port)
- # Wait until the server is responsive
- timeout(5) do
- begin
- client.start
- rescue Errno::ECONNREFUSED
- sleep(0.01)
- retry
- end
+ def wait_until_server_responds_to(client)
+ Timeout.timeout(5, TestApplicationNotResponsive) do
+ client.start
+ rescue Errno::ECONNREFUSED
+ sleep(0.01)
+ retry
end
- client
end
- before do
- @adapter = described_class.new(application)
+ before(:all) do
+ @port = find_free_port
+ application = create_test_application(@port)
- Thread.abort_on_exception = true
- @server_thread = Thread.new { @adapter.run }
- sleep(0.01)
+ adapter_class = described_class
+ @server_thread = run_application(adapter_class, application)
+
+ @client = Net::HTTP.new(application.configuration.ip, @port)
+ wait_until_server_responds_to(client)
end
- after do
- client.finish
+ after(:all) do
+ @client.finish
@server_thread.kill
end
- it "provides the request URI" do
- request = Net::HTTP::Get.new("/test")
- request["Accept"] = "test/response.request_uri"
+ it 'provides the request URI' do
+ request = Net::HTTP::Get.new('/test')
+ request['Accept'] = 'test/response.request_uri'
response = client.request(request)
- expect(response.body).to eq("http://#{address}:#{port}/test")
+ expect(response.body).to eq("http://#{ADDRESS}:#{@port}/test")
end
- context do
- let(:address) { "::1" }
+ # context do
+ # let(:address) { "::1" }
- it "provides the IPv6 request URI" do
- if RUBY_VERSION =~ /^2\.(0|1)\./
- skip "Net::HTTP regression in Ruby 2.(0|1)"
- end
-
- request = Net::HTTP::Get.new("/test")
- request["Accept"] = "test/response.request_uri"
- response = client.request(request)
- expect(response.body).to eq("http://[#{address}]:#{port}/test")
- end
- end
+ # it "provides the IPv6 request URI" do
+ # request = Net::HTTP::Get.new("/test")
+ # request["Accept"] = "test/response.request_uri"
+ # response = client.request(request)
+ # expect(response.body).to eq("http://[#{address}]:#{port}/test")
+ # end
+ # end
- it "provides a string-like request body" do
- request = Net::HTTP::Put.new("/test")
- request.body = "Hello, World!"
- request["Content-Type"] = "test/request.stringbody"
+ it 'provides a string-like request body' do
+ request = Net::HTTP::Put.new('/test')
+ request.body = 'Hello, World!'
+ request['Content-Type'] = 'test/request.stringbody'
response = client.request(request)
- expect(response["Content-Length"]).to eq("21")
- expect(response.body).to eq("String: Hello, World!")
+ expect(response['Content-Length']).to eq('21')
+ expect(response.body).to eq('String: Hello, World!')
end
- it "provides an enumerable request body" do
- request = Net::HTTP::Put.new("/test")
- request.body = "Hello, World!"
- request["Content-Type"] = "test/request.enumbody"
+ it 'provides an enumerable request body' do
+ request = Net::HTTP::Put.new('/test')
+ request.body = 'Hello, World!'
+ request['Content-Type'] = 'test/request.enumbody'
response = client.request(request)
- expect(response["Content-Length"]).to eq("19")
- expect(response.body).to eq("Enum: Hello, World!")
+ expect(response['Content-Length']).to eq('19')
+ expect(response.body).to eq('Enum: Hello, World!')
end
- it "handles missing pages" do
- request = Net::HTTP::Get.new("/missing")
+ it 'handles missing pages' do
+ request = Net::HTTP::Get.new('/missing')
response = client.request(request)
- expect(response.code).to eq("404")
- expect(response["Content-Type"]).to eq("text/html")
+ expect(response.code).to eq('404')
+ expect(response['Content-Type']).to eq('text/html')
end
- it "handles empty response bodies" do
- request = Net::HTTP::Post.new("/test")
- request.body = ""
+ it 'handles empty response bodies' do
+ request = Net::HTTP::Post.new('/test')
+ request.body = ''
response = client.request(request)
- expect(response.code).to eq("204")
- expect(["0", nil]).to include(response["Content-Length"])
+ expect(response.code).to eq('204')
+ expect(['0', nil]).to include(response['Content-Length'])
expect(response.body).to be_nil
end
- it "handles string response bodies" do
- request = Net::HTTP::Get.new("/test")
- request["Accept"] = "test/response.stringbody"
+ it 'handles string response bodies' do
+ request = Net::HTTP::Get.new('/test')
+ request['Accept'] = 'test/response.stringbody'
response = client.request(request)
- expect(response["Content-Length"]).to eq("20")
- expect(response.body).to eq("String response body")
+ expect(response['Content-Length']).to eq('20')
+ expect(response.body).to eq('String response body')
end
- it "handles enumerable response bodies" do
- request = Net::HTTP::Get.new("/test")
- request["Accept"] = "test/response.enumbody"
+ it 'handles enumerable response bodies' do
+ request = Net::HTTP::Get.new('/test')
+ request['Accept'] = 'test/response.enumbody'
response = client.request(request)
- expect(response["Transfer-Encoding"]).to eq("chunked")
- expect(response.body).to eq("Enumerable response body")
+ expect(response['Transfer-Encoding']).to eq('chunked')
+ expect(response.body).to eq('Enumerable response body')
end
- it "handles proc response bodies" do
- request = Net::HTTP::Get.new("/test")
- request["Accept"] = "test/response.procbody"
+ it 'handles proc response bodies' do
+ request = Net::HTTP::Get.new('/test')
+ request['Accept'] = 'test/response.procbody'
response = client.request(request)
- expect(response["Transfer-Encoding"]).to eq("chunked")
- expect(response.body).to eq("Proc response body")
+ expect(response['Transfer-Encoding']).to eq('chunked')
+ expect(response.body).to eq('Proc response body')
end
- it "handles fiber response bodies" do
- request = Net::HTTP::Get.new("/test")
- request["Accept"] = "test/response.fiberbody"
+ it 'handles fiber response bodies' do
+ request = Net::HTTP::Get.new('/test')
+ request['Accept'] = 'test/response.fiberbody'
response = client.request(request)
- expect(response["Transfer-Encoding"]).to eq("chunked")
- expect(response.body).to eq("Fiber response body")
+ expect(response['Transfer-Encoding']).to eq('chunked')
+ expect(response.body).to eq('Fiber response body')
end
- it "handles io response bodies" do
- request = Net::HTTP::Get.new("/test")
- request["Accept"] = "test/response.iobody"
+ it 'handles io response bodies' do
+ request = Net::HTTP::Get.new('/test')
+ request['Accept'] = 'test/response.iobody'
response = client.request(request)
- expect(response["Content-Length"]).to eq("17")
+ expect(response['Content-Length']).to eq('17')
expect(response.body).to eq("IO response body\n")
end
- it "handles request cookies" do
- request = Net::HTTP::Get.new("/test")
- request["Accept"] = "test/response.cookies"
- request["Cookie"] = "echo=echocookie"
+ it 'handles request cookies' do
+ request = Net::HTTP::Get.new('/test')
+ request['Accept'] = 'test/response.cookies'
+ request['Cookie'] = 'echo=echocookie'
response = client.request(request)
- expect(response.body).to eq("echocookie")
+ expect(response.body).to eq('echocookie')
end
- it "handles response cookies" do
- request = Net::HTTP::Get.new("/test")
- request["Accept"] = "test/response.cookies"
+ it 'handles response cookies' do
+ request = Net::HTTP::Get.new('/test')
+ request['Accept'] = 'test/response.cookies'
response = client.request(request)
- expect(response["Set-Cookie"]).to eq("cookie=monster, rodeo=clown")
+ expect(response['Set-Cookie']).to eq('cookie=monster, rodeo=clown')
end
end
diff --git a/lib/webmachine/spec/test_resource.rb b/lib/webmachine/spec/test_resource.rb
index e8d2d00e..cc949216 100644
--- a/lib/webmachine/spec/test_resource.rb
+++ b/lib/webmachine/spec/test_resource.rb
@@ -1,34 +1,35 @@
module Test
class Resource < Webmachine::Resource
def allowed_methods
- ["GET", "PUT", "POST"]
+ ['GET', 'PUT', 'POST']
end
def content_types_accepted
[
- ["test/request.stringbody", :from_string],
- ["test/request.enumbody", :from_enum]
+ ['test/request.stringbody', :from_string],
+ ['test/request.enumbody', :from_enum]
]
end
def content_types_provided
[
- ["test/response.stringbody", :to_string],
- ["test/response.enumbody", :to_enum],
- ["test/response.procbody", :to_proc],
- ["test/response.fiberbody", :to_fiber],
- ["test/response.iobody", :to_io_body],
- ["test/response.cookies", :to_cookies],
- ["test/response.request_uri", :to_request_uri]
+ ['test/response.stringbody', :to_string],
+ ['test/response.enumbody', :to_enum],
+ ['test/response.procbody', :to_proc],
+ ['test/response.fiberbody', :to_fiber],
+ ['test/response.iobody', :to_io_body],
+ ['test/response.cookies', :to_cookies],
+ ['test/response.request_uri', :to_request_uri],
+ ['test/response.rack_env', :to_rack_env]
]
end
def from_string
- response.body = "String: #{request.body.to_s}"
+ response.body = "String: #{request.body}"
end
def from_enum
- response.body = "Enum: "
+ response.body = 'Enum: '
request.body.each do |part|
response.body += part
end
@@ -40,22 +41,22 @@ def process_post
end
def to_string
- "String response body"
+ 'String response body'
end
def to_enum
- ["Enumerable ", "response " "body"]
+ ['Enumerable ', 'response ', 'body']
end
def to_proc
- Proc.new { "Proc response body" }
+ proc { 'Proc response body' }
end
def to_fiber
Fiber.new do
- Fiber.yield "Fiber "
- Fiber.yield "response "
- "body"
+ Fiber.yield 'Fiber '
+ Fiber.yield 'response '
+ 'body'
end
end
@@ -64,16 +65,20 @@ def to_io_body
end
def to_cookies
- response.set_cookie("cookie", "monster")
- response.set_cookie("rodeo", "clown")
+ response.set_cookie('cookie', 'monster')
+ response.set_cookie('rodeo', 'clown')
# FIXME: Mongrel/WEBrick fail if this method returns nil
# Might be a net/http issue. Is this a bug?
# @see Flow#o18, Helpers#encode_body_if_set
- request.cookies["echo"] || ""
+ request.cookies['echo'] || ''
end
def to_request_uri
request.uri.to_s
end
+
+ def to_rack_env
+ request.env.to_json
+ end
end
end
diff --git a/lib/webmachine/streaming/encoder.rb b/lib/webmachine/streaming/encoder.rb
index 0ad9f9e3..90322ede 100644
--- a/lib/webmachine/streaming/encoder.rb
+++ b/lib/webmachine/streaming/encoder.rb
@@ -12,12 +12,13 @@ def initialize(resource, encoder, charsetter, body)
end
protected
+
# @return [true, false] whether the stream will be modified by
# the encoder and/or charsetter. Only returns true if using the
# built-in "encode_identity" and "charset_nop" methods.
def is_unencoded?
- encoder.to_s == "encode_identity" &&
- charsetter.to_s == "charset_nop"
+ encoder.to_s == 'encode_identity' &&
+ charsetter.to_s == 'charset_nop'
end
end # class Encoder
end # module Streaming
diff --git a/lib/webmachine/streaming/io_encoder.rb b/lib/webmachine/streaming/io_encoder.rb
index 55e069b9..3b41d7a9 100644
--- a/lib/webmachine/streaming/io_encoder.rb
+++ b/lib/webmachine/streaming/io_encoder.rb
@@ -7,13 +7,14 @@ module Streaming
# @api private
class IOEncoder < Encoder
include Enumerable
+
CHUNK_SIZE = 8192
# Iterates over the IO, encoding and yielding individual chunks
# of the response entity.
# @yield [chunk]
# @yieldparam [String] chunk a chunk of the response, encoded
def each
- while chunk = body.read(CHUNK_SIZE) and chunk != ""
+ while (chunk = body.read(CHUNK_SIZE)) && (chunk != '')
yield resource.send(encoder, resource.send(charsetter, chunk))
end
end
@@ -26,10 +27,10 @@ def copy_stream(outstream)
if can_copy_stream?
IO.copy_stream(body, outstream)
else
- each {|chunk| outstream << chunk }
+ each { |chunk| outstream << chunk }
end
end
-
+
# Allows the response body to be converted to a IO object.
# @return [IO,nil] the body as a IO object, or nil.
def to_io
@@ -39,7 +40,7 @@ def to_io
# Returns the length of the IO stream, if known. Returns nil if
# the stream uses an encoder or charsetter that might modify the
# length of the stream, or the stream size is unknown.
- # @return [Fixnum] the size, in bytes, of the underlying IO, or
+ # @return [Integer] the size, in bytes, of the underlying IO, or
# nil if unsupported
def size
if is_unencoded?
@@ -60,9 +61,10 @@ def empty?
size == 0
end
- alias bytesize size
+ alias_method :bytesize, :size
private
+
def can_copy_stream?
IO.respond_to?(:copy_stream) && is_unencoded? && !is_string_io?
end
diff --git a/lib/webmachine/trace.rb b/lib/webmachine/trace.rb
index f6aa142b..6481db9b 100644
--- a/lib/webmachine/trace.rb
+++ b/lib/webmachine/trace.rb
@@ -9,10 +9,11 @@ module Webmachine
# Contains means to enable the Webmachine visual debugger.
module Trace
module_function
+
# Classes that implement storage for visual debugger traces.
TRACE_STORES = {
- :memory => Hash,
- :pstore => PStoreTraceStore
+ memory: Hash,
+ pstore: PStoreTraceStore
}
DEFAULT_TRACE_SUBSCRIBER = Webmachine::Events.subscribe(
@@ -71,10 +72,10 @@ def trace_store=(*args)
def trace_store
@trace_store ||= begin
- opts = Array(@trace_store_opts).dup
- type = opts.shift
- TRACE_STORES[type].new(*opts)
- end
+ opts = Array(@trace_store_opts).dup
+ type = opts.shift
+ TRACE_STORES[type].new(*opts)
+ end
end
private :trace_store
diff --git a/lib/webmachine/trace/fsm.rb b/lib/webmachine/trace/fsm.rb
index 49b24ae1..d01b86ca 100644
--- a/lib/webmachine/trace/fsm.rb
+++ b/lib/webmachine/trace/fsm.rb
@@ -23,39 +23,46 @@ def trace?
# Adds the request to the trace.
# @param [Webmachine::Request] request the request to be traced
def trace_request(request)
- response.trace << {
- :type => :request,
- :method => request.method,
- :path => request.uri.request_uri.to_s,
- :headers => request.headers,
- :body => request.body.to_s
- } if trace?
+ if trace?
+ response.trace << {
+ type: :request,
+ method: request.method,
+ path: request.uri.request_uri.to_s,
+ headers: request.headers,
+ body: request.body.to_s
+ }
+ end
end
# Adds the response to the trace and then commits the trace to
# separate storage which can be discovered by the debugger.
# @param [Webmachine::Response] response the response to be traced
def trace_response(response)
- response.trace << {
- :type => :response,
- :code => response.code.to_s,
- :headers => response.headers,
- :body => trace_response_body(response.body)
- } if trace?
+ if trace?
+ response.trace << {
+ type: :response,
+ code: response.code.to_s,
+ headers: response.headers,
+ body: trace_response_body(response.body)
+ }
+ end
ensure
- Webmachine::Events.publish('wm.trace.record', {
- :trace_id => resource.object_id.to_s,
- :trace => response.trace
- }) if trace?
+ if trace?
+ Webmachine::Events.publish('wm.trace.record', {
+ trace_id: resource.object_id.to_s,
+ trace: response.trace
+ })
+ end
end
# Adds a decision to the trace.
# @param [Symbol] decision the decision being processed
def trace_decision(decision)
- response.trace << {:type => :decision, :decision => decision} if trace?
+ response.trace << {type: :decision, decision: decision} if trace?
end
private
+
# Works around streaming encoders where possible
def trace_response_body(body)
case body
diff --git a/lib/webmachine/trace/resource_proxy.rb b/lib/webmachine/trace/resource_proxy.rb
index 71cca9b1..bd6e7de4 100644
--- a/lib/webmachine/trace/resource_proxy.rb
+++ b/lib/webmachine/trace/resource_proxy.rb
@@ -14,7 +14,7 @@ class ResourceProxy
# including body-producing or accepting methods, encoders and
# charsetters.
CALLBACK_REFERRERS = [:content_types_accepted, :content_types_provided,
- :encodings_provided, :charsets_provided]
+ :encodings_provided, :charsets_provided]
# Creates a {ResourceProxy} that decorates the passed
# {Webmachine::Resource} such that callbacks invoked by the
@@ -46,6 +46,7 @@ def finish_request(*args)
end
private
+
# Proxy a given callback to the inner resource, decorating with traces
def proxy_callback(callback, *args)
# Log inputs and attempt
@@ -56,20 +57,20 @@ def proxy_callback(callback, *args)
resource.response.trace << result(_result)
_result
rescue => exc
- exc.backtrace.reject! {|s| s.include?(__FILE__) }
+ exc.backtrace.reject! { |s| s.include?(__FILE__) }
resource.response.trace << exception(exc)
raise
end
# Creates a log entry for the entry to a resource callback.
def attempt(callback, args)
- log = {:type => :attempt}
+ log = {type: :attempt}
method = resource.method(callback)
if method.owner == ::Webmachine::Resource::Callbacks
log[:name] = "(default)##{method.name}"
else
log[:name] = "#{method.owner.name}##{method.name}"
- log[:source] = method.source_location.join(":") if method.respond_to?(:source_location)
+ log[:source] = method.source_location.join(':') if method.respond_to?(:source_location)
end
unless args.empty?
log[:args] = args
@@ -79,15 +80,15 @@ def attempt(callback, args)
# Creates a log entry for the result of a resource callback
def result(result)
- {:type => :result, :value => result}
+ {type: :result, value: result}
end
# Creates a log entry for an exception that was raised from a callback
def exception(e)
- {:type => :exception,
- :class => e.class.name,
- :backtrace => e.backtrace,
- :message => e.message }
+ {type: :exception,
+ class: e.class.name,
+ backtrace: e.backtrace,
+ message: e.message}
end
# Adds proxy methods for callbacks that are dynamically referred to.
diff --git a/lib/webmachine/trace/static/http-headers-status-v3.png b/lib/webmachine/trace/static/http-headers-status-v3.png
index 23131606..18e754f1 100644
Binary files a/lib/webmachine/trace/static/http-headers-status-v3.png and b/lib/webmachine/trace/static/http-headers-status-v3.png differ
diff --git a/lib/webmachine/trace/trace_resource.rb b/lib/webmachine/trace/trace_resource.rb
index e8d60d6b..be9855c2 100644
--- a/lib/webmachine/trace/trace_resource.rb
+++ b/lib/webmachine/trace/trace_resource.rb
@@ -7,15 +7,14 @@ module Trace
# includes serving the static files (the PNG flow diagram, CSS and
# JS for the UI) and the HTML for the individual traces.
class TraceResource < Resource
-
- MAP_EXTERNAL = %w{static map.png}
- MAP_FILE = File.expand_path("../static/http-headers-status-v3.png", __FILE__)
- SCRIPT_EXTERNAL = %w{static wmtrace.js}
- SCRIPT_FILE = File.expand_path("../#{SCRIPT_EXTERNAL.join '/'}", __FILE__)
- STYLE_EXTERNAL = %w{static wmtrace.css}
- STYLE_FILE = File.expand_path("../#{STYLE_EXTERNAL.join '/'}", __FILE__)
- TRACELIST_ERB = File.expand_path("../static/tracelist.erb", __FILE__)
- TRACE_ERB = File.expand_path("../static/trace.erb", __FILE__)
+ MAP_EXTERNAL = %w[static map.png]
+ MAP_FILE = File.expand_path('../static/http-headers-status-v3.png', __FILE__)
+ SCRIPT_EXTERNAL = %w[static wmtrace.js]
+ SCRIPT_FILE = File.expand_path("../#{SCRIPT_EXTERNAL.join "/"}", __FILE__)
+ STYLE_EXTERNAL = %w[static wmtrace.css]
+ STYLE_FILE = File.expand_path("../#{STYLE_EXTERNAL.join "/"}", __FILE__)
+ TRACELIST_ERB = File.expand_path('../static/tracelist.erb', __FILE__)
+ TRACE_ERB = File.expand_path('../static/trace.erb', __FILE__)
# The ERB template for the trace list
def self.tracelist
@@ -30,15 +29,15 @@ def self.trace
def content_types_provided
case request.path_tokens
when []
- [["text/html", :produce_list]]
+ [['text/html', :produce_list]]
when MAP_EXTERNAL
- [["image/png", :produce_file]]
+ [['image/png', :produce_file]]
when SCRIPT_EXTERNAL
- [["text/javascript", :produce_file]]
+ [['text/javascript', :produce_file]]
when STYLE_EXTERNAL
- [["text/css", :produce_file]]
+ [['text/css', :produce_file]]
else
- [["text/html", :produce_trace]]
+ [['text/html', :produce_trace]]
end
end
@@ -73,12 +72,12 @@ def produce_file
# TODO: Add support for IO objects as response bodies,
# allowing server optimizations like sendfile or chunked
# downloads
- open(@file, "rb") {|io| io.read }
+ File.binread(@file)
end
def produce_list
- base = request.uri.path.chomp("/")
- traces = Trace.traces.map {|t| [ t, "#{base}/#{t}" ] }
+ base = request.uri.path.chomp('/')
+ traces = Trace.traces.map { |t| [t, "#{base}/#{t}"] }
self.class.tracelist.result(binding)
end
@@ -96,22 +95,22 @@ def encode_trace(data)
tres = data.pop.dup
treq.delete :type
tres.delete :type
- [ MultiJson.dump(treq), MultiJson.dump(tres), MultiJson.dump(encode_decisions(data)) ]
+ [MultiJson.dump(treq), MultiJson.dump(tres), MultiJson.dump(encode_decisions(data))]
end
def encode_decisions(decisions)
- decisions.inject([]) do |list, event|
+ decisions.each_with_object([]) do |event, list|
case event[:type]
when :decision
# Don't produce new decisions for sub-steps in the graph
- unless event[:decision].to_s =~ /[a-z]$/
+ unless /[a-z]$/.match?(event[:decision].to_s)
list << {'d' => event[:decision], 'calls' => []}
end
when :attempt
list.last['calls'] << {
- "call" => event[:name],
- "source" => event[:source],
- "input" => event[:args] && event[:args].inspect
+ 'call' => event[:name],
+ 'source' => event[:source],
+ 'input' => event[:args] && event[:args].inspect
}
when :result
list.last['calls'].last['output'] = event[:value].inspect
@@ -122,7 +121,6 @@ def encode_decisions(decisions)
'message' => event[:message]
}
end
- list
end
end
end
diff --git a/lib/webmachine/translation.rb b/lib/webmachine/translation.rb
index 16210db7..f55ed682 100644
--- a/lib/webmachine/translation.rb
+++ b/lib/webmachine/translation.rb
@@ -1,6 +1,7 @@
+require 'set'
require 'i18n'
I18n.enforce_available_locales = true if I18n.respond_to?(:enforce_available_locales)
-I18n.config.load_path << File.expand_path("../locale/en.yml", __FILE__)
+I18n.config.load_path << File.expand_path('../locale/en.yml', __FILE__)
module Webmachine
# Provides an interface to the I18n library specifically for
@@ -11,8 +12,8 @@ module Translation
# @param [Hash] options options to pass to I18n, including
# variables to interpolate.
# @return [String] the interpolated string
- def t(key, options={})
- ::I18n.t(key, options.merge(:scope => :webmachine))
+ def t(key, options = {})
+ ::I18n.t(key, **options.merge(scope: :webmachine))
end
end
end
diff --git a/lib/webmachine/version.rb b/lib/webmachine/version.rb
index d1f0cce3..f00d2830 100644
--- a/lib/webmachine/version.rb
+++ b/lib/webmachine/version.rb
@@ -1,8 +1,8 @@
-module Webmachine
+module Webmachine
# Library version
- VERSION = "1.2.2"
+ VERSION = '2.0.1'.freeze
# String for use in "Server" HTTP response header, which includes
# the {VERSION}.
- SERVER_STRING = "Webmachine-Ruby/#{VERSION}"
+ SERVER_STRING = "Webmachine-Ruby/#{VERSION}".freeze
end
diff --git a/memory_test.rb b/memory_test.rb
new file mode 100644
index 00000000..29abc5ff
--- /dev/null
+++ b/memory_test.rb
@@ -0,0 +1,36 @@
+$:.push File.expand_path('../lib', __FILE__)
+require 'webmachine'
+
+class Constantized < Webmachine::Resource
+ HELLO_WORLD = 'Hello World'.freeze
+ ALLOWED_METHODS = ['GET'.freeze].freeze
+ CONTENT_TYPES_PROVIDED = [['text/html'.freeze, :to_html].freeze].freeze
+
+ def allowed_methods
+ ALLOWED_METHODS
+ end
+
+ def content_types_provided
+ CONTENT_TYPES_PROVIDED
+ end
+
+ def to_html
+ HELLO_WORLD
+ end
+end
+
+Webmachine.application.routes do
+ add ['constantized'], Constantized
+end
+
+require 'webmachine/test'
+session = Webmachine::Test::Session.new(Webmachine.application)
+CONSTANTIZED = '/constantized'.freeze
+require 'memory_profiler'
+report = MemoryProfiler.report do
+ 100.times do
+ session.get(CONSTANTIZED)
+ end
+end
+
+report.pretty_print
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index abc5000e..a63215df 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,15 +1,15 @@
-require "bundler/setup"
-Bundler.require :default, :test, :webservers
+require 'bundler/setup'
+Bundler.require :default, :test
require 'logger'
class NullLogger < Logger
- def add(severity, message=nil, progname=nil, &block)
+ def add(severity, message = nil, progname = nil, &block)
end
end
RSpec.configure do |config|
config.mock_with :rspec
- config.filter_run :focus => true
+ config.filter_run focus: true
config.run_all_when_everything_filtered = true
config.formatter = :documentation if ENV['CI']
if defined?(::Java)
@@ -18,10 +18,18 @@ def add(severity, message=nil, progname=nil, &block)
config.order = :random
end
+ config.before :each do
+ Webmachine::RescuableException.remove(RSpec::Mocks::MockExpectationError)
+ end
+
+ config.after :each do
+ Webmachine::RescuableException.default!
+ end
+
config.before(:suite) do
options = {
- :Logger => NullLogger.new(STDERR),
- :AccessLog => []
+ Logger: NullLogger.new($stderr),
+ AccessLog: []
}
Webmachine::Adapters::WEBrick::DEFAULT_OPTIONS.merge! options
Webmachine::Adapters::Rack::DEFAULT_OPTIONS.merge! options if defined?(Webmachine::Adapters::Rack)
@@ -29,18 +37,18 @@ def add(severity, message=nil, progname=nil, &block)
end
# For use in specs that need a fully initialized resource
-shared_context "default resource" do
+shared_context 'default resource' do
let(:method) { 'GET' }
- let(:uri) { URI.parse("http://localhost/") }
+ let(:uri) { URI.parse('http://localhost/') }
let(:headers) { Webmachine::Headers.new }
- let(:body) { "" }
+ let(:body) { '' }
let(:request) { Webmachine::Request.new(method, uri, headers, body) }
let(:response) { Webmachine::Response.new }
let(:resource_class) do
Class.new(Webmachine::Resource) do
def to_html
- "Hello, world!"
+ 'Hello, world!'
end
end
end
diff --git a/spec/webmachine/adapter_spec.rb b/spec/webmachine/adapter_spec.rb
index 28b5d7c3..e949919b 100644
--- a/spec/webmachine/adapter_spec.rb
+++ b/spec/webmachine/adapter_spec.rb
@@ -1,4 +1,4 @@
-require "spec_helper"
+require 'spec_helper'
describe Webmachine::Adapter do
let(:application) { Webmachine::Application.new }
@@ -10,19 +10,19 @@
described_class.new(application)
end
- describe "#initialize" do
- it "stores the provided application" do
+ describe '#initialize' do
+ it 'stores the provided application' do
expect(adapter.application).to eq(application)
end
end
- describe ".run" do
- it "creates a new adapter and runs it" do
+ describe '.run' do
+ it 'creates a new adapter and runs it' do
adapter = double(described_class)
- expect(described_class).to receive(:new).
- with(application).
- and_return(adapter)
+ expect(described_class).to receive(:new)
+ .with(application)
+ .and_return(adapter)
expect(adapter).to receive(:run)
@@ -30,10 +30,9 @@
end
end
- describe "#run" do
- it "raises a NotImplementedError" do
+ describe '#run' do
+ it 'raises a NotImplementedError' do
expect { adapter.run }.to raise_exception(NotImplementedError)
end
end
-
end
diff --git a/spec/webmachine/adapters/httpkit_spec.rb b/spec/webmachine/adapters/httpkit_spec.rb
deleted file mode 100644
index f2ee7dde..00000000
--- a/spec/webmachine/adapters/httpkit_spec.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require "spec_helper"
-require "webmachine/spec/adapter_lint"
-
-begin
- describe Webmachine::Adapters::HTTPkit do
- it_should_behave_like :adapter_lint
- end
-rescue LoadError
- warn "Platform is #{RUBY_PLATFORM}: skipping httpkit adapter spec."
-end
diff --git a/spec/webmachine/adapters/rack_mapped_spec.rb b/spec/webmachine/adapters/rack_mapped_spec.rb
new file mode 100644
index 00000000..ca944639
--- /dev/null
+++ b/spec/webmachine/adapters/rack_mapped_spec.rb
@@ -0,0 +1,71 @@
+require 'webmachine/adapter'
+require 'webmachine/adapters/rack_mapped'
+require 'spec_helper'
+require 'webmachine/spec/adapter_lint'
+require 'rack/test'
+
+describe Webmachine::Adapters::RackMapped do
+ it_should_behave_like :adapter_lint do
+ it 'should set Server header' do
+ response = client.request(Net::HTTP::Get.new('/test'))
+ expect(response['Server']).to match(/Webmachine/)
+ expect(response['Server']).to match(/Rack/)
+ end
+ end
+end
+
+describe Webmachine::Adapters::RackMapped do
+ class CreateResource < Webmachine::Resource
+ def allowed_methods
+ ['POST']
+ end
+
+ def content_types_accepted
+ [['application/json', :from_json]]
+ end
+
+ def content_types_provided
+ [['application/json', :to_json]]
+ end
+
+ def post_is_create?
+ true
+ end
+
+ def create_path
+ 'created_path_here/123'
+ end
+
+ def from_json
+ response.body = %( {"foo": "bar"} )
+ end
+ end
+
+ let(:app) do
+ Rack::Builder.new do
+ map '/some/route' do
+ run(Webmachine::Application.new do |app|
+ app.add_route(['test'], Test::Resource)
+ app.add_route(['create_test'], CreateResource)
+ app.configure do |config|
+ config.adapter = :RackMapped
+ end
+ end.adapter)
+ end
+ end
+ end
+
+ context 'using Rack::Test' do
+ include Rack::Test::Methods
+
+ it 'provides the full request URI' do
+ rack_response = get 'some/route/test', nil, {'HTTP_ACCEPT' => 'test/response.request_uri'}
+ expect(rack_response.body).to eq 'http://example.org/some/route/test'
+ end
+
+ it 'provides LOCATION header using custom base_uri when creating from POST request' do
+ rack_response = post '/some/route/create_test', %({"foo": "bar"}), {'HTTP_ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json'}
+ expect(rack_response.headers['Location']).to eq('http://example.org/some/route/created_path_here/123')
+ end
+ end
+end
diff --git a/spec/webmachine/adapters/rack_spec.rb b/spec/webmachine/adapters/rack_spec.rb
index 0725b12a..e76f3ca0 100644
--- a/spec/webmachine/adapters/rack_spec.rb
+++ b/spec/webmachine/adapters/rack_spec.rb
@@ -2,35 +2,62 @@
require 'webmachine/adapters/rack'
require 'spec_helper'
require 'webmachine/spec/adapter_lint'
+require 'rack/test'
+require 'json'
describe Webmachine::Adapters::Rack do
it_should_behave_like :adapter_lint do
- it "should set Server header" do
- response = client.request(Net::HTTP::Get.new("/test"))
- expect(response["Server"]).to match(/Webmachine/)
- expect(response["Server"]).to match(/Rack/)
+ it 'should set Server header' do
+ response = client.request(Net::HTTP::Get.new('/test'))
+ expect(response['Server']).to match(/Webmachine/)
+ expect(response['Server']).to match(/Rack/)
end
end
end
describe Webmachine::Adapters::Rack::RackResponse do
- context "on Rack < 1.5 release" do
- before { allow(Rack).to receive_messages(:release => "1.4") }
+ context 'on Rack < 1.5 release' do
+ before { allow(Rack).to receive_messages(release: '1.4') }
- it "should add Content-Type header on not acceptable response" do
+ it 'should add Content-Type header on not acceptable response' do
rack_response = described_class.new(double(:body), 406, {})
- rack_status, rack_headers, rack_body = rack_response.finish
- expect(rack_headers).to have_key("Content-Type")
+ _rack_status, rack_headers, _rack_body = rack_response.finish
+ expect(rack_headers).to have_key('Content-Type')
end
end
- context "on Rack >= 1.5 release" do
- before { allow(Rack).to receive_messages(:release => "1.5") }
+ context 'on Rack >= 1.5 release' do
+ before { allow(Rack).to receive_messages(release: '1.5') }
- it "should not add Content-Type header on not acceptable response" do
+ it 'should not add Content-Type header on not acceptable response' do
rack_response = described_class.new(double(:body), 406, {})
- rack_status, rack_headers, rack_body = rack_response.finish
- expect(rack_headers).not_to have_key("Content-Type")
+ _rack_status, rack_headers, _rack_body = rack_response.finish
+ expect(rack_headers).not_to have_key('Content-Type')
+ end
+ end
+end
+
+describe Webmachine::Adapters::Rack do
+ let(:app) do
+ Webmachine::Application.new do |app|
+ app.add_route(['test'], Test::Resource)
+ app.configure do |config|
+ config.adapter = :Rack
+ end
+ end.adapter
+ end
+
+ context 'using Rack::Test' do
+ include Rack::Test::Methods
+
+ it 'provides the full request URI' do
+ rack_response = get 'test', nil, {'HTTP_ACCEPT' => 'test/response.request_uri'}
+ expect(rack_response.body).to eq 'http://example.org/test'
+ end
+
+ it 'provides the rack env on the request' do
+ rack_response = get 'test', nil, {'HTTP_ACCEPT' => 'test/response.rack_env'}
+ expect(JSON.parse(rack_response.body).keys).to include 'rack.input'
end
end
end
diff --git a/spec/webmachine/adapters/reel_spec.rb b/spec/webmachine/adapters/reel_spec.rb
deleted file mode 100644
index 9d954b44..00000000
--- a/spec/webmachine/adapters/reel_spec.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-require 'spec_helper'
-require 'webmachine/spec/adapter_lint'
-describe Webmachine::Adapters::Reel do
- context 'lint' do
- it_should_behave_like :adapter_lint
- end
-
- context 'websockets' do
- let(:application) { Webmachine::Application.new }
- let(:adapter) do
- server = TCPServer.new('0.0.0.0', 0)
- application.configuration.port = server.addr[1]
- server.close
- described_class.new(application)
- end
-
- let(:example_host) { "www.example.com" }
- let(:example_path) { "/example"}
- let(:example_url) { "ws://#{example_host}#{example_path}" }
- let :handshake_headers do
- {
- "Host" => example_host,
- "Upgrade" => "websocket",
- "Connection" => "Upgrade",
- "Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==",
- "Origin" => "http://example.com",
- "Sec-WebSocket-Protocol" => "chat, superchat",
- "Sec-WebSocket-Version" => "13"
- }
- end
- let(:client_message) { "Hi server!" }
- let(:server_message) { "Hi client!" }
-
- it 'supports websockets' do
- application.configuration.adapter_options[:websocket_handler] = proc do |socket|
- expect(socket.read).to eq client_message
- socket << server_message
- end
-
- reel_server(adapter) do |client|
- client << WebSocket::ClientHandshake.new(:get, example_url, handshake_headers).to_data
-
- # Discard handshake response
- # FIXME: hax
- client.readpartial(4096)
-
- client << WebSocket::Message.new(client_message).to_data
- parser = WebSocket::Parser.new
- parser.append client.readpartial(4096) until message = parser.next_message
-
- expect(message).to eq server_message
- end
- end
- end
-
- def reel_server(adptr = adapter)
- thread = Thread.new { adptr.run }
- begin
- timeout(5) do
- begin
- sock = TCPSocket.new(adptr.application.configuration.ip, adptr.application.configuration.port)
- begin
- yield(sock)
- ensure
- sock.close
- end
- rescue Errno::ECONNREFUSED
- Thread.pass
- retry
- end
- end
- ensure
- thread.kill if thread
- end
- end
-end
diff --git a/spec/webmachine/adapters/webrick_spec.rb b/spec/webmachine/adapters/webrick_spec.rb
index 2beb0c32..dfdc8489 100644
--- a/spec/webmachine/adapters/webrick_spec.rb
+++ b/spec/webmachine/adapters/webrick_spec.rb
@@ -1,12 +1,12 @@
-require "spec_helper"
-require "webmachine/spec/adapter_lint"
+require 'spec_helper'
+require 'webmachine/spec/adapter_lint'
describe Webmachine::Adapters::WEBrick do
it_should_behave_like :adapter_lint do
- it "should set Server header" do
- response = client.request(Net::HTTP::Get.new("/test"))
- expect(response["Server"]).to match(/Webmachine/)
- expect(response["Server"]).to match(/WEBrick/)
+ it 'should set Server header' do
+ response = client.request(Net::HTTP::Get.new('/test'))
+ expect(response['Server']).to match(/Webmachine/)
+ expect(response['Server']).to match(/WEBrick/)
end
end
end
diff --git a/spec/webmachine/application_spec.rb b/spec/webmachine/application_spec.rb
index e7894bb8..2469b2bb 100644
--- a/spec/webmachine/application_spec.rb
+++ b/spec/webmachine/application_spec.rb
@@ -4,12 +4,12 @@
let(:application) { described_class.new }
let(:test_resource) { Class.new(Webmachine::Resource) }
- it "accepts a Configuration when initialized" do
+ it 'accepts a Configuration when initialized' do
config = Webmachine::Configuration.new('1.1.1.1', 9999, :Reel, {})
expect(described_class.new(config).configuration).to be(config)
end
- it "is yielded into a block provided during initialization" do
+ it 'is yielded into a block provided during initialization' do
yielded_app = nil
returned_app = described_class.new do |app|
expect(app).to be_kind_of(Webmachine::Application)
@@ -18,25 +18,25 @@
expect(returned_app).to be(yielded_app)
end
- it "is initialized with the default Configration if none is given" do
+ it 'is initialized with the default Configration if none is given' do
expect(application.configuration).to eq(Webmachine::Configuration.default)
end
- it "returns the receiver from the configure call so you can chain it" do
+ it 'returns the receiver from the configure call so you can chain it' do
expect(application.configure { |c| }).to equal(application)
end
- it "is configurable" do
+ it 'is configurable' do
application.configure do |config|
expect(config).to be_kind_of(Webmachine::Configuration)
end
end
- it "is initialized with an empty Dispatcher" do
+ it 'is initialized with an empty Dispatcher' do
expect(application.dispatcher.routes).to be_empty
end
- it "can have routes added" do
+ it 'can have routes added' do
route = nil
resource = test_resource # overcome instance_eval :/
@@ -50,24 +50,24 @@
expect(application.routes).to eq([route])
end
- describe "#adapter" do
+ describe '#adapter' do
let(:adapter_class) { application.adapter_class }
it "returns an instance of it's adapter class" do
expect(application.adapter).to be_an_instance_of(adapter_class)
end
- it "is memoized" do
+ it 'is memoized' do
expect(application.adapter).to eql application.adapter
end
end
- it "can be run" do
+ it 'can be run' do
expect(application.adapter).to receive(:run)
application.run
end
- it "can be queried about its configured adapter" do
+ it 'can be queried about its configured adapter' do
expected = Webmachine::Adapters.const_get(application.configuration.adapter)
expect(application.adapter_class).to equal(expected)
end
diff --git a/spec/webmachine/chunked_body_spec.rb b/spec/webmachine/chunked_body_spec.rb
index 96bbf163..0a0620f7 100644
--- a/spec/webmachine/chunked_body_spec.rb
+++ b/spec/webmachine/chunked_body_spec.rb
@@ -2,7 +2,7 @@
require 'webmachine/chunked_body'
describe Webmachine::ChunkedBody do
- it "builds a proper body" do
+ it 'builds a proper body' do
body = ''
Webmachine::ChunkedBody.new(['foo', 'bar', '', 'j', 'webmachine']).each do |chunk|
body << chunk
@@ -10,8 +10,8 @@
expect(body).to eq("3\r\nfoo\r\n3\r\nbar\r\n1\r\nj\r\na\r\nwebmachine\r\n0\r\n\r\n")
end
- context "with an empty body" do
- it "builds a proper body" do
+ context 'with an empty body' do
+ it 'builds a proper body' do
body = ''
Webmachine::ChunkedBody.new([]).each do |chunk|
body << chunk
@@ -20,9 +20,9 @@
end
end
- describe "#each" do
- context "without a block given" do
- it "returns an Enumerator" do
+ describe '#each' do
+ context 'without a block given' do
+ it 'returns an Enumerator' do
expect(Webmachine::ChunkedBody.new([]).each).to respond_to(:next)
end
end
diff --git a/spec/webmachine/configuration_spec.rb b/spec/webmachine/configuration_spec.rb
index b97e65c6..4148e2ea 100644
--- a/spec/webmachine/configuration_spec.rb
+++ b/spec/webmachine/configuration_spec.rb
@@ -3,25 +3,25 @@
describe Webmachine::Configuration do
before { Webmachine.configuration = nil }
- %w{ip port adapter adapter_options}.each do |field|
+ %w[ip port adapter adapter_options].each do |field|
it { is_expected.to respond_to(field) }
it { is_expected.to respond_to("#{field}=") }
end
- it "should yield configuration to the block" do
+ it 'should yield configuration to the block' do
Webmachine.configure do |config|
expect(config).to be_kind_of(described_class)
end
end
- it "should set the global configuration from the yielded instance" do
+ it 'should set the global configuration from the yielded instance' do
Webmachine.configure do |config|
@config = config
end
expect(@config).to eq Webmachine.configuration
end
- it "should return the module from the configure call so you can chain it" do
- expect(Webmachine.configure {|c|}).to eq Webmachine
+ it 'should return the module from the configure call so you can chain it' do
+ expect(Webmachine.configure { |c| }).to eq Webmachine
end
end
diff --git a/spec/webmachine/cookie_spec.rb b/spec/webmachine/cookie_spec.rb
index ca2d66fc..de815c1f 100644
--- a/spec/webmachine/cookie_spec.rb
+++ b/spec/webmachine/cookie_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
describe Webmachine::Cookie do
- describe "creating a cookie" do
- let(:name) { "monster" }
- let(:value) { "mash" }
+ describe 'creating a cookie' do
+ let(:name) { 'monster' }
+ let(:value) { 'mash' }
let(:attributes) { {} }
let(:cookie) { Webmachine::Cookie.new(name, value, attributes) }
@@ -13,32 +13,32 @@
its(:name) { should == name }
its(:value) { should == value }
- its(:to_s) { should == "monster=mash" }
+ its(:to_s) { should == 'monster=mash' }
- describe "a cookie with whitespace in name and value" do
- let(:name) { "cookie name" }
- let(:value) { "cookie value" }
+ describe 'a cookie with whitespace in name and value' do
+ let(:name) { 'cookie name' }
+ let(:value) { 'cookie value' }
- its(:to_s) { should == "cookie+name=cookie+value" }
+ its(:to_s) { should == 'cookie+name=cookie+value' }
end
- describe "a cookie with attributes set" do
- let(:domain) { "www.server.com" }
- let(:path) { "/" }
- let(:comment) { "comment with spaces" }
+ describe 'a cookie with attributes set' do
+ let(:domain) { 'www.server.com' }
+ let(:path) { '/' }
+ let(:comment) { 'comment with spaces' }
let(:version) { 1 }
let(:maxage) { 60 }
- let(:expires) { Time.gm(2010,3,14, 3, 14, 0) }
+ let(:expires) { Time.gm(2010, 3, 14, 3, 14, 0) }
let(:attributes) {
{
- :comment => comment,
- :domain => domain,
- :path => path,
- :secure => true,
- :httponly => true,
- :version => version,
- :maxage => maxage,
- :expires => expires
+ comment: comment,
+ domain: domain,
+ path: path,
+ secure: true,
+ httponly: true,
+ version: version,
+ maxage: maxage,
+ expires: expires
}
}
@@ -51,49 +51,49 @@
its(:maxage) { should == maxage }
its(:expires) { should == expires }
- it "should include the attributes in its string version" do
+ it 'should include the attributes in its string version' do
str = subject.to_s
- expect(str).to include "Secure"
- expect(str).to include "HttpOnly"
- expect(str).to include "Comment=comment+with+spaces"
- expect(str).to include "Domain=www.server.com"
- expect(str).to include "Path=/"
- expect(str).to include "Version=1"
- expect(str).to include "Max-Age=60"
- expect(str).to include "Expires=Sun, 14-Mar-2010 03:14:00 GMT"
+ expect(str).to include 'Secure'
+ expect(str).to include 'HttpOnly'
+ expect(str).to include 'Comment=comment+with+spaces'
+ expect(str).to include 'Domain=www.server.com'
+ expect(str).to include 'Path=/'
+ expect(str).to include 'Version=1'
+ expect(str).to include 'Max-Age=60'
+ expect(str).to include 'Expires=Sun, 14 Mar 2010 03:14:00 GMT'
end
end
end
- describe "parsing a cookie parameter" do
- let(:str) { "cookie = monster" }
+ describe 'parsing a cookie parameter' do
+ let(:str) { 'cookie = monster' }
subject { Webmachine::Cookie.parse(str) }
- it("should have the cookie") { expect(subject).to eq({ "cookie" => "monster" }) }
+ it('should have the cookie') { expect(subject).to eq({'cookie' => 'monster'}) }
- describe "parsing multiple cookie parameters" do
- let(:str) { "cookie=monster; monster=mash" }
+ describe 'parsing multiple cookie parameters' do
+ let(:str) { 'cookie=monster; monster=mash' }
- it("should have both cookies") { expect(subject).to eq({ "cookie" => "monster", "monster" => "mash" }) }
+ it('should have both cookies') { expect(subject).to eq({'cookie' => 'monster', 'monster' => 'mash'}) }
end
- describe "parsing an encoded cookie" do
- let(:str) { "cookie=yum+yum" }
+ describe 'parsing an encoded cookie' do
+ let(:str) { 'cookie=yum+yum' }
- it("should decode the cookie") { expect(subject).to eq({ "cookie" => "yum yum" }) }
+ it('should decode the cookie') { expect(subject).to eq({'cookie' => 'yum yum'}) }
end
- describe "parsing nil" do
+ describe 'parsing nil' do
let(:str) { nil }
- it("should return empty hash") { expect(subject).to eq({}) }
+ it('should return empty hash') { expect(subject).to eq({}) }
end
- describe "parsing duplicate cookies" do
- let(:str) { "cookie=monster; cookie=yum+yum" }
+ describe 'parsing duplicate cookies' do
+ let(:str) { 'cookie=monster; cookie=yum+yum' }
- it("should return the first instance of the cookie") { expect(subject).to eq({ "cookie" => "monster" }) }
+ it('should return the first instance of the cookie') { expect(subject).to eq({'cookie' => 'monster'}) }
end
end
end
diff --git a/spec/webmachine/decision/conneg_spec.rb b/spec/webmachine/decision/conneg_spec.rb
index 07428b0c..58b9f846 100644
--- a/spec/webmachine/decision/conneg_spec.rb
+++ b/spec/webmachine/decision/conneg_spec.rb
@@ -1,163 +1,160 @@
require 'spec_helper'
describe Webmachine::Decision::Conneg do
- include_context "default resource"
+ include_context 'default resource'
subject do
Webmachine::Decision::FSM.new(resource, request, response)
end
- context "choosing a media type" do
- it "should not choose a type when none are provided" do
- expect(subject.choose_media_type([], "*/*")).to be_nil
+ context 'choosing a media type' do
+ it 'should not choose a type when none are provided' do
+ expect(subject.choose_media_type([], '*/*')).to be_nil
end
- it "should not choose a type when none are acceptable" do
- expect(subject.choose_media_type(["text/html"], "application/json")).to be_nil
+ it 'should not choose a type when none are acceptable' do
+ expect(subject.choose_media_type(['text/html'], 'application/json')).to be_nil
end
- it "should choose the first acceptable type" do
- expect(subject.choose_media_type(["text/html", "application/xml"],
- "application/xml, text/html, */*")).to eq("application/xml")
+ it 'should choose the first acceptable type' do
+ expect(subject.choose_media_type(['text/html', 'application/xml'],
+ 'application/xml, text/html, */*')).to eq('application/xml')
end
- it "should choose the type that matches closest when matching subparams" do
- expect(subject.choose_media_type(["text/html",
- ["text/html", {"charset" => "iso8859-1"}]],
- "text/html;charset=iso8859-1, application/xml")).
- to eq("text/html;charset=iso8859-1")
+ it 'should choose the type that matches closest when matching subparams' do
+ expect(subject.choose_media_type(['text/html',
+ ['text/html', {'charset' => 'iso8859-1'}]],
+ 'text/html;charset=iso8859-1, application/xml'))
+ .to eq('text/html;charset=iso8859-1')
end
- it "should choose a type more specific than requested when an exact match is not present" do
- expect(subject.choose_media_type(["application/json;v=3;foo=bar", "application/json;v=2"],
- "text/html, application/json")).
- to eq("application/json;v=3;foo=bar")
+ it 'should choose a type more specific than requested when an exact match is not present' do
+ expect(subject.choose_media_type(['application/json;v=3;foo=bar', 'application/json;v=2'],
+ 'text/html, application/json'))
+ .to eq('application/json;v=3;foo=bar')
end
-
- it "should choose the preferred type over less-preferred types" do
- expect(subject.choose_media_type(["text/html", "application/xml"],
- "application/xml;q=0.7, text/html, */*")).to eq("text/html")
-
+ it 'should choose the preferred type over less-preferred types' do
+ expect(subject.choose_media_type(['text/html', 'application/xml'],
+ 'application/xml;q=0.7, text/html, */*')).to eq('text/html')
end
- it "should raise an exception when a media-type is improperly formatted" do
+ it 'should raise an error when a media-type is improperly formatted' do
expect {
- subject.choose_media_type(["text/html", "application/xml"],
- "bah;")
+ subject.choose_media_type(['text/html', 'application/xml'],
+ 'bah;')
}.to raise_error(Webmachine::MalformedRequest)
end
-
- it "should choose a type when more than one accept header is present" do
- expect(subject.choose_media_type(["text/html"],
- ["text/html", "text/plain"])).to eq("text/html")
+ it 'should choose a type when more than one accept header is present' do
+ expect(subject.choose_media_type(['text/html'],
+ ['text/html', 'text/plain'])).to eq('text/html')
end
end
- context "choosing an encoding" do
- it "should not set the encoding when none are provided" do
- subject.choose_encoding({}, "identity, gzip")
+ context 'choosing an encoding' do
+ it 'should not set the encoding when none are provided' do
+ subject.choose_encoding({}, 'identity, gzip')
expect(subject.metadata['Content-Encoding']).to be_nil
expect(subject.response.headers['Content-Encoding']).to be_nil
end
- it "should not set the Content-Encoding header when it is identity" do
- subject.choose_encoding({"gzip"=> :encode_gzip, "identity" => :encode_identity}, "identity")
+ it 'should not set the Content-Encoding header when it is identity' do
+ subject.choose_encoding({'gzip' => :encode_gzip, 'identity' => :encode_identity}, 'identity')
expect(subject.metadata['Content-Encoding']).to eq('identity')
expect(response.headers['Content-Encoding']).to be_nil
end
- it "should choose the first acceptable encoding" do
- subject.choose_encoding({"gzip" => :encode_gzip}, "identity, gzip")
+ it 'should choose the first acceptable encoding' do
+ subject.choose_encoding({'gzip' => :encode_gzip}, 'identity, gzip')
expect(subject.metadata['Content-Encoding']).to eq('gzip')
expect(response.headers['Content-Encoding']).to eq('gzip')
end
- it "should choose the first acceptable encoding" \
- ", even when no white space after comma" do
- subject.choose_encoding({"gzip" => :encode_gzip}, "identity,gzip")
+ it 'should choose the first acceptable encoding' \
+ ', even when no white space after comma' do
+ subject.choose_encoding({'gzip' => :encode_gzip}, 'identity,gzip')
expect(subject.metadata['Content-Encoding']).to eq('gzip')
expect(response.headers['Content-Encoding']).to eq('gzip')
end
- it "should choose the preferred encoding over less-preferred encodings" do
- subject.choose_encoding({"gzip" => :encode_gzip, "identity" => :encode_identity}, "gzip, identity;q=0.7")
+ it 'should choose the preferred encoding over less-preferred encodings' do
+ subject.choose_encoding({'gzip' => :encode_gzip, 'identity' => :encode_identity}, 'gzip, identity;q=0.7')
expect(subject.metadata['Content-Encoding']).to eq('gzip')
expect(response.headers['Content-Encoding']).to eq('gzip')
end
- it "should not set the encoding if none are acceptable" do
- subject.choose_encoding({"gzip" => :encode_gzip}, "identity")
+ it 'should not set the encoding if none are acceptable' do
+ subject.choose_encoding({'gzip' => :encode_gzip}, 'identity')
expect(subject.metadata['Content-Encoding']).to be_nil
expect(response.headers['Content-Encoding']).to be_nil
end
end
- context "choosing a charset" do
- it "should not set the charset when none are provided" do
- subject.choose_charset([], "ISO-8859-1")
+ context 'choosing a charset' do
+ it 'should not set the charset when none are provided' do
+ subject.choose_charset([], 'ISO-8859-1')
expect(subject.metadata['Charset']).to be_nil
end
- it "should choose the first acceptable charset" do
- subject.choose_charset([["UTF-8", :to_utf8],["US-ASCII", :to_ascii]], "US-ASCII, UTF-8")
- expect(subject.metadata['Charset']).to eq("US-ASCII")
+ it 'should choose the first acceptable charset' do
+ subject.choose_charset([['UTF-8', :to_utf8], ['US-ASCII', :to_ascii]], 'US-ASCII, UTF-8')
+ expect(subject.metadata['Charset']).to eq('US-ASCII')
end
- it "should choose the preferred charset over less-preferred charsets" do
- subject.choose_charset([["UTF-8", :to_utf8],["US-ASCII", :to_ascii]], "US-ASCII;q=0.7, UTF-8")
- expect(subject.metadata['Charset']).to eq("UTF-8")
+ it 'should choose the preferred charset over less-preferred charsets' do
+ subject.choose_charset([['UTF-8', :to_utf8], ['US-ASCII', :to_ascii]], 'US-ASCII;q=0.7, UTF-8')
+ expect(subject.metadata['Charset']).to eq('UTF-8')
end
- it "should not set the charset if none are acceptable" do
- subject.choose_charset([["UTF-8", :to_utf8],["US-ASCII", :to_ascii]], "ISO-8859-1")
+ it 'should not set the charset if none are acceptable' do
+ subject.choose_charset([['UTF-8', :to_utf8], ['US-ASCII', :to_ascii]], 'ISO-8859-1')
expect(subject.metadata['Charset']).to be_nil
end
- it "should choose a charset case-insensitively" do
- subject.choose_charset([["UtF-8", :to_utf8],["US-ASCII", :to_ascii]], "iso-8859-1, utf-8")
- expect(subject.metadata['Charset']).to eq("utf-8")
+ it 'should choose a charset case-insensitively' do
+ subject.choose_charset([['UtF-8', :to_utf8], ['US-ASCII', :to_ascii]], 'iso-8859-1, utf-8')
+ expect(subject.metadata['Charset']).to eq('utf-8')
end
end
- context "choosing a language" do
- it "should not set the language when none are provided" do
- subject.choose_language([], "en")
+ context 'choosing a language' do
+ it 'should not set the language when none are provided' do
+ subject.choose_language([], 'en')
expect(subject.metadata['Language']).to be_nil
end
- it "should choose the first acceptable language" do
- subject.choose_language(['en', 'en-US', 'es'], "en-US, es")
- expect(subject.metadata['Language']).to eq("en-US")
- expect(response.headers['Content-Language']).to eq("en-US")
+ it 'should choose the first acceptable language' do
+ subject.choose_language(['en', 'en-US', 'es'], 'en-US, es')
+ expect(subject.metadata['Language']).to eq('en-US')
+ expect(response.headers['Content-Language']).to eq('en-US')
end
- it "should choose the preferred language over less-preferred languages" do
- subject.choose_language(['en', 'en-US', 'es'], "en-US;q=0.6, es")
- expect(subject.metadata['Language']).to eq("es")
- expect(response.headers['Content-Language']).to eq("es")
+ it 'should choose the preferred language over less-preferred languages' do
+ subject.choose_language(['en', 'en-US', 'es'], 'en-US;q=0.6, es')
+ expect(subject.metadata['Language']).to eq('es')
+ expect(response.headers['Content-Language']).to eq('es')
end
- it "should select the first language if all are acceptable" do
- subject.choose_language(['en', 'fr', 'es'], "*")
- expect(subject.metadata['Language']).to eq("en")
- expect(response.headers['Content-Language']).to eq("en")
+ it 'should select the first language if all are acceptable' do
+ subject.choose_language(['en', 'fr', 'es'], '*')
+ expect(subject.metadata['Language']).to eq('en')
+ expect(response.headers['Content-Language']).to eq('en')
end
- it "should select the closest acceptable language when an exact match is not available" do
- subject.choose_language(['en-US', 'es'], "en, fr")
+ it 'should select the closest acceptable language when an exact match is not available' do
+ subject.choose_language(['en-US', 'es'], 'en, fr')
expect(subject.metadata['Language']).to eq('en-US')
expect(response.headers['Content-Language']).to eq('en-US')
end
- it "should not set the language if none are acceptable" do
+ it 'should not set the language if none are acceptable' do
subject.choose_language(['en'], 'es')
expect(subject.metadata['Language']).to be_nil
expect(response.headers).not_to include('Content-Language')
end
- it "should choose a language case-insensitively" do
+ it 'should choose a language case-insensitively' do
subject.choose_language(['en-US', 'ZH'], 'zh-ch, EN')
expect(subject.metadata['Language']).to eq('en-US')
expect(response.headers['Content-Language']).to eq('en-US')
diff --git a/spec/webmachine/decision/falsey_spec.rb b/spec/webmachine/decision/falsey_spec.rb
index 2a8bd210..52a4e337 100644
--- a/spec/webmachine/decision/falsey_spec.rb
+++ b/spec/webmachine/decision/falsey_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe Webmachine::Decision::Falsey do
- specify { expect(described_class.=== false).to be(true) }
- specify { expect(described_class.=== nil).to be(true) }
- specify { expect(described_class.=== true).to be(false) }
- specify { expect(described_class.=== []).to be(false) }
+ specify { expect(described_class === false).to be(true) }
+ specify { expect(described_class === nil).to be(true) }
+ specify { expect(described_class === true).to be(false) }
+ specify { expect(described_class === []).to be(false) }
end
diff --git a/spec/webmachine/decision/flow_spec.rb b/spec/webmachine/decision/flow_spec.rb
index 60bdb4e0..d1e696f6 100644
--- a/spec/webmachine/decision/flow_spec.rb
+++ b/spec/webmachine/decision/flow_spec.rb
@@ -3,9 +3,9 @@
describe Webmachine::Decision::Flow do
subject { Webmachine::Decision::FSM.new(resource, request, response) }
let(:method) { 'GET' }
- let(:uri) { URI.parse("http://localhost/") }
+ let(:uri) { URI.parse('http://localhost/') }
let(:headers) { Webmachine::Headers.new }
- let(:body) { "" }
+ let(:body) { '' }
let(:request) { Webmachine::Request.new(method, uri, headers, body) }
let(:response) { Webmachine::Response.new }
let(:default_resource) { resource_with }
@@ -22,207 +22,246 @@
def resource_with(&block)
klass = Class.new(Webmachine::Resource) do
- def to_html; "test resource"; end
+ def to_html
+ 'test resource'
+ end
end
- klass.module_eval(&block) if block_given?
+ klass.module_eval(&block) if block
klass.new(request, response)
end
def missing_resource_with(&block)
resource_with do
- def resource_exists?; false; end
- self.module_eval(&block) if block
+ def resource_exists?
+ false
+ end
+ module_eval(&block) if block
end
end
- describe "#b13 (Service Available?)" do
+ describe '#b13 (Service Available?)' do
let(:resource) do
resource_with do
attr_accessor :available
- def service_available?; @available; end
+ def service_available?
+ @available
+ end
end
end
- it "should respond with 503 when the service is unavailable" do
+ it 'should respond with 503 when the service is unavailable' do
resource.available = false
subject.run
expect(response.code).to eq 503
end
end
- describe "#b12 (Known method?)" do
+ describe '#b12 (Known method?)' do
let(:resource) do
resource_with do
- def known_methods; ['HEAD']; end
+ def known_methods
+ ['HEAD']
+ end
end
end
- it "should respond with 501 when the method is unknown" do
+ it 'should respond with 501 when the method is unknown' do
subject.run
expect(response.code).to eq 501
end
end
- describe "#b11 (URI too long?)" do
+ describe '#b11 (URI too long?)' do
let(:resource) do
resource_with do
- def uri_too_long?(uri); true; end
+ def uri_too_long?(uri)
+ true
+ end
end
end
- it "should respond with 414 when the URI is too long" do
+ it 'should respond with 414 when the URI is too long' do
subject.run
expect(response.code).to eq 414
end
end
- describe "#b10 (Method allowed?)" do
+ describe '#b10 (Method allowed?)' do
let(:resource) do
resource_with do
- def allowed_methods; ['POST']; end
+ def allowed_methods
+ ['POST']
+ end
end
end
- it "should respond with 405 when the method is not allowed" do
+ it 'should respond with 405 when the method is not allowed' do
subject.run
expect(response.code).to eq 405
- expect(response.headers['Allow']).to eq "POST"
+ expect(response.headers['Allow']).to eq 'POST'
end
end
- describe "#b9 (Malformed request?)" do
- let(:resource) { resource_with { def malformed_request?; true; end } }
+ describe '#b9 (Malformed request?)' do
+ let(:resource) {
+ resource_with {
+ def malformed_request?
+ true
+ end
+ }
+ }
- it "should respond with 400 when the request is malformed" do
+ it 'should respond with 400 when the request is malformed' do
subject.run
expect(response.code).to eq 400
end
- context "when the Content-MD5 header is present" do
+ context 'when the Content-MD5 header is present' do
let(:resource) do
resource_with do
- def allowed_methods; ['POST']; end;
- def process_post; true; end;
+ def allowed_methods
+ ['POST']
+ end
+
+ def process_post
+ true
+ end
attr_accessor :validation
- def validate_content_checksum; @validation; end
+ def validate_content_checksum
+ @validation
+ end
end
end
- let(:method) { "POST" }
- let(:body) { "This is the body." }
- let(:headers) { Webmachine::Headers["Content-Type" => "text/plain"] }
+ let(:method) { 'POST' }
+ let(:body) { 'This is the body.' }
+ let(:headers) { Webmachine::Headers['Content-Type' => 'text/plain'] }
- it "should respond with 204 when the request body does match the header" do
+ it 'should respond with 204 when the request body does match the header' do
headers['Content-MD5'] = Base64.encode64 Digest::MD5.hexdigest(body)
subject.run
expect(response.code).to eq 204
end
- it "should bypass validation when the header has a nil value" do
+ it 'should bypass validation when the header has a nil value' do
headers['Content-MD5'] = nil
subject.run
expect(response.code).to eq 204
end
- it "should respond with 400 when the header has a empty string value" do
- headers['Content-MD5'] = ""
+ it 'should respond with 400 when the header has a empty string value' do
+ headers['Content-MD5'] = ''
subject.run
expect(response.code).to eq 400
end
- it "should respond with 400 when the header has a non-hashed, non-encoded value" do
- headers["Content-MD5"] = body
+ it 'should respond with 400 when the header has a non-hashed, non-encoded value' do
+ headers['Content-MD5'] = body
subject.run
expect(response.code).to eq 400
end
- it "should respond with 400 when the header is not encoded as Base64 but digest matches the body" do
+ it 'should respond with 400 when the header is not encoded as Base64 but digest matches the body' do
headers['Content-MD5'] = Digest::MD5.hexdigest(body)
subject.run
expect(response.code).to eq 400
end
- it "should respond with 400 when the request body does not match the header" do
- headers['Content-MD5'] = Base64.encode64 Digest::MD5.hexdigest("thiswillnotmatchthehash")
+ it 'should respond with 400 when the request body does not match the header' do
+ headers['Content-MD5'] = Base64.encode64 Digest::MD5.hexdigest('thiswillnotmatchthehash')
subject.run
expect(response.code).to eq 400
end
- it "should respond with 400 when the resource invalidates the checksum" do
+ it 'should respond with 400 when the resource invalidates the checksum' do
resource.validation = false
- headers['Content-MD5'] = Base64.encode64 Digest::MD5.hexdigest("thiswillnotmatchthehash")
+ headers['Content-MD5'] = Base64.encode64 Digest::MD5.hexdigest('thiswillnotmatchthehash')
subject.run
expect(response.code).to eq 400
end
- it "should not respond with 400 when the resource validates the checksum" do
+ it 'should not respond with 400 when the resource validates the checksum' do
resource.validation = true
- headers['Content-MD5'] = Base64.encode64 Digest::MD5.hexdigest("thiswillnotmatchthehash")
+ headers['Content-MD5'] = Base64.encode64 Digest::MD5.hexdigest('thiswillnotmatchthehash')
subject.run
expect(response.code).to_not eq 400
end
- it "should respond with the given code when the resource returns a code while validating" do
+ it 'should respond with the given code when the resource returns a code while validating' do
resource.validation = 500
- headers['Content-MD5'] = Base64.encode64 Digest::MD5.hexdigest("thiswillnotmatchthehash")
+ headers['Content-MD5'] = Base64.encode64 Digest::MD5.hexdigest('thiswillnotmatchthehash')
subject.run
expect(response.code).to eq 500
end
end
end
- describe "#b8 (Authorized?)" do
- let(:resource) { resource_with { attr_accessor :auth; def is_authorized?(header); @auth; end } }
+ describe '#b8 (Authorized?)' do
+ let(:resource) {
+ resource_with {
+ attr_accessor :auth
+ def is_authorized?(header)
+ @auth
+ end
+ }
+ }
- it "should reply with 401 when the client is unauthorized" do
+ it 'should reply with 401 when the client is unauthorized' do
resource.auth = false
subject.run
expect(response.code).to eq 401
end
- it "should reply with 401 when the resource gives a challenge" do
- resource.auth = "Basic realm=Webmachine"
+ it 'should reply with 401 when the resource gives a challenge' do
+ resource.auth = 'Basic realm=Webmachine'
subject.run
expect(response.code).to eq 401
- expect(response.headers['WWW-Authenticate']).to eq "Basic realm=Webmachine"
+ expect(response.headers['WWW-Authenticate']).to eq 'Basic realm=Webmachine'
end
- it "should halt with the given code when the resource returns a status code" do
+ it 'should halt with the given code when the resource returns a status code' do
resource.auth = 400
subject.run
expect(response.code).to eq 400
end
- it "should not reply with 401 when the client is authorized" do
+ it 'should not reply with 401 when the client is authorized' do
resource.auth = true
subject.run
expect(response.code).to_not eq 401
end
end
- describe "#b7 (Forbidden?)" do
- let(:resource) { resource_with { attr_accessor :forbid; def forbidden?; @forbid; end } }
+ describe '#b7 (Forbidden?)' do
+ let(:resource) {
+ resource_with {
+ attr_accessor :forbid
+ def forbidden?
+ @forbid
+ end
+ }
+ }
- it "should reply with 403 when the request is forbidden" do
+ it 'should reply with 403 when the request is forbidden' do
resource.forbid = true
subject.run
expect(response.code).to eq 403
end
- it "should not reply with 403 when the request is permitted" do
+ it 'should not reply with 403 when the request is permitted' do
resource.forbid = false
subject.run
expect(response.code).to_not eq 403
end
- it "should halt with the given code when the resource returns a status code" do
+ it 'should halt with the given code when the resource returns a status code' do
resource.forbid = 400
subject.run
expect(response.code).to eq 400
end
end
- describe "#b6 (Unsupported Content-* header?)" do
+ describe '#b6 (Unsupported Content-* header?)' do
let(:resource) do
resource_with do
def valid_content_headers?(contents)
@@ -231,101 +270,126 @@ def valid_content_headers?(contents)
end
end
- it "should reply with 501 when an invalid Content-* header is present" do
- headers['Content-Fail'] = "yup"
+ it 'should reply with 501 when an invalid Content-* header is present' do
+ headers['Content-Fail'] = 'yup'
subject.run
expect(response.code).to eq 501
end
- it "should not reply with 501 when all Content-* headers are valid" do
+ it 'should not reply with 501 when all Content-* headers are valid' do
subject.run
expect(response.code).to_not eq 501
end
end
- describe "#b5 (Known Content-Type?)" do
- let(:method) { "POST" }
- let(:body) { "This is the body." }
+ describe '#b5 (Known Content-Type?)' do
+ let(:method) { 'POST' }
+ let(:body) { 'This is the body.' }
let(:resource) do
resource_with do
- def known_content_type?(type) type !~ /unknown/; end;
- def process_post; true; end
- def allowed_methods; %w{POST}; end
+ def known_content_type?(type)
+ type !~ /unknown/
+ end
+
+ def process_post
+ true
+ end
+
+ def allowed_methods
+ %w[POST]
+ end
end
end
before { headers['Content-Length'] = body.length.to_s }
- it "should reply with 415 when the Content-Type is unknown" do
- headers['Content-Type'] = "application/x-unknown-type"
+ it 'should reply with 415 when the Content-Type is unknown' do
+ headers['Content-Type'] = 'application/x-unknown-type'
subject.run
expect(response.code).to eq 415
end
- it "should not reply with 415 when the Content-Type is known" do
- headers['Content-Type'] = "text/plain"
+ it 'should not reply with 415 when the Content-Type is known' do
+ headers['Content-Type'] = 'text/plain'
subject.run
expect(response.code).to_not eq 415
end
end
- describe "#b4 (Request entity too large?)" do
+ describe '#b4 (Request entity too large?)' do
let(:resource) do
resource_with do
- def allowed_methods; %w{POST}; end
- def process_post; true; end
- def valid_entity_length?(length); length.to_i < 100; end
+ def allowed_methods
+ %w[POST]
+ end
+
+ def process_post
+ true
+ end
+
+ def valid_entity_length?(length)
+ length.to_i < 100
+ end
end
end
- let(:method) { "POST" }
- before { headers['Content-Type'] = "text/plain"; headers['Content-Length'] = body.size.to_s }
+ let(:method) { 'POST' }
+ before {
+ headers['Content-Type'] = 'text/plain'
+ headers['Content-Length'] = body.size.to_s
+ }
- context "when the request body is too large" do
- let(:body) { "Big" * 100 }
- it "should reply with 413" do
+ context 'when the request body is too large' do
+ let(:body) { 'Big' * 100 }
+ it 'should reply with 413' do
subject.run
expect(response.code).to eq 413
end
end
- context "when the request body is not too large" do
- let(:body) { "small" }
+ context 'when the request body is not too large' do
+ let(:body) { 'small' }
- it "should not reply with 413" do
+ it 'should not reply with 413' do
subject.run
expect(response.code).to_not eq 413
end
end
end
- describe "#b3 (OPTIONS?)" do
- let(:method){ "OPTIONS" }
- let(:resource){ resource_with { def allowed_methods; %w[GET HEAD OPTIONS]; end } }
- it "should reply with 200 when the request method is OPTIONS" do
+ describe '#b3 (OPTIONS?)' do
+ let(:method) { 'OPTIONS' }
+ let(:resource) {
+ resource_with {
+ def allowed_methods
+ %w[GET HEAD OPTIONS]
+ end
+ }
+ }
+ it 'should reply with 200 when the request method is OPTIONS' do
subject.run
expect(response.code).to eq 200
end
end
- describe "#c3, #c4 (Acceptable media types)" do
+ describe '#c3, #c4 (Acceptable media types)' do
let(:resource) { default_resource }
- context "when the Accept header exists" do
- it "should reply with 406 when the type is unacceptable" do
- headers['Accept'] = "text/plain"
+ context 'when the Accept header exists' do
+ it 'should reply with 406 when the type is unacceptable' do
+ headers['Accept'] = 'text/plain'
subject.run
expect(response.code).to eq 406
end
- it "should not reply with 406 when the type is acceptable" do
- headers['Accept'] = "text/*"
+ it 'should not reply with 406 when the type is acceptable' do
+ headers['Accept'] = 'text/*'
subject.run
expect(response.code).to_not eq 406
- expect(response.headers['Content-Type']).to eq "text/html"
+ expect(response.headers['Content-Type']).to eq 'text/html'
end
end
- context "when the Accept header does not exist" do
- it "should not negotiate a media type" do
+ context 'when the Accept header does not exist' do
+ it 'should not negotiate a media type' do
expect(headers['Accept']).to be_nil
expect(subject).to_not receive(:c4)
subject.run
@@ -334,26 +398,32 @@ def valid_entity_length?(length); length.to_i < 100; end
end
end
- describe "#d4, #d5 (Acceptable languages)" do
- let(:resource) { resource_with { def languages_provided; %w{en-US fr}; end } }
- context "when the Accept-Language header exists" do
- it "should reply with 406 when the language is unacceptable" do
- headers['Accept-Language'] = "es, de"
+ describe '#d4, #d5 (Acceptable languages)' do
+ let(:resource) {
+ resource_with {
+ def languages_provided
+ %w[en-US fr]
+ end
+ }
+ }
+ context 'when the Accept-Language header exists' do
+ it 'should reply with 406 when the language is unacceptable' do
+ headers['Accept-Language'] = 'es, de'
subject.run
expect(response.code).to eq 406
end
- it "should not reply with 406 when the language is acceptable" do
- headers['Accept-Language'] = "en-GB, en;q=0.7"
+ it 'should not reply with 406 when the language is acceptable' do
+ headers['Accept-Language'] = 'en-GB, en;q=0.7'
subject.run
expect(response.code).to_not eq 406
- expect(response.headers['Content-Language']).to eq "en-US"
+ expect(response.headers['Content-Language']).to eq 'en-US'
expect(resource.instance_variable_get(:@language)).to eq 'en-US'
end
end
- context "when the Accept-Language header is absent" do
- it "should not negotiate the language" do
+ context 'when the Accept-Language header is absent' do
+ it 'should not negotiate the language' do
expect(headers['Accept-Language']).to be_nil
expect(subject).to_not receive(:d5)
subject.run
@@ -363,34 +433,40 @@ def valid_entity_length?(length); length.to_i < 100; end
end
end
- describe "#e5, #e6 (Acceptable charsets)" do
+ describe '#e5, #e6 (Acceptable charsets)' do
let(:resource) do
resource_with do
def charsets_provided
- [["iso8859-1", :to_iso],["utf-8", :to_utf]];
+ [['iso8859-1', :to_iso], ['utf-8', :to_utf]]
+ end
+
+ def to_iso(chunk)
+ chunk
+ end
+
+ def to_utf(chunk)
+ chunk
end
- def to_iso(chunk); chunk; end
- def to_utf(chunk); chunk; end
end
end
- context "when the Accept-Charset header exists" do
- it "should reply with 406 when the charset is unacceptable" do
- headers['Accept-Charset'] = "utf-16"
+ context 'when the Accept-Charset header exists' do
+ it 'should reply with 406 when the charset is unacceptable' do
+ headers['Accept-Charset'] = 'utf-16'
subject.run
expect(response.code).to eq 406
end
- it "should not reply with 406 when the charset is acceptable" do
- headers['Accept-Charset'] = "iso8859-1"
+ it 'should not reply with 406 when the charset is acceptable' do
+ headers['Accept-Charset'] = 'iso8859-1'
subject.run
expect(response.code).to_not eq 406
- expect(response.headers['Content-Type']).to eq "text/html;charset=iso8859-1"
+ expect(response.headers['Content-Type']).to eq 'text/html;charset=iso8859-1'
end
end
- context "when the Accept-Charset header is absent" do
- it "should not negotiate the language" do
+ context 'when the Accept-Charset header is absent' do
+ it 'should not negotiate the language' do
expect(headers['Accept-Charset']).to be_nil
expect(subject).to_not receive(:e6)
subject.run
@@ -399,23 +475,23 @@ def to_utf(chunk); chunk; end
end
end
- describe "#f6, #f7 (Acceptable encodings)" do
+ describe '#f6, #f7 (Acceptable encodings)' do
let(:resource) do
resource_with do
def encodings_provided
- super.merge("gzip" => :encode_gzip)
+ super.merge('gzip' => :encode_gzip)
end
end
end
- context "when the Accept-Encoding header is present" do
- it "should reply with 406 if the encoding is unacceptable" do
+ context 'when the Accept-Encoding header is present' do
+ it 'should reply with 406 if the encoding is unacceptable' do
headers['Accept-Encoding'] = 'deflate, identity;q=0.0'
subject.run
expect(response.code).to eq 406
end
- it "should not reply with 406 if the encoding is acceptable" do
+ it 'should not reply with 406 if the encoding is acceptable' do
headers['Accept-Encoding'] = 'gzip, deflate'
subject.run
expect(response.code).to_not eq 406
@@ -425,8 +501,8 @@ def encodings_provided
end
end
- context "when the Accept-Encoding header is not present" do
- it "should not negotiate an encoding" do
+ context 'when the Accept-Encoding header is not present' do
+ it 'should not negotiate an encoding' do
expect(headers['Accept-Encoding']).to be_nil
expect(subject).to_not receive(:f7)
subject.run
@@ -437,30 +513,37 @@ def encodings_provided
end
end
- describe "#g7 (Resource exists?)" do
- let(:resource) { resource_with { attr_accessor :exist; def resource_exists?; @exist; end } }
+ describe '#g7 (Resource exists?)' do
+ let(:resource) {
+ resource_with {
+ attr_accessor :exist
+ def resource_exists?
+ @exist
+ end
+ }
+ }
- it "should not enter conditional requests if missing (and eventually reply with 404)" do
+ it 'should not enter conditional requests if missing (and eventually reply with 404)' do
resource.exist = false
expect(subject).to_not receive(:g8)
subject.run
expect(response.code).to eq 404
end
- it "should not reply with 404 if it does exist" do
+ it 'should not reply with 404 if it does exist' do
resource.exist = true
expect(subject).to_not receive(:h7)
subject.run
expect(response.code).to_not eq 404
end
- it "should not reply with 404 for truthy non-booleans" do
+ it 'should not reply with 404 for truthy non-booleans' do
resource.exist = []
subject.run
expect(response.code).to_not eq 404
end
- it "should reply with 404 for nil" do
+ it 'should reply with 404 for nil' do
resource.exist = nil
subject.run
expect(response.code).to eq 404
@@ -468,37 +551,50 @@ def encodings_provided
end
# Conditional requests/preconditions
- describe "#g8, #g9, #g10 (ETag match)" do
- let(:resource) { resource_with { def generate_etag; "etag"; end } }
- it "should skip ETag matching when If-Match is missing" do
+ describe '#g8, #g9, #g10 (ETag match)' do
+ let(:resource) {
+ resource_with {
+ def generate_etag
+ 'etag'
+ end
+ }
+ }
+ it 'should skip ETag matching when If-Match is missing' do
expect(headers['If-Match']).to be_nil
expect(subject).to_not receive(:g9)
expect(subject).to_not receive(:g11)
subject.run
expect(response.code).to_not eq 412
end
- it "should not reply with 304 when If-Match is *" do
- headers['If-Match'] = "*"
+ it 'should not reply with 304 when If-Match is *' do
+ headers['If-Match'] = '*'
subject.run
expect(response.code).to_not eq 412
end
- it "should reply with 412 if the ETag is not in If-Match" do
+ it 'should reply with 412 if the ETag is not in If-Match' do
headers['If-Match'] = '"notetag"'
subject.run
expect(response.code).to eq 412
end
- it "should not reply with 412 if the ETag is in If-Match" do
+ it 'should not reply with 412 if the ETag is in If-Match' do
headers['If-Match'] = '"etag"'
subject.run
expect(response.code).to_not eq 412
end
end
- describe "#h10, #h11, #h12 (If-Unmodified-Since match [IUMS])" do
- let(:resource) { resource_with { attr_accessor :now; def last_modified; @now; end } }
+ describe '#h10, #h11, #h12 (If-Unmodified-Since match [IUMS])' do
+ let(:resource) {
+ resource_with {
+ attr_accessor :now
+ def last_modified
+ @now
+ end
+ }
+ }
before { @now = resource.now = Time.now }
- it "should skip LM matching if IUMS is missing" do
+ it 'should skip LM matching if IUMS is missing' do
expect(headers['If-Unmodified-Since']).to be_nil
expect(subject).to_not receive(:h11)
expect(subject).to_not receive(:h12)
@@ -506,90 +602,106 @@ def encodings_provided
expect(response.code).to_not eq 412
end
- it "should skip LM matching if IUMS is an invalid date" do
- headers['If-Unmodified-Since'] = "garbage"
+ it 'should skip LM matching if IUMS is an invalid date' do
+ headers['If-Unmodified-Since'] = 'garbage'
expect(subject).to_not receive(:h12)
subject.run
expect(response.code).to_not eq 412
end
- it "should not reply with 412 if LM is <= IUMS" do
+ it 'should not reply with 412 if LM is <= IUMS' do
headers['If-Unmodified-Since'] = (@now + 100).httpdate
subject.run
expect(response.code).to_not eq 412
end
- it "should reply with 412 if LM is > IUMS" do
+ it 'should reply with 412 if LM is > IUMS' do
headers['If-Unmodified-Since'] = (@now - 100).httpdate
subject.run
expect(response.code).to eq 412
end
end
- describe "#i12, #i13, #k13, #j18 (If-None-Match match)" do
+ describe '#i12, #i13, #k13, #j18 (If-None-Match match)' do
let(:resource) do
resource_with do
- def generate_etag; "etag"; end;
- def process_post; true; end
- def allowed_methods; %w{GET HEAD POST}; end
+ def generate_etag
+ 'etag'
+ end
+
+ def process_post
+ true
+ end
+
+ def allowed_methods
+ %w[GET HEAD POST]
+ end
end
end
- it "should skip ETag matching if If-None-Match is missing" do
+ it 'should skip ETag matching if If-None-Match is missing' do
expect(headers['If-None-Match']).to be_nil
- %w{i13 k13 j18}.each do |m|
+ %w[i13 k13 j18].each do |m|
expect(subject).to_not receive(m.to_sym)
end
subject.run
expect([304, 412]).to_not include(response.code)
end
- it "should not reply with 412 or 304 if the ETag is not in If-None-Match" do
+ it 'should not reply with 412 or 304 if the ETag is not in If-None-Match' do
headers['If-None-Match'] = '"notetag"'
subject.run
expect([304, 412]).to_not include(response.code)
end
- context "when the method is GET or HEAD" do
- let(:method){ %w{GET HEAD}[rand(1)] }
- it "should reply with 304 when If-None-Match is *" do
+ context 'when the method is GET or HEAD' do
+ let(:method) { %w[GET HEAD][rand(2)] }
+ it 'should reply with 304 when If-None-Match is *' do
headers['If-None-Match'] = '*'
end
- it "should reply with 304 when the ETag is in If-None-Match" do
+ it 'should reply with 304 when the ETag is in If-None-Match' do
headers['If-None-Match'] = '"etag", "foobar"'
end
- after { subject.run; expect(response.code).to eq 304 }
+ after {
+ subject.run
+ expect(response.code).to eq 304
+ }
end
- context "when the method is not GET or HEAD" do
- let(:method){ "POST" }
- let(:body) { "This is the body." }
- let(:headers){ Webmachine::Headers["Content-Type" => "text/plain"] }
+ context 'when the method is not GET or HEAD' do
+ let(:method) { 'POST' }
+ let(:body) { 'This is the body.' }
+ let(:headers) { Webmachine::Headers['Content-Type' => 'text/plain'] }
- it "should reply with 412 when If-None-Match is *" do
+ it 'should reply with 412 when If-None-Match is *' do
headers['If-None-Match'] = '*'
end
- it "should reply with 412 when the ETag is in If-None-Match" do
+ it 'should reply with 412 when the ETag is in If-None-Match' do
headers['If-None-Match'] = '"etag"'
end
- after { subject.run; expect(response.code).to eq 412 }
+ after {
+ subject.run
+ expect(response.code).to eq 412
+ }
end
- context "when the resource does not define an ETag" do
+ context 'when the resource does not define an ETag' do
let(:resource) do
resource_with do
- def generate_etag; nil; end
+ def generate_etag
+ nil
+ end
end
end
- it "should reply with 200 when If-None-Match is missing" do
+ it 'should reply with 200 when If-None-Match is missing' do
headers.delete 'If-None-Match'
subject.run
expect(response.code).to eq 200
end
- it "should reply with 200 when If-None-Match is present" do
+ it 'should reply with 200 when If-None-Match is present' do
headers['If-None-Match'] = '"etag"'
subject.run
expect(response.code).to eq 200
@@ -597,42 +709,49 @@ def generate_etag; nil; end
end
end
- describe "#l13, #l14, #l15, #l17 (If-Modified-Since match)" do
- let(:resource) { resource_with { attr_accessor :now; def last_modified; @now; end } }
+ describe '#l13, #l14, #l15, #l17 (If-Modified-Since match)' do
+ let(:resource) {
+ resource_with {
+ attr_accessor :now
+ def last_modified
+ @now
+ end
+ }
+ }
before { @now = resource.now = Time.now }
- it "should skip LM matching if IMS is missing" do
+ it 'should skip LM matching if IMS is missing' do
expect(headers['If-Modified-Since']).to be_nil
- %w{l14 l15 l17}.each do |m|
+ %w[l14 l15 l17].each do |m|
expect(subject).to_not receive(m.to_sym)
end
subject.run
expect(response.code).to_not eq 304
end
- it "should skip LM matching if IMS is an invalid date" do
- headers['If-Modified-Since'] = "garbage"
- %w{l15 l17}.each do |m|
+ it 'should skip LM matching if IMS is an invalid date' do
+ headers['If-Modified-Since'] = 'garbage'
+ %w[l15 l17].each do |m|
expect(subject).to_not receive(m.to_sym)
end
subject.run
expect(response.code).to_not eq 304
end
- it "should skip LM matching if IMS is later than current time" do
+ it 'should skip LM matching if IMS is later than current time' do
headers['If-Modified-Since'] = (@now + 1000).httpdate
expect(subject).to_not receive(:l17)
subject.run
expect(response.code).to_not eq 304
end
- it "should reply with 304 if LM is <= IMS" do
+ it 'should reply with 304 if LM is <= IMS' do
headers['If-Modified-Since'] = (@now - 1).httpdate
resource.now = @now - 1000
subject.run
expect(response.code).to eq 304
end
- it "should not reply with 304 if LM is > IMS" do
+ it 'should not reply with 304 if LM is > IMS' do
headers['If-Modified-Since'] = (@now - 1000).httpdate
subject.run
expect(response.code).to_not eq 304
@@ -640,35 +759,40 @@ def generate_etag; nil; end
end
# Resource missing branch (upper right)
- describe "#h7 (If-Match: * exists?)" do
+ describe '#h7 (If-Match: * exists?)' do
let(:resource) { missing_resource }
- it "should reply with 412 when the If-Match header is *" do
+ it 'should reply with 412 when the If-Match header is *' do
headers['If-Match'] = '"*"'
subject.run
expect(response.code).to eq 412
end
- it "should not reply with 412 when the If-Match header is missing or not *" do
- headers['If-Match'] = ['"etag"', nil][rand(1)]
+ it 'should not reply with 412 when the If-Match header is missing or not *' do
+ headers['If-Match'] = ['"etag"', nil][rand(2)]
subject.run
expect(response.code).to_not eq 412
end
end
- describe "#i7 (PUT?)" do
+ describe '#i7 (PUT?)' do
let(:resource) do
missing_resource_with do
- def allowed_methods; %w{GET HEAD PUT POST}; end
- def process_post; true; end
+ def allowed_methods
+ %w[GET HEAD PUT POST]
+ end
+
+ def process_post
+ true
+ end
end
end
- let(:body) { %W{GET HEAD DELETE}.include?(method) ? nil : "This is the body." }
+ let(:body) { %W[GET HEAD DELETE].include?(method) ? nil : 'This is the body.' }
before { headers['Content-Type'] = 'text/plain' }
- context "when the method is PUT" do
- let(:method){ "PUT" }
+ context 'when the method is PUT' do
+ let(:method) { 'PUT' }
- it "should not reach state k7" do
+ it 'should not reach state k7' do
expect(subject).to_not receive(:k7)
subject.run
end
@@ -676,10 +800,10 @@ def process_post; true; end
after { expect([404, 410, 303]).to_not include(response.code) }
end
- context "when the method is not PUT" do
- let(:method){ %W{GET HEAD POST DELETE}[rand(4)] }
+ context 'when the method is not PUT' do
+ let(:method) { %W[GET HEAD POST DELETE][rand(4)] }
- it "should not reach state i4" do
+ it 'should not reach state i4' do
expect(subject).to_not receive(:i4)
subject.run
end
@@ -688,90 +812,112 @@ def process_post; true; end
end
end
- describe "#i4 (Apply to a different URI?)" do
+ describe '#i4 (Apply to a different URI?)' do
let(:resource) do
missing_resource_with do
attr_accessor :location
- def moved_permanently?; @location; end
- def allowed_methods; %w[PUT]; end
+ def moved_permanently?
+ @location
+ end
+
+ def allowed_methods
+ %w[PUT]
+ end
end
end
- let(:method){ "PUT" }
- let(:body){ "This is the body." }
- let(:headers) { Webmachine::Headers["Content-Type" => "text/plain", "Content-Length" => body.size.to_s] }
+ let(:method) { 'PUT' }
+ let(:body) { 'This is the body.' }
+ let(:headers) { Webmachine::Headers['Content-Type' => 'text/plain', 'Content-Length' => body.size.to_s] }
- it "should reply with 301 when the resource has moved" do
- resource.location = URI.parse("http://localhost:8098/newuri")
+ it 'should reply with 301 when the resource has moved' do
+ resource.location = URI.parse('http://localhost:8098/newuri')
subject.run
expect(response.code).to eq 301
expect(response.headers['Location']).to eq resource.location.to_s
end
- it "should not reply with 301 when resource has not moved" do
+ it 'should not reply with 301 when resource has not moved' do
resource.location = false
subject.run
expect(response.code).to_not eq 301
end
end
- describe "Redirection (Resource previously existed)" do
+ describe 'Redirection (Resource previously existed)' do
let(:resource) do
missing_resource_with do
attr_writer :moved_perm, :moved_temp, :allow_missing
- def previously_existed?; true; end
- def moved_permanently?; @moved_perm; end
- def moved_temporarily?; @moved_temp; end
- def allow_missing_post?; @allow_missing; end
- def allowed_methods; %W{GET POST}; end
- def process_post; true; end
+ def previously_existed?
+ true
+ end
+
+ def moved_permanently?
+ @moved_perm
+ end
+
+ def moved_temporarily?
+ @moved_temp
+ end
+
+ def allow_missing_post?
+ @allow_missing
+ end
+
+ def allowed_methods
+ %W[GET POST]
+ end
+
+ def process_post
+ true
+ end
end
end
- let(:method){ @method || "GET" }
+ let(:method) { @method || 'GET' }
- describe "#k5 (Moved permanently?)" do
- it "should reply with 301 when the resource has moved permanently" do
- uri = resource.moved_perm = URI.parse("http://www.google.com/")
+ describe '#k5 (Moved permanently?)' do
+ it 'should reply with 301 when the resource has moved permanently' do
+ uri = resource.moved_perm = URI.parse('http://www.google.com/')
subject.run
expect(response.code).to eq 301
expect(response.headers['Location']).to eq uri.to_s
end
- it "should not reply with 301 when the resource has not moved permanently" do
+ it 'should not reply with 301 when the resource has not moved permanently' do
resource.moved_perm = false
subject.run
expect(response.code).to_not eq 301
end
end
- describe "#l5 (Moved temporarily?)" do
+ describe '#l5 (Moved temporarily?)' do
before { resource.moved_perm = false }
- it "should reply with 307 when the resource has moved temporarily" do
- uri = resource.moved_temp = URI.parse("http://www.basho.com/")
+ it 'should reply with 307 when the resource has moved temporarily' do
+ uri = resource.moved_temp = URI.parse('http://www.basho.com/')
subject.run
expect(response.code).to eq 307
expect(response.headers['Location']).to eq uri.to_s
end
- it "should not reply with 307 when the resource has not moved temporarily" do
+ it 'should not reply with 307 when the resource has not moved temporarily' do
resource.moved_temp = false
subject.run
expect(response.code).to_not eq 307
end
end
- describe "#m5 (POST?), #n5 (POST to missing resource?)" do
+ describe '#m5 (POST?), #n5 (POST to missing resource?)' do
before { resource.moved_perm = resource.moved_temp = false }
- it "should reply with 410 when the method is not POST" do
- expect(method).to_not eq "POST"
+ it 'should reply with 410 when the method is not POST' do
+ expect(method).to_not eq 'POST'
subject.run
expect(response.code).to eq 410
end
- it "should reply with 410 when the resource disallows missing POSTs" do
- @method = "POST"
+ it 'should reply with 410 when the resource disallows missing POSTs' do
+ @method = 'POST'
resource.allow_missing = false
subject.run
expect(response.code).to eq 410
end
- it "should not reply with 410 when the resource allows missing POSTs" do
- @method = "POST"
+ it 'should not reply with 410 when the resource allows missing POSTs' do
+ @method = 'POST'
resource.allow_missing = true
subject.run
expect(response.code).to eq 410
@@ -779,51 +925,67 @@ def process_post; true; end
end
end
- describe "#l7 (POST?), #m7 (POST to missing resource?)" do
+ describe '#l7 (POST?), #m7 (POST to missing resource?)' do
let(:resource) do
missing_resource_with do
attr_accessor :allow_missing
- def allowed_methods; %W{GET POST}; end
- def previously_existed?; false; end
- def allow_missing_post?; @allow_missing; end
- def process_post; true; end
+ def allowed_methods
+ %W[GET POST]
+ end
+
+ def previously_existed?
+ false
+ end
+
+ def allow_missing_post?
+ @allow_missing
+ end
+
+ def process_post
+ true
+ end
end
end
- let(:method){ @method || "GET" }
- it "should reply with 404 when the method is not POST" do
- expect(method).to_not eq "POST"
+ let(:method) { @method || 'GET' }
+ it 'should reply with 404 when the method is not POST' do
+ expect(method).to_not eq 'POST'
subject.run
expect(response.code).to eq 404
end
- it "should reply with 404 when the resource disallows missing POSTs" do
- @method = "POST"
+ it 'should reply with 404 when the resource disallows missing POSTs' do
+ @method = 'POST'
resource.allow_missing = false
subject.run
expect(response.code).to eq 404
end
- it "should not reply with 404 when the resource allows missing POSTs" do
- @method = "POST"
+ it 'should not reply with 404 when the resource allows missing POSTs' do
+ @method = 'POST'
resource.allow_missing = true
subject.run
expect(response.code).to_not eq 404
end
end
- describe "#p3 (Conflict?)" do
+ describe '#p3 (Conflict?)' do
let(:resource) do
missing_resource_with do
attr_writer :conflict
- def allowed_methods; %W{PUT}; end
- def is_conflict?; @conflict; end
+ def allowed_methods
+ %W[PUT]
+ end
+
+ def is_conflict?
+ @conflict
+ end
end
end
- let(:method){ "PUT" }
- it "should reply with 409 if the resource is in conflict" do
+ let(:method) { 'PUT' }
+ it 'should reply with 409 if the resource is in conflict' do
resource.conflict = true
subject.run
expect(response.code).to eq 409
end
- it "should not reply with 409 if the resource is in conflict" do
+ it 'should not reply with 409 if the resource is in conflict' do
resource.conflict = false
subject.run
expect(response.code).to_not eq 409
@@ -831,13 +993,19 @@ def is_conflict?; @conflict; end
end
# Bottom right
- describe "#n11 (Redirect?)" do
- let(:method) { "POST" }
+ describe '#n11 (Redirect?)' do
+ let(:method) { 'POST' }
let(:resource) do
resource_with do
attr_writer :new_loc, :exist
- def allowed_methods; %w{POST}; end
- def allow_missing_post?; true; end
+ def allowed_methods
+ %w[POST]
+ end
+
+ def allow_missing_post?
+ true
+ end
+
def process_post
response.redirect_to(@new_loc) if @new_loc
true
@@ -845,17 +1013,17 @@ def process_post
end
end
[true, false].each do |e|
- context "and the resource #{ e ? "does not exist" : 'exists'}" do
+ context "and the resource #{e ? "does not exist" : "exists"}" do
before { resource.exist = e }
- it "should reply with 303 if the resource redirected" do
- resource.new_loc = URI.parse("/foo/bar")
+ it 'should reply with 303 if the resource redirected' do
+ resource.new_loc = URI.parse('/foo/bar')
subject.run
expect(response.code).to eq 303
- expect(response.headers['Location']).to eq "/foo/bar"
+ expect(response.headers['Location']).to eq '/foo/bar'
end
- it "should not reply with 303 if the resource did not redirect" do
+ it 'should not reply with 303 if the resource did not redirect' do
resource.new_loc = nil
subject.run
expect(response.code).to_not eq 303
@@ -864,40 +1032,61 @@ def process_post
end
end
- describe "#p11 (New resource?)" do
+ describe '#p11 (New resource?)' do
let(:resource) do
resource_with do
attr_writer :exist, :new_loc, :create
- def allowed_methods; %W{PUT POST}; end
- def resource_exists?; @exist; end
- def process_post; true; end
- def allow_missing_post?; true; end
- def post_is_create?; @create; end
- def create_path; @new_loc; end
- def content_types_accepted; [["text/plain", :accept_text]]; end
+ def allowed_methods
+ %W[PUT POST]
+ end
+
+ def resource_exists?
+ @exist
+ end
+
+ def process_post
+ true
+ end
+
+ def allow_missing_post?
+ true
+ end
+
+ def post_is_create?
+ @create
+ end
+
+ def create_path
+ @new_loc
+ end
+
+ def content_types_accepted
+ [['text/plain', :accept_text]]
+ end
+
def accept_text
response.headers['Location'] = @new_loc.to_s if @new_loc
true
end
end
end
- let(:body) { "new content" }
- let(:headers){ Webmachine::Headers['content-type' => 'text/plain'] }
+ let(:body) { 'new content' }
+ let(:headers) { Webmachine::Headers['content-type' => 'text/plain'] }
- context "when the method is PUT" do
- let(:method){ "PUT" }
+ context 'when the method is PUT' do
+ let(:method) { 'PUT' }
[true, false].each do |e|
- context "and the resource #{ e ? "does not exist" : 'exists'}" do
+ context "and the resource #{e ? "does not exist" : "exists"}" do
before { resource.exist = e }
- it "should reply with 201 when the Location header has been set" do
+ it 'should reply with 201 when the Location header has been set' do
resource.exist = e
- resource.new_loc = "http://ruby-doc.org/"
+ resource.new_loc = 'http://ruby-doc.org/'
subject.run
expect(response.code).to eq 201
end
- it "should not reply with 201 when the Location header has been set" do
+ it 'should not reply with 201 when the Location header has been set' do
resource.exist = e
subject.run
expect(response.headers['Location']).to be_nil
@@ -907,25 +1096,25 @@ def accept_text
end
end
- context "when the method is POST" do
- let(:method){ "POST" }
+ context 'when the method is POST' do
+ let(:method) { 'POST' }
[true, false].each do |e|
- context "and the resource #{ e ? 'exists' : "does not exist"}" do
+ context "and the resource #{e ? "exists" : "does not exist"}" do
before { resource.exist = e }
- it "should reply with 201 when post_is_create is true and create_path returns a URI" do
- resource.new_loc = created = "/foo/bar/baz"
+ it 'should reply with 201 when post_is_create is true and create_path returns a URI' do
+ resource.new_loc = created = '/foo/bar/baz'
resource.create = true
subject.run
expect(response.code).to eq 201
expect(response.headers['Location']).to eq created
end
- it "should reply with 500 when post_is_create is true and create_path returns nil" do
+ it 'should reply with 500 when post_is_create is true and create_path returns nil' do
resource.create = true
subject.run
expect(response.code).to eq 500
expect(response.error).to_not be_nil
end
- it "should not reply with 201 when post_is_create is false" do
+ it 'should not reply with 201 when post_is_create is false' do
resource.create = false
subject.run
expect(response.code).to_not eq 201
@@ -935,54 +1124,67 @@ def accept_text
end
end
- describe "#o14 (Conflict?)" do
+ describe '#o14 (Conflict?)' do
let(:resource) do
resource_with do
attr_writer :conflict
- def allowed_methods; %W{PUT}; end
- def is_conflict?; @conflict; end
+ def allowed_methods
+ %W[PUT]
+ end
+
+ def is_conflict?
+ @conflict
+ end
end
end
- let(:method){ "PUT" }
- it "should reply with 409 if the resource is in conflict" do
+ let(:method) { 'PUT' }
+ it 'should reply with 409 if the resource is in conflict' do
resource.conflict = true
subject.run
expect(response.code).to eq 409
end
- it "should not reply with 409 if the resource is in conflict" do
+ it 'should not reply with 409 if the resource is in conflict' do
resource.conflict = false
subject.run
expect(response.code).to_not eq 409
end
end
- describe "#m16 (DELETE?), #m20 (Delete enacted?)" do
- let(:method){ @method || "DELETE" }
+ describe '#m16 (DELETE?), #m20 (Delete enacted?)' do
+ let(:method) { @method || 'DELETE' }
let(:resource) do
resource_with do
attr_writer :deleted, :completed
- def allowed_methods; %w{GET DELETE}; end
- def delete_resource; @deleted; end
- def delete_completed?; @completed; end
+ def allowed_methods
+ %w[GET DELETE]
+ end
+
+ def delete_resource
+ @deleted
+ end
+
+ def delete_completed?
+ @completed
+ end
end
end
- it "should not reply with 202 if the method is not DELETE" do
- @method = "GET"
+ it 'should not reply with 202 if the method is not DELETE' do
+ @method = 'GET'
subject.run
expect(response.code).to_not eq 202
end
- it "should reply with 500 if the DELETE fails" do
+ it 'should reply with 500 if the DELETE fails' do
resource.deleted = false
subject.run
expect(response.code).to eq 500
end
- it "should reply with 202 if the DELETE succeeds but is not complete" do
+ it 'should reply with 202 if the DELETE succeeds but is not complete' do
resource.deleted = true
resource.completed = false
subject.run
expect(response.code).to eq 202
end
- it "should not reply with 202 if the DELETE succeeds and completes" do
+ it 'should not reply with 202 if the DELETE succeeds and completes' do
resource.completed = resource.deleted = true
subject.run
expect(response.code).to_not eq 202
@@ -994,45 +1196,65 @@ def delete_completed?; @completed; end
# describe "#n16 (POST?)" do it; end
# describe "#o16 (PUT?)" do it; end
- describe "#o18 (Multiple representations?)" do
+ describe '#o18 (Multiple representations?)' do
let(:resource) do
resource_with do
attr_writer :exist, :multiple
def delete_resource
- response.body = "Response content."
+ response.body = 'Response content.'
+ true
+ end
+
+ def delete_completed?
true
end
- def delete_completed?; true; end
- def allowed_methods; %W{GET HEAD PUT POST DELETE}; end
- def resource_exists?; @exist; end
- def allow_missing_post?; true; end
- def content_types_accepted; [[request.content_type, :accept_all]]; end
- def multiple_choices?; @multiple; end
+
+ def allowed_methods
+ %W[GET HEAD PUT POST DELETE]
+ end
+
+ def resource_exists?
+ @exist
+ end
+
+ def allow_missing_post?
+ true
+ end
+
+ def content_types_accepted
+ [[request.content_type, :accept_all]]
+ end
+
+ def multiple_choices?
+ @multiple
+ end
+
def process_post
- response.body = "Response content."
+ response.body = 'Response content.'
true
end
+
def accept_all
- response.body = "Response content."
+ response.body = 'Response content.'
true
end
end
end
- [["GET", true],["HEAD", true],["PUT", true],["PUT", false],["POST",true],["POST",false],
- ["DELETE", true]].each do |m, e|
- context "when the method is #{m} and the resource #{e ? 'exists' : 'does not exist' }" do
- let(:method){ m }
- let(:body) { %W{PUT POST}.include?(m) ? "request body" : "" }
- let(:headers) { %W{PUT POST}.include?(m) ? Webmachine::Headers['content-type' => 'text/plain'] : Webmachine::Headers.new }
+ [['GET', true], ['HEAD', true], ['PUT', true], ['PUT', false], ['POST', true], ['POST', false],
+ ['DELETE', true]].each do |m, e|
+ context "when the method is #{m} and the resource #{e ? "exists" : "does not exist"}" do
+ let(:method) { m }
+ let(:body) { %W[PUT POST].include?(m) ? 'request body' : '' }
+ let(:headers) { %W[PUT POST].include?(m) ? Webmachine::Headers['content-type' => 'text/plain'] : Webmachine::Headers.new }
before { resource.exist = e }
- it "should reply with 200 if there are not multiple representations" do
+ it 'should reply with 200 if there are not multiple representations' do
resource.multiple = false
subject.run
puts response.error if response.code == 500
expect(response.code).to eq 200
end
- it "should reply with 300 if there are multiple representations" do
+ it 'should reply with 300 if there are multiple representations' do
resource.multiple = true
subject.run
puts response.error if response.code == 500
@@ -1042,37 +1264,56 @@ def accept_all
end
end
- describe "#o20 (Response has entity?)" do
+ describe '#o20 (Response has entity?)' do
let(:resource) do
resource_with do
attr_writer :exist, :body
- def delete_resource; true; end
- def delete_completed?; true; end
- def allowed_methods; %{GET PUT POST DELETE}; end
- def resource_exists?; @exist; end
- def allow_missing_post?; true; end
- def content_types_accepted; [[request.content_type, :accept_all]]; end
+ def delete_resource
+ true
+ end
+
+ def delete_completed?
+ true
+ end
+
+ def allowed_methods
+ %(GET PUT POST DELETE)
+ end
+
+ def resource_exists?
+ @exist
+ end
+
+ def allow_missing_post?
+ true
+ end
+
+ def content_types_accepted
+ [[request.content_type, :accept_all]]
+ end
+
def process_post
response.body = @body if @body
true
end
+
def accept_all
response.body = @body if @body
true
end
end
end
- let(:method) { @method || "GET" }
- let(:headers) { %{PUT POST}.include?(method) ? Webmachine::Headers["content-type" => "text/plain"] : Webmachine::Headers.new }
- let(:body) { %{PUT POST}.include?(method) ? "This is the body." : nil }
- context "when a response body is present" do
- before { resource.body = "Hello, world!" }
+ let(:method) { @method || 'GET' }
+ let(:headers) { %(PUT POST).include?(method) ? Webmachine::Headers['content-type' => 'text/plain'] : Webmachine::Headers.new }
+ let(:body) { %(PUT POST).include?(method) ? 'This is the body.' : nil }
+ context 'when a response body is present' do
+ before { resource.body = 'Hello, world!' }
[
- ["PUT", false],
- ["POST", false],
- ["DELETE", true],
- ["POST", true],
- ["PUT", true]
+ ['PUT', false],
+ ['POST', false],
+ ['DELETE', true],
+ ['POST', true],
+ ['PUT', true]
].each do |m, e|
it "should not reply with 204 (via exists:#{e}, #{m})" do
@method = m
@@ -1082,13 +1323,13 @@ def accept_all
end
end
end
- context "when a response body is not present" do
+ context 'when a response body is not present' do
[
- ["PUT", false],
- ["POST", false],
- ["DELETE", true],
- ["POST", true],
- ["PUT", true]
+ ['PUT', false],
+ ['POST', false],
+ ['DELETE', true],
+ ['POST', true],
+ ['PUT', true]
].each do |m, e|
it "should reply with 204 (via exists:#{e}, #{m})" do
@method = m
@@ -1100,8 +1341,8 @@ def accept_all
end
end
- describe "On exception" do
- context "handle_exception is inherited." do
+ describe 'On error' do
+ context 'handle_exception is inherited.' do
let :resource do
resource_with do
def to_html
@@ -1110,22 +1351,22 @@ def to_html
end
end
- it "calls handle_exception" do
+ it 'calls handle_exception' do
expect(resource).to receive(:handle_exception).with instance_of(RuntimeError)
subject.run
end
- it "sets the response code to 500" do
+ it 'sets the response code to 500' do
subject.run
expect(response.code).to eq 500
end
end
- context "handle_exception is defined" do
+ context 'handle_exception is defined' do
let :resource do
resource_with do
def handle_exception(e)
- response.body = "error"
+ response.body = 'error'
end
def to_html
@@ -1134,12 +1375,12 @@ def to_html
end
end
- it "can define a response body" do
+ it 'can define a response body' do
subject.run
- expect(response.body).to eq "error"
+ expect(response.body).to eq 'error'
end
- it "sets the response code to 500" do
+ it 'sets the response code to 500' do
subject.run
expect(response.code).to eq 500
end
diff --git a/spec/webmachine/decision/fsm_spec.rb b/spec/webmachine/decision/fsm_spec.rb
index 8d20ea48..e1f8e800 100644
--- a/spec/webmachine/decision/fsm_spec.rb
+++ b/spec/webmachine/decision/fsm_spec.rb
@@ -5,15 +5,56 @@
subject { described_class.new(resource, request, response) }
+ let(:run_with_exception) do
+ subject.run
+ rescue Exception
+ end
+
describe 'handling of exceptions from decision methods' do
- let(:exception) { Exception.new }
+ let(:UNRESCUABLE_exceptions) do
+ Webmachine::RescuableException::UNRESCUABLE
+ end
+
+ describe 'rescueable exceptions' do
+ it 'does rescue Exception' do
+ allow(subject).to receive(Webmachine::Decision::Flow::START) { raise(Exception) }
+ expect(resource).to receive(:handle_exception).with instance_of(Exception)
+ expect { subject.run }.to_not raise_error
+ end
+
+ it 'does rescue a failed require' do
+ allow(subject).to receive(Webmachine::Decision::Flow::START) { require 'laterequire' }
+ expect(resource).to receive(:handle_exception).with instance_of(LoadError)
+ expect { subject.run }.to_not raise_error
+ end
+ end
+
+ describe 'UNRESCUABLE exceptions' do
+ shared_examples 'UNRESCUABLE' do |e|
+ specify "#{e} is not rescued" do
+ allow(subject).to receive(Webmachine::Decision::Flow::START) { raise(e) }
+ expect(resource).to_not receive(:handle_exception).with instance_of(e)
+ expect { subject.run }.to raise_error(e)
+ end
+ end
+ eary = Webmachine::RescuableException::UNRESCUABLE_DEFAULTS - [
+ Webmachine::MalformedRequest, # Webmachine rescues by default, so it won't re-raise.
+ SignalException # Requires raise in form 'raise SignalException, "SIGSOMESIGNAL"'.
+ # Haven't found a good no-op signal to use here.
+ ]
+ eary.each { |e| include_examples 'UNRESCUABLE', e }
+ end
+ end
+
+ describe 'handling of errors from decision methods' do
+ let(:error) { RuntimeError.new }
before do
- allow(subject).to receive(Webmachine::Decision::Flow::START) { raise exception }
+ allow(subject).to receive(Webmachine::Decision::Flow::START) { raise error }
end
it 'calls resource.handle_exception' do
- expect(resource).to receive(:handle_exception).with(exception)
+ expect(resource).to receive(:handle_exception).with(error)
subject.run
end
@@ -23,12 +64,12 @@
end
end
- describe 'handling of exceptions from resource.handle_exception' do
- let(:exception) { Exception.new('an error message') }
+ describe 'handling of errors from resource.handle_exception' do
+ let(:error) { RuntimeError.new('an error message') }
before do
allow(subject).to receive(Webmachine::Decision::Flow::START) { raise }
- allow(resource).to receive(:handle_exception) { raise exception }
+ allow(resource).to receive(:handle_exception) { raise error }
end
it 'does not call resource.handle_exception again' do
@@ -42,22 +83,41 @@
end
it 'renders an error' do
- expect(Webmachine).
- to receive(:render_error).
- with(500, request, response, { :message => exception.message })
+ expect(Webmachine)
+ .to receive(:render_error)
+ .with(500, request, response, {message: error.message})
subject.run
end
end
describe 'handling of exceptions from resource.finish_request' do
- let(:exception) { Exception.new }
+ let(:exception) { Class.new(RuntimeError).new }
before do
+ Webmachine::RescuableException.remove(exception)
allow(resource).to receive(:finish_request) { raise exception }
end
+ it 'does not call resource.handle_exception' do
+ expect(resource).to_not receive(:handle_exception)
+ run_with_exception
+ end
+
+ it 'does not call resource.finish_request again' do
+ expect(resource).to receive(:finish_request).once
+ run_with_exception
+ end
+ end
+
+ describe 'handling of errors from resource.finish_request' do
+ let(:error) { RuntimeError.new }
+
+ before do
+ allow(resource).to receive(:finish_request) { raise error }
+ end
+
it 'calls resource.handle_exception' do
- expect(resource).to receive(:handle_exception).with(exception)
+ expect(resource).to receive(:handle_exception).with(error)
subject.run
end
@@ -67,7 +127,7 @@
end
end
- it "sets the response code before calling finish_request" do
+ it 'sets the response code before calling finish_request' do
resource_class.class_eval do
class << self
attr_accessor :current_response_code
diff --git a/spec/webmachine/decision/helpers_spec.rb b/spec/webmachine/decision/helpers_spec.rb
index cd1b09d5..4caab4bb 100644
--- a/spec/webmachine/decision/helpers_spec.rb
+++ b/spec/webmachine/decision/helpers_spec.rb
@@ -1,20 +1,22 @@
require 'spec_helper'
describe Webmachine::Decision::Helpers do
- include_context "default resource"
+ include_context 'default resource'
subject { Webmachine::Decision::FSM.new(resource, request, response) }
def resource_with(&block)
klass = Class.new(Webmachine::Resource) do
- def to_html; "test resource"; end
+ def to_html
+ 'test resource'
+ end
end
- klass.module_eval(&block) if block_given?
+ klass.module_eval(&block) if block
klass.new(request, response)
end
let(:resource) { resource_with }
- describe "accepting request bodies" do
+ describe 'accepting request bodies' do
let(:resource) do
resource_with do
def initialize
@@ -22,31 +24,34 @@ def initialize
end
attr_accessor :accepted, :result
def content_types_accepted
- (accepted || []).map {|t| Array === t ? t : [t, :accept_doc] }
+ (accepted || []).map { |t| (Array === t) ? t : [t, :accept_doc] }
+ end
+
+ def accept_doc
+ result
end
- def accept_doc; result; end
end
end
- it "should return 415 when no types are accepted" do
+ it 'should return 415 when no types are accepted' do
expect(subject.accept_helper).to eq 415
end
- it "should return 415 when the posted type is not acceptable" do
- resource.accepted = %W{application/json}
- headers['Content-Type'] = "text/xml"
+ it 'should return 415 when the posted type is not acceptable' do
+ resource.accepted = %W[application/json]
+ headers['Content-Type'] = 'text/xml'
expect(subject.accept_helper).to eq 415
end
- it "should call the method for the first acceptable type, taking into account params" do
- resource.accepted = ["application/json;v=3", ["application/json", :other]]
+ it 'should call the method for the first acceptable type, taking into account params' do
+ resource.accepted = ['application/json;v=3', ['application/json', :other]]
expect(resource).to receive(:other).and_return(true)
headers['Content-Type'] = 'application/json;v=2'
expect(subject.accept_helper).to be(true)
end
end
- context "setting the Content-Length header when responding" do
+ context 'setting the Content-Length header when responding' do
[204, 205, 304].each do |code|
it "removes the header for entity-less response code #{code}" do
response.headers['Content-Length'] = '0'
@@ -81,82 +86,82 @@ def accept_doc; result; end
end
end
- describe "#encode_body" do
+ describe '#encode_body' do
before { subject.run }
- context "with a String body" do
+ context 'with a String body' do
before { response.body = '' }
- it "does not modify the response body" do
+ it 'does not modify the response body' do
subject.encode_body
expect(response.body).to be_instance_of(String)
end
- it "sets the Content-Length header in the response" do
+ it 'sets the Content-Length header in the response' do
subject.encode_body
expect(response.headers['Content-Length']).to eq response.body.bytesize.to_s
end
end
- shared_examples_for "a non-String body" do
- it "does not set the Content-Length header in the response" do
+ shared_examples_for 'a non-String body' do
+ it 'does not set the Content-Length header in the response' do
subject.encode_body
expect(response.headers).to_not have_key('Content-Length')
end
- it "sets the Transfer-Encoding response header to chunked" do
+ it 'sets the Transfer-Encoding response header to chunked' do
subject.encode_body
expect(response.headers['Transfer-Encoding']).to eq 'chunked'
end
end
- context "with an Enumerable body" do
+ context 'with an Enumerable body' do
before { response.body = ['one', 'two'] }
- it "wraps the response body in an EnumerableEncoder" do
+ it 'wraps the response body in an EnumerableEncoder' do
subject.encode_body
expect(response.body).to be_instance_of(Webmachine::Streaming::EnumerableEncoder)
end
- it_should_behave_like "a non-String body"
+ it_should_behave_like 'a non-String body'
end
- context "with a callable body" do
- before { response.body = Proc.new { 'proc' } }
+ context 'with a callable body' do
+ before { response.body = proc { 'proc' } }
- it "wraps the response body in a CallableEncoder" do
+ it 'wraps the response body in a CallableEncoder' do
subject.encode_body
expect(response.body).to be_instance_of(Webmachine::Streaming::CallableEncoder)
end
-
- it_should_behave_like "a non-String body"
+
+ it_should_behave_like 'a non-String body'
end
- context "with a Fiber body" do
- before { response.body = Fiber.new { Fiber.yield "foo" } }
+ context 'with a Fiber body' do
+ before { response.body = Fiber.new { Fiber.yield 'foo' } }
- it "wraps the response body in a FiberEncoder" do
+ it 'wraps the response body in a FiberEncoder' do
subject.encode_body
expect(response.body).to be_instance_of(Webmachine::Streaming::FiberEncoder)
end
- it_should_behave_like "a non-String body"
+ it_should_behave_like 'a non-String body'
end
- context "with a File body" do
- before { response.body = File.open("spec/spec_helper.rb", "r") }
+ context 'with a File body' do
+ before { response.body = File.open('spec/spec_helper.rb', 'r') }
- it "wraps the response body in an IOEncoder" do
+ it 'wraps the response body in an IOEncoder' do
subject.encode_body
expect(response.body).to be_instance_of(Webmachine::Streaming::IOEncoder)
end
- it "sets the Content-Length header to the size of the file" do
+ it 'sets the Content-Length header to the size of the file' do
subject.encode_body
expect(response.headers['Content-Length']).to eq File.stat('spec/spec_helper.rb').size.to_s
end
- it "progressively yields file contents for each enumeration" do
+ it 'progressively yields file contents for each enumeration' do
subject.encode_body
body_size = 0
response.body.each do |chunk|
@@ -166,50 +171,50 @@ def accept_doc; result; end
expect(body_size).to eq File.stat('spec/spec_helper.rb').size
end
- context "when the resource provides a non-identity encoding that the client accepts" do
+ context 'when the resource provides a non-identity encoding that the client accepts' do
let(:resource) do
resource_with do
def encodings_provided
- { "deflate" => :encode_deflate, "identity" => :encode_identity }
+ {'deflate' => :encode_deflate, 'identity' => :encode_identity}
end
end
end
let(:headers) do
- Webmachine::Headers.new({"Accept-Encoding" => "deflate, identity"})
+ Webmachine::Headers.new({'Accept-Encoding' => 'deflate, identity'})
end
- it_should_behave_like "a non-String body"
+ it_should_behave_like 'a non-String body'
end
end
- context "with a StringIO body" do
- before { response.body = StringIO.new("A VERY LONG STRING, NOT") }
+ context 'with a StringIO body' do
+ before { response.body = StringIO.new('A VERY LONG STRING, NOT') }
- it "wraps the response body in an IOEncoder" do
+ it 'wraps the response body in an IOEncoder' do
subject.encode_body
expect(response.body).to be_instance_of(Webmachine::Streaming::IOEncoder)
end
- it "sets the Content-Length header to the size of the string" do
+ it 'sets the Content-Length header to the size of the string' do
subject.encode_body
expect(response.headers['Content-Length']).to eq response.body.size.to_s
end
- context "when the resource provides a non-identity encoding that the client accepts" do
+ context 'when the resource provides a non-identity encoding that the client accepts' do
let(:resource) do
resource_with do
def encodings_provided
- { "deflate" => :encode_deflate, "identity" => :encode_identity }
+ {'deflate' => :encode_deflate, 'identity' => :encode_identity}
end
end
end
let(:headers) do
- Webmachine::Headers.new({"Accept-Encoding" => "deflate, identity"})
+ Webmachine::Headers.new({'Accept-Encoding' => 'deflate, identity'})
end
- it_should_behave_like "a non-String body"
+ it_should_behave_like 'a non-String body'
end
end
end
diff --git a/spec/webmachine/dispatcher/rfc3986_percent_decode_spec.rb b/spec/webmachine/dispatcher/rfc3986_percent_decode_spec.rb
new file mode 100644
index 00000000..b2a0236e
--- /dev/null
+++ b/spec/webmachine/dispatcher/rfc3986_percent_decode_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Webmachine::Dispatcher::Route do
+ describe '#rfc3986_percent_decode' do
+ def call_subject(value)
+ Webmachine::Dispatcher::Route.rfc3986_percent_decode(value)
+ end
+
+ it 'does not change un-encoded strings' do
+ expect(call_subject('this is a normal string, I think')).to eq 'this is a normal string, I think'
+ expect(call_subject('')).to eq ''
+ end
+
+ it 'decodes percent encoded sequences' do
+ expect(call_subject('/tenants/esckimo+test%20%65')).to eq '/tenants/esckimo+test e'
+ end
+
+ it 'leaves incorrectly encoded sequences as is' do
+ expect(call_subject('/tenants/esckimo+test%2%65')).to eq '/tenants/esckimo+test%2e'
+ end
+ end
+end
diff --git a/spec/webmachine/dispatcher/route_spec.rb b/spec/webmachine/dispatcher/route_spec.rb
index c5cdbb77..27272ad0 100644
--- a/spec/webmachine/dispatcher/route_spec.rb
+++ b/spec/webmachine/dispatcher/route_spec.rb
@@ -1,14 +1,17 @@
require 'spec_helper'
Webmachine::Dispatcher::Route.class_eval do
- def warn(*msgs); end # silence warnings for tests
+ # silence warnings for tests
+ def warn(*msgs)
+ end
end
describe Webmachine::Dispatcher::Route do
- let(:method) { "GET" }
- let(:uri) { URI.parse("http://localhost:8080/") }
- let(:request){ Webmachine::Request.new(method, uri, Webmachine::Headers.new, "") }
- let(:resource){ Class.new(Webmachine::Resource) }
+ let(:method) { 'GET' }
+ let(:uri) { URI.parse('http://localhost:8080/') }
+ let(:routing_tokens) { nil }
+ let(:request) { Webmachine::Request.new(method, uri, Webmachine::Headers.new, '', routing_tokens) }
+ let(:resource) { Class.new(Webmachine::Resource) }
describe '#apply' do
let(:route) {
@@ -16,13 +19,11 @@ def warn(*msgs); end # silence warnings for tests
}
describe 'a path_info fragment' do
- before do
- uri.path = '/hello/planet%20earth%20++'
- end
+ let(:uri) { URI.parse('http://localhost:8080/hello/planet%20earth%20++') }
it 'should decode the value' do
route.apply(request)
- expect(request.path_info).to eq({:string => 'planet earth ++'})
+ expect(request.path_info).to eq({string: 'planet earth ++'})
end
end
end
@@ -30,8 +31,10 @@ def warn(*msgs); end # silence warnings for tests
matcher :match_route do |*expected|
route = Webmachine::Dispatcher::Route.new(expected[0], Class.new(Webmachine::Resource), expected[1] || {})
match do |actual|
- request.uri.path = actual if String === actual
- route.match?(request)
+ uri = URI.parse('http://localhost:8080')
+ uri.path = actual
+ req = Webmachine::Request.new('GET', uri, Webmachine::Headers.new, '', routing_tokens)
+ route.match?(req)
end
failure_message do |_|
@@ -42,61 +45,61 @@ def warn(*msgs); end # silence warnings for tests
end
end
- it "warns about the deprecated string splat when initializing" do
- [["*"],["foo", "*"],["foo", :bar, "*"]].each do |path|
+ it 'warns about the deprecated string splat when initializing' do
+ [['*'], ['foo', '*'], ['foo', :bar, '*']].each do |path|
route = described_class.allocate
expect(route).to receive(:warn)
route.send :initialize, path, resource, {}
end
end
- context "matching a request" do
- context "on the root path" do
- subject { "/" }
+ context 'matching a request' do
+ context 'on the root path' do
+ subject { '/' }
it { is_expected.to match_route([]) }
it { is_expected.to match_route ['*'] }
it { is_expected.to match_route [:*] }
- it { is_expected.not_to match_route %w{foo} }
+ it { is_expected.not_to match_route %w[foo] }
it { is_expected.not_to match_route [:id] }
end
- context "on a deep path" do
- subject { "/foo/bar/baz" }
- it { is_expected.to match_route %w{foo bar baz} }
- it { is_expected.to match_route ['foo', :id, "baz"] }
+ context 'on a deep path' do
+ subject { '/foo/bar/baz' }
+ it { is_expected.to match_route %w[foo bar baz] }
+ it { is_expected.to match_route ['foo', :id, 'baz'] }
it { is_expected.to match_route ['foo', :*] }
it { is_expected.to match_route [:id, :*] }
it { is_expected.not_to match_route [] }
it { is_expected.not_to match_route ['bar', :*] }
end
- context "with a guard on the request method" do
- let(:uri){ URI.parse("http://localhost:8080/notes") }
+ context 'with a guard on the request method' do
+ let(:uri) { URI.parse('http://localhost:8080/notes') }
let(:route) do
described_class.new(
- ["notes"],
- lambda { |request| request.method == "POST" },
- resource
- )
+ ['notes'],
+ lambda { |request| request.method == 'POST' },
+ resource
+ )
end
subject { route }
- context "when guard passes" do
- let(:method){ "POST" }
+ context 'when guard passes' do
+ let(:method) { 'POST' }
it { is_expected.to be_match(request) }
- context "but the path match fails" do
- let(:uri){ URI.parse("http://localhost:8080/other") }
+ context 'but the path match fails' do
+ let(:uri) { URI.parse('http://localhost:8080/other') }
it { is_expected.not_to be_match(request) }
end
end
- context "when guard fails" do
- let(:method) { "GET" }
+ context 'when guard fails' do
+ let(:method) { 'GET' }
it { is_expected.not_to be_match(request) }
end
- context "when the guard responds to #call" do
+ context 'when the guard responds to #call' do
let(:guard_class) do
Class.new do
def initialize(method)
@@ -110,101 +113,136 @@ def call(request)
end
let(:route) do
- described_class.new(["notes"], guard_class.new("POST"), resource)
+ described_class.new(['notes'], guard_class.new('POST'), resource)
end
- context "when the guard passes" do
- let(:method){ "POST" }
+ context 'when the guard passes' do
+ let(:method) { 'POST' }
it { is_expected.to be_match(request) }
end
- context "when the guard fails" do
+ context 'when the guard fails' do
# let(:method){ "GET" }
it { is_expected.not_to be_match(request) }
end
end
end
+
+ context 'with a request with explicitly specified routing tokens' do
+ subject { '/some/route/foo/bar' }
+ let(:routing_tokens) { ['foo', 'bar'] }
+ it { is_expected.to match_route(['foo', 'bar']) }
+ it { is_expected.to match_route(['foo', :id]) }
+ it { is_expected.to match_route ['*'] }
+ it { is_expected.to match_route [:*] }
+ it { is_expected.not_to match_route(['some', 'route', 'foo', 'bar']) }
+ it { is_expected.not_to match_route %w[foo] }
+ it { is_expected.not_to match_route [:id] }
+ end
end
- context "applying bindings" do
- context "on the root path" do
+ context 'applying bindings' do
+ context 'on the root path' do
subject { described_class.new([], resource) }
before { subject.apply(request) }
- it "should assign the dispatched path to the empty string" do
- expect(request.disp_path).to eq("")
+ it 'should assign the dispatched path to the empty string' do
+ expect(request.disp_path).to eq('')
end
- it "should assign empty bindings" do
+ it 'should assign empty bindings' do
expect(request.path_info).to eq({})
end
- it "should assign empty path tokens" do
+ it 'should assign empty path tokens' do
expect(request.path_tokens).to eq([])
end
- context "with extra user-defined bindings" do
- subject { described_class.new([], resource, "bar" => "baz") }
+ context 'with extra user-defined bindings' do
+ subject { described_class.new([], resource, 'bar' => 'baz') }
- it "should assign the user-defined bindings" do
- expect(request.path_info).to eq({"bar" => "baz"})
+ it 'should assign the user-defined bindings' do
+ expect(request.path_info).to eq({'bar' => 'baz'})
end
end
- context "with a splat" do
+ context 'with a splat' do
subject { described_class.new([:*], resource) }
- it "should assign empty path tokens" do
+ it 'should assign empty path tokens' do
expect(request.path_tokens).to eq([])
end
end
- context "with a deprecated splat string" do
+ context 'with a deprecated splat string' do
subject { described_class.new(['*'], resource) }
- it "should assign empty path tokens" do
+ it 'should assign empty path tokens' do
expect(request.path_tokens).to eq([])
end
end
end
+ context 'on a deep path' do
+ subject { described_class.new(%w[foo bar baz], resource) }
+ let(:uri) { URI.parse('http://localhost:8080/foo/bar/baz') }
+ before { subject.apply(request) }
- context "on a deep path" do
- subject { described_class.new(%w{foo bar baz}, resource) }
- before { request.uri.path = "/foo/bar/baz"; subject.apply(request) }
-
- it "should assign the dispatched path as the path past the initial slash" do
- expect(request.disp_path).to eq("foo/bar/baz")
+ it 'should assign the dispatched path as the path past the initial slash' do
+ expect(request.disp_path).to eq('foo/bar/baz')
end
- it "should assign empty bindings" do
+ it 'should assign empty bindings' do
expect(request.path_info).to eq({})
end
- it "should assign empty path tokens" do
+ it 'should assign empty path tokens' do
expect(request.path_tokens).to eq([])
end
- context "with path variables" do
+ context 'with path variables' do
subject { described_class.new(['foo', :id, 'baz'], resource) }
- it "should assign the path variables in the bindings" do
- expect(request.path_info).to eq({:id => "bar"})
+ it 'should assign the path variables in the bindings' do
+ expect(request.path_info).to eq({id: 'bar'})
+ end
+ end
+ context 'with regex' do
+ subject { described_class.new([/foo/, /(.*)/, 'baz'], resource) }
+
+ it 'should assign the captures path variables' do
+ expect(request.path_info).to eq({captures: ['bar']})
+ end
+ end
+ context 'with multi-capture regex' do
+ subject { described_class.new([/foo/, /(.*)/, /baz\.(.*)/], resource) }
+ let(:uri) { URI.parse('http://localhost:8080/foo/bar/baz.json') }
+
+ it 'should assign the captures path variables' do
+ expect(request.path_info).to eq({captures: ['bar', 'json']})
+ end
+ end
+ context 'with named capture regex' do
+ subject { described_class.new(['foo', :bar, /(?[^.]+)\.(?.*)/], resource) }
+ let(:uri) { URI.parse('http://localhost:8080/foo/bar/baz.json') }
+
+ it 'should assign the captures path variables' do
+ expect(request.path_info).to eq({bar: 'bar', baz: 'baz', format: 'json'})
end
end
- context "with a splat" do
+ context 'with a splat' do
subject { described_class.new(['foo', :*], resource) }
- it "should capture the path tokens matched by the splat" do
- expect(request.path_tokens).to eq(%w{ bar baz })
+ it 'should capture the path tokens matched by the splat' do
+ expect(request.path_tokens).to eq(%w[bar baz])
end
end
- context "with a deprecated splat string" do
- subject { described_class.new(%w{foo *}, resource) }
+ context 'with a deprecated splat string' do
+ subject { described_class.new(%w[foo *], resource) }
- it "should capture the path tokens matched by the splat" do
- expect(request.path_tokens).to eq(%w{ bar baz })
+ it 'should capture the path tokens matched by the splat' do
+ expect(request.path_tokens).to eq(%w[bar baz])
end
end
end
diff --git a/spec/webmachine/dispatcher_spec.rb b/spec/webmachine/dispatcher_spec.rb
index faeede65..795ff0ab 100644
--- a/spec/webmachine/dispatcher_spec.rb
+++ b/spec/webmachine/dispatcher_spec.rb
@@ -2,23 +2,36 @@
describe Webmachine::Dispatcher do
let(:dispatcher) { Webmachine.application.dispatcher }
- let(:request) { Webmachine::Request.new("GET", URI.parse("http://localhost:8080/"), Webmachine::Headers["accept" => "*/*"], "") }
+ let(:request) { Webmachine::Request.new('GET', URI.parse('http://localhost:8080/'), Webmachine::Headers['accept' => '*/*'], '') }
+ let(:request2) { Webmachine::Request.new('GET', URI.parse('http://localhost:8080/hello/bob.html'), Webmachine::Headers['accept' => '*/*'], '') }
let(:response) { Webmachine::Response.new }
let(:resource) do
Class.new(Webmachine::Resource) do
- def to_html; "hello world!"; end
+ def to_html
+ 'hello world!'
+ end
end
end
let(:resource2) do
Class.new(Webmachine::Resource) do
- def to_html; "goodbye, cruel world"; end
+ def to_html
+ 'goodbye, cruel world'
+ end
end
end
- let(:fsm){ double }
+ let(:resource3) do
+ Class.new(Webmachine::Resource) do
+ def to_html
+ name, format = request.path_info[:captures]
+ "Hello #{name} with #{format}"
+ end
+ end
+ end
+ let(:fsm) { double }
before { dispatcher.reset }
- it "should add routes from a block" do
+ it 'should add routes from a block' do
_resource = resource
expect(Webmachine.routes do
add [:*], _resource
@@ -26,27 +39,33 @@ def to_html; "goodbye, cruel world"; end
expect(dispatcher.routes.size).to eq(1)
end
- it "should add routes" do
+ it 'should add routes' do
expect {
dispatcher.add_route [:*], resource
}.to_not raise_error
end
- it "should have add_route return the newly created route" do
+ it 'should have add_route return the newly created route' do
route = dispatcher.add_route [:*], resource
expect(route).to be_instance_of Webmachine::Dispatcher::Route
end
- it "should route to the proper resource" do
- dispatcher.add_route ["goodbye"], resource2
+ it 'should route to the proper resource' do
+ dispatcher.add_route ['goodbye'], resource2
dispatcher.add_route [:*], resource
expect(Webmachine::Decision::FSM).to receive(:new).with(instance_of(resource), request, response).and_return(fsm)
expect(fsm).to receive(:run)
dispatcher.dispatch(request, response)
end
+ it 'should handle regex path segments in route definition' do
+ dispatcher.add_route ['hello', /(.*)\.(.*)/], resource3
+ expect(Webmachine::Decision::FSM).to receive(:new).with(instance_of(resource3), request2, response).and_return(fsm)
+ expect(fsm).to receive(:run)
+ dispatcher.dispatch(request2, response)
+ end
- it "should apply route to request before creating the resource" do
- route = dispatcher.add_route [:*], resource
+ it 'should apply route to request before creating the resource' do
+ route = dispatcher.add_route [:*], resource
applied = false
expect(route).to receive(:apply) { applied = true }
@@ -58,32 +77,32 @@ def to_html; "goodbye, cruel world"; end
dispatcher.dispatch(request, response)
end
- it "should add routes with guards" do
- dispatcher.add [], lambda {|req| req.method == "POST" }, resource
+ it 'should add routes with guards' do
+ dispatcher.add [], lambda { |req| req.method == 'POST' }, resource
dispatcher.add [:*], resource2 do |req|
!req.query.empty?
end
- request.uri.query = "?foo=bar"
+ request.uri.query = '?foo=bar'
expect(dispatcher.routes.size).to eq(2)
expect(Webmachine::Decision::FSM).to receive(:new).with(instance_of(resource2), request, response).and_return(fsm)
expect(fsm).to receive(:run)
dispatcher.dispatch(request, response)
end
- it "should respond with a valid resource for a 404" do
+ it 'should respond with a valid resource for a 404' do
dispatcher.dispatch(request, response)
- expect(response.code).to eq(404)
+ expect(response.code).to eq(404)
expect(response.body).to_not be_empty
- expect(response.headers).to have_key('Content-Length')
- expect(response.headers).to have_key('Date')
+ expect(response.headers).to have_key('Content-Length')
+ expect(response.headers).to have_key('Date')
end
- it "should respond with a valid resource for a 404 with a custom Accept header" do
- request.headers['Accept'] = "application/json"
+ it 'should respond with a valid resource for a 404 with a custom Accept header' do
+ request.headers['Accept'] = 'application/json'
dispatcher.dispatch(request, response)
- expect(response.code).to eq(404)
+ expect(response.code).to eq(404)
expect(response.body).to_not be_empty
- expect(response.headers).to have_key('Content-Length')
- expect(response.headers).to have_key('Date')
+ expect(response.headers).to have_key('Content-Length')
+ expect(response.headers).to have_key('Date')
end
end
diff --git a/spec/webmachine/errors_spec.rb b/spec/webmachine/errors_spec.rb
index 84b9c493..2b10ae59 100644
--- a/spec/webmachine/errors_spec.rb
+++ b/spec/webmachine/errors_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
-describe "Webmachine errors" do
- describe ".render_error" do
- it "sets the given response code on the response object" do
- req = double('request', :method => 'GET').as_null_object
+describe 'Webmachine errors' do
+ describe '.render_error' do
+ it 'sets the given response code on the response object' do
+ req = double('request', method: 'GET').as_null_object
res = Webmachine::Response.new
Webmachine.render_error(404, req, res)
diff --git a/spec/webmachine/etags_spec.rb b/spec/webmachine/etags_spec.rb
index c3266c03..e8e65629 100644
--- a/spec/webmachine/etags_spec.rb
+++ b/spec/webmachine/etags_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Webmachine::ETag do
- let(:etag_str){ '"deadbeef12345678"' }
+ let(:etag_str) { '"deadbeef12345678"' }
let(:etag) { described_class.new etag_str }
subject { etag }
@@ -12,7 +12,7 @@
its(:etag) { should == '"deadbeef12345678"' }
it { is_expected.to eq(described_class.new(etag_str.dup)) }
- context "when the original etag is unquoted" do
+ context 'when the original etag is unquoted' do
let(:etag_str) { 'deadbeef12345678' }
it { is_expected.to eq(etag_str) }
@@ -21,7 +21,7 @@
it { is_expected.to eq(described_class.new(etag_str.dup)) }
end
- context "when the original etag contains unbalanced quotes" do
+ context 'when the original etag contains unbalanced quotes' do
let(:etag_str) { 'deadbeef"12345678' }
it { is_expected.to eq(etag_str) }
@@ -32,7 +32,7 @@
end
describe Webmachine::WeakETag do
- let(:strong_etag){ '"deadbeef12345678"' }
+ let(:strong_etag) { '"deadbeef12345678"' }
let(:weak_etag) { described_class.new strong_etag }
subject { weak_etag }
@@ -43,7 +43,7 @@
its(:etag) { should == '"deadbeef12345678"' }
it { is_expected.to eq(described_class.new(strong_etag.dup)) }
- context "when the original etag is unquoted" do
+ context 'when the original etag is unquoted' do
let(:strong_etag) { 'deadbeef12345678' }
it { is_expected.to eq(strong_etag) }
@@ -53,7 +53,7 @@
it { is_expected.to eq(described_class.new(strong_etag.dup)) }
end
- context "when the original etag contains unbalanced quotes" do
+ context 'when the original etag contains unbalanced quotes' do
let(:strong_etag) { 'deadbeef"12345678' }
it { is_expected.to eq(strong_etag) }
@@ -63,7 +63,7 @@
it { is_expected.to eq(described_class.new(strong_etag.dup)) }
end
- context "when the original etag is already a weak tag" do
+ context 'when the original etag is already a weak tag' do
let(:strong_etag) { 'W/"deadbeef12345678"' }
it { is_expected.to eq(strong_etag) }
diff --git a/spec/webmachine/events_spec.rb b/spec/webmachine/events_spec.rb
index aa703476..889802be 100644
--- a/spec/webmachine/events_spec.rb
+++ b/spec/webmachine/events_spec.rb
@@ -1,54 +1,54 @@
require 'spec_helper'
describe Webmachine::Events do
- describe ".backend" do
- it "defaults to AS::Notifications" do
+ describe '.backend' do
+ it 'defaults to AS::Notifications' do
expect(described_class.backend).to be(AS::Notifications)
end
end
- describe ".publish" do
- it "calls the backend" do
+ describe '.publish' do
+ it 'calls the backend' do
expect(described_class.backend).to receive(:publish).with('test.event', 1, 'two')
described_class.publish('test.event', 1, 'two')
end
end
- describe ".instrument" do
- it "calls the backend" do
+ describe '.instrument' do
+ it 'calls the backend' do
expect(described_class.backend).to receive(:instrument).with(
'test.event', {}
).and_yield
- described_class.instrument('test.event') { }
+ described_class.instrument('test.event') {}
end
end
- describe ".subscribe" do
- it "calls the backend" do
+ describe '.subscribe' do
+ it 'calls the backend' do
expect(described_class.backend).to receive(:subscribe).with(
'test.event'
).and_yield
- described_class.subscribe('test.event') { }
+ described_class.subscribe('test.event') {}
end
end
- describe ".subscribed" do
- it "calls the backend" do
- callback = Proc.new { }
+ describe '.subscribed' do
+ it 'calls the backend' do
+ callback = proc {}
expect(described_class.backend).to receive(:subscribed).with(
callback, 'test.event'
).and_yield
- described_class.subscribed(callback, 'test.event') { }
+ described_class.subscribed(callback, 'test.event') {}
end
end
- describe ".unsubscribe" do
- it "calls the backend" do
- subscriber = described_class.subscribe('test.event') { }
+ describe '.unsubscribe' do
+ it 'calls the backend' do
+ subscriber = described_class.subscribe('test.event') {}
expect(described_class.backend).to receive(:unsubscribe).with(subscriber)
diff --git a/spec/webmachine/headers_spec.rb b/spec/webmachine/headers_spec.rb
index bc2c497c..fe1107ae 100644
--- a/spec/webmachine/headers_spec.rb
+++ b/spec/webmachine/headers_spec.rb
@@ -1,22 +1,22 @@
require 'spec_helper'
describe Webmachine::Headers do
- it "should set and access values insensitive to case" do
- subject['Content-TYPE'] = "text/plain"
+ it 'should set and access values insensitive to case' do
+ subject['Content-TYPE'] = 'text/plain'
expect(subject['CONTENT-TYPE']).to eq('text/plain')
expect(subject.delete('CoNtEnT-tYpE')).to eq('text/plain')
end
- describe "#from_cgi" do
- it "should understand the Content-Length header" do
- headers = described_class.from_cgi("CONTENT_LENGTH" => 14)
- expect(headers["content-length"]).to eq(14)
+ describe '#from_cgi' do
+ it 'should understand the Content-Length header' do
+ headers = described_class.from_cgi('CONTENT_LENGTH' => 14)
+ expect(headers['content-length']).to eq(14)
end
end
- describe ".[]" do
+ describe '.[]' do
context "Webmachine::Headers['Content-Type', 'application/json']" do
- it "creates a hash with lowercase keys" do
+ it 'creates a hash with lowercase keys' do
headers = described_class[
'Content-Type', 'application/json',
'Accept', 'application/json'
@@ -30,7 +30,7 @@
end
context "Webmachine::Headers[[['Content-Type', 'application/json']]]" do
- it "creates a hash with lowercase keys" do
+ it 'creates a hash with lowercase keys' do
headers = described_class[
[
['Content-Type', 'application/json'],
@@ -46,7 +46,7 @@
end
context "Webmachine::Headers['Content-Type' => 'application/json']" do
- it "creates a hash with lowercase keys" do
+ it 'creates a hash with lowercase keys' do
headers = described_class[
'Content-Type' => 'application/json',
'Accept' => 'application/json'
@@ -60,39 +60,39 @@
end
end
- describe "#fetch" do
+ describe '#fetch' do
subject { described_class['Content-Type' => 'application/json'] }
- it "returns the value for the given key" do
+ it 'returns the value for the given key' do
expect(subject.fetch('conTent-tYpe')).to eq('application/json')
end
- context "acessing a missing key" do
- it "raises an IndexError" do
+ context 'acessing a missing key' do
+ it 'raises an IndexError' do
expect { subject.fetch('accept') }.to raise_error(IndexError)
end
- context "and a default value given" do
- it "returns the default value if the key does not exist" do
+ context 'and a default value given' do
+ it 'returns the default value if the key does not exist' do
expect(subject.fetch('accept', 'text/html')).to eq('text/html')
end
end
- context "and a block given" do
+ context 'and a block given' do
it "passes the value to the block and returns the block's result" do
- expect(subject.fetch('access') {|k| "#{k} not found"}).to eq('access not found')
+ expect(subject.fetch('access') { |k| "#{k} not found" }).to eq('access not found')
end
end
end
end
- context "filtering with #grep" do
- subject { described_class["content-type" => "text/plain", "etag" => '"abcdef1234567890"'] }
- it "should filter keys by the given pattern" do
- expect(subject.grep(/content/i)).to include("content-type")
+ context 'filtering with #grep' do
+ subject { described_class['content-type' => 'text/plain', 'etag' => '"abcdef1234567890"'] }
+ it 'should filter keys by the given pattern' do
+ expect(subject.grep(/content/i)).to include('content-type')
end
- it "should return a Headers instance" do
+ it 'should return a Headers instance' do
expect(subject.grep(/etag/i)).to be_instance_of(described_class)
end
end
diff --git a/spec/webmachine/media_type_spec.rb b/spec/webmachine/media_type_spec.rb
index 9d7b7598..e9cc4144 100644
--- a/spec/webmachine/media_type_spec.rb
+++ b/spec/webmachine/media_type_spec.rb
@@ -1,85 +1,85 @@
require 'spec_helper'
describe Webmachine::MediaType do
- let(:raw_type){ "application/xml;charset=UTF-8" }
- subject { described_class.new("application/xml", {"charset" => "UTF-8"}) }
+ let(:raw_type) { 'application/xml;charset=UTF-8' }
+ subject { described_class.new('application/xml', {'charset' => 'UTF-8'}) }
- context "equivalence" do
+ context 'equivalence' do
it { is_expected.to eq(raw_type) }
it { is_expected.to eq(described_class.parse(raw_type)) }
end
- context "when it is the wildcard type" do
- subject { described_class.new("*/*") }
+ context 'when it is the wildcard type' do
+ subject { described_class.new('*/*') }
it { is_expected.to be_matches_all }
end
- context "parsing a type" do
- it "should return MediaTypes untouched" do
+ context 'parsing a type' do
+ it 'should return MediaTypes untouched' do
expect(described_class.parse(subject)).to equal(subject)
end
- it "should parse a String" do
+ it 'should parse a String' do
type = described_class.parse(raw_type)
expect(type).to be_kind_of(described_class)
- expect(type.type).to eq("application/xml")
- expect(type.params).to eq({"charset" => "UTF-8"})
+ expect(type.type).to eq('application/xml')
+ expect(type.params).to eq({'charset' => 'UTF-8'})
end
- it "should parse a type/params pair" do
- type = described_class.parse(["application/xml", {"charset" => "UTF-8"}])
+ it 'should parse a type/params pair' do
+ type = described_class.parse(['application/xml', {'charset' => 'UTF-8'}])
expect(type).to be_kind_of(described_class)
- expect(type.type).to eq("application/xml")
- expect(type.params).to eq({"charset" => "UTF-8"})
+ expect(type.type).to eq('application/xml')
+ expect(type.params).to eq({'charset' => 'UTF-8'})
end
- it "should parse a type/params pair where the type has some params in the string" do
- type = described_class.parse(["application/xml;version=1", {"charset" => "UTF-8"}])
+ it 'should parse a type/params pair where the type has some params in the string' do
+ type = described_class.parse(['application/xml;version=1', {'charset' => 'UTF-8'}])
expect(type).to be_kind_of(described_class)
- expect(type.type).to eq("application/xml")
- expect(type.params).to eq({"charset" => "UTF-8", "version" => "1"})
+ expect(type.type).to eq('application/xml')
+ expect(type.params).to eq({'charset' => 'UTF-8', 'version' => '1'})
end
- it "should parse a type/params pair with params and whitespace in the string" do
- type = described_class.parse(["multipart/form-data; boundary=----------------------------2c46a7bec2b9", {"charset" => "UTF-8"}])
+ it 'should parse a type/params pair with params and whitespace in the string' do
+ type = described_class.parse(['multipart/form-data; boundary=----------------------------2c46a7bec2b9', {'charset' => 'UTF-8'}])
expect(type).to be_kind_of(described_class)
- expect(type.type).to eq("multipart/form-data")
- expect(type.params).to eq({"boundary" => "----------------------------2c46a7bec2b9", "charset" => "UTF-8"})
+ expect(type.type).to eq('multipart/form-data')
+ expect(type.params).to eq({'boundary' => '----------------------------2c46a7bec2b9', 'charset' => 'UTF-8'})
end
- it "should parse a type/params pair where type has single-token params" do
- type = described_class.parse(["text/html;q=1;rdfa", {"charset" => "UTF-8"}])
+ it 'should parse a type/params pair where type has single-token params' do
+ type = described_class.parse(['text/html;q=1;rdfa', {'charset' => 'UTF-8'}])
expect(type).to be_kind_of(described_class)
- expect(type.type).to eq("text/html")
- expect(type.params).to eq({"q" => "1", "rdfa" => "", "charset" => "UTF-8"})
+ expect(type.type).to eq('text/html')
+ expect(type.params).to eq({'q' => '1', 'rdfa' => '', 'charset' => 'UTF-8'})
end
- it "should raise an error when given an invalid type/params pair" do
+ it 'should raise an error when given an invalid type/params pair' do
expect {
- described_class.parse([false, "blah"])
+ described_class.parse([false, 'blah'])
}.to raise_error(ArgumentError)
end
end
- describe "matching a requested type" do
- it { is_expected.to be_exact_match("application/xml;charset=UTF-8") }
- it { is_expected.to be_exact_match("application/*;charset=UTF-8") }
- it { is_expected.to be_exact_match("*/*;charset=UTF-8") }
- it { is_expected.to be_exact_match("*;charset=UTF-8") }
- it { is_expected.not_to be_exact_match("text/xml") }
- it { is_expected.not_to be_exact_match("application/xml") }
- it { is_expected.not_to be_exact_match("application/xml;version=1") }
+ describe 'matching a requested type' do
+ it { is_expected.to be_exact_match('application/xml;charset=UTF-8') }
+ it { is_expected.to be_exact_match('application/*;charset=UTF-8') }
+ it { is_expected.to be_exact_match('*/*;charset=UTF-8') }
+ it { is_expected.to be_exact_match('*;charset=UTF-8') }
+ it { is_expected.not_to be_exact_match('text/xml') }
+ it { is_expected.not_to be_exact_match('application/xml') }
+ it { is_expected.not_to be_exact_match('application/xml;version=1') }
- it { is_expected.to be_type_matches("application/xml") }
- it { is_expected.to be_type_matches("application/*") }
- it { is_expected.to be_type_matches("*/*") }
- it { is_expected.to be_type_matches("*") }
- it { is_expected.not_to be_type_matches("text/xml") }
- it { is_expected.not_to be_type_matches("text/*") }
+ it { is_expected.to be_type_matches('application/xml') }
+ it { is_expected.to be_type_matches('application/*') }
+ it { is_expected.to be_type_matches('*/*') }
+ it { is_expected.to be_type_matches('*') }
+ it { is_expected.not_to be_type_matches('text/xml') }
+ it { is_expected.not_to be_type_matches('text/*') }
- it { is_expected.to be_params_match({}) }
- it { is_expected.to be_params_match({"charset" => "UTF-8"}) }
- it { is_expected.not_to be_params_match({"charset" => "Windows-1252"}) }
- it { is_expected.not_to be_params_match({"version" => "3"}) }
+ it { is_expected.to be_params_match({}) }
+ it { is_expected.to be_params_match({'charset' => 'UTF-8'}) }
+ it { is_expected.not_to be_params_match({'charset' => 'Windows-1252'}) }
+ it { is_expected.not_to be_params_match({'version' => '3'}) }
end
end
diff --git a/spec/webmachine/request_spec.rb b/spec/webmachine/request_spec.rb
index ff08b344..28c93596 100644
--- a/spec/webmachine/request_spec.rb
+++ b/spec/webmachine/request_spec.rb
@@ -3,53 +3,64 @@
describe Webmachine::Request do
subject { request }
- let(:uri) { URI.parse("http://localhost:8080/some/resource") }
- let(:http_method) { "GET" }
- let(:headers) { Webmachine::Headers.new }
- let(:body) { "" }
- let(:request) { Webmachine::Request.new(http_method, uri, headers, body) }
-
- it "should provide access to the headers via brackets" do
- subject.headers['Accept'] = "*/*"
- expect(subject["accept"]).to eq("*/*")
+ let(:uri) { URI.parse('http://localhost:8080/some/resource') }
+ let(:http_method) { 'GET' }
+ let(:headers) { Webmachine::Headers.new }
+ let(:body) { '' }
+ let(:routing_tokens) { nil }
+ let(:base_uri) { nil }
+ let(:request) { Webmachine::Request.new(http_method, uri, headers, body, routing_tokens, base_uri) }
+
+ it 'should provide access to the headers via brackets' do
+ subject.headers['Accept'] = '*/*'
+ expect(subject['accept']).to eq('*/*')
end
- it "should provide access to the cookies" do
- subject.headers['Cookie'] = 'name=value;name2=value2';
- expect(subject.cookies).to eq({ 'name' => 'value', 'name2' => 'value2' })
+ it 'should provide access to the cookies' do
+ subject.headers['Cookie'] = 'name=value;name2=value2'
+ expect(subject.cookies).to eq({'name' => 'value', 'name2' => 'value2'})
end
- it "should handle cookies with extra whitespace" do
- subject.headers['Cookie'] = 'name = value; name2 = value2';
- expect(subject.cookies).to eq({ 'name' => 'value', 'name2' => 'value2' })
+ it 'should handle cookies with extra whitespace' do
+ subject.headers['Cookie'] = 'name = value; name2 = value2'
+ expect(subject.cookies).to eq({'name' => 'value', 'name2' => 'value2'})
end
- it "should provide access to the headers via underscored methods" do
- subject.headers["Accept-Encoding"] = "identity"
- expect(subject.accept_encoding).to eq("identity")
+ it 'should provide access to the headers via underscored methods' do
+ subject.headers['Accept-Encoding'] = 'identity'
+ expect(subject.accept_encoding).to eq('identity')
expect(subject.content_md5).to be_nil
end
- it "should calculate a base URI" do
- expect(subject.base_uri).to eq(URI.parse("http://localhost:8080/"))
+ context 'base_uri' do
+ it 'should calculate a base URI' do
+ expect(subject.base_uri).to eq(URI.parse('http://localhost:8080/'))
+ end
+
+ context 'when base_uri has been explicitly set' do
+ let(:base_uri) { URI.parse('http://localhost:8080/some_base_uri/here') }
+ it 'should use the provided base_uri' do
+ expect(subject.base_uri).to eq(URI.parse('http://localhost:8080/some_base_uri/here'))
+ end
+ end
end
- it "should provide a hash of query parameters" do
- subject.uri.query = "foo=bar&baz=bam"
- expect(subject.query).to eq({"foo" => "bar", "baz" => "bam"})
+ it 'should provide a hash of query parameters' do
+ subject.uri.query = 'foo=bar&baz=bam'
+ expect(subject.query).to eq({'foo' => 'bar', 'baz' => 'bam'})
end
- it "should handle = being encoded as a query value." do
- subject.uri.query = "foo=bar%3D%3D"
- expect(subject.query).to eq({ "foo" => "bar=="})
+ it 'should handle = being encoded as a query value.' do
+ subject.uri.query = 'foo=bar%3D%3D'
+ expect(subject.query).to eq({'foo' => 'bar=='})
end
it "should treat '+' characters in query parameters as spaces" do
- subject.uri.query = "a%20b=foo+bar&c+d=baz%20quux"
- expect(subject.query).to eq({"a b" => "foo bar", "c d" => "baz quux"})
+ subject.uri.query = 'a%20b=foo+bar&c+d=baz%20quux'
+ expect(subject.query).to eq({'a b' => 'foo bar', 'c d' => 'baz quux'})
end
- it "should handle a query parameter value of nil" do
+ it 'should handle a query parameter value of nil' do
subject.uri.query = nil
expect(subject.query).to eq({})
end
@@ -57,38 +68,43 @@
describe '#has_body?' do
let(:wreq) do
Class.new {
- def initialize(body); @body = body; end
- def body; block_given? ? yield(@body) : @body; end
+ def initialize(body)
+ @body = body
+ end
+
+ def body
+ block_given? ? yield(@body) : @body
+ end
}
end
subject { request.has_body? }
- context "when body is nil" do
+ context 'when body is nil' do
let(:body) { nil }
it { is_expected.to be(false) }
end
- context "when body is an empty string" do
+ context 'when body is an empty string' do
let(:body) { '' }
it { is_expected.to be(false) }
end
- context "when body is not empty" do
+ context 'when body is not empty' do
let(:body) { 'foo' }
it { is_expected.to be(true) }
end
- context "when body is an empty LazyRequestBody" do
+ context 'when body is an empty LazyRequestBody' do
let(:body) { Webmachine::Adapters::LazyRequestBody.new(wreq.new('')) }
it { is_expected.to be(false) }
end
- context "when body is a LazyRequestBody" do
+ context 'when body is a LazyRequestBody' do
let(:body) { Webmachine::Adapters::LazyRequestBody.new(wreq.new('foo')) }
it { is_expected.to be(true) }
@@ -98,14 +114,14 @@ def body; block_given? ? yield(@body) : @body; end
describe '#https?' do
subject { request.https? }
- context "when the request was issued via HTTPS" do
- let(:uri) { URI.parse("https://localhost.com:8080/some/resource") }
+ context 'when the request was issued via HTTPS' do
+ let(:uri) { URI.parse('https://localhost.com:8080/some/resource') }
it { is_expected.to be(true) }
end
- context "when the request was not issued via HTTPS" do
- let(:uri) { URI.parse("http://localhost.com:8080/some/resource") }
+ context 'when the request was not issued via HTTPS' do
+ let(:uri) { URI.parse('http://localhost.com:8080/some/resource') }
it { is_expected.to be(false) }
end
@@ -114,14 +130,14 @@ def body; block_given? ? yield(@body) : @body; end
describe '#get?' do
subject { request.get? }
- context "when the request method is GET" do
- let(:http_method) { "GET" }
+ context 'when the request method is GET' do
+ let(:http_method) { 'GET' }
it { is_expected.to be(true) }
end
- context "when the request method is not GET" do
- let(:http_method) { "POST" }
+ context 'when the request method is not GET' do
+ let(:http_method) { 'POST' }
it { is_expected.to be(false) }
end
@@ -130,14 +146,14 @@ def body; block_given? ? yield(@body) : @body; end
describe '#head?' do
subject { request.head? }
- context "when the request method is HEAD" do
- let(:http_method) { "HEAD" }
+ context 'when the request method is HEAD' do
+ let(:http_method) { 'HEAD' }
it { is_expected.to be(true) }
end
- context "when the request method is not HEAD" do
- let(:http_method) { "GET" }
+ context 'when the request method is not HEAD' do
+ let(:http_method) { 'GET' }
it { is_expected.to be(false) }
end
@@ -146,14 +162,14 @@ def body; block_given? ? yield(@body) : @body; end
describe '#post?' do
subject { request.post? }
- context "when the request method is POST" do
- let(:http_method) { "POST" }
+ context 'when the request method is POST' do
+ let(:http_method) { 'POST' }
it { is_expected.to be(true) }
end
- context "when the request method is not POST" do
- let(:http_method) { "GET" }
+ context 'when the request method is not POST' do
+ let(:http_method) { 'GET' }
it { is_expected.to be(false) }
end
@@ -162,14 +178,14 @@ def body; block_given? ? yield(@body) : @body; end
describe '#put?' do
subject { request.put? }
- context "when the request method is PUT" do
- let(:http_method) { "PUT" }
+ context 'when the request method is PUT' do
+ let(:http_method) { 'PUT' }
it { is_expected.to be(true) }
end
- context "when the request method is not PUT" do
- let(:http_method) { "GET" }
+ context 'when the request method is not PUT' do
+ let(:http_method) { 'GET' }
it { is_expected.to be(false) }
end
@@ -178,14 +194,14 @@ def body; block_given? ? yield(@body) : @body; end
describe '#delete?' do
subject { request.delete? }
- context "when the request method is DELETE" do
- let(:http_method) { "DELETE" }
+ context 'when the request method is DELETE' do
+ let(:http_method) { 'DELETE' }
it { is_expected.to be(true) }
end
- context "when the request method is not DELETE" do
- let(:http_method) { "GET" }
+ context 'when the request method is not DELETE' do
+ let(:http_method) { 'GET' }
it { is_expected.to be(false) }
end
@@ -194,14 +210,14 @@ def body; block_given? ? yield(@body) : @body; end
describe '#trace?' do
subject { request.trace? }
- context "when the request method is TRACE" do
- let(:http_method) { "TRACE" }
+ context 'when the request method is TRACE' do
+ let(:http_method) { 'TRACE' }
it { is_expected.to be(true) }
end
- context "when the request method is not TRACE" do
- let(:http_method) { "GET" }
+ context 'when the request method is not TRACE' do
+ let(:http_method) { 'GET' }
it { is_expected.to be(false) }
end
@@ -210,14 +226,14 @@ def body; block_given? ? yield(@body) : @body; end
describe '#connect?' do
subject { request.connect? }
- context "when the request method is CONNECT" do
- let(:http_method) { "CONNECT" }
+ context 'when the request method is CONNECT' do
+ let(:http_method) { 'CONNECT' }
it { is_expected.to be(true) }
end
- context "when the request method is not CONNECT" do
- let(:http_method) { "GET" }
+ context 'when the request method is not CONNECT' do
+ let(:http_method) { 'GET' }
it { is_expected.to be(false) }
end
@@ -226,17 +242,35 @@ def body; block_given? ? yield(@body) : @body; end
describe '#options?' do
subject { request.options? }
- context "when the request method is OPTIONS" do
- let(:http_method) { "OPTIONS" }
+ context 'when the request method is OPTIONS' do
+ let(:http_method) { 'OPTIONS' }
it { is_expected.to be(true) }
end
- context "when the request method is not OPTIONS" do
- let(:http_method) { "GET" }
+ context 'when the request method is not OPTIONS' do
+ let(:http_method) { 'GET' }
it { is_expected.to be(false) }
end
end
+ describe '#routing_tokens' do
+ subject { request.routing_tokens }
+
+ context "haven't been explicitly set" do
+ let(:routing_tokens) { nil }
+ it 'extracts the routing tokens from the path portion of the uri' do
+ expect(subject).to eq(['some', 'resource'])
+ end
+ end
+
+ context 'have been explicitly set' do
+ let(:routing_tokens) { ['foo', 'bar'] }
+
+ it 'uses the specified routing_tokens' do
+ expect(subject).to eq(['foo', 'bar'])
+ end
+ end
+ end
end
diff --git a/spec/webmachine/rescueable_exception_spec.rb b/spec/webmachine/rescueable_exception_spec.rb
new file mode 100644
index 00000000..0147d2ed
--- /dev/null
+++ b/spec/webmachine/rescueable_exception_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+RSpec.describe Webmachine::RescuableException do
+ before { described_class.default! }
+
+ describe '.UNRESCUABLEs' do
+ specify 'returns an array of UNRESCUABLE exceptions' do
+ expect(described_class.UNRESCUABLEs).to eq(described_class::UNRESCUABLE_DEFAULTS)
+ end
+
+ specify 'returns an array of UNRESCUABLE exceptions, with custom exceptions added' do
+ described_class.remove(Exception)
+ expect(described_class.UNRESCUABLEs).to eq(described_class::UNRESCUABLE_DEFAULTS.dup.concat([Exception]))
+ end
+ end
+end
diff --git a/spec/webmachine/resource/authentication_spec.rb b/spec/webmachine/resource/authentication_spec.rb
index 8dad61bc..d7f89d24 100644
--- a/spec/webmachine/resource/authentication_spec.rb
+++ b/spec/webmachine/resource/authentication_spec.rb
@@ -3,63 +3,66 @@
describe Webmachine::Resource::Authentication do
subject { Webmachine::Decision::FSM.new(resource, request, response) }
let(:method) { 'GET' }
- let(:uri) { URI.parse("http://localhost/") }
+ let(:uri) { URI.parse('http://localhost/') }
let(:headers) { Webmachine::Headers.new }
- let(:body) { "" }
+ let(:body) { '' }
let(:request) { Webmachine::Request.new(method, uri, headers, body) }
let(:response) { Webmachine::Response.new }
def resource_with(&block)
klass = Class.new(Webmachine::Resource) do
- def to_html; "test resource"; end
+ def to_html
+ 'test resource'
+ end
end
- klass.module_eval(&block) if block_given?
+ klass.module_eval(&block) if block
klass.new(request, response)
end
- describe "Basic authentication" do
+ describe 'Basic authentication' do
let(:resource) do
resource_with do
include Webmachine::Resource::Authentication
+
attr_accessor :realm
def is_authorized?(auth)
- basic_auth(auth, @realm || "Webmachine") {|u,p| u == "webmachine" && p == "http" }
+ basic_auth(auth, @realm || 'Webmachine') { |u, p| u == 'webmachine' && p == 'http' }
end
end
end
- context "when no authorization is sent by the client" do
- it "should reply with a 401 Unauthorized and a WWW-Authenticate header using Basic" do
+ context 'when no authorization is sent by the client' do
+ it 'should reply with a 401 Unauthorized and a WWW-Authenticate header using Basic' do
subject.run
expect(response.code).to eq(401)
expect(response.headers['WWW-Authenticate']).to eq('Basic realm="Webmachine"')
end
- it "should use the specified realm in the WWW-Authenticate header" do
- resource.realm = "My App"
+ it 'should use the specified realm in the WWW-Authenticate header' do
+ resource.realm = 'My App'
subject.run
expect(response.headers['WWW-Authenticate']).to eq('Basic realm="My App"')
end
end
- context "when the client sends invalid authorization" do
+ context 'when the client sends invalid authorization' do
before do
- headers['Authorization'] = "Basic " + ["invalid:auth"].pack('m*').chomp
+ headers['Authorization'] = 'Basic ' + ['invalid:auth'].pack('m*').chomp
end
- it "should reply with a 401 Unauthorized and a WWW-Authenticate header using Basic" do
+ it 'should reply with a 401 Unauthorized and a WWW-Authenticate header using Basic' do
subject.run
expect(response.code).to eq(401)
expect(response.headers['WWW-Authenticate']).to eq('Basic realm="Webmachine"')
end
end
- context "when the client sends valid authorization" do
+ context 'when the client sends valid authorization' do
before do
- headers['Authorization'] = "Basic " + ["webmachine:http"].pack('m*').chomp
+ headers['Authorization'] = 'Basic ' + ['webmachine:http'].pack('m*').chomp
end
- it "should not reply with 401 Unauthorized" do
+ it 'should not reply with 401 Unauthorized' do
subject.run
expect(response.code).not_to eq(401)
end
diff --git a/spec/webmachine/response_spec.rb b/spec/webmachine/response_spec.rb
index f82ddf5b..4a81e060 100644
--- a/spec/webmachine/response_spec.rb
+++ b/spec/webmachine/response_spec.rb
@@ -1,44 +1,49 @@
require 'spec_helper'
describe Webmachine::Response do
-
- it "should have sane default values" do
+ it 'should have sane default values' do
expect(subject.code).to eq(200)
expect(subject.is_redirect?).to be(false)
expect(subject.headers).to be_empty
end
- describe "a redirected response" do
- let(:redirect_url) { "/" }
+ describe 'a redirected response' do
+ let(:redirect_url) { '/' }
before(:each) { subject.redirect_to redirect_url }
its(:is_redirect?) { should be(true) }
- it "should have a proper Location header" do
- expect(subject.headers["Location"]).to eq(redirect_url)
+ it 'should have a proper Location header' do
+ expect(subject.headers['Location']).to eq(redirect_url)
end
end
- describe "setting a cookie" do
- let(:cookie) { "monster" }
- let(:cookie_value) { "mash" }
+ describe 'setting a cookie' do
+ let(:cookie) { 'monster' }
+ let(:cookie_value) { 'mash' }
before(:each) { subject.set_cookie(cookie, cookie_value) }
- it "should have a proper Set-Cookie header" do
- expect(subject.headers["Set-Cookie"]).to include "monster=mash"
+ it 'should have a proper Set-Cookie header' do
+ expect(subject.headers['Set-Cookie']).to include 'monster=mash'
end
- describe "setting multiple cookies" do
- let(:cookie2) { "rodeo" }
- let(:cookie2_value) { "clown" }
- before(:each) { subject.set_cookie(cookie2, cookie2_value) }
+ describe 'setting multiple cookies' do
+ let(:cookie2) { 'rodeo' }
+ let(:cookie2_value) { 'clown' }
+ let(:cookie3) { 'color' }
+ let(:cookie3_value) { 'blue' }
+ before(:each) do
+ subject.set_cookie(cookie2, cookie2_value)
+ subject.set_cookie(cookie3, cookie3_value)
+ end
- it "should have a proper Set-Cookie header" do
- expect(subject.headers["Set-Cookie"]).to be_a Array
- expect(subject.headers["Set-Cookie"]).to include "rodeo=clown"
- expect(subject.headers["Set-Cookie"]).to include "monster=mash"
+ it 'should have a proper Set-Cookie header' do
+ expect(subject.headers['Set-Cookie']).to be_a Array
+ expect(subject.headers['Set-Cookie']).to include 'rodeo=clown'
+ expect(subject.headers['Set-Cookie']).to include 'monster=mash'
+ expect(subject.headers['Set-Cookie']).to include 'color=blue'
end
end
end
diff --git a/spec/webmachine/trace/fsm_spec.rb b/spec/webmachine/trace/fsm_spec.rb
index 0ecc8a29..6e7a6a57 100644
--- a/spec/webmachine/trace/fsm_spec.rb
+++ b/spec/webmachine/trace/fsm_spec.rb
@@ -1,34 +1,34 @@
require 'spec_helper'
describe Webmachine::Trace::FSM do
- include_context "default resource"
+ include_context 'default resource'
subject { Webmachine::Decision::FSM.new(resource, request, response) }
before { Webmachine::Trace.trace_store = :memory }
- context "when tracing is enabled" do
+ context 'when tracing is enabled' do
before { allow(Webmachine::Trace).to receive(:trace?).and_return(true) }
- it "proxies the resource" do
+ it 'proxies the resource' do
expect(subject.resource).to be_kind_of(Webmachine::Trace::ResourceProxy)
end
- it "records a trace" do
+ it 'records a trace' do
subject.run
expect(response.trace).to_not be_empty
expect(Webmachine::Trace.traces.size).to eq(1)
end
- it "commits the trace to separate storage when the request has finished processing" do
+ it 'commits the trace to separate storage when the request has finished processing' do
expect(Webmachine::Trace).to receive(:record).with(subject.resource.object_id.to_s, response.trace).and_return(true)
subject.run
end
end
- context "when tracing is disabled" do
+ context 'when tracing is disabled' do
before { allow(Webmachine::Trace).to receive(:trace?).and_return(false) }
- it "leaves no trace" do
+ it 'leaves no trace' do
subject.run
expect(response.trace).to be_empty
expect(Webmachine::Trace.traces).to be_empty
diff --git a/spec/webmachine/trace/resource_proxy_spec.rb b/spec/webmachine/trace/resource_proxy_spec.rb
index 4d87bf32..86e9cc3f 100644
--- a/spec/webmachine/trace/resource_proxy_spec.rb
+++ b/spec/webmachine/trace/resource_proxy_spec.rb
@@ -2,33 +2,32 @@
require 'webmachine/trace/resource_proxy'
describe Webmachine::Trace::ResourceProxy do
- include_context "default resource"
+ include_context 'default resource'
subject { described_class.new(resource) }
- it "duck-types all callback methods" do
+ it 'duck-types all callback methods' do
Webmachine::Resource::Callbacks.instance_methods(false).each do |m|
expect(subject).to respond_to(m)
end
end
- it "logs invocations of callbacks" do
+ it 'logs invocations of callbacks' do
subject.generate_etag
- expect(response.trace).to eq([{:type => :attempt, :name => "(default)#generate_etag"},
- {:type => :result, :value => nil}])
-
+ expect(response.trace).to eq([{type: :attempt, name: '(default)#generate_etag'},
+ {type: :result, value: nil}])
end
- it "logs invocations of body-producing methods" do
- expect(subject.content_types_provided).to eq([["text/html", :to_html]])
+ it 'logs invocations of body-producing methods' do
+ expect(subject.content_types_provided).to eq([['text/html', :to_html]])
subject.to_html
expect(response.trace[-2][:type]).to eq(:attempt)
expect(response.trace[-2][:name]).to match(/to_html$/)
- expect(response.trace[-2][:source]).to include("spec_helper.rb") if response.trace[-2][:source]
- expect(response.trace[-1]).to eq({:type => :result, :value => "Hello, world!"})
+ expect(response.trace[-2][:source]).to include('spec_helper.rb') if response.trace[-2][:source]
+ expect(response.trace[-1]).to eq({type: :result, value: 'Hello, world!'})
end
- it "sets the trace id header when the request has finished processing" do
+ it 'sets the trace id header when the request has finished processing' do
subject.finish_request
- expect(response.headers["X-Webmachine-Trace-Id"]).to eq(subject.object_id.to_s)
+ expect(response.headers['X-Webmachine-Trace-Id']).to eq(subject.object_id.to_s)
end
end
diff --git a/spec/webmachine/trace/trace_store_spec.rb b/spec/webmachine/trace/trace_store_spec.rb
index 324d80fa..9b4692e3 100644
--- a/spec/webmachine/trace/trace_store_spec.rb
+++ b/spec/webmachine/trace/trace_store_spec.rb
@@ -1,29 +1,29 @@
require 'spec_helper'
require 'fileutils'
-shared_examples_for "trace storage" do
+shared_examples_for 'trace storage' do
it { is_expected.to respond_to(:[]=) }
it { is_expected.to respond_to(:keys) }
it { is_expected.to respond_to(:fetch) }
- it "stores a trace" do
- subject["foo"] = [:bar]
- expect(subject.fetch("foo")).to eq([:bar])
+ it 'stores a trace' do
+ subject['foo'] = [:bar]
+ expect(subject.fetch('foo')).to eq([:bar])
end
- it "lists a stored trace in the keys" do
- subject["foo"] = [:bar]
- expect(subject.keys).to eq(["foo"])
+ it 'lists a stored trace in the keys' do
+ subject['foo'] = [:bar]
+ expect(subject.keys).to eq(['foo'])
end
end
describe Webmachine::Trace::PStoreTraceStore do
- subject { described_class.new("./wmtrace") }
- after { FileUtils.rm_rf("./wmtrace") }
- it_behaves_like "trace storage"
+ subject { described_class.new('./wmtrace') }
+ after { FileUtils.rm_rf('./wmtrace') }
+ it_behaves_like 'trace storage'
end
-describe "Webmachine::Trace :memory Trace Store (Hash)" do
- subject { Hash.new }
- it_behaves_like "trace storage"
+describe 'Webmachine::Trace :memory Trace Store (Hash)' do
+ subject { {} }
+ it_behaves_like 'trace storage'
end
diff --git a/spec/webmachine/trace_spec.rb b/spec/webmachine/trace_spec.rb
index 346e8d27..d855a34d 100644
--- a/spec/webmachine/trace_spec.rb
+++ b/spec/webmachine/trace_spec.rb
@@ -3,13 +3,13 @@
describe Webmachine::Trace do
subject { described_class }
- context "determining whether the resource should be traced" do
- include_context "default resource"
- it "does not trace by default" do
+ context 'determining whether the resource should be traced' do
+ include_context 'default resource'
+ it 'does not trace by default' do
expect(subject.trace?(resource)).to be(false)
end
- it "traces when the resource enables tracing" do
+ it 'traces when the resource enables tracing' do
expect(resource).to receive(:trace?).and_return(true)
expect(subject.trace?(resource)).to be(true)
end
diff --git a/webmachine.gemspec b/webmachine.gemspec
index 07b87e7f..cbc61ac0 100644
--- a/webmachine.gemspec
+++ b/webmachine.gemspec
@@ -1,25 +1,36 @@
-$:.push File.expand_path("../lib", __FILE__)
-require 'webmachine/version'
+require_relative 'lib/webmachine/version'
Gem::Specification.new do |gem|
- gem.name = "webmachine"
+ gem.name = 'webmachine'
gem.version = Webmachine::VERSION
- gem.summary = %Q{webmachine is a toolkit for building HTTP applications,}
+ gem.summary = %(webmachine is a toolkit for building HTTP applications,)
gem.description = <<-DESC.gsub(/\s+/, ' ')
webmachine is a toolkit for building HTTP applications in a declarative fashion, that avoids
the confusion of going through a CGI-style interface like Rack. It is strongly influenced
by the original Erlang project of the same name and shares its opinionated nature about HTTP.
DESC
- gem.homepage = "http://github.com/seancribbs/webmachine-ruby"
- gem.authors = ["Sean Cribbs"]
- gem.email = ["sean@basho.com"]
- gem.license = "Apache 2.0"
+ gem.homepage = 'https://github.com/webmachine/webmachine-ruby'
+ gem.authors = ['Sean Cribbs']
+ gem.email = ['sean@basho.com']
+ gem.license = 'Apache-2.0'
- gem.add_runtime_dependency(%q, [">= 0.4.0"])
- gem.add_runtime_dependency(%q)
- gem.add_runtime_dependency(%q, ["~> 1.0"])
+ gem.metadata['allowed_push_host'] = 'https://rubygems.org'
+ gem.metadata['bug_tracker_uri'] = "#{gem.homepage}/issues"
+ gem.metadata['changelog_uri'] = "#{gem.homepage}/blob/HEAD/CHANGELOG.md"
+ gem.metadata['documentation_uri'] = "https://www.rubydoc.info/gems/webmachine/#{gem.version}"
+ gem.metadata['homepage_uri'] = gem.homepage
+ gem.metadata['source_code_uri'] = gem.homepage
+ gem.metadata['wiki_uri'] = "#{gem.homepage}/wiki"
- ignores = File.read(".gitignore").split(/\r?\n/).reject{ |f| f =~ /^(#.+|\s*)$/ }.map {|f| Dir[f] }.flatten
- gem.files = (Dir['**/*','.gitignore'] - ignores).reject {|f| !File.file?(f) }
- gem.test_files = (Dir['spec/**/*','features/**/*','.gitignore'] - ignores).reject {|f| !File.file?(f) }
+ gem.required_ruby_version = '>= 2.6.0'
+
+ gem.add_runtime_dependency('as-notifications', ['>= 1.0.2', '< 2.0'])
+ gem.add_runtime_dependency('base64')
+ gem.add_runtime_dependency('i18n', ['>= 0.4.0'])
+ gem.add_runtime_dependency('multi_json')
+
+ ignores = File.read('.gitignore').split(/\r?\n/).reject { |f| f =~ /^(#.+|\s*)$/ }.map { |f| Dir[f] }.flatten
+ gem.files = (Dir['**/*', '.gitignore'] - ignores).reject do |f|
+ !File.file?(f) || f.start_with?(*%w[. Gemfile RELEASING Rakefile doc/ memory_test pkg/ spec/ vendor/ webmachine.gemspec])
+ end
end