angelo 0.1.7 → 0.1.8
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 +4 -4
- data/.travis.yml +2 -1
- data/CHANGELOG.md +6 -0
- data/Gemfile +2 -9
- data/README.md +182 -14
- data/Rakefile +7 -0
- data/lib/angelo/base.rb +1 -1
- data/lib/angelo/{rspec → minitest}/helpers.rb +9 -9
- data/lib/angelo/mustermann.rb +2 -2
- data/lib/angelo/responder/websocket.rb +1 -1
- data/lib/angelo/version.rb +1 -1
- data/{spec → test}/angelo/erb_spec.rb +2 -2
- data/{spec → test}/angelo/mustermann_spec.rb +5 -5
- data/{spec → test}/angelo/params_spec.rb +6 -6
- data/test/angelo/static_spec.rb +72 -0
- data/{spec → test}/angelo/websocket_spec.rb +9 -9
- data/{spec → test}/angelo_spec.rb +29 -28
- data/{spec → test}/spec_helper.rb +5 -3
- data/{spec → test}/test_app_root/public/test.css +0 -0
- data/{spec → test}/test_app_root/public/test.html +0 -0
- data/{spec → test}/test_app_root/public/test.js +0 -0
- data/{spec → test}/test_app_root/public/what.png +0 -0
- data/{spec → test}/test_app_root/views/index.html.erb +0 -0
- data/{spec → test}/test_app_root/views/layout.html.erb +0 -0
- metadata +27 -39
- data/spec/angelo/static_spec.rb +0 -72
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c515e055a1dbd1a233df60640c31b82866edf6a
|
4
|
+
data.tar.gz: 617d5e38dec4a3ea40c27dc16eb094081f7b6b8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51acea14b590f1ed28b8017c2b9d88b953a77f24ee5c1784e6681d1d0521487faf7c18d228d99a9f3bd98b0b168c6f69d4e58e95d0b6463738053c35282433d1
|
7
|
+
data.tar.gz: 95de633d4b58ffeb51c4f43f03ce09bdae4f68191bf6e0cd278d9c1a77204907ed64ef3ab6cca2371698b98ae6818fe9b431503da77a1671890fbf8eb0957faf
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
changelog
|
2
2
|
=========
|
3
3
|
|
4
|
+
### 0.1.8 6 may 2014
|
5
|
+
|
6
|
+
* leave RSpec for Minitest
|
7
|
+
* fix for Reel::Connection::StateError -> Reel::StateError
|
8
|
+
* rename "socket" route definition to "websocket"
|
9
|
+
|
4
10
|
### 0.1.7 17 apr 2014
|
5
11
|
|
6
12
|
* reel 0.5.0 support (Reel::Server -> Reel::Server::HTTP)
|
data/Gemfile
CHANGED
@@ -5,13 +5,7 @@ gem 'tilt'
|
|
5
5
|
gem 'mime-types'
|
6
6
|
gem 'websocket-driver'
|
7
7
|
|
8
|
-
platform :
|
9
|
-
gem 'rubysl-cgi'
|
10
|
-
gem 'rubysl-erb'
|
11
|
-
gem 'rubysl-prettyprint'
|
12
|
-
end
|
13
|
-
|
14
|
-
platform :ruby_20 do
|
8
|
+
platform :ruby_20, :ruby_21 do
|
15
9
|
gem 'mustermann'
|
16
10
|
end
|
17
11
|
|
@@ -28,6 +22,5 @@ end
|
|
28
22
|
|
29
23
|
group :test do
|
30
24
|
gem 'httpclient'
|
31
|
-
gem '
|
32
|
-
gem 'rspec-pride'
|
25
|
+
gem 'minitest'
|
33
26
|
end
|
data/README.md
CHANGED
@@ -5,18 +5,163 @@ Angelo
|
|
5
5
|
|
6
6
|
A [Sinatra](https://github.com/sinatra/sinatra)-esque DSL for [Reel](https://github.com/celluloid/reel).
|
7
7
|
|
8
|
-
###
|
8
|
+
### tl;dr
|
9
9
|
|
10
|
-
*
|
11
|
-
*
|
12
|
-
*
|
10
|
+
* websocket support via `websocket('/path'){|s| ... }` route builder
|
11
|
+
* contextual websocket stashing via `websockets` helper
|
12
|
+
* `task` handling via `async` and `future` helpers
|
13
13
|
* no rack
|
14
14
|
* optional tilt/erb support
|
15
15
|
* optional mustermann support
|
16
16
|
|
17
|
+
### What is Angelo?
|
18
|
+
|
19
|
+
Just like Sinatra, Angelo gives you an expressive DSL for creating web applications. There are some
|
20
|
+
notable differences, but the basics remain the same. It mostly follows the subclass style of Sinatra:
|
21
|
+
you must define a subclass of `Angelo::Base` but also `.run` on that class for the service to start.
|
22
|
+
In addition, and perhaps more importantly, **Angelo is built upon Reel, which is, in turn, built upon
|
23
|
+
Celluloid::IO and gives you a reactor with evented IO in Ruby!**
|
24
|
+
|
25
|
+
Note: There currently is no "standalone" capability where one can define route handlers at the top level.
|
26
|
+
|
27
|
+
Things will feel very familiar to anyone experienced with Sinatra. Inside the subclass, you can define
|
28
|
+
route handlers denoted by HTTP verb and path. Unlike Sinatra, the only acceptable return value from a
|
29
|
+
route block is the body of the response in full. Chunked response support was recently added to Reel,
|
30
|
+
and look for that support in Angelo soon.
|
31
|
+
|
32
|
+
There is also [Mustermann](#mustermann) support for full-on, Sinatra-like path
|
33
|
+
matching and params.
|
34
|
+
|
35
|
+
### Websockets!
|
36
|
+
|
37
|
+
One of the main motivations for Angelo was the ability to define websocket handlers with ease. Through
|
38
|
+
the addition of a `websocket` route builder and a `websockets` helper, Angelo attempts to make it easy
|
39
|
+
for you to build real-time web applications.
|
40
|
+
|
41
|
+
##### Route Builder
|
42
|
+
|
43
|
+
The `websocket` route builder accepts a path and a block, and passes the actual websocket to the block
|
44
|
+
as the only argument. This socket is an instance of Reel's
|
45
|
+
[WebSocket](https://github.com/celluloid/reel/blob/master/lib/reel/websocket.rb) class, and, as such,
|
46
|
+
responds to methods like `on_message` and `on_close`. A service-wide `on_pong` handler (defined at the
|
47
|
+
class-level of the Angelo app) is available to customize the behavior when a pong frame comes back from
|
48
|
+
a connected websocket client.
|
49
|
+
|
50
|
+
##### `websockets` helper
|
51
|
+
|
52
|
+
Angelo includes a "stash" helper for connected websockets. One can `<<` a websocket into `websockets`
|
53
|
+
from inside a websocket handler block. These can "later" be iterated over so one can do things like
|
54
|
+
emit a message on every connected websocket when the service receives a POST request.
|
55
|
+
|
56
|
+
The `websockets` helper also includes a context ability, so you can stash connected websocket clients
|
57
|
+
into different "sections".
|
58
|
+
|
59
|
+
##### Example!
|
60
|
+
|
61
|
+
Here is an example of the `websocket` route builder, the `websockets` helper, and the context feature:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
require 'angelo'
|
65
|
+
|
66
|
+
class Foo < Angelo::Base
|
67
|
+
|
68
|
+
websocket '/' do |ws|
|
69
|
+
websockets << ws
|
70
|
+
end
|
71
|
+
|
72
|
+
websocket '/bar' do |ws|
|
73
|
+
websockets[:bar] << ws
|
74
|
+
end
|
75
|
+
|
76
|
+
post '/' do
|
77
|
+
websockets.each {|ws| ws.write params[:foo]}
|
78
|
+
end
|
79
|
+
|
80
|
+
post '/bar' do
|
81
|
+
websockets[:bar].each {|ws| ws.write params[:bar]}
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
Foo.run
|
87
|
+
```
|
88
|
+
|
89
|
+
In this case, any clients that connected to a websocket at the path '/' would be stashed in the
|
90
|
+
default websockets array; clients that connected to '/bar' would be stashed into the `:bar` section.
|
91
|
+
|
92
|
+
Each "section" can accessed with a familiar, `Hash`-like syntax, and can be iterated over with
|
93
|
+
a `.each` block.
|
94
|
+
|
95
|
+
When a `POST /` with a 'foo' param is received, any value is messaged out to any '/' connected
|
96
|
+
websockets. When a `POST /bar` with a 'bar' param is received, any value is messaged out to all
|
97
|
+
websockets that connected to '/bar'.
|
98
|
+
|
99
|
+
### Tasks + Async / Future
|
100
|
+
|
101
|
+
Angelo is built on Reel and Celluloid::IO, giving your web application class the ability to define
|
102
|
+
"tasks" and call them from route handler blocks in an `async` or `future` style.
|
103
|
+
|
104
|
+
##### `task` builder
|
105
|
+
|
106
|
+
You can define a task on the reactor using the `task` class method and giving it a symbol and a
|
107
|
+
block. The block can take arguments that you can pass later, with `async` or `future`.
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
# defining a task on the reactor called `:in_sec` which will sleep for
|
111
|
+
# given number of seconds, then return the given message.
|
112
|
+
#
|
113
|
+
task :in_sec do |sec, msg|
|
114
|
+
sleep sec.to_i
|
115
|
+
msg
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
##### `async` helper
|
120
|
+
|
121
|
+
This helper is directly analogous to the Celluoid method of the same name. Once tasks are defined,
|
122
|
+
you can call them with this helper method, passing the symbol of the task name and any arguments.
|
123
|
+
The task will run on the reactor, asynchronously, and return immediately.
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
get '/' do
|
127
|
+
# run the task defined above asynchronously, return immediately
|
128
|
+
#
|
129
|
+
async :in_sec, params[:sec], params[:msg]
|
130
|
+
|
131
|
+
# NOTE: params[:msg] is discarded, the return value of tasks called with `async` is nil.
|
132
|
+
|
133
|
+
# return this response body while the task is still running
|
134
|
+
# assuming params[:sec] is > 0
|
135
|
+
#
|
136
|
+
'hi'
|
137
|
+
end
|
138
|
+
```
|
139
|
+
|
140
|
+
##### `future` helper
|
141
|
+
|
142
|
+
Just like `async`, this comes from Celluloid as well. It behaves exactly like `async`, with the
|
143
|
+
notable exception of returing a "future" object that you can call `#value` on later to retreive
|
144
|
+
the return value of the task. Once `#value` is called, things will "block" until the task is
|
145
|
+
finished.
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
get '/' do
|
149
|
+
# run the task defined above asynchronously, return immediately
|
150
|
+
#
|
151
|
+
f = future :in_sec, params[:sec], params[:msg]
|
152
|
+
|
153
|
+
# now, block until the task is finished and return the task's value
|
154
|
+
# as a response body
|
155
|
+
#
|
156
|
+
f.value
|
157
|
+
end
|
158
|
+
```
|
159
|
+
|
160
|
+
### WORK LEFT TO DO
|
161
|
+
|
17
162
|
Lots of work left to do!
|
18
163
|
|
19
|
-
###
|
164
|
+
### Full-ish example
|
20
165
|
|
21
166
|
```ruby
|
22
167
|
require 'angelo'
|
@@ -25,18 +170,28 @@ require 'angelo/mustermann'
|
|
25
170
|
class Foo < Angelo::Base
|
26
171
|
include Angelo::Mustermann
|
27
172
|
|
173
|
+
# just some constants to use in routes later...
|
174
|
+
#
|
28
175
|
TEST = {foo: "bar", baz: 123, bat: false}.to_json
|
29
|
-
|
30
176
|
HEART = '<3'
|
177
|
+
|
178
|
+
# a flag to know if the :heart task is running
|
179
|
+
#
|
31
180
|
@@hearting = false
|
32
181
|
|
182
|
+
# you can define instance methods, just like Sinatra!
|
183
|
+
#
|
33
184
|
def pong; 'pong'; end
|
34
185
|
def foo; params[:foo]; end
|
35
186
|
|
187
|
+
# standard HTTP GET handler
|
188
|
+
#
|
36
189
|
get '/ping' do
|
37
190
|
pong
|
38
191
|
end
|
39
192
|
|
193
|
+
# standard HTTP POST handler
|
194
|
+
#
|
40
195
|
post '/foo' do
|
41
196
|
foo
|
42
197
|
end
|
@@ -45,12 +200,19 @@ class Foo < Angelo::Base
|
|
45
200
|
params.to_json
|
46
201
|
end
|
47
202
|
|
203
|
+
# emit the TEST JSON value on all :emit_test websockets
|
204
|
+
# return the params posted as JSON
|
205
|
+
#
|
48
206
|
post '/emit' do
|
49
207
|
websockets[:emit_test].each {|ws| ws.write TEST}
|
50
208
|
params.to_json
|
51
209
|
end
|
52
210
|
|
53
|
-
|
211
|
+
# handle websocket requests at '/ws'
|
212
|
+
# stash them in the :emit_test context
|
213
|
+
# write 6 messages to the websocket whenever a message is received
|
214
|
+
#
|
215
|
+
websocket '/ws' do |ws|
|
54
216
|
websockets[:emit_test] << ws
|
55
217
|
ws.on_message do |msg|
|
56
218
|
5.times { ws.write TEST }
|
@@ -58,19 +220,24 @@ class Foo < Angelo::Base
|
|
58
220
|
end
|
59
221
|
end
|
60
222
|
|
223
|
+
# emit the TEST JSON value on all :other websockets
|
224
|
+
#
|
61
225
|
post '/other' do
|
62
226
|
websockets[:other].each {|ws| ws.write TEST}
|
227
|
+
''
|
63
228
|
end
|
64
229
|
|
65
|
-
|
230
|
+
# stash '/other/ws' connected websockets in the :other context
|
231
|
+
#
|
232
|
+
websocket '/other/ws' do |ws|
|
66
233
|
websockets[:other] << ws
|
67
234
|
end
|
68
235
|
|
69
|
-
|
236
|
+
websocket '/hearts' do |ws|
|
70
237
|
|
71
|
-
# this is a call to Base#async, actually calling
|
238
|
+
# this is a call to Base#async, actually calling
|
72
239
|
# the reactor to start the task
|
73
|
-
#
|
240
|
+
#
|
74
241
|
async :hearts unless @@hearting
|
75
242
|
|
76
243
|
websockets[:hearts] << ws
|
@@ -87,12 +254,15 @@ class Foo < Angelo::Base
|
|
87
254
|
post '/in/:sec/sec/:msg' do
|
88
255
|
|
89
256
|
# this is a call to Base#future, telling the reactor
|
90
|
-
# do this thing and we'
|
257
|
+
# do this thing and we'll want the value eventually
|
91
258
|
#
|
92
259
|
f = future :in_sec params[:sec], params[:msg]
|
93
260
|
f.value
|
94
261
|
end
|
95
262
|
|
263
|
+
# define a task on the reactor that sleeps for the given number of
|
264
|
+
# seconds and returns the given message
|
265
|
+
#
|
96
266
|
task :in_sec do |sec, msg|
|
97
267
|
sleep sec.to_i
|
98
268
|
msg
|
@@ -155,8 +325,6 @@ class Foo < Angelo::Base
|
|
155
325
|
end
|
156
326
|
```
|
157
327
|
|
158
|
-
NOTE: this always sets the Mustermann object's `type` to `:sinatra`
|
159
|
-
|
160
328
|
### Contributing
|
161
329
|
|
162
330
|
YES, HAVE SOME
|
data/Rakefile
ADDED
data/lib/angelo/base.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'websocket/driver'
|
2
2
|
|
3
3
|
module Angelo
|
4
|
-
module
|
4
|
+
module Minitest
|
5
5
|
|
6
6
|
module Helpers
|
7
7
|
|
@@ -61,16 +61,16 @@ module Angelo
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
-
def
|
65
|
-
last_response.status.
|
66
|
-
last_response.body.
|
67
|
-
last_response.headers['Content-Type'].split(';').
|
64
|
+
def last_response_must_be_html body = ''
|
65
|
+
last_response.status.must_equal 200
|
66
|
+
last_response.body.must_equal body
|
67
|
+
last_response.headers['Content-Type'].split(';').must_include HTML_TYPE
|
68
68
|
end
|
69
69
|
|
70
|
-
def
|
71
|
-
last_response.status.
|
72
|
-
JSON.parse(last_response.body).
|
73
|
-
last_response.headers['Content-Type'].split(';').
|
70
|
+
def last_response_must_be_json obj = {}
|
71
|
+
last_response.status.must_equal 200
|
72
|
+
JSON.parse(last_response.body).must_equal obj
|
73
|
+
last_response.headers['Content-Type'].split(';').must_include JSON_TYPE
|
74
74
|
end
|
75
75
|
|
76
76
|
end
|
data/lib/angelo/mustermann.rb
CHANGED
data/lib/angelo/version.rb
CHANGED
@@ -42,7 +42,7 @@ locals :bar - bat
|
|
42
42
|
</body>
|
43
43
|
</html>
|
44
44
|
HTML
|
45
|
-
|
45
|
+
last_response_must_be_html expected
|
46
46
|
end
|
47
47
|
|
48
48
|
it 'renders templates without layout' do
|
@@ -51,7 +51,7 @@ HTML
|
|
51
51
|
foo - asdf
|
52
52
|
locals :bar - bat
|
53
53
|
HTML
|
54
|
-
|
54
|
+
last_response_must_be_html expected
|
55
55
|
end
|
56
56
|
|
57
57
|
end
|
@@ -30,13 +30,13 @@ if RUBY_VERSION =~ /^2\./
|
|
30
30
|
it 'matches via mustermann routes objects' do
|
31
31
|
path = '/some/things/are_good'
|
32
32
|
get path
|
33
|
-
|
33
|
+
last_response_must_be_json mm_pattern.params(path)
|
34
34
|
end
|
35
35
|
|
36
36
|
it 'overrides query string params' do
|
37
37
|
path = '/some/things/are_good'
|
38
38
|
get path, foo: 'other', bar: 'are_bad'
|
39
|
-
|
39
|
+
last_response_must_be_json mm_pattern.params(path)
|
40
40
|
end
|
41
41
|
|
42
42
|
it 'overrides post body params' do
|
@@ -44,14 +44,14 @@ if RUBY_VERSION =~ /^2\./
|
|
44
44
|
headers = {Angelo::CONTENT_TYPE_HEADER_KEY => Angelo::JSON_TYPE}
|
45
45
|
[:post, :put].each do |m|
|
46
46
|
__send__ m, path, {foo: 'other', bar: 'are_bad'}.to_json, headers
|
47
|
-
|
47
|
+
last_response_must_be_json mm_pattern.params(path)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
51
|
it '404s correctly for not found routes' do
|
52
52
|
path = '/bad/monkey'
|
53
53
|
get path
|
54
|
-
last_response.status.
|
54
|
+
last_response.status.must_equal 404
|
55
55
|
end
|
56
56
|
|
57
57
|
end
|
@@ -87,7 +87,7 @@ locals :bar - alpaca
|
|
87
87
|
</body>
|
88
88
|
</html>
|
89
89
|
HTML
|
90
|
-
|
90
|
+
last_response_must_be_html expected
|
91
91
|
end
|
92
92
|
|
93
93
|
end
|
@@ -43,21 +43,21 @@ describe Angelo::ParamsParser do
|
|
43
43
|
let(:parser) { ParamsTester.new }
|
44
44
|
|
45
45
|
it 'parses query string params in the normal, non-racked-up, way' do
|
46
|
-
parser.parse_formencoded(get_params).
|
46
|
+
parser.parse_formencoded(get_params).must_equal params_s
|
47
47
|
end
|
48
48
|
|
49
49
|
it 'parses formencoded POST bodies in the normal, non-racked-up, way' do
|
50
50
|
parser.form_encoded = true
|
51
51
|
parser.json = false
|
52
52
|
parser.body = get_params
|
53
|
-
parser.parse_post_body.
|
53
|
+
parser.parse_post_body.must_equal params_s
|
54
54
|
end
|
55
55
|
|
56
56
|
it 'parses JSON POST bodies params' do
|
57
57
|
parser.form_encoded = false
|
58
58
|
parser.json = true
|
59
59
|
parser.body = json_params
|
60
|
-
parser.parse_post_body.
|
60
|
+
parser.parse_post_body.must_equal post_params
|
61
61
|
end
|
62
62
|
|
63
63
|
it 'should override query string with JSON POST bodies params' do
|
@@ -65,7 +65,7 @@ describe Angelo::ParamsParser do
|
|
65
65
|
parser.json = true
|
66
66
|
parser.query_string = get_params
|
67
67
|
parser.body = json_params
|
68
|
-
parser.parse_post_body.
|
68
|
+
parser.parse_post_body.must_equal post_params
|
69
69
|
end
|
70
70
|
|
71
71
|
it 'does not parse POST bodies if no Content-Type' do
|
@@ -73,8 +73,8 @@ describe Angelo::ParamsParser do
|
|
73
73
|
parser.json = false
|
74
74
|
parser.query_string = get_params
|
75
75
|
parser.body = nil
|
76
|
-
parser.parse_post_body.
|
77
|
-
parser.parse_query_string.
|
76
|
+
parser.parse_post_body.must_equal params_s
|
77
|
+
parser.parse_query_string.must_equal params_s
|
78
78
|
end
|
79
79
|
|
80
80
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
describe Angelo::Server do
|
5
|
+
|
6
|
+
describe 'serving static files' do
|
7
|
+
|
8
|
+
let(:css_etag) do
|
9
|
+
fs = File::Stat.new File.join(TEST_APP_ROOT, 'public', 'test.css')
|
10
|
+
OpenSSL::Digest::SHA.hexdigest fs.ino.to_s + fs.size.to_s + fs.mtime.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
define_app do
|
14
|
+
|
15
|
+
@root = TEST_APP_ROOT
|
16
|
+
|
17
|
+
get '/test.html' do
|
18
|
+
'you should not see this'
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'serves static files for gets' do
|
24
|
+
get '/test.css'
|
25
|
+
last_response.status.must_equal 200
|
26
|
+
last_response.headers['Content-Type'].must_equal 'text/css'
|
27
|
+
last_response.headers['Content-Disposition'].must_equal 'attachment; filename=test.css'
|
28
|
+
last_response.headers['Content-Length'].must_equal '116'
|
29
|
+
last_response.headers['Etag'].must_equal css_etag
|
30
|
+
last_response.body.length.must_equal 116
|
31
|
+
last_response.body.must_equal File.read(File.join TEST_APP_ROOT, 'public', 'test.css')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'serves headers for static files on head' do
|
35
|
+
head '/test.css'
|
36
|
+
last_response.status.must_equal 200
|
37
|
+
last_response.headers['Content-Type'].must_equal 'text/css'
|
38
|
+
last_response.headers['Content-Disposition'].must_equal 'attachment; filename=test.css'
|
39
|
+
last_response.headers['Content-Length'].must_equal '116'
|
40
|
+
last_response.headers['Etag'].must_equal css_etag
|
41
|
+
last_response.body.length.must_equal 0
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'serves static file over route' do
|
45
|
+
get '/test.html'
|
46
|
+
last_response.status.must_equal 200
|
47
|
+
last_response.headers['Content-Type'].must_equal 'text/html'
|
48
|
+
last_response.headers['Content-Disposition'].must_equal 'attachment; filename=test.html'
|
49
|
+
last_response.body.must_equal File.read(File.join TEST_APP_ROOT, 'public', 'test.html')
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'not modifieds when if-none-match matched etag' do
|
53
|
+
get '/test.css', {}, {'If-None-Match' => css_etag}
|
54
|
+
last_response.status.must_equal 304
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'serves proper content-types' do
|
58
|
+
{ 'test.js' => 'application/javascript',
|
59
|
+
'test.html' => 'text/html',
|
60
|
+
'test.css' => 'text/css',
|
61
|
+
'what.png' => 'image/png' }.each do |k,v|
|
62
|
+
|
63
|
+
get "/#{k}"
|
64
|
+
last_response.status.must_equal 200
|
65
|
+
last_response.headers['Content-Type'].must_equal v
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -32,7 +32,7 @@ describe Angelo::WebsocketResponder do
|
|
32
32
|
describe 'basics' do
|
33
33
|
|
34
34
|
define_app do
|
35
|
-
|
35
|
+
websocket '/' do |ws|
|
36
36
|
while msg = ws.read do
|
37
37
|
ws.write msg
|
38
38
|
end
|
@@ -44,7 +44,7 @@ describe Angelo::WebsocketResponder do
|
|
44
44
|
latch = CountDownLatch.new 500
|
45
45
|
|
46
46
|
wsh.on_message = ->(e) {
|
47
|
-
|
47
|
+
assert_match /hi there \d/, e.data
|
48
48
|
latch.count_down
|
49
49
|
}
|
50
50
|
|
@@ -72,7 +72,7 @@ describe Angelo::WebsocketResponder do
|
|
72
72
|
Reactor.testers[:wshs] = Array.new(CONCURRENCY).map do
|
73
73
|
wsh = socket '/'
|
74
74
|
wsh.on_message = ->(e) {
|
75
|
-
|
75
|
+
assert_match /hi there \d/, e.data
|
76
76
|
latch.count_down
|
77
77
|
}
|
78
78
|
wsh.init
|
@@ -118,7 +118,7 @@ describe Angelo::WebsocketResponder do
|
|
118
118
|
end
|
119
119
|
end
|
120
120
|
|
121
|
-
|
121
|
+
websocket '/concur' do |ws|
|
122
122
|
websockets << ws
|
123
123
|
end
|
124
124
|
|
@@ -129,7 +129,7 @@ describe Angelo::WebsocketResponder do
|
|
129
129
|
latch = CountDownLatch.new CONCURRENCY * Angelo::HTTPABLE.length
|
130
130
|
|
131
131
|
expectation = ->(e){
|
132
|
-
|
132
|
+
assert_match /from http (#{Angelo::HTTPABLE.map(&:to_s).join('|')})/, e.data
|
133
133
|
}
|
134
134
|
|
135
135
|
socket_wait_for '/concur', latch, expectation do
|
@@ -143,7 +143,7 @@ describe Angelo::WebsocketResponder do
|
|
143
143
|
|
144
144
|
describe 'helper contexts' do
|
145
145
|
let(:obj){ {'foo' => 'bar'} }
|
146
|
-
let(:wait_for_block){ ->(e){
|
146
|
+
let(:wait_for_block){ ->(e){ assert_equal obj, JSON.parse(e.data) }}
|
147
147
|
|
148
148
|
define_app do
|
149
149
|
|
@@ -152,7 +152,7 @@ describe Angelo::WebsocketResponder do
|
|
152
152
|
''
|
153
153
|
end
|
154
154
|
|
155
|
-
|
155
|
+
websocket '/' do |ws|
|
156
156
|
websockets << ws
|
157
157
|
while msg = ws.read do
|
158
158
|
ws.write msg.to_json
|
@@ -164,7 +164,7 @@ describe Angelo::WebsocketResponder do
|
|
164
164
|
''
|
165
165
|
end
|
166
166
|
|
167
|
-
|
167
|
+
websocket '/one' do |ws|
|
168
168
|
websockets[:one] << ws
|
169
169
|
while msg = ws.read do
|
170
170
|
ws.write msg.to_json
|
@@ -176,7 +176,7 @@ describe Angelo::WebsocketResponder do
|
|
176
176
|
''
|
177
177
|
end
|
178
178
|
|
179
|
-
|
179
|
+
websocket '/other' do |ws|
|
180
180
|
websockets[:other] << ws
|
181
181
|
while msg = ws.read do
|
182
182
|
ws.write msg.to_json
|
@@ -2,12 +2,13 @@ require_relative './spec_helper'
|
|
2
2
|
|
3
3
|
describe Angelo::Base do
|
4
4
|
|
5
|
-
|
5
|
+
def obj
|
6
6
|
{'foo' => 'bar', 'bar' => 123.4567890123456, 'bat' => true}
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
|
+
def obj_s
|
9
10
|
obj.keys.reduce({}){|h,k| h[k] = obj[k].to_s; h}
|
10
|
-
|
11
|
+
end
|
11
12
|
|
12
13
|
describe 'the basics' do
|
13
14
|
|
@@ -35,24 +36,24 @@ describe Angelo::Base do
|
|
35
36
|
it 'responds to http requests properly' do
|
36
37
|
Angelo::HTTPABLE.each do |m|
|
37
38
|
__send__ m, '/'
|
38
|
-
|
39
|
+
last_response_must_be_html m.to_s
|
39
40
|
end
|
40
41
|
end
|
41
42
|
|
42
43
|
it 'responds to get requests with json properly' do
|
43
44
|
get '/json', obj
|
44
|
-
|
45
|
+
last_response_must_be_json obj_s
|
45
46
|
end
|
46
47
|
|
47
48
|
it 'responds to post requests with json properly' do
|
48
49
|
post '/json', obj.to_json, {'Content-Type' => Angelo::JSON_TYPE}
|
49
|
-
|
50
|
+
last_response_must_be_json obj
|
50
51
|
end
|
51
52
|
|
52
53
|
it 'redirects' do
|
53
54
|
get '/redirect'
|
54
|
-
|
55
|
-
|
55
|
+
last_response.status.must_equal 301
|
56
|
+
last_response.headers['Location'].must_equal '/'
|
56
57
|
end
|
57
58
|
|
58
59
|
end
|
@@ -77,11 +78,11 @@ describe Angelo::Base do
|
|
77
78
|
it 'runs before filters before routes' do
|
78
79
|
|
79
80
|
get '/before', obj
|
80
|
-
|
81
|
+
last_response_must_be_json obj_s
|
81
82
|
|
82
83
|
[:post, :put].each do |m|
|
83
84
|
__send__ m, '/before', obj.to_json, {Angelo::CONTENT_TYPE_HEADER_KEY => Angelo::JSON_TYPE}
|
84
|
-
|
85
|
+
last_response_must_be_json obj
|
85
86
|
end
|
86
87
|
|
87
88
|
end
|
@@ -115,8 +116,8 @@ describe Angelo::Base do
|
|
115
116
|
b = [4, 12, 28, 60]
|
116
117
|
Angelo::HTTPABLE.each_with_index do |m,i|
|
117
118
|
__send__ m, '/after', obj
|
118
|
-
|
119
|
-
invoked.
|
119
|
+
last_response_must_be_html a[i]
|
120
|
+
invoked.must_equal b[i]
|
120
121
|
end
|
121
122
|
end
|
122
123
|
|
@@ -138,16 +139,16 @@ describe Angelo::Base do
|
|
138
139
|
|
139
140
|
it 'sets headers for a response' do
|
140
141
|
put '/incr'
|
141
|
-
|
142
|
+
last_response.headers['X-Http-Angelo-Server'].must_equal 'catbutt'
|
142
143
|
end
|
143
144
|
|
144
145
|
it 'does not carry headers over responses' do
|
145
146
|
headers_count = 0
|
146
147
|
put '/incr'
|
147
|
-
|
148
|
+
last_response.headers['X-Http-Angelo-Server'].must_equal 'catbutt'
|
148
149
|
|
149
150
|
put '/incr'
|
150
|
-
|
151
|
+
last_response.headers['X-Http-Angelo-Server'].must_be_nil
|
151
152
|
end
|
152
153
|
|
153
154
|
end
|
@@ -190,35 +191,35 @@ describe Angelo::Base do
|
|
190
191
|
it 'sets html content type for current route' do
|
191
192
|
Angelo::HTTPABLE.each do |m|
|
192
193
|
__send__ m, '/html'
|
193
|
-
|
194
|
+
last_response_must_be_html '<html><body>hi</body></html>'
|
194
195
|
end
|
195
196
|
end
|
196
197
|
|
197
198
|
it 'sets json content type for current route and to_jsons hashes' do
|
198
199
|
Angelo::HTTPABLE.each do |m|
|
199
200
|
__send__ m, '/json'
|
200
|
-
|
201
|
+
last_response_must_be_json 'hi' => 'there'
|
201
202
|
end
|
202
203
|
end
|
203
204
|
|
204
205
|
it 'does not to_json strings' do
|
205
206
|
Angelo::HTTPABLE.each do |m|
|
206
207
|
__send__ m, '/json_s'
|
207
|
-
|
208
|
+
last_response_must_be_json 'woo' => 'woo'
|
208
209
|
end
|
209
210
|
end
|
210
211
|
|
211
212
|
it '500s on html hashes' do
|
212
213
|
Angelo::HTTPABLE.each do |m|
|
213
214
|
__send__ m, '/bad_html_h'
|
214
|
-
last_response.status.
|
215
|
+
last_response.status.must_equal 500
|
215
216
|
end
|
216
217
|
end
|
217
218
|
|
218
219
|
it '500s on bad json strings' do
|
219
220
|
Angelo::HTTPABLE.each do |m|
|
220
221
|
__send__ m, '/bad_json_s'
|
221
|
-
last_response.status.
|
222
|
+
last_response.status.must_equal 500
|
222
223
|
end
|
223
224
|
end
|
224
225
|
|
@@ -240,7 +241,7 @@ describe Angelo::Base do
|
|
240
241
|
it 'sets default content type' do
|
241
242
|
Angelo::HTTPABLE.each do |m|
|
242
243
|
__send__ m, '/html'
|
243
|
-
|
244
|
+
last_response_must_be_html '<html><body>hi</body></html>'
|
244
245
|
end
|
245
246
|
end
|
246
247
|
|
@@ -260,7 +261,7 @@ describe Angelo::Base do
|
|
260
261
|
it 'sets default content type' do
|
261
262
|
Angelo::HTTPABLE.each do |m|
|
262
263
|
__send__ m, '/json'
|
263
|
-
|
264
|
+
last_response_must_be_json 'hi' => 'there'
|
264
265
|
end
|
265
266
|
end
|
266
267
|
end
|
@@ -284,7 +285,7 @@ describe Angelo::Base do
|
|
284
285
|
it 'sets html content type for current route when default is set json' do
|
285
286
|
Angelo::HTTPABLE.each do |m|
|
286
287
|
__send__ m, '/json'
|
287
|
-
|
288
|
+
last_response_must_be_json 'hi' => 'there'
|
288
289
|
end
|
289
290
|
end
|
290
291
|
|
@@ -305,7 +306,7 @@ describe Angelo::Base do
|
|
305
306
|
it 'sets json content type for current route when default is set html' do
|
306
307
|
Angelo::HTTPABLE.each do |m|
|
307
308
|
__send__ m, '/html'
|
308
|
-
|
309
|
+
last_response_must_be_html '<html><body>hi</body></html>'
|
309
310
|
end
|
310
311
|
end
|
311
312
|
|
@@ -330,17 +331,17 @@ describe Angelo::Base do
|
|
330
331
|
|
331
332
|
it 'parses formencoded body when content-type is formencoded' do
|
332
333
|
post '/json', obj, {'Content-Type' => Angelo::FORM_TYPE}
|
333
|
-
|
334
|
+
last_response_must_be_json obj_s
|
334
335
|
end
|
335
336
|
|
336
337
|
it 'does not parse JSON body when content-type is formencoded' do
|
337
338
|
post '/json', obj.to_json, {'Content-Type' => Angelo::FORM_TYPE}
|
338
|
-
last_response.status.
|
339
|
+
last_response.status.must_equal 400
|
339
340
|
end
|
340
341
|
|
341
342
|
it 'does not parse body when request content-type not set' do
|
342
343
|
post '/json', obj, {'Content-Type' => ''}
|
343
|
-
|
344
|
+
last_response_must_be_json({})
|
344
345
|
end
|
345
346
|
|
346
347
|
end
|
@@ -375,7 +376,7 @@ describe Angelo::Base do
|
|
375
376
|
}
|
376
377
|
|
377
378
|
get '/rh', ps, hs
|
378
|
-
|
379
|
+
last_response_must_be_json 'values' => hs.values
|
379
380
|
end
|
380
381
|
|
381
382
|
end
|
@@ -1,11 +1,13 @@
|
|
1
|
-
$:.unshift File.expand_path '
|
1
|
+
$:.unshift File.expand_path '../../lib', __FILE__
|
2
2
|
|
3
3
|
require 'bundler'
|
4
4
|
Bundler.require :default, :development, :test
|
5
|
+
require 'minitest/pride'
|
6
|
+
require 'minitest/autorun'
|
5
7
|
require 'angelo'
|
6
|
-
require 'angelo/
|
8
|
+
require 'angelo/minitest/helpers'
|
7
9
|
Celluloid.logger.level = ::Logger::ERROR
|
8
|
-
include Angelo::
|
10
|
+
include Angelo::Minitest::Helpers
|
9
11
|
|
10
12
|
TEST_APP_ROOT = File.expand_path '../test_app_root', __FILE__
|
11
13
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
metadata
CHANGED
@@ -1,41 +1,41 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: angelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenichi Nakamura
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: reel
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: mime-types
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
description: A Sinatra-esque DSL for Reel
|
@@ -45,36 +45,37 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
-
- .gitignore
|
49
|
-
- .travis.yml
|
48
|
+
- ".gitignore"
|
49
|
+
- ".travis.yml"
|
50
50
|
- CHANGELOG.md
|
51
51
|
- Gemfile
|
52
52
|
- LICENSE
|
53
53
|
- README.md
|
54
|
+
- Rakefile
|
54
55
|
- angelo.gemspec
|
55
56
|
- lib/angelo.rb
|
56
57
|
- lib/angelo/base.rb
|
58
|
+
- lib/angelo/minitest/helpers.rb
|
57
59
|
- lib/angelo/mustermann.rb
|
58
60
|
- lib/angelo/params_parser.rb
|
59
61
|
- lib/angelo/responder.rb
|
60
62
|
- lib/angelo/responder/websocket.rb
|
61
|
-
- lib/angelo/rspec/helpers.rb
|
62
63
|
- lib/angelo/server.rb
|
63
64
|
- lib/angelo/tilt/erb.rb
|
64
65
|
- lib/angelo/version.rb
|
65
|
-
-
|
66
|
-
-
|
67
|
-
-
|
68
|
-
-
|
69
|
-
-
|
70
|
-
-
|
71
|
-
-
|
72
|
-
-
|
73
|
-
-
|
74
|
-
-
|
75
|
-
-
|
76
|
-
-
|
77
|
-
-
|
66
|
+
- test/angelo/erb_spec.rb
|
67
|
+
- test/angelo/mustermann_spec.rb
|
68
|
+
- test/angelo/params_spec.rb
|
69
|
+
- test/angelo/static_spec.rb
|
70
|
+
- test/angelo/websocket_spec.rb
|
71
|
+
- test/angelo_spec.rb
|
72
|
+
- test/spec_helper.rb
|
73
|
+
- test/test_app_root/public/test.css
|
74
|
+
- test/test_app_root/public/test.html
|
75
|
+
- test/test_app_root/public/test.js
|
76
|
+
- test/test_app_root/public/what.png
|
77
|
+
- test/test_app_root/views/index.html.erb
|
78
|
+
- test/test_app_root/views/layout.html.erb
|
78
79
|
homepage: https://github.com/kenichi/angelo
|
79
80
|
licenses:
|
80
81
|
- apache
|
@@ -85,31 +86,18 @@ require_paths:
|
|
85
86
|
- lib
|
86
87
|
required_ruby_version: !ruby/object:Gem::Requirement
|
87
88
|
requirements:
|
88
|
-
- -
|
89
|
+
- - ">="
|
89
90
|
- !ruby/object:Gem::Version
|
90
91
|
version: '0'
|
91
92
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
93
|
requirements:
|
93
|
-
- -
|
94
|
+
- - ">="
|
94
95
|
- !ruby/object:Gem::Version
|
95
96
|
version: '0'
|
96
97
|
requirements: []
|
97
98
|
rubyforge_project:
|
98
|
-
rubygems_version: 2.
|
99
|
+
rubygems_version: 2.2.2
|
99
100
|
signing_key:
|
100
101
|
specification_version: 4
|
101
102
|
summary: A Sinatra-esque DSL for Reel
|
102
|
-
test_files:
|
103
|
-
- spec/angelo/erb_spec.rb
|
104
|
-
- spec/angelo/mustermann_spec.rb
|
105
|
-
- spec/angelo/params_spec.rb
|
106
|
-
- spec/angelo/static_spec.rb
|
107
|
-
- spec/angelo/websocket_spec.rb
|
108
|
-
- spec/angelo_spec.rb
|
109
|
-
- spec/spec_helper.rb
|
110
|
-
- spec/test_app_root/public/test.css
|
111
|
-
- spec/test_app_root/public/test.html
|
112
|
-
- spec/test_app_root/public/test.js
|
113
|
-
- spec/test_app_root/public/what.png
|
114
|
-
- spec/test_app_root/views/index.html.erb
|
115
|
-
- spec/test_app_root/views/layout.html.erb
|
103
|
+
test_files: []
|
data/spec/angelo/static_spec.rb
DELETED
@@ -1,72 +0,0 @@
|
|
1
|
-
require_relative '../spec_helper'
|
2
|
-
require 'openssl'
|
3
|
-
|
4
|
-
describe Angelo::Server do
|
5
|
-
|
6
|
-
describe 'serving static files' do
|
7
|
-
|
8
|
-
let(:test_css_etag) do
|
9
|
-
fs = File::Stat.new File.join(TEST_APP_ROOT, 'public', 'test.css')
|
10
|
-
OpenSSL::Digest::SHA.hexdigest fs.ino.to_s + fs.size.to_s + fs.mtime.to_s
|
11
|
-
end
|
12
|
-
|
13
|
-
define_app do
|
14
|
-
|
15
|
-
@root = TEST_APP_ROOT
|
16
|
-
|
17
|
-
get '/test.html' do
|
18
|
-
'you should not see this'
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
|
23
|
-
it 'serves static files for gets' do
|
24
|
-
get '/test.css'
|
25
|
-
expect(last_response.status).to be(200)
|
26
|
-
expect(last_response.headers['Content-Type']).to eq('text/css')
|
27
|
-
expect(last_response.headers['Content-Disposition']).to eq('attachment; filename=test.css')
|
28
|
-
expect(last_response.headers['Content-Length']).to eq('116')
|
29
|
-
expect(last_response.headers['Etag']).to eq(test_css_etag)
|
30
|
-
expect(last_response.body.length).to be(116)
|
31
|
-
expect(last_response.body).to eq(File.read(File.join TEST_APP_ROOT, 'public', 'test.css'))
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'serves headers for static files on head' do
|
35
|
-
head '/test.css'
|
36
|
-
expect(last_response.status).to be(200)
|
37
|
-
expect(last_response.headers['Content-Type']).to eq('text/css')
|
38
|
-
expect(last_response.headers['Content-Disposition']).to eq('attachment; filename=test.css')
|
39
|
-
expect(last_response.headers['Content-Length']).to eq('116')
|
40
|
-
expect(last_response.headers['Etag']).to eq(test_css_etag)
|
41
|
-
expect(last_response.body.length).to be(0)
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'serves static file over route' do
|
45
|
-
get '/test.html'
|
46
|
-
expect(last_response.status).to be(200)
|
47
|
-
expect(last_response.headers['Content-Type']).to eq('text/html')
|
48
|
-
expect(last_response.headers['Content-Disposition']).to eq('attachment; filename=test.html')
|
49
|
-
expect(last_response.body).to eq(File.read(File.join TEST_APP_ROOT, 'public', 'test.html'))
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'not modifieds when if-none-match matched etag' do
|
53
|
-
get '/test.css', {}, {'If-None-Match' => test_css_etag}
|
54
|
-
expect(last_response.status).to be(304)
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'serves proper content-types' do
|
58
|
-
{ 'test.js' => 'application/javascript',
|
59
|
-
'test.html' => 'text/html',
|
60
|
-
'test.css' => 'text/css',
|
61
|
-
'what.png' => 'image/png' }.each do |k,v|
|
62
|
-
|
63
|
-
get "/#{k}"
|
64
|
-
expect(last_response.status).to be(200)
|
65
|
-
expect(last_response.headers['Content-Type']).to eq(v)
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
end
|