angelo 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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