rocketio 0.0.0 → 0.0.1

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 (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