angelo 0.4.1 → 0.5.0

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.
@@ -134,9 +134,12 @@ module Angelo
134
134
  class Reactor
135
135
  include Celluloid::IO
136
136
  extend Cellper
137
+ def wait_for_stop
138
+ every(0.01){ terminate if Reactor.stop? }
139
+ end
137
140
  end
138
141
 
139
- class ActorPool
142
+ class Actor
140
143
  include Celluloid
141
144
  extend Cellper
142
145
  end
@@ -144,7 +147,7 @@ module Angelo
144
147
  end
145
148
 
146
149
  class WebsocketHelper
147
- include Celluloid::Logger
150
+ include Celluloid::Internals::Logger
148
151
 
149
152
  extend Forwardable
150
153
  def_delegator :@socket, :write
@@ -192,8 +195,11 @@ module Angelo
192
195
 
193
196
  def go
194
197
  @driver.start
195
- while msg = @socket.readpartial(4096)
196
- @driver.parse msg
198
+ begin
199
+ while msg = @socket.readpartial(4096)
200
+ @driver.parse msg
201
+ end
202
+ rescue EOFError, SystemCallError => e
197
203
  end
198
204
  end
199
205
 
@@ -21,19 +21,28 @@ module Angelo
21
21
  end
22
22
 
23
23
  def parse_post_body
24
- body = request.body.to_s
24
+ body = request_body rescue request.body.to_s
25
25
  case
26
26
  when form_encoded?
27
27
  parse_formencoded body
28
28
  when json? && !body.empty?
29
- SymHash.new JSON.parse body
29
+ parsed_body = JSON.parse body
30
+ parsed_body = SymHash.new parsed_body if Hash === parsed_body
31
+ parsed_body
30
32
  else
31
33
  {}
32
34
  end
33
35
  end
34
36
 
35
37
  def parse_query_string_and_post_body
36
- parse_query_string.merge! parse_post_body
38
+ parsed_body = parse_post_body
39
+ case parsed_body
40
+ when Hash
41
+ parse_query_string.merge! parse_post_body
42
+ when Array
43
+ self.request_body = parsed_body
44
+ parse_query_string
45
+ end
37
46
  end
38
47
 
39
48
  def form_encoded?
@@ -1,7 +1,7 @@
1
1
  module Angelo
2
2
 
3
3
  class Responder
4
- include Celluloid::Logger
4
+ include Celluloid::Internals::Logger
5
5
 
6
6
  class << self
7
7
 
@@ -30,8 +30,8 @@ module Angelo
30
30
  attr_accessor :connection, :mustermann, :request
31
31
  attr_writer :base
32
32
 
33
- def initialize &block
34
- @response_handler = block
33
+ def initialize method, &block
34
+ @method, @response_handler = method, block
35
35
  end
36
36
 
37
37
  def reset!
@@ -70,7 +70,7 @@ module Angelo
70
70
 
71
71
  def handle_error _error, type = :internal_server_error, report = @base.report_errors?
72
72
  err_msg = error_message _error
73
- Angelo.log @connection, @request, nil, type, err_msg.size
73
+ Angelo.log @method, @connection, @request, nil, type, err_msg.size
74
74
  @connection.respond type, headers, err_msg
75
75
  @connection.close
76
76
  if report
@@ -170,11 +170,11 @@ module Angelo
170
170
  end
171
171
  end
172
172
 
173
- status ||= @redirect.nil? ? :ok : :moved_permanently
174
- headers LOCATION_HEADER_KEY => @redirect if @redirect
173
+ status ||= @redirect.nil? ? :ok : @redirect[1]
174
+ headers LOCATION_HEADER_KEY => @redirect[0] if @redirect
175
175
 
176
176
  if @chunked
177
- Angelo.log @connection, @request, nil, status
177
+ Angelo.log @method, @connection, @request, nil, status
178
178
  @request.respond status, headers
179
179
  err = nil
180
180
  begin
@@ -190,7 +190,7 @@ module Angelo
190
190
  end
191
191
  else
192
192
  size = @body.nil? ? 0 : @body.size
193
- Angelo.log @connection, @request, nil, status, size
193
+ Angelo.log @method, @connection, @request, nil, status, size
194
194
  @request.respond status, headers, @body
195
195
  end
196
196
 
@@ -198,11 +198,15 @@ module Angelo
198
198
  handle_error e, :internal_server_error
199
199
  end
200
200
 
201
- def redirect url
202
- @redirect = url
201
+ def redirect url, permanent = false
202
+ @redirect = [url, permanent ? :moved_permanently : :found]
203
203
  nil
204
204
  end
205
205
 
206
+ def redirect! url
207
+ redirect url, true
208
+ end
209
+
206
210
  def on_close= on_close
207
211
  raise ArgumentError.new unless Proc === on_close
208
212
  @on_close = on_close
@@ -4,7 +4,7 @@ module Angelo
4
4
 
5
5
  def initialize _headers = nil, &block
6
6
  headers _headers if _headers
7
- super &block
7
+ super :get, &block
8
8
  end
9
9
 
10
10
  def request= request
@@ -40,7 +40,7 @@ module Angelo
40
40
  end
41
41
 
42
42
  def respond
43
- Angelo.log @connection, @request, nil, :ok
43
+ Angelo.log :sse, @connection, @request, nil, :ok
44
44
  @request.respond 200, headers, nil
45
45
  end
46
46
 
@@ -2,16 +2,6 @@ module Angelo
2
2
  class Responder
3
3
  class Websocket < Responder
4
4
 
5
- class << self
6
-
7
- attr_writer :on_pong
8
-
9
- def on_pong
10
- @on_pong ||= ->(e){}
11
- end
12
-
13
- end
14
-
15
5
  def request= request
16
6
  @params = nil
17
7
  @request = request
@@ -21,8 +11,7 @@ module Angelo
21
11
  def handle_request
22
12
  begin
23
13
  if @response_handler
24
- Angelo.log @connection, @request, @websocket, :switching_protocols
25
- @websocket.on_pong &Responder::Websocket.on_pong
14
+ Angelo.log :ws, @connection, @request, @websocket, :switching_protocols
26
15
  @base.filter :before
27
16
  @base.instance_exec(@websocket, &@response_handler)
28
17
  @base.filter :after
@@ -45,6 +34,8 @@ module Angelo
45
34
 
46
35
  def close_websocket
47
36
  @websocket.close
37
+ rescue IOError
38
+ ensure
48
39
  @base.websockets.remove_socket @websocket
49
40
  end
50
41
 
data/lib/angelo/server.rb CHANGED
@@ -5,7 +5,7 @@ module Angelo
5
5
 
6
6
  class Server < Reel::Server::HTTP
7
7
  extend Forwardable
8
- include Celluloid::Logger
8
+ include Celluloid::Internals::Logger
9
9
 
10
10
  def_delegators :@base, :websockets, :sses
11
11
 
@@ -51,11 +51,22 @@ module Angelo
51
51
  route! meth, connection, request
52
52
  end
53
53
  rescue URI::InvalidURIError => e
54
- Angelo.log connection, request, nil, :bad_request
54
+ Angelo.log meth, connection, request, nil, :bad_request
55
55
  connection.respond :bad_request, DEFAULT_RESPONSE_HEADERS, e.message
56
56
  end
57
57
 
58
+ def post_override! meth, request
59
+ if meth == :post and request.headers.has_key? POST_OVERRIDE_REQUEST_HEADER_KEY
60
+ new_meth = request.headers[POST_OVERRIDE_REQUEST_HEADER_KEY].downcase.to_sym
61
+ meth = new_meth if POST_OVERRIDABLE.include? new_meth
62
+ end
63
+ meth
64
+ rescue
65
+ meth
66
+ end
67
+
58
68
  def route! meth, connection, request
69
+ meth = post_override! meth, request
59
70
  if rs = @base.routes[meth][request.path]
60
71
  responder = rs.dup
61
72
  responder.reset!
@@ -65,7 +76,7 @@ module Angelo
65
76
  responder.handle_request
66
77
  responder
67
78
  else
68
- Angelo.log connection, request, nil, :not_found
79
+ Angelo.log meth, connection, request, nil, :not_found
69
80
  connection.respond :not_found, DEFAULT_RESPONSE_HEADERS, NOT_FOUND
70
81
  end
71
82
  end
@@ -77,7 +88,7 @@ module Angelo
77
88
  def static! meth, connection, request, local_path
78
89
  etag = etag_for local_path
79
90
  if request.headers[IF_NONE_MATCH_HEADER_KEY] == etag
80
- Angelo.log connection, request, nil, :not_modified, 0
91
+ Angelo.log meth, connection, request, nil, :not_modified, 0
81
92
  connection.respond :not_modified
82
93
  else
83
94
  headers = {
@@ -96,7 +107,7 @@ module Angelo
96
107
  ETAG_HEADER_KEY => etag
97
108
 
98
109
  }
99
- Angelo.log connection, request, nil, :ok, headers[CONTENT_LENGTH_HEADER_KEY]
110
+ Angelo.log meth, connection, request, nil, :ok, headers[CONTENT_LENGTH_HEADER_KEY]
100
111
  connection.respond :ok, headers, (meth == :head ? nil : File.read(local_path))
101
112
  end
102
113
  end
data/lib/angelo/stash.rb CHANGED
@@ -3,7 +3,7 @@ module Angelo
3
3
  # utility class for stashing connected websockets in arbitrary contexts
4
4
  #
5
5
  module Stash
6
- include Celluloid::Logger
6
+ include Celluloid::Internals::Logger
7
7
 
8
8
  module ClassMethods
9
9
 
@@ -29,6 +29,7 @@ module Angelo
29
29
  def initialize server, context = :default
30
30
  raise ArgumentError.new "symbol required" unless Symbol === context
31
31
  @context, @server = context, server
32
+ @mutex = Mutex.new
32
33
  stashes[@context] ||= []
33
34
  end
34
35
 
@@ -51,7 +52,8 @@ module Angelo
51
52
  # as needed
52
53
  #
53
54
  def each &block
54
- stash.dup.each do |s|
55
+ stash_dup = @mutex.synchronize { stash.dup }
56
+ stash_dup.each do |s|
55
57
  begin
56
58
  yield s
57
59
  rescue Reel::SocketError, IOError, SystemCallError => e
@@ -69,6 +71,7 @@ module Angelo
69
71
  if stash.include? s
70
72
  warn "removing socket from context ':#{@context}' (#{peeraddrs[s][2]})"
71
73
  stash.delete s
74
+
72
75
  peeraddrs.delete s
73
76
  end
74
77
  end
@@ -83,12 +86,14 @@ module Angelo
83
86
  # ping_websockets task
84
87
  #
85
88
  def all_each
86
- stashes.values.flatten.each do |s|
89
+ all_stashed = @mutex.synchronize { stashes.values.flatten }
90
+ all_stashed.each do |s|
87
91
  begin
88
92
  yield s
89
93
  rescue Reel::SocketError, IOError, SystemCallError => e
90
94
  debug "all - #{e.message}"
91
95
  remove_socket s
96
+ stashes.values.each {|_s| _s.delete s}
92
97
  end
93
98
  end
94
99
  end
@@ -0,0 +1,166 @@
1
+ require 'tilt'
2
+
3
+ module Angelo
4
+ module Templates
5
+
6
+ # When this code refers to Tilt, use ::Tilt not Angelo::Tilt.
7
+
8
+ Tilt = ::Tilt
9
+
10
+ # Add instance methods to render views with each template engine
11
+ # I'm using. The methods are called "haml", "markdown", etc.
12
+
13
+ # They render files (Symbols) or Strings, with no frills other
14
+ # than locals. Files must be in the self.class.views_dir
15
+ # directory, with an extension that Tilt maps to the
16
+ # template_type.
17
+
18
+ # Angelo::Tilt::ERB includes an erb method built on top of _erb
19
+ # (below) which provides slightly differnet semantics. For
20
+ # compatibilty, we don't overwrite that method here and instead
21
+ # define _erb. At least for now.
22
+
23
+ [:haml, :markdown].each do |template_type|
24
+ define_method(template_type) do |view, opts = {}|
25
+ render(template_type, view, opts)
26
+ end
27
+ end
28
+
29
+ def _erb(view, opts = {})
30
+ render(:erb, view, opts)
31
+ end
32
+
33
+ private
34
+
35
+ def render(template_type, view, opts = {})
36
+ # Extract the options that belong to us. Any remaining options
37
+ # will be passed to the engine when the template is instantiated.
38
+
39
+ locals = opts.delete(:locals) || {}
40
+ layout_engine = opts.delete(:layout_engine) || template_type
41
+ layout =
42
+ if opts.has_key?(:layout)
43
+ layout = opts.delete(:layout)
44
+ case layout
45
+ when true
46
+ :layout
47
+ when nil
48
+ false
49
+ else
50
+ layout
51
+ end
52
+ else
53
+ # Use the default layout.
54
+ :layout
55
+ end
56
+
57
+ template = self.class.get_template(template_type, view, self.class.views_dir, opts)
58
+ if !template
59
+ raise ArgumentError, "Can't find template `#{view}' of type `#{template_type}'"
60
+ end
61
+
62
+ render = ->{ template.render(self, locals) }
63
+ if layout
64
+ layout_template = self.class.get_template(layout_engine, layout,
65
+ File.join(self.class.views_dir), opts)
66
+ end
67
+ if layout_template
68
+ layout_template.render(self, &render)
69
+ else
70
+ render.call
71
+ end
72
+ end
73
+
74
+ def self.included(klass)
75
+ klass.extend TemplateCaching
76
+ end
77
+
78
+ module TemplateCaching
79
+ # A Cache which, unlike Tilt::Cache, will cache nil so when a
80
+ # template isn't found we'll remmeber that and won't try/fail to
81
+ # load it again. This happens most commonly when there is no
82
+ # default layout file.
83
+
84
+ # A pull request has been accepted for Tilt which makes
85
+ # Tilt::Cache work like our Cache so we can remove this when a
86
+ # new Tilt gem is released (currently 2.0.1), but Tilt doesn't
87
+ # seem to be getting much love these days.
88
+
89
+ class Cache
90
+ def initialize
91
+ @cache = {}
92
+ end
93
+
94
+ def fetch(*key)
95
+ @cache.fetch(key) do
96
+ @cache[key] = yield
97
+ end
98
+ end
99
+ end
100
+
101
+ # Create a template cache that all subclasses of Angelo::Base
102
+ # will share. And a non_cache object that will stand in for the cache
103
+ # when reloading templates, which is set per-class.
104
+
105
+ @@template_cache = Cache.new
106
+ @@non_cache = Object.new.tap do |o|
107
+ def o.fetch(*args)
108
+ yield
109
+ end
110
+ end
111
+
112
+ def get_template(*args)
113
+ template_cache.fetch(*args) do
114
+ instantiate_template(*args)
115
+ end
116
+ end
117
+
118
+ private
119
+
120
+ def template_cache
121
+ @reload_templates ? @@non_cache : @@template_cache
122
+ end
123
+
124
+ def instantiate_template(template_type, view, views_dir, opts)
125
+ case view
126
+ when Symbol
127
+ instantiate_template_from_file(template_type, view.to_s, views_dir, opts)
128
+ when String
129
+ instantiate_template_from_string(template_type, view, opts)
130
+ else
131
+ raise ArgumentError, "view must be a Symbol or a String"
132
+ end
133
+ end
134
+
135
+ def instantiate_template_from_file(template_type, view, views_dir, opts)
136
+ # Find a file in the views directory with a correct extension
137
+ # for this template_type.
138
+ file = find_view_file_for_template_type(views_dir, view, template_type)
139
+ if file
140
+ # Use absolute filenames so backtraces have absolute filenames.
141
+ absolute_file = File.expand_path(file)
142
+ Tilt.new(absolute_file, opts)
143
+ end
144
+ end
145
+
146
+ def instantiate_template_from_string(template_type, string, opts)
147
+ # To make Tilt use a String as a template we pass Tilt.new a
148
+ # block that returns the String. Passing a block makes
149
+ # Tilt.new ignore the file argument. Except the file argument
150
+ # is still used to figure out which template engine to use.
151
+ # Fortunately we can just pass template_type, except the file
152
+ # argument needs to respond to to_str and symbols don't
153
+ # respond to that so we convert it to a String.
154
+ Tilt.new(template_type.to_s, opts) {string}
155
+ end
156
+
157
+ def find_view_file_for_template_type(views, name, template_type)
158
+ template_class = Tilt[template_type]
159
+ exts = Tilt.default_mapping.extensions_for(template_class).uniq
160
+ filenames = exts.map{|ext| File.join(views, "#{name}.#{ext}")}
161
+ filenames.find{|f| File.exists?(f)}
162
+ end
163
+ end
164
+
165
+ end
166
+ end