angelo 0.0.4 → 0.0.7
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.
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG +22 -0
- data/Gemfile +25 -2
- data/README.md +16 -6
- data/angelo.gemspec +2 -2
- data/lib/angelo.rb +18 -2
- data/lib/angelo/base.rb +36 -24
- data/lib/angelo/params_parser.rb +1 -1
- data/lib/angelo/responder.rb +26 -5
- data/lib/angelo/responder/websocket.rb +1 -1
- data/lib/angelo/rspec/helpers.rb +108 -0
- data/lib/angelo/server.rb +4 -1
- data/lib/angelo/version.rb +2 -1
- data/spec/angelo/erb_spec.rb +60 -0
- data/spec/angelo/params_spec.rb +71 -0
- data/spec/angelo/views/index.html.erb +2 -0
- data/spec/angelo/views/layout.html.erb +9 -0
- data/spec/angelo/websocket_spec.rb +163 -0
- data/spec/angelo_spec.rb +116 -0
- data/spec/spec_helper.rb +8 -0
- metadata +19 -6
- data/example/foo/foo.rb +0 -60
- data/example/foo/views/index.html.erb +0 -20
- data/example/foo/views/layout.html.erb +0 -10
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/CHANGELOG
CHANGED
@@ -1,6 +1,28 @@
|
|
1
1
|
changelog
|
2
2
|
=========
|
3
3
|
|
4
|
+
### 0.0.7 5 nov 2013 gunpowder treason
|
5
|
+
|
6
|
+
* fix gemspec
|
7
|
+
* add codename
|
8
|
+
* travis
|
9
|
+
|
10
|
+
### 0.0.6 5 nov 2013
|
11
|
+
|
12
|
+
* rspec and tests!
|
13
|
+
* lots of fixing broken that testing found
|
14
|
+
* contextualized websockets helper
|
15
|
+
|
16
|
+
### 0.0.5 31 oct 2013 SPOOOOKEEEE
|
17
|
+
|
18
|
+
* add Base.content_type
|
19
|
+
* Responder#content_type= -> Responder#content_type
|
20
|
+
* properly delegated to Responder
|
21
|
+
|
22
|
+
### 0.0.4 30 oct 2013
|
23
|
+
|
24
|
+
* inadvertent yank of 0.0.3
|
25
|
+
|
4
26
|
### 0.0.3 30 oct 2013
|
5
27
|
|
6
28
|
|
data/Gemfile
CHANGED
@@ -1,5 +1,28 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
gem 'reel'
|
4
|
-
gem '
|
5
|
-
|
4
|
+
gem 'tilt'
|
5
|
+
|
6
|
+
platform :rbx do
|
7
|
+
gem 'rubysl-cgi'
|
8
|
+
gem 'rubysl-erb'
|
9
|
+
gem 'rubysl-json'
|
10
|
+
gem 'rubysl-prettyprint'
|
11
|
+
end
|
12
|
+
|
13
|
+
group :development do
|
14
|
+
gem 'pry'
|
15
|
+
gem 'pry-nav'
|
16
|
+
end
|
17
|
+
|
18
|
+
group :profile do
|
19
|
+
platform :mri do
|
20
|
+
gem 'ruby-prof'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
group :test do
|
25
|
+
gem 'httpclient'
|
26
|
+
gem 'rspec'
|
27
|
+
gem 'rspec-pride'
|
28
|
+
end
|
data/README.md
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
Angelo
|
2
2
|
======
|
3
3
|
|
4
|
+
[](https://travis-ci.org/kenichi/angelo)
|
5
|
+
|
4
6
|
A Sinatra-esque DSL for Reel.
|
5
7
|
|
6
8
|
__SUPER ALPHA!__
|
@@ -30,18 +32,26 @@ class Foo < Angelo::Base
|
|
30
32
|
end
|
31
33
|
|
32
34
|
post '/emit' do
|
33
|
-
websockets.each {|ws| ws.write TEST}
|
35
|
+
websockets[:emit_test].each {|ws| ws.write TEST}
|
34
36
|
params.to_json
|
35
37
|
end
|
36
38
|
|
37
|
-
socket '/ws' do |
|
38
|
-
websockets <<
|
39
|
-
while msg =
|
40
|
-
5.times {
|
41
|
-
|
39
|
+
socket '/ws' do |ws|
|
40
|
+
websockets[:emit_test] << ws
|
41
|
+
while msg = ws.read
|
42
|
+
5.times { ws.write TEST }
|
43
|
+
ws.write foo.to_json
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
47
|
+
post '/other' do
|
48
|
+
websockets[:other].each {|ws| ws.write TEST}
|
49
|
+
end
|
50
|
+
|
51
|
+
socket '/other/ws' do |ws|
|
52
|
+
websockets[:other] << ws
|
53
|
+
end
|
54
|
+
|
45
55
|
end
|
46
56
|
|
47
57
|
Foo.run
|
data/angelo.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |gem|
|
|
6
6
|
gem.email = ["kenichi.nakamura@gmail.com"]
|
7
7
|
gem.description = gem.summary = "A Sinatra-esque DSL for Reel"
|
8
8
|
gem.homepage = "https://github.com/kenichi/angelo"
|
9
|
-
gem.files = `git ls-files | grep -Ev '^
|
10
|
-
gem.test_files = `git ls-files --
|
9
|
+
gem.files = `git ls-files | grep -Ev '^example'`.split("\n")
|
10
|
+
gem.test_files = `git ls-files -- spec/*`.split("\n")
|
11
11
|
gem.name = "angelo"
|
12
12
|
gem.require_paths = ["lib"]
|
13
13
|
gem.version = Angelo::VERSION
|
data/lib/angelo.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
require 'reel'
|
2
2
|
require 'json'
|
3
|
-
|
4
|
-
require '
|
3
|
+
|
4
|
+
# require 'ruby-prof'
|
5
|
+
#
|
6
|
+
# RubyProf.start
|
7
|
+
# RubyProf.pause
|
5
8
|
|
6
9
|
module Angelo
|
7
10
|
|
@@ -11,12 +14,18 @@ module Angelo
|
|
11
14
|
DELETE = 'DELETE'.freeze
|
12
15
|
OPTIONS = 'OPTIONS'.freeze
|
13
16
|
|
17
|
+
ROUTABLE = [:get, :post, :put, :delete, :socket]
|
18
|
+
HTTPABLE = [:get, :post, :put, :delete]
|
19
|
+
|
14
20
|
CONTENT_TYPE_HEADER_KEY = 'Content-Type'.freeze
|
15
21
|
|
16
22
|
HTML_TYPE = 'text/html'.freeze
|
17
23
|
JSON_TYPE = 'application/json'.freeze
|
18
24
|
FORM_TYPE = 'application/x-www-form-urlencoded'.freeze
|
19
25
|
|
26
|
+
DEFAULT_ADDR = '127.0.0.1'.freeze
|
27
|
+
DEFAULT_PORT = 4567
|
28
|
+
|
20
29
|
DEFAULT_RESPONSE_HEADERS = {
|
21
30
|
CONTENT_TYPE_HEADER_KEY => HTML_TYPE
|
22
31
|
}
|
@@ -31,3 +40,10 @@ require 'angelo/server'
|
|
31
40
|
require 'angelo/base'
|
32
41
|
require 'angelo/responder'
|
33
42
|
require 'angelo/responder/websocket'
|
43
|
+
|
44
|
+
# trap "INT" do
|
45
|
+
# result = RubyProf.stop
|
46
|
+
# printer = RubyProf::MultiPrinter.new(result)
|
47
|
+
# printer.print(path: './profiler', profile: 'foo')
|
48
|
+
# exit
|
49
|
+
# end
|
data/lib/angelo/base.rb
CHANGED
@@ -5,7 +5,7 @@ module Angelo
|
|
5
5
|
include Celluloid::Logger
|
6
6
|
|
7
7
|
extend Forwardable
|
8
|
-
|
8
|
+
def_delegators :@responder, :content_type, :headers, :request
|
9
9
|
|
10
10
|
attr_accessor :responder
|
11
11
|
|
@@ -37,23 +37,21 @@ module Angelo
|
|
37
37
|
|
38
38
|
def routes
|
39
39
|
@routes ||= {}
|
40
|
-
|
40
|
+
ROUTABLE.each do |m|
|
41
41
|
@routes[m] ||= {}
|
42
42
|
end
|
43
43
|
@routes
|
44
44
|
end
|
45
45
|
|
46
|
-
def before &block
|
47
|
-
# @before = compile! :before, &block
|
46
|
+
def before opts = {}, &block
|
48
47
|
define_method :before, &block
|
49
48
|
end
|
50
49
|
|
51
|
-
def after &block
|
52
|
-
# @after = compile! :after, &block
|
50
|
+
def after opts = {}, &block
|
53
51
|
define_method :after, &block
|
54
52
|
end
|
55
53
|
|
56
|
-
|
54
|
+
HTTPABLE.each do |m|
|
57
55
|
define_method m do |path, &block|
|
58
56
|
routes[m][path] = Responder.new &block
|
59
57
|
end
|
@@ -64,43 +62,57 @@ module Angelo
|
|
64
62
|
end
|
65
63
|
|
66
64
|
def websockets
|
67
|
-
|
68
|
-
@websockets = []
|
69
|
-
def @websockets.each &block
|
70
|
-
super do |ws|
|
71
|
-
begin
|
72
|
-
yield ws
|
73
|
-
rescue Reel::SocketError => rse
|
74
|
-
warn "#{rse.class} - #{rse.message}"
|
75
|
-
delete ws
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
65
|
+
@websockets ||= WebsocketsArray.new
|
80
66
|
@websockets.reject! &:closed?
|
81
67
|
@websockets
|
82
68
|
end
|
83
69
|
|
84
|
-
def
|
70
|
+
def content_type type
|
71
|
+
Responder.content_type type
|
72
|
+
end
|
73
|
+
|
74
|
+
def run host = DEFAULT_ADDR, port = DEFAULT_PORT
|
85
75
|
@server = Angelo::Server.new self, host, port
|
76
|
+
trap "INT" do
|
77
|
+
@server.terminate if @server and @server.alive?
|
78
|
+
exit
|
79
|
+
end
|
86
80
|
sleep
|
87
81
|
end
|
88
82
|
|
89
83
|
end
|
90
84
|
|
91
|
-
def before; end;
|
92
|
-
def after; end;
|
93
|
-
|
94
85
|
def params
|
95
86
|
@params ||= case request.method
|
96
87
|
when GET; parse_query_string
|
97
88
|
when POST; parse_post_body
|
89
|
+
when PUT; parse_post_body
|
98
90
|
end
|
99
91
|
@params
|
100
92
|
end
|
101
93
|
|
102
94
|
def websockets; self.class.websockets; end
|
103
95
|
|
96
|
+
class WebsocketsArray < Array
|
97
|
+
|
98
|
+
def each &block
|
99
|
+
super do |ws|
|
100
|
+
begin
|
101
|
+
yield ws
|
102
|
+
rescue Reel::SocketError => rse
|
103
|
+
warn "#{rse.class} - #{rse.message}"
|
104
|
+
delete ws
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def [] context
|
110
|
+
@@websockets ||= {}
|
111
|
+
@@websockets[context] ||= self.class.new
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
104
116
|
end
|
105
117
|
|
106
118
|
end
|
data/lib/angelo/params_parser.rb
CHANGED
data/lib/angelo/responder.rb
CHANGED
@@ -5,6 +5,25 @@ module Angelo
|
|
5
5
|
|
6
6
|
class << self
|
7
7
|
|
8
|
+
attr_writer :default_headers
|
9
|
+
|
10
|
+
def content_type type
|
11
|
+
dhs = self.default_headers
|
12
|
+
case type
|
13
|
+
when :json
|
14
|
+
self.default_headers = dhs.merge CONTENT_TYPE_HEADER_KEY => JSON_TYPE
|
15
|
+
when :html
|
16
|
+
self.default_headers = dhs.merge CONTENT_TYPE_HEADER_KEY => HTML_TYPE
|
17
|
+
else
|
18
|
+
raise ArgumentError.new "invalid content_type: #{type}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def default_headers
|
23
|
+
@default_headers ||= DEFAULT_RESPONSE_HEADERS
|
24
|
+
@default_headers
|
25
|
+
end
|
26
|
+
|
8
27
|
def symhash
|
9
28
|
Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
|
10
29
|
end
|
@@ -33,9 +52,9 @@ module Angelo
|
|
33
52
|
def handle_request
|
34
53
|
begin
|
35
54
|
if @response_handler
|
36
|
-
@base.before
|
55
|
+
@base.before if @base.respond_to? :before
|
37
56
|
@body = @response_handler.bind(@base).call || ''
|
38
|
-
@base.after
|
57
|
+
@base.after if @base.respond_to? :after
|
39
58
|
else
|
40
59
|
raise NotImplementedError
|
41
60
|
end
|
@@ -54,15 +73,17 @@ module Angelo
|
|
54
73
|
end
|
55
74
|
|
56
75
|
def headers hs = nil
|
57
|
-
@headers ||=
|
76
|
+
@headers ||= self.class.default_headers.dup
|
58
77
|
@headers.merge! hs if hs
|
59
78
|
@headers
|
60
79
|
end
|
61
80
|
|
62
|
-
def content_type
|
81
|
+
def content_type type
|
63
82
|
case type
|
64
83
|
when :json
|
65
84
|
headers CONTENT_TYPE_HEADER_KEY => JSON_TYPE
|
85
|
+
when :html
|
86
|
+
headers CONTENT_TYPE_HEADER_KEY => HTML_TYPE
|
66
87
|
else
|
67
88
|
raise ArgumentError.new "invalid content_type: #{type}"
|
68
89
|
end
|
@@ -79,7 +100,7 @@ module Angelo
|
|
79
100
|
|
80
101
|
def respond
|
81
102
|
@body = @body.to_json if respond_with? :json
|
82
|
-
@connection.respond :ok,
|
103
|
+
@connection.respond :ok, headers, @body
|
83
104
|
end
|
84
105
|
|
85
106
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Angelo
|
2
|
+
module RSpec
|
3
|
+
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
HTTP_URL = 'http://%s:%d'.freeze
|
7
|
+
WS_URL = 'ws://%s:%d'.freeze
|
8
|
+
|
9
|
+
attr_reader :last_response
|
10
|
+
|
11
|
+
def define_app &block
|
12
|
+
|
13
|
+
before do
|
14
|
+
app = Class.new Angelo::Base, &block
|
15
|
+
@server = Angelo::Server.new app
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
sleep 0.1
|
20
|
+
@server.terminate if @server and @server.alive?
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def hc
|
26
|
+
@hc ||= HTTPClient.new
|
27
|
+
@hc
|
28
|
+
end
|
29
|
+
private :hc
|
30
|
+
|
31
|
+
def hc_req method, path, params = {}, headers = {}
|
32
|
+
url = HTTP_URL % [DEFAULT_ADDR, DEFAULT_PORT]
|
33
|
+
@last_response = hc.__send__ method, url+path, params, headers
|
34
|
+
end
|
35
|
+
private :hc_req
|
36
|
+
|
37
|
+
[:get, :post, :put, :delete, :options].each do |m|
|
38
|
+
define_method m do |path, params = {}, headers = {}|
|
39
|
+
hc_req m, path, params, headers
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def socket path, params = {}, &block
|
44
|
+
begin
|
45
|
+
client = TCPSocket.new DEFAULT_ADDR, DEFAULT_PORT
|
46
|
+
|
47
|
+
params = params.keys.reduce([]) {|a,k|
|
48
|
+
a << CGI.escape(k) + '=' + CGI.escape(params[k])
|
49
|
+
a
|
50
|
+
}.join('&')
|
51
|
+
|
52
|
+
url = WS_URL % [DEFAULT_ADDR, DEFAULT_PORT] + path + "?#{params}"
|
53
|
+
|
54
|
+
handshake = WebSocket::ClientHandshake.new :get, url, {
|
55
|
+
"Host" => "www.example.com",
|
56
|
+
"Upgrade" => "websocket",
|
57
|
+
"Connection" => "Upgrade",
|
58
|
+
"Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==",
|
59
|
+
"Origin" => "http://example.com",
|
60
|
+
"Sec-WebSocket-Protocol" => "chat, superchat",
|
61
|
+
"Sec-WebSocket-Version" => "13"
|
62
|
+
}
|
63
|
+
|
64
|
+
client << handshake.to_data
|
65
|
+
yield WebsocketHelper.new client
|
66
|
+
ensure
|
67
|
+
client.close
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def last_response_should_be_html body = ''
|
72
|
+
last_response.status.should eq 200
|
73
|
+
last_response.body.should eq body
|
74
|
+
last_response.headers['Content-Type'].split(';').should include HTML_TYPE
|
75
|
+
end
|
76
|
+
|
77
|
+
def last_response_should_be_json obj = {}
|
78
|
+
last_response.status.should eq 200
|
79
|
+
JSON.parse(last_response.body).should eq obj
|
80
|
+
last_response.headers['Content-Type'].split(';').should include JSON_TYPE
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
class WebsocketHelper
|
86
|
+
|
87
|
+
def initialize client
|
88
|
+
@client = client
|
89
|
+
@client.readpartial 4096 # ditch response handshake
|
90
|
+
end
|
91
|
+
|
92
|
+
def parser
|
93
|
+
@parser ||= WebSocket::Parser.new
|
94
|
+
end
|
95
|
+
|
96
|
+
def send msg
|
97
|
+
@client << WebSocket::Message.new(msg).to_data
|
98
|
+
end
|
99
|
+
|
100
|
+
def recv
|
101
|
+
parser.append @client.readpartial(4096) until msg = parser.next_message
|
102
|
+
msg
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
data/lib/angelo/server.rb
CHANGED
@@ -5,11 +5,13 @@ module Angelo
|
|
5
5
|
|
6
6
|
def initialize base, host = '127.0.0.1', port = 4567
|
7
7
|
@base = base
|
8
|
-
info "Angelo
|
8
|
+
info "Angelo #{VERSION} \"#{CODENAME}\""
|
9
|
+
info "listening on #{host}:#{port}"
|
9
10
|
super host, port, &method(:on_connection)
|
10
11
|
end
|
11
12
|
|
12
13
|
def on_connection connection
|
14
|
+
# RubyProf.resume
|
13
15
|
connection.each_request do |request|
|
14
16
|
if request.websocket?
|
15
17
|
debug "got websocket request..."
|
@@ -18,6 +20,7 @@ module Angelo
|
|
18
20
|
route_request connection, request
|
19
21
|
end
|
20
22
|
end
|
23
|
+
# RubyProf.pause
|
21
24
|
end
|
22
25
|
|
23
26
|
private
|
data/lib/angelo/version.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'angelo/tilt/erb'
|
3
|
+
|
4
|
+
ROOT = File.expand_path '..', __FILE__
|
5
|
+
|
6
|
+
describe Angelo::Base do
|
7
|
+
describe Angelo::Tilt::ERB do
|
8
|
+
|
9
|
+
define_app do
|
10
|
+
|
11
|
+
include Angelo::Tilt::ERB
|
12
|
+
|
13
|
+
@root = ROOT
|
14
|
+
|
15
|
+
def set_vars
|
16
|
+
@title = 'test'
|
17
|
+
@foo = params[:foo]
|
18
|
+
end
|
19
|
+
|
20
|
+
get '/' do
|
21
|
+
set_vars
|
22
|
+
erb :index, locals: {bar: 'bat'}
|
23
|
+
end
|
24
|
+
|
25
|
+
get '/no_layout' do
|
26
|
+
set_vars
|
27
|
+
erb :index, layout: false, locals: {bar: 'bat'}
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'renders templates with layout' do
|
33
|
+
get '/', foo: 'asdf'
|
34
|
+
expected = <<HTML
|
35
|
+
<!doctype html>
|
36
|
+
<html>
|
37
|
+
<head>
|
38
|
+
<title>test</title>
|
39
|
+
</head>
|
40
|
+
<body>
|
41
|
+
foo - asdf
|
42
|
+
locals :bar - bat
|
43
|
+
|
44
|
+
</body>
|
45
|
+
</html>
|
46
|
+
HTML
|
47
|
+
last_response_should_be_html expected
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'renders templates without layout' do
|
51
|
+
get '/no_layout', foo: 'asdf'
|
52
|
+
expected = <<HTML
|
53
|
+
foo - asdf
|
54
|
+
locals :bar - bat
|
55
|
+
HTML
|
56
|
+
last_response_should_be_html expected
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
class ParamsTester
|
4
|
+
include Angelo::ParamsParser
|
5
|
+
|
6
|
+
attr_accessor :body, :form_encoded, :json, :query_string
|
7
|
+
|
8
|
+
def request
|
9
|
+
if @request.nil?
|
10
|
+
@request = OpenStruct.new
|
11
|
+
@request.body = @body
|
12
|
+
@request.query_string = @query_string
|
13
|
+
end
|
14
|
+
@request
|
15
|
+
end
|
16
|
+
|
17
|
+
def form_encoded?; @form_encoded; end
|
18
|
+
def json?; @json; end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
describe Angelo::ParamsParser do
|
23
|
+
|
24
|
+
let(:get_params) {
|
25
|
+
'foo=bar&bar=123456.78901234567&bat=true&array%5B%5D=wat&array%5B%5D=none'
|
26
|
+
}
|
27
|
+
|
28
|
+
let(:post_params) {
|
29
|
+
{
|
30
|
+
'foo' => 'bar',
|
31
|
+
'bar' => 123456.78901234567,
|
32
|
+
'bat' => true,
|
33
|
+
'array[]' => 'none'
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
let(:json_params) { post_params.to_json }
|
38
|
+
|
39
|
+
let(:params_s) {
|
40
|
+
post_params.keys.reduce({}){|h,k| h[k] = post_params[k].to_s; h}
|
41
|
+
}
|
42
|
+
|
43
|
+
let(:parser) { ParamsTester.new }
|
44
|
+
|
45
|
+
it 'parses query string params in the normal, non-racked-up, way' do
|
46
|
+
parser.parse_formencoded(get_params).should eq params_s
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'parses formencoded POST bodies in the normal, non-racked-up, way' do
|
50
|
+
parser.form_encoded = true
|
51
|
+
parser.json = false
|
52
|
+
parser.body = get_params
|
53
|
+
parser.parse_post_body.should eq params_s
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'parses JSON POST bodies params' do
|
57
|
+
parser.form_encoded = false
|
58
|
+
parser.json = true
|
59
|
+
parser.body = json_params
|
60
|
+
parser.parse_post_body.should eq post_params
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should override query string with JSON POST bodies params' do
|
64
|
+
parser.form_encoded = false
|
65
|
+
parser.json = true
|
66
|
+
parser.query_string = get_params
|
67
|
+
parser.body = json_params
|
68
|
+
parser.parse_post_body.should eq post_params
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
CK = 'ANGELO_CONCURRENCY' # concurrency key
|
4
|
+
DC = 5 # default concurrency
|
5
|
+
|
6
|
+
describe Angelo::WebsocketResponder do
|
7
|
+
|
8
|
+
let(:concurrency){ ENV.key?(CK) ? ENV[CK].to_i : DC }
|
9
|
+
|
10
|
+
def socket_wait_for path, &block
|
11
|
+
Array.new(concurrency).map {|n| Thread.new {socket path, &block}}
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'basics' do
|
15
|
+
|
16
|
+
define_app do
|
17
|
+
socket '/' do |ws|
|
18
|
+
while msg = ws.read do
|
19
|
+
ws.write msg
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'responds on websockets properly' do
|
25
|
+
socket '/' do |client|
|
26
|
+
5.times {|n|
|
27
|
+
client.send "hi there #{n}"
|
28
|
+
client.recv.should eq "hi there #{n}"
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'responds on multiple websockets properly' do
|
34
|
+
5.times do
|
35
|
+
Thread.new do
|
36
|
+
socket '/' do |client|
|
37
|
+
5.times {|n|
|
38
|
+
client.send "hi there #{n}"
|
39
|
+
client.recv.should eq "hi there #{n}"
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'concurrency' do
|
49
|
+
|
50
|
+
define_app do
|
51
|
+
|
52
|
+
Angelo::HTTPABLE.each do |m|
|
53
|
+
__send__ m, '/concur' do
|
54
|
+
websockets.each do |ws|
|
55
|
+
msg = "from #{params ? params[:foo] : 'http'} #{m.to_s}"
|
56
|
+
ws.write msg
|
57
|
+
end
|
58
|
+
''
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
socket '/concur' do |ws|
|
63
|
+
websockets << ws
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'works with http requests' do
|
69
|
+
|
70
|
+
ts = socket_wait_for '/concur' do |client|
|
71
|
+
Angelo::HTTPABLE.each do |m|
|
72
|
+
client.recv.should eq "from http #{m}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
sleep 0.1
|
77
|
+
Angelo::HTTPABLE.each {|m| __send__ m, '/concur', foo: 'http'}
|
78
|
+
ts.each &:join
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
describe 'helper contexts' do
|
85
|
+
let(:obj){ {'foo' => 'bar'} }
|
86
|
+
let(:wait_for_block) { ->(client){ JSON.parse(client.recv).should eq obj}}
|
87
|
+
|
88
|
+
define_app do
|
89
|
+
|
90
|
+
post '/' do
|
91
|
+
websockets.each {|ws| ws.write params.to_json}
|
92
|
+
''
|
93
|
+
end
|
94
|
+
|
95
|
+
socket '/' do |ws|
|
96
|
+
websockets << ws
|
97
|
+
while msg = ws.read do
|
98
|
+
ws.write msg.to_json
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
post '/one' do
|
103
|
+
websockets[:one].each {|ws| ws.write params.to_json}
|
104
|
+
''
|
105
|
+
end
|
106
|
+
|
107
|
+
socket '/one' do |ws|
|
108
|
+
websockets[:one] << ws
|
109
|
+
while msg = ws.read do
|
110
|
+
ws.write msg.to_json
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
post '/other' do
|
115
|
+
websockets[:other].each {|ws| ws.write params.to_json}
|
116
|
+
''
|
117
|
+
end
|
118
|
+
|
119
|
+
socket '/other' do |ws|
|
120
|
+
websockets[:other] << ws
|
121
|
+
while msg = ws.read do
|
122
|
+
ws.write msg.to_json
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'handles single context' do
|
129
|
+
ts = socket_wait_for '/', &wait_for_block
|
130
|
+
sleep 0.1
|
131
|
+
post '/', obj
|
132
|
+
ts.each &:join
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'handles multiple contexts' do
|
136
|
+
ts = socket_wait_for '/', &wait_for_block
|
137
|
+
one_ts = socket_wait_for '/one', &wait_for_block
|
138
|
+
other_ts = socket_wait_for '/other', &wait_for_block
|
139
|
+
|
140
|
+
sleep 0.1
|
141
|
+
post '/one', obj
|
142
|
+
|
143
|
+
ts.each {|t| t.should be_alive}
|
144
|
+
one_ts.each &:join
|
145
|
+
other_ts.each {|t| t.should be_alive}
|
146
|
+
|
147
|
+
sleep 0.1
|
148
|
+
post '/other', obj
|
149
|
+
|
150
|
+
ts.each {|t| t.should be_alive}
|
151
|
+
one_ts.each {|t| t.should_not be_alive}
|
152
|
+
other_ts.each &:join
|
153
|
+
|
154
|
+
sleep 0.1
|
155
|
+
post '/', obj
|
156
|
+
|
157
|
+
ts.each &:join
|
158
|
+
one_ts.each {|t| t.should_not be_alive}
|
159
|
+
other_ts.each {|t| t.should_not be_alive}
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
data/spec/angelo_spec.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require_relative './spec_helper'
|
2
|
+
|
3
|
+
describe Angelo::Base do
|
4
|
+
|
5
|
+
let :obj do
|
6
|
+
{'foo' => 'bar', 'bar' => 123.4567890123456, 'bat' => true}
|
7
|
+
end
|
8
|
+
let(:obj_s) {
|
9
|
+
obj.keys.reduce({}){|h,k| h[k] = obj[k].to_s; h}
|
10
|
+
}
|
11
|
+
|
12
|
+
describe 'the basics' do
|
13
|
+
|
14
|
+
define_app do
|
15
|
+
|
16
|
+
Angelo::HTTPABLE.each do |m|
|
17
|
+
__send__ m, '/' do
|
18
|
+
m.to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
[:get, :post].each do |m|
|
23
|
+
__send__ m, '/json' do
|
24
|
+
content_type :json
|
25
|
+
params
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'responds to http requests properly' do
|
32
|
+
Angelo::HTTPABLE.each do |m|
|
33
|
+
__send__ m, '/'
|
34
|
+
last_response_should_be_html m.to_s
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'responds to get requests with json properly' do
|
39
|
+
get '/json', obj
|
40
|
+
string_vals = obj.keys.reduce({}){|h,k| h[k] = obj[k].to_s; h}
|
41
|
+
last_response_should_be_json string_vals
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'responds to post requests with json properly' do
|
45
|
+
post '/json', obj.to_json, {'Content-Type' => Angelo::JSON_TYPE}
|
46
|
+
last_response_should_be_json obj
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'before filter' do
|
52
|
+
|
53
|
+
define_app do
|
54
|
+
|
55
|
+
before do
|
56
|
+
@set_by_before = params
|
57
|
+
end
|
58
|
+
|
59
|
+
[:get, :post, :put].each do |m|
|
60
|
+
__send__ m, '/before' do
|
61
|
+
content_type :json
|
62
|
+
@set_by_before
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'runs before filters before routes' do
|
69
|
+
|
70
|
+
get '/before', obj
|
71
|
+
last_response_should_be_json obj_s
|
72
|
+
|
73
|
+
[:post, :put].each do |m|
|
74
|
+
__send__ m, '/before', obj.to_json, {Angelo::CONTENT_TYPE_HEADER_KEY => Angelo::JSON_TYPE}
|
75
|
+
last_response_should_be_json obj
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'after filter' do
|
83
|
+
|
84
|
+
invoked = 0
|
85
|
+
|
86
|
+
define_app do
|
87
|
+
|
88
|
+
before do
|
89
|
+
invoked += 2
|
90
|
+
end
|
91
|
+
|
92
|
+
after do
|
93
|
+
invoked *= 2
|
94
|
+
end
|
95
|
+
|
96
|
+
Angelo::HTTPABLE.each do |m|
|
97
|
+
__send__ m, '/after' do
|
98
|
+
invoked.to_s
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'runs after filters after routes' do
|
105
|
+
a = %w[2 6 14 30]
|
106
|
+
b = [4, 12, 28, 60]
|
107
|
+
Angelo::HTTPABLE.each_with_index do |m,i|
|
108
|
+
__send__ m, '/after', obj
|
109
|
+
last_response_should_be_html a[i]
|
110
|
+
invoked.should eq b[i]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: angelo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: reel
|
@@ -35,22 +35,28 @@ extensions: []
|
|
35
35
|
extra_rdoc_files: []
|
36
36
|
files:
|
37
37
|
- .gitignore
|
38
|
+
- .travis.yml
|
38
39
|
- CHANGELOG
|
39
40
|
- Gemfile
|
40
41
|
- LICENSE
|
41
42
|
- README.md
|
42
43
|
- angelo.gemspec
|
43
|
-
- example/foo/foo.rb
|
44
|
-
- example/foo/views/index.html.erb
|
45
|
-
- example/foo/views/layout.html.erb
|
46
44
|
- lib/angelo.rb
|
47
45
|
- lib/angelo/base.rb
|
48
46
|
- lib/angelo/params_parser.rb
|
49
47
|
- lib/angelo/responder.rb
|
50
48
|
- lib/angelo/responder/websocket.rb
|
49
|
+
- lib/angelo/rspec/helpers.rb
|
51
50
|
- lib/angelo/server.rb
|
52
51
|
- lib/angelo/tilt/erb.rb
|
53
52
|
- lib/angelo/version.rb
|
53
|
+
- spec/angelo/erb_spec.rb
|
54
|
+
- spec/angelo/params_spec.rb
|
55
|
+
- spec/angelo/views/index.html.erb
|
56
|
+
- spec/angelo/views/layout.html.erb
|
57
|
+
- spec/angelo/websocket_spec.rb
|
58
|
+
- spec/angelo_spec.rb
|
59
|
+
- spec/spec_helper.rb
|
54
60
|
homepage: https://github.com/kenichi/angelo
|
55
61
|
licenses:
|
56
62
|
- apache
|
@@ -76,4 +82,11 @@ rubygems_version: 1.8.23
|
|
76
82
|
signing_key:
|
77
83
|
specification_version: 3
|
78
84
|
summary: A Sinatra-esque DSL for Reel
|
79
|
-
test_files:
|
85
|
+
test_files:
|
86
|
+
- spec/angelo/erb_spec.rb
|
87
|
+
- spec/angelo/params_spec.rb
|
88
|
+
- spec/angelo/views/index.html.erb
|
89
|
+
- spec/angelo/views/layout.html.erb
|
90
|
+
- spec/angelo/websocket_spec.rb
|
91
|
+
- spec/angelo_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
data/example/foo/foo.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
$:.unshift File.expand_path '../../../lib', __FILE__
|
2
|
-
|
3
|
-
require 'angelo'
|
4
|
-
require 'angelo/tilt/erb'
|
5
|
-
|
6
|
-
class Foo < Angelo::Base
|
7
|
-
include Angelo::Tilt::ERB
|
8
|
-
|
9
|
-
TEST = {foo: "bar", baz: 123, bat: false}.to_json
|
10
|
-
|
11
|
-
def pong; 'pong'; end
|
12
|
-
def foo; params[:foo]; end
|
13
|
-
def time_ms; Time.now.to_f * 1000.0; end
|
14
|
-
|
15
|
-
before do
|
16
|
-
info "request: #{request.method} #{request.path}"
|
17
|
-
@foo = request.path
|
18
|
-
@timing = time_ms
|
19
|
-
end
|
20
|
-
|
21
|
-
after do
|
22
|
-
info "timing: #{time_ms - @timing}ms"
|
23
|
-
end
|
24
|
-
|
25
|
-
get '/' do
|
26
|
-
@name = params[:name]
|
27
|
-
@host = request.headers['Host']
|
28
|
-
erb :index, locals: {zzz: 'word'}
|
29
|
-
end
|
30
|
-
|
31
|
-
get '/ping' do
|
32
|
-
debug "@foo: #{@foo}"
|
33
|
-
pong
|
34
|
-
end
|
35
|
-
|
36
|
-
post '/foo' do
|
37
|
-
foo
|
38
|
-
end
|
39
|
-
|
40
|
-
post '/bar' do
|
41
|
-
content_type = :json
|
42
|
-
params
|
43
|
-
end
|
44
|
-
|
45
|
-
post '/emit' do
|
46
|
-
websockets.each {|ws| ws.write TEST}
|
47
|
-
params.to_json
|
48
|
-
end
|
49
|
-
|
50
|
-
socket '/ws' do |s|
|
51
|
-
websockets << s
|
52
|
-
while msg = s.read
|
53
|
-
5.times { s.write TEST }
|
54
|
-
s.write foo.to_json
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
Foo.run unless $0 == 'irb'
|
@@ -1,20 +0,0 @@
|
|
1
|
-
hi <%= @name %><br/>
|
2
|
-
this is a local - <%= zzz %><br/>
|
3
|
-
<br/>
|
4
|
-
<a href="#" onclick="openws();">websocket!</a>
|
5
|
-
<br/>
|
6
|
-
<a href="#" onclick="emit();">emit!</a>
|
7
|
-
|
8
|
-
<script>
|
9
|
-
var ws;
|
10
|
-
function openws() {
|
11
|
-
ws = new WebSocket('ws://<%= @host %>/ws');
|
12
|
-
ws.onmessage = function(e) { console.log(e.data); };
|
13
|
-
ws.onopen = function(e) {
|
14
|
-
ws.send('hi');
|
15
|
-
};
|
16
|
-
}
|
17
|
-
function emit() {
|
18
|
-
$.post('/emit', {data: {"foo": "bar"}});
|
19
|
-
}
|
20
|
-
</script>
|