webmachine 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/CHANGELOG.md +4 -0
  4. data/Gemfile +13 -11
  5. data/README.md +85 -89
  6. data/Rakefile +0 -1
  7. data/documentation/adapters.md +39 -0
  8. data/documentation/authentication-and-authorization.md +37 -0
  9. data/documentation/configurator.md +19 -0
  10. data/documentation/error-handling.md +86 -0
  11. data/documentation/examples.md +215 -0
  12. data/documentation/how-it-works.md +76 -0
  13. data/documentation/routes.md +97 -0
  14. data/documentation/validation.md +159 -0
  15. data/documentation/versioning-apis.md +74 -0
  16. data/documentation/visual-debugger.md +38 -0
  17. data/examples/application.rb +2 -2
  18. data/examples/debugger.rb +1 -1
  19. data/lib/webmachine.rb +3 -1
  20. data/lib/webmachine/adapter.rb +7 -13
  21. data/lib/webmachine/adapters.rb +1 -2
  22. data/lib/webmachine/adapters/httpkit.rb +74 -0
  23. data/lib/webmachine/adapters/lazy_request_body.rb +1 -2
  24. data/lib/webmachine/adapters/rack.rb +37 -21
  25. data/lib/webmachine/adapters/reel.rb +21 -23
  26. data/lib/webmachine/adapters/webrick.rb +16 -16
  27. data/lib/webmachine/application.rb +2 -2
  28. data/lib/webmachine/chunked_body.rb +3 -4
  29. data/lib/webmachine/constants.rb +75 -0
  30. data/lib/webmachine/decision/conneg.rb +12 -10
  31. data/lib/webmachine/decision/flow.rb +31 -21
  32. data/lib/webmachine/decision/fsm.rb +10 -18
  33. data/lib/webmachine/decision/helpers.rb +9 -37
  34. data/lib/webmachine/dispatcher.rb +13 -10
  35. data/lib/webmachine/dispatcher/route.rb +18 -8
  36. data/lib/webmachine/errors.rb +7 -1
  37. data/lib/webmachine/header_negotiation.rb +25 -0
  38. data/lib/webmachine/headers.rb +7 -2
  39. data/lib/webmachine/locale/en.yml +7 -5
  40. data/lib/webmachine/media_type.rb +10 -8
  41. data/lib/webmachine/request.rb +44 -15
  42. data/lib/webmachine/resource.rb +1 -1
  43. data/lib/webmachine/resource/callbacks.rb +6 -4
  44. data/lib/webmachine/spec/IO_response.body +1 -0
  45. data/lib/webmachine/spec/adapter_lint.rb +70 -36
  46. data/lib/webmachine/spec/test_resource.rb +10 -4
  47. data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
  48. data/lib/webmachine/streaming/io_encoder.rb +6 -0
  49. data/lib/webmachine/trace.rb +1 -0
  50. data/lib/webmachine/trace/fsm.rb +20 -10
  51. data/lib/webmachine/trace/resource_proxy.rb +2 -0
  52. data/lib/webmachine/translation.rb +2 -1
  53. data/lib/webmachine/version.rb +3 -3
  54. data/memory_test.rb +37 -0
  55. data/spec/spec_helper.rb +9 -9
  56. data/spec/webmachine/adapter_spec.rb +14 -15
  57. data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
  58. data/spec/webmachine/adapters/rack_spec.rb +6 -6
  59. data/spec/webmachine/adapters/reel_spec.rb +15 -11
  60. data/spec/webmachine/adapters/webrick_spec.rb +2 -2
  61. data/spec/webmachine/application_spec.rb +18 -17
  62. data/spec/webmachine/chunked_body_spec.rb +3 -3
  63. data/spec/webmachine/configuration_spec.rb +5 -5
  64. data/spec/webmachine/cookie_spec.rb +13 -13
  65. data/spec/webmachine/decision/conneg_spec.rb +48 -42
  66. data/spec/webmachine/decision/falsey_spec.rb +4 -4
  67. data/spec/webmachine/decision/flow_spec.rb +194 -144
  68. data/spec/webmachine/decision/fsm_spec.rb +17 -17
  69. data/spec/webmachine/decision/helpers_spec.rb +20 -20
  70. data/spec/webmachine/dispatcher/route_spec.rb +73 -27
  71. data/spec/webmachine/dispatcher_spec.rb +34 -24
  72. data/spec/webmachine/errors_spec.rb +1 -1
  73. data/spec/webmachine/etags_spec.rb +19 -19
  74. data/spec/webmachine/events_spec.rb +6 -6
  75. data/spec/webmachine/headers_spec.rb +14 -14
  76. data/spec/webmachine/media_type_spec.rb +36 -36
  77. data/spec/webmachine/request_spec.rb +33 -33
  78. data/spec/webmachine/resource/authentication_spec.rb +6 -6
  79. data/spec/webmachine/response_spec.rb +12 -12
  80. data/spec/webmachine/trace/fsm_spec.rb +8 -8
  81. data/spec/webmachine/trace/resource_proxy_spec.rb +9 -9
  82. data/spec/webmachine/trace/trace_store_spec.rb +5 -5
  83. data/spec/webmachine/trace_spec.rb +3 -3
  84. data/webmachine.gemspec +2 -6
  85. metadata +48 -206
  86. data/lib/webmachine/adapters/hatetepe.rb +0 -108
  87. data/lib/webmachine/adapters/mongrel.rb +0 -127
  88. data/lib/webmachine/dispatcher/not_found_resource.rb +0 -5
  89. data/lib/webmachine/fiber18.rb +0 -88
  90. data/spec/webmachine/adapters/hatetepe_spec.rb +0 -60
  91. data/spec/webmachine/adapters/mongrel_spec.rb +0 -16
@@ -0,0 +1,74 @@
1
+ # Versioning APIs
2
+
3
+ ## By URL
4
+
5
+ ```ruby
6
+
7
+ class MyResourceV1 < Webmachine::Resource
8
+
9
+ end
10
+
11
+ class MyResourceV2 < Webmachine::Resource
12
+
13
+ end
14
+
15
+ App = Webmachine::Application.new do |app|
16
+ app.routes do
17
+ add ["api", "v1", "myresource"], MyResourceV1
18
+ add ["api", "v2", "myresource"], MyResourceV2
19
+ end
20
+ end
21
+
22
+ ```
23
+
24
+ ## By Content-Type
25
+
26
+ Note: if no Accept header is specified, then the first content type in the list will be chosen.
27
+
28
+ ```ruby
29
+
30
+ class MyResource < Webmachine::Resource
31
+
32
+ def content_types_provided
33
+ [
34
+ ["application/myapp.v2+json", :to_json_v2],
35
+ ["application/myapp.v1+json", :to_json_v1]
36
+ ]
37
+ end
38
+
39
+ end
40
+
41
+ ```
42
+
43
+ ## By Header value
44
+
45
+ ```ruby
46
+
47
+ class MyResourceV1 < Webmachine::Resource
48
+
49
+ end
50
+
51
+ class MyResourceV2 < Webmachine::Resource
52
+
53
+ end
54
+
55
+ class VersionGuard
56
+
57
+ def initialize version
58
+ @version = version
59
+ end
60
+
61
+ def call(request)
62
+ request.headers['X-My-App-Version'] == @version
63
+ end
64
+
65
+ end
66
+
67
+ App = Webmachine::Application.new do |app|
68
+ app.routes do
69
+ add ["api", "myresource"], VersionGuard.new("1"), MyResourceV1
70
+ add ["api", "myresource"], VersionGuard.new("2"), MyResourceV2
71
+ end
72
+ end
73
+
74
+ ```
@@ -0,0 +1,38 @@
1
+ ### Visual debugger
2
+
3
+ In development, you can turn on tracing of the
4
+ decision graph for a resource by implementing the `#trace?` callback
5
+ so that it returns true:
6
+
7
+ ```ruby
8
+ class MyTracedResource < Webmachine::Resource
9
+ def trace?
10
+ true
11
+ end
12
+
13
+ # The rest of your callbacks...
14
+ end
15
+ ```
16
+
17
+ Then enable the visual debugger resource by adding a route to your
18
+ configuration:
19
+
20
+ ```ruby
21
+ Webmachine.application.routes do
22
+ # This can be any path as long as it ends with :*
23
+ add ['trace', :*], Webmachine::Trace::TraceResource
24
+ # The rest of your routes...
25
+ end
26
+ ```
27
+
28
+ Now when you visit your traced resource, a trace of the request
29
+ process will be recorded in memory. Open your browser to `/trace` to
30
+ list the recorded traces and inspect the result. The response from your
31
+ traced resource will also include the `X-Webmachine-Trace-Id` that you
32
+ can use to lookup the trace. It might look something like this:
33
+
34
+ ![preview calls at decision](http://seancribbs-skitch.s3.amazonaws.com/Webmachine_Trace_2156885920-20120625-100153.png)
35
+
36
+ Refer to
37
+ [examples/debugger.rb](/examples/debugger.rb)
38
+ for an example of how to enable the debugger.
@@ -25,10 +25,10 @@ MyApp = Webmachine::Application.new do |app|
25
25
  config.adapter = :WEBrick
26
26
  end
27
27
  # And add routes like this:
28
- app.add_route ['fizz', :buzz, '*'], RouteDebugResource
28
+ app.add_route ['fizz', :buzz, :*], RouteDebugResource
29
29
  # OR add routes this way:
30
30
  app.routes do
31
- add [:test, :foo, '*'], RouteDebugResource
31
+ add [:test, :foo, :*], RouteDebugResource
32
32
  end
33
33
  end
34
34
 
data/examples/debugger.rb CHANGED
@@ -31,7 +31,7 @@ end
31
31
 
32
32
  TraceExample = Webmachine::Application.new do |app|
33
33
  app.routes do
34
- add ['trace', '*'], Webmachine::Trace::TraceResource
34
+ add ['trace', :*], Webmachine::Trace::TraceResource
35
35
  add [], MyTracedResource
36
36
  end
37
37
  end
data/lib/webmachine.rb CHANGED
@@ -1,10 +1,12 @@
1
- require 'webmachine/configuration'
1
+ require 'webmachine/configuration'
2
+ require 'webmachine/constants'
2
3
  require 'webmachine/cookie'
3
4
  require 'webmachine/headers'
4
5
  require 'webmachine/request'
5
6
  require 'webmachine/response'
6
7
  require 'webmachine/etags'
7
8
  require 'webmachine/errors'
9
+ require 'webmachine/header_negotiation'
8
10
  require 'webmachine/decision'
9
11
  require 'webmachine/streaming'
10
12
  require 'webmachine/adapter'
@@ -5,23 +5,17 @@ module Webmachine
5
5
  # @abstract Subclass and override {#run} to implement a custom adapter.
6
6
  class Adapter
7
7
 
8
- # @return [Webmachine::Configuration] the application's configuration.
9
- attr_reader :configuration
8
+ # @return [Webmachine::Application] returns the application
9
+ attr_reader :application
10
10
 
11
- # @return [Webmachine::Dispatcher] the application's dispatcher.
12
- attr_reader :dispatcher
13
-
14
- # @param [Webmachine::Configuration] configuration the application's
15
- # configuration.
16
- # @param [Webmachine::Dispatcher] dispatcher the application's dispatcher.
17
- def initialize(configuration, dispatcher)
18
- @configuration = configuration
19
- @dispatcher = dispatcher
11
+ # @param [Webmachine::Application] application the application
12
+ def initialize(application)
13
+ @application = application
20
14
  end
21
15
 
22
16
  # Create a new adapter and run it.
23
- def self.run(configuration, dispatcher)
24
- new(configuration, dispatcher).run
17
+ def self.run(application)
18
+ new(application).run
25
19
  end
26
20
 
27
21
  # Start the adapter.
@@ -5,8 +5,7 @@ module Webmachine
5
5
  # Contains classes and modules that connect Webmachine to Ruby
6
6
  # application servers.
7
7
  module Adapters
8
- autoload :Mongrel, 'webmachine/adapters/mongrel'
9
8
  autoload :Reel, 'webmachine/adapters/reel'
10
- autoload :Hatetepe, 'webmachine/adapters/hatetepe'
9
+ autoload :HTTPkit, 'webmachine/adapters/httpkit'
11
10
  end
12
11
  end
@@ -0,0 +1,74 @@
1
+ require 'webmachine/adapter'
2
+ require 'webmachine/constants'
3
+ require 'webmachine/version'
4
+ require 'httpkit'
5
+ require 'webmachine/version'
6
+ require 'webmachine/response'
7
+ require 'webmachine/request'
8
+ require 'webmachine/headers'
9
+
10
+ module Webmachine
11
+ module Adapters
12
+ class HTTPkit < Adapter
13
+ VERSION_STRING = "#{Webmachine::SERVER_STRING} HTTPkit/#{::HTTPkit::VERSION}".freeze
14
+
15
+ def options
16
+ @options ||= {
17
+ :address => application.configuration.ip,
18
+ :port => application.configuration.port,
19
+ :handlers => [
20
+ ::HTTPkit::Server::TimeoutsHandler.new,
21
+ ::HTTPkit::Server::KeepAliveHandler.new,
22
+ self
23
+ ]
24
+ }
25
+ end
26
+
27
+ def run
28
+ ::HTTPkit.start do
29
+ ::HTTPkit::Server.start(options)
30
+ end
31
+ end
32
+
33
+ # Called by HTTPkit::Server for every request
34
+ def serve(request, served)
35
+ response = Webmachine::Response.new
36
+ application.dispatcher.dispatch(convert_request(request), response)
37
+
38
+ served.fulfill(convert_response(response))
39
+ end
40
+
41
+ private
42
+
43
+ # Converts HTTPkit::Request to Webmachine::Request
44
+ def convert_request(request)
45
+ Webmachine::Request.new(
46
+ request.http_method.to_s.upcase,
47
+ request.uri,
48
+ Webmachine::Headers[request.headers.dup],
49
+ request.body)
50
+ end
51
+
52
+ # Converts Webmachine::Response to HTTPkit::Response
53
+ def convert_response(response)
54
+ response.headers[SERVER] = VERSION_STRING
55
+
56
+
57
+ ::HTTPkit::Response.new(
58
+ response.code.to_i,
59
+ response.headers,
60
+ convert_body(response.body))
61
+ end
62
+
63
+ # HTTPkit::Body accepts strings and enumerables, i.e. Webmachine's
64
+ # Callable, Enumerable, IO, and Fiber encoders are supported.
65
+ def convert_body(body)
66
+ if body.respond_to?(:call)
67
+ [body.call]
68
+ else
69
+ body || ''
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,5 +1,4 @@
1
-
2
- module Webmachine
1
+ module Webmachine
3
2
  module Adapters
4
3
  # Wraps a request body so that it can be passed to
5
4
  # {Request} while still lazily evaluating the body.
@@ -1,17 +1,20 @@
1
+ require 'webmachine/adapter'
1
2
  require 'rack'
2
- require 'webmachine/version'
3
+ require 'webmachine/constants'
3
4
  require 'webmachine/headers'
4
5
  require 'webmachine/request'
5
6
  require 'webmachine/response'
6
- require 'webmachine/dispatcher'
7
+ require 'webmachine/version'
7
8
  require 'webmachine/chunked_body'
8
9
 
9
10
  module Webmachine
10
11
  module Adapters
11
12
  # A minimal "shim" adapter to allow Webmachine to interface with Rack. The
12
13
  # intention here is to allow Webmachine to run under Rack-compatible
13
- # web-servers, like unicorn and pow, and is not intended to allow Webmachine
14
- # to be "plugged in" to an existing Rack app as middleware.
14
+ # web-servers, like unicorn and pow.
15
+ # The adapter expects your Webmachine application to be mounted at the root path -
16
+ # it will NOT allow you to nest your Webmachine application at an arbitrary path
17
+ # eg. map "/api" { run MyWebmachineAPI }
15
18
  #
16
19
  # To use this adapter, create a config.ru file and populate it like so:
17
20
  #
@@ -26,7 +29,7 @@ module Webmachine
26
29
  # all "just work".
27
30
  #
28
31
  # And for development or testing your application can be run with Rack's
29
- # builtin Server identically to the Mongrel and WEBrick adapters with:
32
+ # builtin Server identically to the WEBrick adapter with:
30
33
  #
31
34
  # MyApplication.run
32
35
  #
@@ -34,22 +37,22 @@ module Webmachine
34
37
  # Used to override default Rack server options (useful in testing)
35
38
  DEFAULT_OPTIONS = {}
36
39
 
40
+ REQUEST_URI = 'REQUEST_URI'.freeze
41
+ VERSION_STRING = "#{Webmachine::SERVER_STRING} Rack/#{::Rack.version}".freeze
42
+ NEWLINE = "\n".freeze
43
+
37
44
  # Start the Rack adapter
38
45
  def run
39
46
  options = DEFAULT_OPTIONS.merge({
40
47
  :app => self,
41
- :Port => configuration.port,
42
- :Host => configuration.ip
43
- }).merge(configuration.adapter_options)
48
+ :Port => application.configuration.port,
49
+ :Host => application.configuration.ip
50
+ }).merge(application.configuration.adapter_options)
44
51
 
45
52
  @server = ::Rack::Server.new(options)
46
53
  @server.start
47
54
  end
48
55
 
49
- def shutdown
50
- @server.server.shutdown if @server
51
- end
52
-
53
56
  # Handles a Rack-based request.
54
57
  # @param [Hash] env the Rack environment
55
58
  def call(env)
@@ -57,26 +60,28 @@ module Webmachine
57
60
 
58
61
  rack_req = ::Rack::Request.new env
59
62
  request = Webmachine::Request.new(rack_req.request_method,
60
- URI.parse(rack_req.url),
63
+ env[REQUEST_URI],
61
64
  headers,
62
65
  RequestBody.new(rack_req))
63
66
 
64
67
  response = Webmachine::Response.new
65
- @dispatcher.dispatch(request, response)
68
+ application.dispatcher.dispatch(request, response)
66
69
 
67
- response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ")
70
+ response.headers[SERVER] = VERSION_STRING
68
71
 
69
72
  rack_status = response.code
70
- rack_headers = response.headers.flattened("\n")
73
+ rack_headers = response.headers.flattened(NEWLINE)
71
74
  rack_body = case response.body
72
75
  when String # Strings are enumerable in ruby 1.8
73
76
  [response.body]
74
77
  else
75
- if response.body.respond_to?(:call)
78
+ if (io_body = IO.try_convert(response.body))
79
+ io_body
80
+ elsif response.body.respond_to?(:call)
76
81
  Webmachine::ChunkedBody.new(Array(response.body.call))
77
82
  elsif response.body.respond_to?(:each)
78
83
  # This might be an IOEncoder with a Content-Length, which shouldn't be chunked.
79
- if response.headers["Transfer-Encoding"] == "chunked"
84
+ if response.headers[TRANSFER_ENCODING] == "chunked"
80
85
  Webmachine::ChunkedBody.new(response.body)
81
86
  else
82
87
  response.body
@@ -91,6 +96,8 @@ module Webmachine
91
96
  end
92
97
 
93
98
  class RackResponse
99
+ ONE_FIVE = '1.5'.freeze
100
+
94
101
  def initialize(body, status, headers)
95
102
  @body = body
96
103
  @status = status
@@ -98,8 +105,8 @@ module Webmachine
98
105
  end
99
106
 
100
107
  def finish
101
- @headers['Content-Type'] ||= 'text/html' if rack_release_enforcing_content_type
102
- @headers.delete('Content-Type') if response_without_body
108
+ @headers[CONTENT_TYPE] ||= TEXT_HTML if rack_release_enforcing_content_type
109
+ @headers.delete(CONTENT_TYPE) if response_without_body
103
110
  [@status, @headers, @body]
104
111
  end
105
112
 
@@ -110,7 +117,7 @@ module Webmachine
110
117
  end
111
118
 
112
119
  def rack_release_enforcing_content_type
113
- ::Rack.release < '1.5'
120
+ ::Rack.release < ONE_FIVE
114
121
  end
115
122
  end
116
123
 
@@ -123,6 +130,15 @@ module Webmachine
123
130
  @request = request
124
131
  end
125
132
 
133
+ # Rack Servers differ in the way you can access their request bodys.
134
+ # While some allow you to directly get a Ruby IO object others don't.
135
+ # You have to check the methods they expose, like #gets, #read, #each, #rewind and maybe others.
136
+ # See: https://github.com/rack/rack/blob/rack-1.5/lib/rack/lint.rb#L296
137
+ # @return [IO] the request body
138
+ def to_io
139
+ @request.body
140
+ end
141
+
126
142
  # Converts the body to a String so you can work with the entire
127
143
  # thing.
128
144
  # @return [String] the request body as a string
@@ -1,40 +1,41 @@
1
+ require 'webmachine/adapter'
2
+ require 'webmachine/constants'
3
+ require 'set'
1
4
  require 'reel'
2
- require 'webmachine/version'
3
5
  require 'webmachine/headers'
4
6
  require 'webmachine/request'
5
7
  require 'webmachine/response'
6
- require 'webmachine/dispatcher'
7
- require 'set'
8
8
 
9
9
  module Webmachine
10
10
  module Adapters
11
11
  class Reel < Adapter
12
12
  # Used to override default Reel server options (useful in testing)
13
13
  DEFAULT_OPTIONS = {}
14
-
14
+
15
15
  def run
16
16
  @options = DEFAULT_OPTIONS.merge({
17
- :port => configuration.port,
18
- :host => configuration.ip
19
- }).merge(configuration.adapter_options)
17
+ :port => application.configuration.port,
18
+ :host => application.configuration.ip
19
+ }).merge(application.configuration.adapter_options)
20
20
 
21
- if extra_verbs = configuration.adapter_options[:extra_verbs]
21
+ if extra_verbs = application.configuration.adapter_options[:extra_verbs]
22
22
  @extra_verbs = Set.new(extra_verbs.map(&:to_s).map(&:upcase))
23
23
  else
24
24
  @extra_verbs = Set.new
25
25
  end
26
26
 
27
- @server = ::Reel::Server.supervise(@options[:host], @options[:port], &method(:process))
27
+ if @options[:ssl]
28
+ unless @options[:ssl][:cert] && @options[:ssl][:key]
29
+ raise ArgumentError, 'Certificate or Private key missing for HTTPS Server'
30
+ end
31
+ @server = ::Reel::Server::HTTPS.supervise(@options[:host], @options[:port], @options[:ssl], &method(:process))
32
+ else
33
+ @server = ::Reel::Server::HTTP.supervise(@options[:host], @options[:port], &method(:process))
34
+ end
28
35
 
29
- # FIXME: this will no longer work on Ruby 2.0. We need Celluloid.trap
30
- trap("INT") { @server.terminate; exit 0 }
31
36
  Celluloid::Actor.join(@server)
32
37
  end
33
38
 
34
- def shutdown
35
- @server.terminate! if @server
36
- end
37
-
38
39
  def process(connection)
39
40
  connection.each_request do |request|
40
41
  # Users of the adapter can configure a custom WebSocket handler
@@ -54,7 +55,7 @@ module Webmachine
54
55
  # state machine. Do the "Railsy" thing and handle them like POSTs
55
56
  # with a magical parameter
56
57
  if @extra_verbs.include?(request.method)
57
- method = "POST"
58
+ method = POST_METHOD
58
59
  param = "_method=#{request.method}"
59
60
  uri = request_uri(request.url, request.headers, param)
60
61
  else
@@ -64,8 +65,9 @@ module Webmachine
64
65
 
65
66
  wm_headers = Webmachine::Headers[request.headers.dup]
66
67
  wm_request = Webmachine::Request.new(method, uri, wm_headers, request.body)
68
+
67
69
  wm_response = Webmachine::Response.new
68
- @dispatcher.dispatch(wm_request, wm_response)
70
+ application.dispatcher.dispatch(wm_request, wm_response)
69
71
 
70
72
  fixup_headers(wm_response)
71
73
  fixup_callable_encoder(wm_response)
@@ -77,13 +79,9 @@ module Webmachine
77
79
  end
78
80
 
79
81
  def request_uri(path, headers, extra_query_params = nil)
80
- host_parts = headers.fetch('Host').split(':')
81
82
  path_parts = path.split('?')
82
-
83
- uri_hash = {host: host_parts.first, path: path_parts.first}
84
-
85
- uri_hash[:port] = host_parts.last.to_i if host_parts.length == 2
86
- uri_hash[:query] = path_parts.last if path_parts.length == 2
83
+ uri_hash = {path: path_parts.first}
84
+ uri_hash[:query] = path_parts.last if path_parts.length == 2
87
85
 
88
86
  if extra_query_params
89
87
  if uri_hash[:query]