angelo 0.0.4 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/kenichi/angelo.png?branch=master)](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>
|