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