rack-app 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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: {}