rack-app 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 32f29dd3c7583a4e67a8d7d120490ab60d8525aa
4
- data.tar.gz: 7b038445705c0b9f610b0b1d43eef0238ddd18b1
3
+ metadata.gz: d997c9befda0175c0b2b1c176a9e26ed16610608
4
+ data.tar.gz: 262f5320b2e82ea7330694dd02d0b66bc1699cd0
5
5
  SHA512:
6
- metadata.gz: 6ed668e4f0f109ed99554c18f99d2e0a73869498d0e4632d9ba63817599c0fa33e2e5a81f914b6563338621f68dbc468d430c33b5ef957d67378d9111db1faae
7
- data.tar.gz: e2c2d8b39453daaeaaf3646b56bb41879372f0caad64c578ce54c388e20d26b7312e7e7c358d78546be41c6c0ef8f0f734c014dbf811166c1b4114bf5b41f775
6
+ metadata.gz: 641be2541ffe9cca5a775a3f691678db4685ef6c99c4f31584266e229123b3c6d3bea85e511724c7bb8880c6cb84d55cf52a6f48f5498f0bb8b2a876b78b4f13
7
+ data.tar.gz: 0c2c35043330ab3b389cb97d1d05104cff383b7bd5b96b875ab0fe012c07919491bf06b624053aa8a8b13873ac362985e08bc256a71d705cfa3a5464d517ece7
data/Gemfile CHANGED
@@ -1,4 +1,2 @@
1
1
  source 'https://rubygems.org'
2
- gemspec
3
-
4
- gem 'grape'
2
+ gemspec
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rack-app (0.2.0)
5
+ rack
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.2.5)
11
+ rack (1.6.4)
12
+ rake (10.4.2)
13
+ rspec (3.3.0)
14
+ rspec-core (~> 3.3.0)
15
+ rspec-expectations (~> 3.3.0)
16
+ rspec-mocks (~> 3.3.0)
17
+ rspec-core (3.3.2)
18
+ rspec-support (~> 3.3.0)
19
+ rspec-expectations (3.3.1)
20
+ diff-lcs (>= 1.2.0, < 2.0)
21
+ rspec-support (~> 3.3.0)
22
+ rspec-mocks (3.3.2)
23
+ diff-lcs (>= 1.2.0, < 2.0)
24
+ rspec-support (~> 3.3.0)
25
+ rspec-support (3.3.0)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ bundler (~> 1.10)
32
+ rack-app!
33
+ rake (~> 10.0)
34
+ rspec
35
+
36
+ BUNDLED WITH
37
+ 1.10.6
data/README.md CHANGED
@@ -6,6 +6,7 @@ The idea behind is simple.
6
6
  Have a little framework that can allow you write pure rack apps,
7
7
  that will do nothing more than what you defined.
8
8
 
9
+ This includes that it do not depend on fat libs like activesupport.
9
10
 
10
11
  ## Installation
11
12
 
@@ -56,7 +57,10 @@ run YourAwesomeApp
56
57
 
57
58
  * benchmark for rails, padrino, sinatra, grape etc to prove awesomeness
58
59
  * more verbose readme
59
- * drink less coffee
60
+ * drink less coffee
61
+ * support restful endpoints
62
+ * params
63
+ * route-matching
60
64
 
61
65
  ## Development
62
66
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -1,11 +1,11 @@
1
1
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__),'..','lib'))
2
2
  require 'rack/app'
3
3
 
4
- require_relative 'first_controller'
4
+ require_relative 'mounted_controller'
5
5
 
6
- class SampleApp < Rack::APP
6
+ class YourAwesomeApp < Rack::APP
7
7
 
8
- mount FirstController
8
+ mount MountedController
9
9
 
10
10
  get '/hello' do
11
11
  'Hello World!'
@@ -13,9 +13,12 @@ class SampleApp < Rack::APP
13
13
 
14
14
  get '/nope' do
15
15
  response.write 'nope nope nope...'
16
+ end
16
17
 
18
+ get '/users/:user_id' do
19
+ params['user_id']
17
20
  end
18
21
 
19
22
  end
20
23
 
21
- run SampleApp
24
+ run YourAwesomeApp
@@ -1,4 +1,4 @@
1
- class FirstController < Rack::APP
1
+ class MountedController < Rack::APP
2
2
 
3
3
  get '/first' do
4
4
  first
@@ -0,0 +1,5 @@
1
+ faraday-cli http://localhost:9292/nope
2
+ faraday-cli http://localhost:9292/hello
3
+ faraday-cli http://localhost:9292/first
4
+ faraday-cli http://localhost:9292/users/123
5
+ faraday-cli http://localhost:9292/not_found
@@ -5,27 +5,31 @@ class Rack::APP
5
5
 
6
6
  require 'rack/app/version'
7
7
 
8
+ require 'rack/app/utils'
9
+ require 'rack/app/router'
8
10
  require 'rack/app/endpoint'
9
11
  require 'rack/app/endpoint/not_found'
10
-
11
12
  require 'rack/app/runner'
12
13
 
13
- require 'rack/app/syntax_sugar'
14
- extend Rack::APP::SyntaxSugar
14
+ require 'rack/app/class_methods'
15
+ extend Rack::APP::ClassMethods
15
16
 
16
17
  require 'rack/app/request_helpers'
17
18
 
18
19
  include Rack::APP::RequestHelpers
19
20
 
21
+ def self.call(request_env)
22
+ Rack::APP::Runner.response_for(self,request_env)
23
+ end
24
+
20
25
  attr_reader :request, :response
21
26
 
22
- def initialize(request, response)
27
+ protected
28
+
29
+ def initialize(request, response,options = {})
23
30
  @response = response
24
31
  @request = request
25
- end
26
-
27
- def self.call(request_env)
28
- Rack::APP::Runner.response_for(self,request_env)
32
+ @options = options
29
33
  end
30
34
 
31
35
  end
@@ -1,4 +1,4 @@
1
- module Rack::APP::SyntaxSugar
1
+ module Rack::APP::ClassMethods
2
2
 
3
3
  def description(*description_texts)
4
4
  @last_description = description_texts.join("\n")
@@ -32,10 +32,13 @@ module Rack::APP::SyntaxSugar
32
32
  add_route('PATCH', path, &block)
33
33
  end
34
34
 
35
+ def router
36
+ @static_router ||= Rack::APP::Router.new
37
+ end
38
+
35
39
  def add_route(request_method, request_path, &block)
36
- request_key = [request_method, request_path]
37
40
 
38
- endpoint = endpoints[request_key]= Rack::APP::Endpoint.new(
41
+ endpoint = Rack::APP::Endpoint.new(
39
42
  self,
40
43
  {
41
44
  request_method: request_method,
@@ -45,13 +48,12 @@ module Rack::APP::SyntaxSugar
45
48
  &block
46
49
  )
47
50
 
51
+ router.add_endpoint(request_method,request_path,endpoint)
52
+
48
53
  @last_description = nil
49
54
  endpoint
50
55
  end
51
56
 
52
- def endpoints
53
- @endpoints ||= {}
54
- end
55
57
 
56
58
  def mount(api_class)
57
59
 
@@ -59,7 +61,7 @@ module Rack::APP::SyntaxSugar
59
61
  raise(ArgumentError, 'Invalid class given for mount, must be a Rack::APP')
60
62
  end
61
63
 
62
- endpoints.merge!(api_class.endpoints)
64
+ router.merge!(api_class.router)
63
65
 
64
66
  nil
65
67
  end
@@ -4,6 +4,7 @@ class Rack::APP::Endpoint
4
4
  @properties = properties
5
5
  @logic_block = logic_block
6
6
  @api_class = api_class
7
+ @path_params_matcher = {}
7
8
  end
8
9
 
9
10
  def execute(request_env)
@@ -11,7 +12,7 @@ class Rack::APP::Endpoint
11
12
  request = Rack::Request.new(request_env)
12
13
  response = Rack::Response.new
13
14
 
14
- request_handler = @api_class.new(request, response)
15
+ request_handler = @api_class.new(request, response,{path_params_matcher: @path_params_matcher})
15
16
  call_return = request_handler.instance_exec(&@logic_block)
16
17
 
17
18
  return call_return if is_a_rack_response_finish?(call_return)
@@ -21,6 +22,10 @@ class Rack::APP::Endpoint
21
22
 
22
23
  end
23
24
 
25
+ def register_path_params_matcher(params_matcher)
26
+ @path_params_matcher.merge!(params_matcher)
27
+ end
28
+
24
29
  protected
25
30
 
26
31
  def add_response_body_if_missing(call_return, response)
@@ -2,15 +2,36 @@ require 'cgi'
2
2
  module Rack::APP::RequestHelpers
3
3
 
4
4
  def params
5
- @__request_params__ ||= CGI.parse(request.env['QUERY_STRING']).freeze.reduce({}) do |params_collection, (k, v)|
6
- if v.is_a?(Array) and v.length === 1
7
- params_collection[k]= v[0]
8
- else
9
- params_collection[k]= v
5
+ @__request_params__ ||= -> {
6
+
7
+ raw_params = CGI.parse(request.env['QUERY_STRING'].to_s).freeze.reduce({}) do |params_collection, (k, v)|
8
+ if v.is_a?(Array) and v.length === 1
9
+ params_collection[k]= v[0]
10
+ else
11
+ params_collection[k]= v
12
+ end
13
+
14
+ params_collection
10
15
  end
11
16
 
12
- params_collection
13
- end
17
+ if @options[:path_params_matcher].is_a?(Hash) and not @options[:path_params_matcher].empty?
18
+
19
+ request_path_parts = Rack::APP::Utils.normalize_path(request.env['REQUEST_PATH']).split('/')
20
+
21
+ path_params = request_path_parts.each.with_index.reduce({}) do |params_col,(path_part,index)|
22
+ if @options[:path_params_matcher][index]
23
+ params_col[@options[:path_params_matcher][index]]= path_part
24
+ end
25
+ params_col
26
+ end
27
+
28
+ raw_params.merge!(path_params)
29
+
30
+ end
31
+
32
+ raw_params
33
+
34
+ }.call
14
35
  end
15
36
 
16
37
  def status(new_status=nil)
@@ -0,0 +1,38 @@
1
+ class Rack::APP::Router
2
+
3
+ require 'rack/app/router/static'
4
+ require 'rack/app/router/dynamic'
5
+
6
+ def add_endpoint(request_method, request_path, endpoint)
7
+ if defined_path_is_dynamic?(Rack::APP::Utils.normalize_path(request_path))
8
+ @dynamic_router.add_endpoint(request_method, request_path, endpoint)
9
+ else
10
+ @static_router.add_endpoint(request_method, request_path, endpoint)
11
+ end
12
+ end
13
+
14
+ def fetch_endpoint(request_method, request_path)
15
+ @static_router.fetch_endpoint(request_method, request_path) or
16
+ @dynamic_router.fetch_endpoint(request_method, request_path) or
17
+ Rack::APP::Endpoint::NOT_FOUND
18
+ end
19
+
20
+ def merge!(router)
21
+ raise(ArgumentError, "invalid router object, must be instance of #{self.class}") unless router.is_a?(self.class)
22
+ @static_router.merge!(router.instance_variable_get(:@static_router))
23
+ @dynamic_router.merge!(router.instance_variable_get(:@dynamic_router))
24
+ nil
25
+ end
26
+
27
+ protected
28
+
29
+ def initialize
30
+ @static_router = Rack::APP::Router::Static.new
31
+ @dynamic_router = Rack::APP::Router::Dynamic.new
32
+ end
33
+
34
+ def defined_path_is_dynamic?(path_str)
35
+ !!(path_str.to_s =~ /\/:\w+/i)
36
+ end
37
+
38
+ end
@@ -0,0 +1,76 @@
1
+ class Rack::APP::Router::Dynamic
2
+
3
+ ANY = 'Rack::APP::Router::Dynamic::ANY'.freeze
4
+
5
+ def add_endpoint(request_method, request_path, endpoint)
6
+ request_path = Rack::APP::Utils.normalize_path(request_path)
7
+
8
+ current_cluster = main_cluster(request_method)
9
+ path_params = {}
10
+ request_path.split('/').each.with_index do |path_part, index|
11
+
12
+ new_cluster_name = if path_part_is_dynamic?(path_part)
13
+ path_params[index]= path_part.sub(/^:/,'')
14
+ ANY
15
+ else
16
+ path_part
17
+ end
18
+
19
+ current_cluster = (current_cluster[new_cluster_name] ||= {})
20
+
21
+ end
22
+
23
+ current_cluster[:endpoint]= endpoint
24
+ current_cluster[:endpoint].register_path_params_matcher(path_params)
25
+
26
+ endpoint
27
+ end
28
+
29
+ def fetch_endpoint(request_method, request_path)
30
+ normalized_request_path = Rack::APP::Utils.normalize_path(request_path)
31
+
32
+ current_cluster = main_cluster(request_method)
33
+ normalized_request_path.split('/').each do |path_part|
34
+ current_cluster = current_cluster[path_part] || current_cluster[ANY]
35
+ return nil if current_cluster.nil?
36
+ end
37
+
38
+ current_cluster[:endpoint]
39
+ end
40
+
41
+ def merge!(router)
42
+ raise(ArgumentError,"invalid route object, must be instance of #{self.class.to_s}") unless router.is_a?(self.class)
43
+ deep_merge!(@http_method_cluster,router.instance_variable_get(:@http_method_cluster))
44
+ nil
45
+ end
46
+
47
+ protected
48
+
49
+ def initialize
50
+ @http_method_cluster = {}
51
+ end
52
+
53
+ def path_part_is_dynamic?(path_part_str)
54
+ !!(path_part_str.to_s =~ /^:\w+$/i)
55
+ end
56
+
57
+ def deep_merge!(hash,other_hash)
58
+ other_hash.each_pair do |current_key, other_value|
59
+
60
+ this_value = hash[current_key]
61
+
62
+ hash[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
63
+ deep_merge!(this_value,other_value)
64
+ else
65
+ other_value
66
+ end
67
+ end
68
+
69
+ hash
70
+ end
71
+
72
+ def main_cluster(request_method)
73
+ (@http_method_cluster[request_method.to_s.upcase] ||= {})
74
+ end
75
+
76
+ end
@@ -0,0 +1,23 @@
1
+ class Rack::APP::Router::Static
2
+
3
+ def add_endpoint(request_method, request_path, endpoint)
4
+ @endpoints[[request_method.to_s.upcase, Rack::APP::Utils.normalize_path(request_path)]]= endpoint
5
+ end
6
+
7
+ def fetch_endpoint(request_method, request_path)
8
+ @endpoints[[request_method, request_path]]
9
+ end
10
+
11
+ def merge!(static_router)
12
+ raise(ArgumentError,"Invalid argument given, must be instance of a #{self.class.to_s}") unless static_router.is_a?(self.class)
13
+ @endpoints.merge!(static_router.instance_variable_get(:@endpoints))
14
+ nil
15
+ end
16
+
17
+ protected
18
+
19
+ def initialize
20
+ @endpoints = {}
21
+ end
22
+
23
+ end
@@ -9,7 +9,7 @@ module Rack::APP::Runner
9
9
  protected
10
10
 
11
11
  def fetch_endpoint(api_class, request_method, request_path)
12
- api_class.endpoints[[request_method, request_path]] || Rack::APP::Endpoint::NOT_FOUND
12
+ api_class.router.fetch_endpoint(request_method, request_path)
13
13
  end
14
14
 
15
15
  end
@@ -0,0 +1,20 @@
1
+ module Rack::APP::Utils
2
+ extend self
3
+
4
+ # Normalizes URI path.
5
+ #
6
+ # Strips off trailing slash and ensures there is a leading slash.
7
+ #
8
+ # normalize_path("/foo") # => "/foo"
9
+ # normalize_path("/foo/") # => "/foo"
10
+ # normalize_path("foo") # => "/foo"
11
+ # normalize_path("") # => "/"
12
+ def normalize_path(path)
13
+ path = "/#{path}"
14
+ path.squeeze!('/')
15
+ path.sub!(%r{/+\Z}, '')
16
+ path = '/' if path == ''
17
+ path
18
+ end
19
+
20
+ end
@@ -0,0 +1,40 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'rack/app'
3
+ require 'securerandom'
4
+
5
+ static_router = Rack::APP::Router::Static.new
6
+ dynamic_router = Rack::APP::Router::Dynamic.new
7
+
8
+ classic_router = []
9
+
10
+ endpoint_paths = []
11
+ 10000.times do
12
+ endpoint_paths << ('/' + 7.times.map { SecureRandom.uuid }.join('/'))
13
+
14
+ static_router.add_endpoint('GET', endpoint_paths.last, -> {})
15
+ static_router.add_endpoint('GET', endpoint_paths.last, -> {})
16
+ classic_router << ['GET',endpoint_paths.last,->{}]
17
+
18
+ end
19
+
20
+ start_time = Time.now
21
+ endpoint_paths.each do |request_path|
22
+ static_router.fetch_endpoint('GET',request_path)
23
+ end
24
+ finish_time_of_static = Time.now - start_time
25
+
26
+ start_time = Time.now
27
+ endpoint_paths.each do |request_path|
28
+ dynamic_router.fetch_endpoint('GET',request_path)
29
+ end
30
+ finish_time_of_dynamic = Time.now - start_time
31
+
32
+ start_time = Time.now
33
+ endpoint_paths.each do |request_path|
34
+ classic_router.find{|ary| ary[0] == 'GET' and ary[1] == request_path }
35
+ end
36
+ finish_time_of_classic = Time.now - start_time
37
+
38
+ puts "time taken by static: #{finish_time_of_static}",
39
+ "time taken by dynamic: #{finish_time_of_dynamic}",
40
+ "time taken by classic(mock): #{finish_time_of_classic}"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-app
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Luzsi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-10-03 00:00:00.000000000 Z
11
+ date: 2015-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -78,20 +78,27 @@ files:
78
78
  - ".rspec"
79
79
  - CODE_OF_CONDUCT.md
80
80
  - Gemfile
81
+ - Gemfile.lock
81
82
  - README.md
82
83
  - Rakefile
83
84
  - VERSION
84
85
  - bin/setup
85
86
  - example/config.ru
86
- - example/first_controller.rb
87
+ - example/mounted_controller.rb
88
+ - example/ping.sh
87
89
  - lib/rack/app.rb
90
+ - lib/rack/app/class_methods.rb
88
91
  - lib/rack/app/endpoint.rb
89
92
  - lib/rack/app/endpoint/not_found.rb
90
93
  - lib/rack/app/request_helpers.rb
94
+ - lib/rack/app/router.rb
95
+ - lib/rack/app/router/dynamic.rb
96
+ - lib/rack/app/router/static.rb
91
97
  - lib/rack/app/runner.rb
92
- - lib/rack/app/syntax_sugar.rb
98
+ - lib/rack/app/utils.rb
93
99
  - lib/rack/app/version.rb
94
100
  - rack-app.gemspec
101
+ - spike/routing_time.rb
95
102
  homepage: https://github.com/adamluzsi/rack-app.rb
96
103
  licenses: []
97
104
  metadata: {}