Class: Angelo::Base

Inherits:
Object
  • Object
show all
Extended by:
DSL, Forwardable
Includes:
ParamsParser, Templates, Tilt::ERB, Celluloid::Internals::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 Templates

Templates::Tilt

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, content_type, default_headers, helpers, log_level, on_pong, ping_time, port, public_dir, reload_templates!, report_errors!, task, views_dir, websocket

Methods included from Tilt::ERB

#erb, #template_type

Methods included from Templates

#_erb

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



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

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

Class Attribute Details

.app_fileObject

Returns the value of attribute app_file.



107
108
109
# File 'lib/angelo/base.rb', line 107

def app_file
  @app_file
end

.serverObject

Returns the value of attribute server.



107
108
109
# File 'lib/angelo/base.rb', line 107

def server
  @server
end

Instance Attribute Details

#request_bodyObject



261
262
263
# File 'lib/angelo/base.rb', line 261

def request_body
  @request_body ||= request.body.to_s
end

#responderObject

Returns the value of attribute responder.



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

def responder
  @responder
end

Class Method Details

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



161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/angelo/base.rb', line 161

def filter which, opts = {}, &block
  case opts
  when String, Regexp
    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



174
175
176
177
# File 'lib/angelo/base.rb', line 174

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

.filtersObject



154
155
156
157
158
159
# File 'lib/angelo/base.rb', line 154

def filters
  @filters ||= {
    before: Hash.new{|h,k| h[k] = []},
    after: Hash.new{|h,k| h[k] = []},
  }
end

.inherited(subclass) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/angelo/base.rb', line 109

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

  subclass.views_dir DEFAULT_VIEWS_DIR
  subclass.public_dir DEFAULT_PUBLIC_DIR

  # 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

end

.local_path(path) ⇒ Object



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

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.



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/angelo/base.rb', line 378

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



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

def report_errors?
  !!@report_errors
end

.rootObject



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

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

.routesObject



150
151
152
# File 'lib/angelo/base.rb', line 150

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

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



195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/angelo/base.rb', line 195

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



191
192
193
# File 'lib/angelo/base.rb', line 191

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

.sse_event(event_name, data) ⇒ Object



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

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



221
222
223
224
# File 'lib/angelo/base.rb', line 221

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

.sses(reject = true) ⇒ Object



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

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

.websockets(reject = true) ⇒ Object



179
180
181
182
183
# File 'lib/angelo/base.rb', line 179

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

Instance Method Details

#async(meth, *args) ⇒ Object



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

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

#chunked_response(&block) ⇒ Object



352
353
354
355
# File 'lib/angelo/base.rb', line 352

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

#eventsource(&block) ⇒ Object



342
343
344
345
346
# File 'lib/angelo/base.rb', line 342

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

#filter(which) ⇒ Object



357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/angelo/base.rb', line 357

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

#future(meth, *args) ⇒ Object



237
238
239
# File 'lib/angelo/base.rb', line 237

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

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



295
296
297
# File 'lib/angelo/base.rb', line 295

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

#paramsObject



241
242
243
244
245
246
247
248
# File 'lib/angelo/base.rb', line 241

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



250
251
252
253
254
255
256
257
258
259
# File 'lib/angelo/base.rb', line 250

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

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



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'lib/angelo/base.rb', line 322

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



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/angelo/base.rb', line 299

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



348
349
350
# File 'lib/angelo/base.rb', line 348

def sleep time
  Celluloid.sleep time
end