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 CHANGED
@@ -1,2 +1,3 @@
1
1
  angelo-*.gem
2
2
  Gemfile.lock
3
+ .rspec
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - "2.0.0"
5
+ - rbx-19mode
6
+ script: bundle exec rspec
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 'pry'
5
- gem 'pry-nav'
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 |s|
38
- websockets << s
39
- while msg = s.read
40
- 5.times { s.write TEST }
41
- s.write foo.to_json
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
@@ -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 '^(myapp|examples)'`.split("\n")
10
- gem.test_files = `git ls-files -- test/*`.split("\n")
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
@@ -1,7 +1,10 @@
1
1
  require 'reel'
2
2
  require 'json'
3
- require 'pry'
4
- require 'pry-nav'
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
@@ -5,7 +5,7 @@ module Angelo
5
5
  include Celluloid::Logger
6
6
 
7
7
  extend Forwardable
8
- def_delegator :@responder, :request
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
- [:get, :post, :put, :delete, :options, :socket].each do |m|
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
- [:get, :post, :put, :delete, :options].each do |m|
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
- if @websockets.nil?
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 run host = '127.0.0.1', port = 4567
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
@@ -9,7 +9,7 @@ module Angelo
9
9
 
10
10
  def parse_formencoded str
11
11
  str.split('&').reduce(Responder.symhash) do |p, kv|
12
- key, value = kv.split('=').map {|s| CGI.escape s}
12
+ key, value = kv.split('=').map {|s| CGI.unescape s}
13
13
  p[key] = value
14
14
  p
15
15
  end
@@ -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= 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, DEFAULT_RESPONSE_HEADERS.merge(headers), @body
103
+ @connection.respond :ok, headers, @body
83
104
  end
84
105
 
85
106
  end
@@ -23,7 +23,7 @@ module Angelo
23
23
  raise NotImplementedError
24
24
  end
25
25
  rescue IOError => ioe
26
- error "#{ioe.class} - #{ioe.message}"
26
+ warn "#{ioe.class} - #{ioe.message}"
27
27
  @websocket.close
28
28
  @base.websockets.delete @websocket
29
29
  rescue => e
@@ -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
@@ -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 listening on #{host}:#{port}"
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
@@ -1,3 +1,4 @@
1
1
  module Angelo
2
- VERSION = '0.0.4'
2
+ CODENAME = 'Gunpowder Treason'
3
+ VERSION = '0.0.7'
3
4
  end
@@ -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,2 @@
1
+ foo - <%= @foo %>
2
+ locals :bar - <%= bar %>
@@ -0,0 +1,9 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title><%= @title %></title>
5
+ </head>
6
+ <body>
7
+ <%= yield %>
8
+ </body>
9
+ </html>
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ $:.unshift File.expand_path '../../../lib', __FILE__
2
+
3
+ require 'bundler'
4
+ Bundler.require :default, :development, :test
5
+ require 'angelo'
6
+ require 'angelo/rspec/helpers'
7
+ Celluloid.logger.level = ::Logger::ERROR
8
+ include Angelo::RSpec::Helpers
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
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-10-31 00:00:00.000000000 Z
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
@@ -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>
@@ -1,10 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <title>example</title>
5
- <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
6
- </head>
7
- <body>
8
- <%= yield %>
9
- </body>
10
- </html>