Class: Angelo::Base

Inherits:
Object
  • Object
show all
Extended by:
DSL, Forwardable
Includes:
ParamsParser, Tilt::ERB, Celluloid::Logger, Mustermann
Defined in:
lib/angelo/base.rb,
lib/angelo/main.rb

Defined Under Namespace

Modules: DSL Classes: ChunkedResponse, EventSource, RouteMap

Constant Summary

Constants included from Tilt::ERB

Tilt::ERB::ACCEPT_ALL, Tilt::ERB::DEFAULT_LAYOUT, Tilt::ERB::DEFAULT_TYPE, Tilt::ERB::LAYOUTS_DIR

Constants included from ParamsParser

ParamsParser::EMPTY_JSON

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DSL

addr, after, before, helpers, log_level, on_pong, ping_time, port, public_dir, report_errors!, task, views_dir, websocket

Methods included from Tilt::ERB

#erb, included, #template_type

Methods included from ParamsParser

#content_type?, #form_encoded?, #json?, #parse_formencoded, #parse_post_body, #parse_query_string, #parse_query_string_and_post_body

Constructor Details

#initialize(responder) ⇒ Base

Returns a new instance of Base.



223
224
225
226
# File 'lib/angelo/base.rb', line 223

def initialize responder
  @responder = responder
  @klass = self.class
end

Class Attribute Details

.app_fileObject

Returns the value of attribute app_file.



17
18
19
# File 'lib/angelo/base.rb', line 17

def app_file
  @app_file
end

.serverObject

Returns the value of attribute server.



17
18
19
# File 'lib/angelo/base.rb', line 17

def server
  @server
end

Instance Attribute Details

#responderObject

Returns the value of attribute responder.



13
14
15
# File 'lib/angelo/base.rb', line 13

def responder
  @responder
end

Class Method Details

.content_type(type) ⇒ Object



182
183
184
# File 'lib/angelo/base.rb', line 182

def content_type type
  Responder.content_type type
end

.filter(which, opts = {}, &block) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/angelo/base.rb', line 151

def filter which, opts = {}, &block
  case opts
  when String
    filter_by which, opts, block
  when Hash
    if opts[:path]
      filter_by which, opts[:path], block
    else
      filters[which][:default] << block
    end
  end
end

.filter_by(which, path, block) ⇒ Object



164
165
166
167
168
# File 'lib/angelo/base.rb', line 164

def filter_by which, path, block
  pattern = ::Mustermann.new path
  filters[which][pattern] ||= []
  filters[which][pattern] << block
end

.filtersObject



147
148
149
# File 'lib/angelo/base.rb', line 147

def filters
  @filters ||= {before: {default: []}, after: {default: []}}
end

.inherited(subclass) ⇒ Object



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/angelo/base.rb', line 19

def inherited subclass

  # Set app_file by groveling up the caller stack until we find
  # the first caller from a directory different from __FILE__.
  # This allows base.rb to be required from an arbitrarily deep
  # nesting of require "angelo/<whatever>" and still set
  # app_file correctly.
  #
  subclass.app_file = caller_locations.map(&:absolute_path).find do |f|
    !f.start_with?(File.dirname(__FILE__) + File::SEPARATOR)
  end

  # bring RequestError into this namespace
  #
  subclass.class_eval 'class RequestError < Angelo::RequestError; end'

  subclass.addr DEFAULT_ADDR
  subclass.port DEFAULT_PORT

  subclass.ping_time DEFAULT_PING_TIME
  subclass.log_level DEFAULT_LOG_LEVEL

  # Parse command line options if angelo/main has been required.
  # They could also be parsed in run, but this makes them
  # available to and overridable by the DSL.
  #
  subclass.parse_options(ARGV.dup) if @angelo_main

  class << subclass

    def root
      @root ||= File.expand_path '..', app_file
    end

  end

end

.local_path(path) ⇒ Object



204
205
206
207
208
209
# File 'lib/angelo/base.rb', line 204

def local_path path
  if public_dir
    lp = File.join(public_dir, path)
    File.file?(lp) ? lp : nil
  end
end

.parse_options(argv) ⇒ Object

It seems more sensible to put this in main.rb since it’s used only if angelo/main is required, but it’s here so it can be tested, since requiring angelo/main doesn’t play well with the test code.



371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/angelo/base.rb', line 371

def self.parse_options(argv)
  require "optparse"

  optparse = OptionParser.new do |op|
    op.banner = "Usage: #{$0} [options]"

    op.on('-p port', OptionParser::DecimalInteger, "set the port (default is #{port})") {|val| port val}
    op.on('-o addr', "set the host (default is #{addr})") {|val| addr val}
    op.on('-h', '--help', "Show this help") do
      puts op
      exit
    end
  end

  begin
    optparse.parse(argv)
  rescue OptionParser::ParseError => ex
    $stderr.puts ex
    $stderr.puts optparse
    exit 1
  end
end

.report_errors?Boolean

Returns:

  • (Boolean)


139
140
141
# File 'lib/angelo/base.rb', line 139

def report_errors?
  !!@report_errors
end

.routesObject



143
144
145
# File 'lib/angelo/base.rb', line 143

def routes
  @routes ||= Hash.new{|h,k| h[k] = RouteMap.new}
end

.run(_addr = addr, _port = port, options = {}, blocking = false) ⇒ Object



190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/angelo/base.rb', line 190

def run _addr = addr, _port = port, options = {}, blocking = false
  Celluloid.logger.level = log_level
  @server = Angelo::Server.new self, _addr, _port, options
  @server.async.ping_websockets
  if blocking
    trap "INT" do
      @server.terminate if @server and @server.alive?
      exit
    end
    sleep
  end
  @server
end

.run!(_addr = addr, _port = port, options = {}) ⇒ Object



186
187
188
# File 'lib/angelo/base.rb', line 186

def run! _addr = addr, _port = port, options = {}
  run _addr, _port, options, true
end

.sse_event(event_name, data) ⇒ Object



211
212
213
214
# File 'lib/angelo/base.rb', line 211

def sse_event event_name, data
  data = data.to_json if Hash === data
  SSE_EVENT_TEMPLATE % [event_name.to_s, data]
end

.sse_message(data) ⇒ Object



216
217
218
219
# File 'lib/angelo/base.rb', line 216

def sse_message data
  data = data.to_json if Hash === data
  SSE_DATA_TEMPLATE % data
end

.sses(reject = true) ⇒ Object



176
177
178
179
180
# File 'lib/angelo/base.rb', line 176

def sses reject = true
  @sses ||= Stash::SSE.new server
  @sses.reject! &:closed? if reject
  @sses
end

.websockets(reject = true) ⇒ Object



170
171
172
173
174
# File 'lib/angelo/base.rb', line 170

def websockets reject = true
  @websockets ||= Stash::Websocket.new server
  @websockets.reject! &:closed? if reject
  @websockets
end

Instance Method Details

#async(meth, *args) ⇒ Object



228
229
230
# File 'lib/angelo/base.rb', line 228

def async meth, *args
  self.class.server.async.__send__ meth, *args
end

#chunked_response(&block) ⇒ Object



345
346
347
348
# File 'lib/angelo/base.rb', line 345

def chunked_response &block
  transfer_encoding :chunked
  ChunkedResponse.new &block
end

#eventsource(&block) ⇒ Object



335
336
337
338
339
# File 'lib/angelo/base.rb', line 335

def eventsource &block
  headers SSE_HEADER
  async :handle_event_source, EventSource.new(responder), block
  halt 200, :sse
end

#filter(which) ⇒ Object



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
# File 'lib/angelo/base.rb', line 350

def filter which
  self.class.filters[which].each do |pattern, filters|
    case pattern
    when :default
      filters.each {|filter| instance_eval &filter}
    when ::Mustermann
      if pattern.match request.path
        @pre_filter_params = params
        @params = @pre_filter_params.merge pattern.params(request.path)
        filters.each {|filter| instance_eval &filter}
        @params = @pre_filter_params
      end
    end
  end
end

#future(meth, *args) ⇒ Object



232
233
234
# File 'lib/angelo/base.rb', line 232

def future meth, *args
  self.class.server.future.__send__ meth, *args
end

#halt(status = 400, body = '') ⇒ Object



288
289
290
# File 'lib/angelo/base.rb', line 288

def halt status = 400, body = ''
  throw :halt, HALT_STRUCT.new(status, body)
end

#paramsObject



236
237
238
239
240
241
242
243
# File 'lib/angelo/base.rb', line 236

def params
  @params ||= case request.method
              when GET, DELETE, OPTIONS
                parse_query_string
              when POST, PUT
                parse_query_string_and_post_body
              end.merge mustermann.params(request.path)
end

#request_headersObject



245
246
247
248
249
250
251
252
253
254
# File 'lib/angelo/base.rb', line 245

def request_headers
  @request_headers ||= Hash.new do |hash, key|
    if Symbol === key
      k = key.to_s.upcase
      k.gsub! UNDERSCORE, DASH
      rhv = request.headers.select {|header_key,v| header_key.upcase == k}
      hash[key] = rhv.values.first
    end
  end
end

#send_data(data, opts = {}) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# File 'lib/angelo/base.rb', line 315

def send_data data, opts = {}
  # Content-Type
  #
  headers CONTENT_TYPE_HEADER_KEY =>
    (MIME::Types.type_for(File.extname(opts[:filename]))[0].content_type rescue HTML_TYPE)

  # Content-Disposition
  #
  if opts[:disposition] == :attachment
    headers CONTENT_DISPOSITION_HEADER_KEY =>
      ATTACHMENT_CONTENT_DISPOSITION % opts[:filename]
  end

  # Content-Length
  #
  headers CONTENT_LENGTH_HEADER_KEY => data.length

  halt 200, data
end

#send_file(local_file, opts = {}) ⇒ Object



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/angelo/base.rb', line 292

def send_file local_file, opts = {}
  lp = local_file[0] == File::SEPARATOR ? local_file : File.expand_path(File.join(self.class.root, local_file))
  halt 404 unless File.exist? lp

  # Content-Type
  #
  headers CONTENT_TYPE_HEADER_KEY =>
    (MIME::Types.type_for(File.extname(lp))[0].content_type rescue HTML_TYPE)

  # Content-Disposition
  #
  if opts[:disposition] == :attachment or opts[:filename]
    headers CONTENT_DISPOSITION_HEADER_KEY =>
      ATTACHMENT_CONTENT_DISPOSITION % (opts[:filename] or File.basename(lp))
  end

  # Content-Length
  #
  headers CONTENT_LENGTH_HEADER_KEY => File.size(lp)

  halt 200, File.read(lp)
end

#sleep(time) ⇒ Object



341
342
343
# File 'lib/angelo/base.rb', line 341

def sleep time
  Celluloid.sleep time
end