rocketio 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -5
  3. data/.pryrc +2 -0
  4. data/.travis.yml +3 -0
  5. data/README.md +22 -5
  6. data/Rakefile +7 -1
  7. data/bin/console +14 -0
  8. data/bin/setup +7 -0
  9. data/lib/rocketio.rb +131 -3
  10. data/lib/rocketio/application.rb +31 -0
  11. data/lib/rocketio/controller.rb +288 -0
  12. data/lib/rocketio/controller/authentication.rb +141 -0
  13. data/lib/rocketio/controller/authorization.rb +53 -0
  14. data/lib/rocketio/controller/cookies.rb +59 -0
  15. data/lib/rocketio/controller/error_handlers.rb +89 -0
  16. data/lib/rocketio/controller/filters.rb +119 -0
  17. data/lib/rocketio/controller/flash.rb +21 -0
  18. data/lib/rocketio/controller/helpers.rb +438 -0
  19. data/lib/rocketio/controller/middleware.rb +32 -0
  20. data/lib/rocketio/controller/render.rb +148 -0
  21. data/lib/rocketio/controller/render/engine.rb +76 -0
  22. data/lib/rocketio/controller/render/layout.rb +27 -0
  23. data/lib/rocketio/controller/render/layouts.rb +85 -0
  24. data/lib/rocketio/controller/render/templates.rb +83 -0
  25. data/lib/rocketio/controller/request.rb +115 -0
  26. data/lib/rocketio/controller/response.rb +84 -0
  27. data/lib/rocketio/controller/sessions.rb +64 -0
  28. data/lib/rocketio/controller/token_auth.rb +118 -0
  29. data/lib/rocketio/controller/websocket.rb +21 -0
  30. data/lib/rocketio/error_templates/404.html +3 -0
  31. data/lib/rocketio/error_templates/409.html +7 -0
  32. data/lib/rocketio/error_templates/500.html +3 -0
  33. data/lib/rocketio/error_templates/501.html +6 -0
  34. data/lib/rocketio/error_templates/layout.html +1 -0
  35. data/lib/rocketio/exceptions.rb +4 -0
  36. data/lib/rocketio/router.rb +65 -0
  37. data/lib/rocketio/util.rb +122 -0
  38. data/lib/rocketio/version.rb +2 -2
  39. data/rocketio.gemspec +21 -17
  40. data/test/aliases_test.rb +54 -0
  41. data/test/authentication_test.rb +307 -0
  42. data/test/authorization_test.rb +91 -0
  43. data/test/cache_control_test.rb +268 -0
  44. data/test/content_type_test.rb +124 -0
  45. data/test/cookies_test.rb +49 -0
  46. data/test/error_handlers_test.rb +125 -0
  47. data/test/etag_test.rb +445 -0
  48. data/test/filters_test.rb +177 -0
  49. data/test/halt_test.rb +73 -0
  50. data/test/helpers_test.rb +171 -0
  51. data/test/middleware_test.rb +57 -0
  52. data/test/redirect_test.rb +135 -0
  53. data/test/render/engine_test.rb +71 -0
  54. data/test/render/get.erb +1 -0
  55. data/test/render/items.erb +1 -0
  56. data/test/render/layout.erb +1 -0
  57. data/test/render/layout_test.rb +104 -0
  58. data/test/render/layouts/master.erb +1 -0
  59. data/test/render/layouts_test.rb +145 -0
  60. data/test/render/master.erb +1 -0
  61. data/test/render/post.erb +1 -0
  62. data/test/render/put.erb +1 -0
  63. data/test/render/render_test.rb +101 -0
  64. data/test/render/setup.rb +14 -0
  65. data/test/render/templates/a/get.erb +1 -0
  66. data/test/render/templates/master.erb +1 -0
  67. data/test/render/templates_test.rb +146 -0
  68. data/test/request_test.rb +105 -0
  69. data/test/response_test.rb +119 -0
  70. data/test/routes_test.rb +70 -0
  71. data/test/sendfile_test.rb +209 -0
  72. data/test/sessions_test.rb +176 -0
  73. data/test/setup.rb +59 -0
  74. metadata +144 -9
  75. data/LICENSE.txt +0 -22
@@ -0,0 +1,122 @@
1
+ module RocketIO
2
+
3
+ # - ensure leading slash
4
+ # - remove trailing slash
5
+ # - remove double slashes
6
+ #
7
+ # given path may consist of multiple chunks passed as arguments
8
+ # given chunks are flattened so the method can accept Array arguments.
9
+ #
10
+ # @param [String, Symbol] *chunks
11
+ # @return [String]
12
+ def rootify_path *chunks
13
+ RocketIO::SLASH + File.join(*chunks.flatten.map!(&:to_s)).
14
+ gsub(/\/+/, RocketIO::SLASH).
15
+ gsub(/\A\/|\/\z/, RocketIO::EMPTY_STRING)
16
+ end
17
+
18
+ # convert CamelCase string/symbol into underscored_name
19
+ #
20
+ # @param [String, Symbol] x
21
+ # @return [String]
22
+ def underscore x
23
+ x.to_s.
24
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
25
+ gsub(/([a-z\d])([A-Z])/, '\1_\2').
26
+ downcase
27
+ end
28
+
29
+ # determines which of given parameters are required/optional/splat
30
+ # and based on this info deduct the min/max required arguments the method can be called with.
31
+ #
32
+ # @param [Array] parameters
33
+ # @return [Array]
34
+ def parameters_policy parameters
35
+ min, max = 0, parameters.size
36
+
37
+ unlimited = false
38
+ parameters.each_with_index do |param, i|
39
+
40
+ increment = param.first == :req
41
+
42
+ if (next_param = parameters.values_at(i+1).first)
43
+ increment = true if next_param[0] == :req
44
+ end
45
+
46
+ if param.first == :rest
47
+ increment = false
48
+ unlimited = true
49
+ end
50
+ min += 1 if increment
51
+ end
52
+ max = :* if unlimited
53
+ {min: min, max: max}.freeze
54
+ end
55
+
56
+ def path_params parameters
57
+ parameters.each_with_index.each_with_object({}) do |(param,i),o|
58
+ o[param[1]] = (i .. (param[0] == :rest ? -parameters[i .. -1].size : i)).freeze
59
+ end.freeze
60
+ end
61
+
62
+ # building a constant name for given engine name.
63
+ # if a class given, return it as is.
64
+ #
65
+ # @example
66
+ # engine_class(:Slim) #=> :SlimTemplate
67
+ #
68
+ # @param engine name
69
+ # @return [Symbol, Class]
70
+ #
71
+ def engine_class engine
72
+ return engine if engine.is_a?(Class)
73
+ (RocketIO::ENGINE_CONST_FORMAT % engine).to_sym
74
+ end
75
+
76
+ def engine_const engine
77
+ return engine if engine.is_a?(Class)
78
+ ::Tilt.const_get(engine)
79
+ end
80
+
81
+ def error_renderer error_code, without_layout = nil, context = {}
82
+ response = RocketIO::ERROR_TEMPLATES[error_code].render(context)
83
+ return response if without_layout
84
+ RocketIO::ERROR_TEMPLATES[:layout].render(context.merge(yield: response))
85
+ end
86
+
87
+
88
+ def caller_to_dirname caller
89
+ ::File.dirname(caller[0].split(/:\d+:in\s+`/)[0])
90
+ end
91
+
92
+ begin # stolen from Sinatra
93
+
94
+ # Lookup or register a mime type in Rack's mime registry. Stolen from Sinatra
95
+ def mime_type type, value = nil
96
+ return type if type.nil?
97
+ return type.to_s if type.to_s.include?(RocketIO::SLASH)
98
+ type = '.%s' % type unless type.to_s[0] == ?.
99
+ return Rack::Mime.mime_type(type, nil) unless value
100
+ Rack::Mime::MIME_TYPES[type] = value
101
+ end
102
+
103
+ # Enable string or symbol key access to the nested params hash.
104
+ def indifferent_params(object)
105
+ case object
106
+ when Hash
107
+ new_hash = indifferent_hash
108
+ object.each { |key, value| new_hash[key] = indifferent_params(value) }
109
+ new_hash
110
+ when Array
111
+ object.map { |item| indifferent_params(item) }
112
+ else
113
+ object
114
+ end
115
+ end
116
+
117
+ # Creates a Hash with indifferent access.
118
+ def indifferent_hash
119
+ Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
120
+ end
121
+ end
122
+ end
@@ -1,3 +1,3 @@
1
- module Rocketio
2
- VERSION = "0.0.0"
1
+ module RocketIO
2
+ VERSION = '0.0.1'.freeze
3
3
  end
data/rocketio.gemspec CHANGED
@@ -1,23 +1,27 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'rocketio/version'
2
+ require File.expand_path('../lib/rocketio/version', __FILE__)
5
3
 
6
4
  Gem::Specification.new do |spec|
7
- spec.name = "rocketio"
8
- spec.version = Rocketio::VERSION
9
- spec.authors = ["Slee Woo"]
10
- spec.email = ["mail@sleewoo.com"]
11
- spec.summary = %q{Write a short summary. Required.}
12
- spec.description = %q{Write a longer description. Optional.}
13
- spec.homepage = ""
14
- spec.license = "MIT"
5
+ spec.name = 'rocketio'
6
+ spec.version = String.new(RocketIO::VERSION)
7
+ spec.authors = ['Slee Woo']
8
+ spec.email = ['mail@sleewoo.com']
9
+ spec.summary = [spec.name, spec.version]*'-',
10
+ spec.description = 'Simple, fast, scalable web framework for Ruby'
11
+ spec.homepage = 'https://github.com/rocketio/' + spec.name
12
+ spec.license = 'MIT'
15
13
 
16
- spec.files = `git ls-files -z`.split("\x0")
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
14
+ spec.files = Dir['**/{*,.[a-z]*}'].reject {|e| e =~ /(gem|lock)\z/}
15
+ spec.require_paths = ['lib']
20
16
 
21
- spec.add_development_dependency "bundler", "~> 1.7"
22
- spec.add_development_dependency "rake", "~> 10.0"
17
+ spec.required_ruby_version = '~> 2.0'
18
+
19
+ spec.add_runtime_dependency 'rack', '~> 1.5'
20
+ spec.add_runtime_dependency 'mustache', '~> 1'
21
+ spec.add_runtime_dependency 'tilt', '~> 2'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.7'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'tokyo', '~> 0'
26
+ spec.add_development_dependency 'rack-radar'
23
27
  end
@@ -0,0 +1,54 @@
1
+ require 'setup'
2
+
3
+ class A < RocketIO::Controller
4
+ alias_url :x
5
+ alias_url :y
6
+
7
+ def get; url end
8
+
9
+ class B < self
10
+ alias_url :x
11
+ alias_url :y
12
+
13
+ def get; url end
14
+ end
15
+ end
16
+
17
+ spec :Alises do
18
+
19
+ it 'uses application root for top-level controllers' do
20
+ assert(A.aliases) == %w[/x /y]
21
+ end
22
+
23
+ it 'uses parent root on nested controllers' do
24
+ assert(A::B.aliases) == %w[/a/x /a/y]
25
+ end
26
+
27
+ context 'HTTP requests' do
28
+ test 'top-level controllers' do
29
+ app mock_app(A)
30
+
31
+ get '/a'
32
+ assert(last_response).is_ok_with_body '/a'
33
+
34
+ get '/x'
35
+ assert(last_response).is_ok_with_body '/a'
36
+
37
+ get '/y'
38
+ assert(last_response).is_ok_with_body '/a'
39
+ end
40
+
41
+ test 'nested controllers' do
42
+ app mock_app(A::B)
43
+
44
+ get '/a/b'
45
+ assert(last_response).is_ok_with_body '/a/b'
46
+
47
+ get '/a/x'
48
+ assert(last_response).is_ok_with_body '/a/b'
49
+
50
+ get '/a/y'
51
+ assert(last_response).is_ok_with_body '/a/b'
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,307 @@
1
+ require 'setup'
2
+
3
+ spec :AuthenticationTest do
4
+ context 'inheritance' do
5
+ it 'inherits basic auth procedures from superclass' do
6
+ a = mock_controller {
7
+ basic_auth {|u,p| [u,p] == %w[u p]}
8
+ }
9
+ b = mock_controller(a)
10
+ app(b)
11
+ get
12
+ assert(last_response).is_unauthorized
13
+ authorize 'u', 'p'
14
+ get
15
+ assert(last_response).is_authorized
16
+ end
17
+
18
+ it 'directly overrides basic auth inherited from superclass' do
19
+ a = mock_controller {
20
+ basic_auth {|u,p| [u,p] == %w[u p]}
21
+ }
22
+ b = mock_controller(a) {
23
+ basic_auth {|u,p| [u,p] == %w[x y]}
24
+ }
25
+ app(b)
26
+ get
27
+ assert(last_response).is_unauthorized
28
+ authorize 'x', 'y'
29
+ get
30
+ assert(last_response).is_authorized
31
+ end
32
+
33
+ it 'uses `inherit` to override basic auth inherited from superclass' do
34
+ a = mock_controller {
35
+ basic_auth {|u,p| [u,p] == %w[u p]}
36
+ }
37
+ b = mock_controller(a) {
38
+ basic_auth {|u,p| [u,p] == %w[x y]}
39
+ }
40
+ c = mock_controller(a) {
41
+ inherit :basic_auth, from: b
42
+ }
43
+ app(c)
44
+ get
45
+ assert(last_response).is_unauthorized
46
+ authorize 'x', 'y'
47
+ get
48
+ assert(last_response).is_authorized
49
+ end
50
+
51
+ it 'inherits basic auth procedures' do
52
+ a = mock_controller {
53
+ basic_auth {|u,p| [u,p] == %w[u p]}
54
+ }
55
+ b = mock_controller {
56
+ inherit :basic_auth, from: a
57
+ }
58
+ app(b)
59
+ get
60
+ assert(last_response).is_unauthorized
61
+ authorize 'u', 'p'
62
+ get
63
+ assert(last_response).is_authorized
64
+ end
65
+
66
+ it 'inherits digest auth procedures from superclass' do
67
+ a = mock_controller {
68
+ digest_auth {|u| {'u' => 'p'}[u]}
69
+ }
70
+ b = mock_controller(a)
71
+ app(b)
72
+ get
73
+ assert(last_response).is_unauthorized
74
+ digest_authorize 'u', 'p'
75
+ get
76
+ assert(last_response).is_authorized
77
+ end
78
+
79
+ it 'directly overrides digest auth inherited from superclass' do
80
+ a = mock_controller {
81
+ digest_auth {|u| {'u' => 'p'}[u]}
82
+ }
83
+ b = mock_controller(a) {
84
+ digest_auth {|u| {'x' => 'y'}[u]}
85
+ }
86
+ app(b)
87
+ get
88
+ assert(last_response).is_unauthorized
89
+ digest_authorize 'x', 'y'
90
+ get
91
+ assert(last_response).is_authorized
92
+ end
93
+
94
+ it 'uses `inherit` to override digest auth inherited from superclass' do
95
+ a = mock_controller {
96
+ digest_auth {|u| {'u' => 'p'}[u]}
97
+ }
98
+ b = mock_controller(a) {
99
+ digest_auth {|u| {'x' => 'y'}[u]}
100
+ }
101
+ c = mock_controller(a) {
102
+ inherit :digest_auth, from: b
103
+ }
104
+ app(b)
105
+ get
106
+ assert(last_response).is_unauthorized
107
+ digest_authorize 'x', 'y'
108
+ get
109
+ assert(last_response).is_authorized
110
+ end
111
+
112
+ it 'inherits digest auth procedures' do
113
+ a = mock_controller {
114
+ digest_auth {|u| {'u' => 'p'}[u]}
115
+ }
116
+ b = mock_controller {
117
+ inherit :digest_auth, from: a
118
+ }
119
+ app(b)
120
+ get
121
+ assert(last_response).is_unauthorized
122
+ digest_authorize 'u', 'p'
123
+ get
124
+ assert(last_response).is_authorized
125
+ end
126
+ end
127
+
128
+ context 'basic auth' do
129
+ context 'protect all request methods' do
130
+ before do
131
+ app mock_controller {
132
+ basic_auth {|u,p| [u,p] == %w[u p]}
133
+ RocketIO::REQUEST_METHODS.each_value {|m| define_method(m) {}}
134
+ }
135
+ end
136
+
137
+ it 'returns "401 Unauthorized" if no authorization given' do
138
+ RocketIO::REQUEST_METHODS.each_value do |rqm|
139
+ send(rqm)
140
+ assert(last_response).is_unauthorized
141
+ end
142
+ end
143
+
144
+ it 'returns "401 Unauthorized" if wrong authorization given' do
145
+ authorize('x', 'y')
146
+ RocketIO::REQUEST_METHODS.values.each do |rqm|
147
+ send(rqm)
148
+ assert(last_response).is_unauthorized
149
+ end
150
+ end
151
+
152
+ it 'returns "200 Ok" response if authorization passed' do
153
+ authorize('u', 'p')
154
+ RocketIO::REQUEST_METHODS.values.each do |rqm|
155
+ send(rqm)
156
+ assert(last_response).ok?
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'protect specific request methods' do
162
+ before do
163
+ @protected = %w[get post]
164
+ app mock_controller {
165
+ basic_auth(:get) {|u,p| [u,p] == ['u', 'get'] }
166
+ basic_auth(:post) {|u,p| [u,p] == ['u', 'post']}
167
+ define_method(:get) {}
168
+ define_method(:post) {}
169
+ define_method(:put) {}
170
+ define_method(:delete) {}
171
+ }
172
+ end
173
+
174
+ it 'returns "200 Ok" for un-protected methods' do
175
+ %w[put delete].each do |rqm|
176
+ send(rqm)
177
+ assert(last_response).ok?
178
+ end
179
+ end
180
+
181
+ it 'returns "401 Unauthorized" if no authorization given' do
182
+ @protected.each do |rqm|
183
+ send(rqm)
184
+ assert(last_response).is_unauthorized
185
+ end
186
+ end
187
+
188
+ it 'returns "401 Unauthorized" if wrong authorization given' do
189
+ authorize('x', 'y')
190
+ @protected.each do |rqm|
191
+ send(rqm)
192
+ assert(last_response).is_unauthorized
193
+ end
194
+ end
195
+
196
+ it 'returns "200 Ok" response if authorization passed' do
197
+ authorize('u', 'get')
198
+ get
199
+ assert(last_response).ok?
200
+
201
+ authorize('u', 'post')
202
+ post
203
+ assert(last_response).ok?
204
+ end
205
+ end
206
+ end
207
+
208
+ context 'digest auth' do
209
+ context 'hashed password' do
210
+ before do
211
+ app mock_controller {
212
+ digest_auth(passwords_hashed: true) {|u| {'u' => '5daad7ee02f846df2874dba8f7522112'}[u]}
213
+ define_method(:get) {}
214
+ }
215
+ end
216
+
217
+ it 'returns "401 Unauthorized" if no authorization given' do
218
+ get
219
+ assert(last_response).is_unauthorized
220
+ end
221
+
222
+ it 'returns "401 Unauthorized" if wrong authorization given' do
223
+ digest_authorize('x', 'y')
224
+ get
225
+ assert(last_response).is_unauthorized
226
+ end
227
+
228
+ it 'returns "200 Ok" response if authorization passed' do
229
+ digest_authorize('u', 'p')
230
+ get
231
+ assert(last_response).ok?
232
+ end
233
+ end
234
+
235
+ context 'plain password' do
236
+ before do
237
+ app mock_controller {
238
+ digest_auth {|u| {'u' => 'p'}[u]}
239
+ define_method(:get) {}
240
+ }
241
+ end
242
+
243
+ it 'returns "401 Unauthorized" if no authorization given' do
244
+ get
245
+ assert(last_response).is_unauthorized
246
+ end
247
+
248
+ it 'returns "401 Unauthorized" if wrong authorization given' do
249
+ digest_authorize('x', 'y')
250
+ get
251
+ assert(last_response).is_unauthorized
252
+ end
253
+
254
+ it 'returns "200 Ok" response if authorization passed' do
255
+ digest_authorize('u', 'p')
256
+ get
257
+ assert(last_response).ok?
258
+ end
259
+ end
260
+
261
+ context 'protect specific request methods' do
262
+ before do
263
+ @protected = %w[get post]
264
+ app mock_controller {
265
+ digest_auth(:get) {|u| {'u' => 'get'}[u] }
266
+ digest_auth(:post) {|u| {'u' => 'post'}[u]}
267
+ define_method(:get) {}
268
+ define_method(:post) {}
269
+ define_method(:put) {}
270
+ define_method(:delete) {}
271
+ }
272
+ end
273
+
274
+ it 'returns "200 Ok" for un-protected methods' do
275
+ %w[put delete].each do |rqm|
276
+ send(rqm)
277
+ assert(last_response).ok?
278
+ end
279
+ end
280
+
281
+ it 'returns "401 Unauthorized" if no authorization given' do
282
+ @protected.each do |rqm|
283
+ send(rqm)
284
+ assert(last_response).is_unauthorized
285
+ end
286
+ end
287
+
288
+ it 'returns "401 Unauthorized" if wrong authorization given' do
289
+ digest_authorize('x', 'y')
290
+ @protected.each do |rqm|
291
+ send(rqm)
292
+ assert(last_response).is_unauthorized
293
+ end
294
+ end
295
+
296
+ it 'returns "200 Ok" response if authorization passed' do
297
+ digest_authorize('u', 'get')
298
+ get
299
+ assert(last_response).ok?
300
+
301
+ digest_authorize('u', 'post')
302
+ post
303
+ assert(last_response).ok?
304
+ end
305
+ end
306
+ end
307
+ end