Class: Ocular::Inputs::HTTP::Input

Inherits:
Base
  • Object
show all
Defined in:
lib/ocular/inputs/http_input.rb

Defined Under Namespace

Classes: NotFound, Request, Response, WebRunContext

Constant Summary collapse

URI_INSTANCE =
URI::Parser.new
DEFAULT_SETTINGS =
{
    :host => '0.0.0.0',
    :port => 8080,
    :verbose => false,
    :silent => false
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(settings_factory) ⇒ Input

Returns a new instance of Input.



519
520
521
522
523
524
525
526
527
# File 'lib/ocular/inputs/http_input.rb', line 519

def initialize(settings_factory)
    settings = settings_factory[:http]

    @routes = {}
    @settings = DEFAULT_SETTINGS.merge(settings)
    @stopsignal = Queue.new()
    @thread = nil

end

Instance Attribute Details

#routesObject (readonly)

Returns the value of attribute routes.



40
41
42
# File 'lib/ocular/inputs/http_input.rb', line 40

def routes
  @routes
end

Instance Method Details

#add_delete(script_name, path, options, proxy, &block) ⇒ Object



261
262
263
264
# File 'lib/ocular/inputs/http_input.rb', line 261

def add_delete(script_name, path, options, proxy, &block)
    name = generate_uri_from_names(script_name, path)
    route('DELETE', name, options, proxy, &block)
end

#add_get(script_name, path, options, proxy, &block) ⇒ Object



251
252
253
254
# File 'lib/ocular/inputs/http_input.rb', line 251

def add_get(script_name, path, options, proxy, &block)
    name = generate_uri_from_names(script_name, path)
    route('GET', name, options, proxy, &block)
end

#add_post(script_name, path, options, proxy, &block) ⇒ Object



256
257
258
259
# File 'lib/ocular/inputs/http_input.rb', line 256

def add_post(script_name, path, options, proxy, &block)
    name = generate_uri_from_names(script_name, path)
    route('POST', name, options, proxy, &block)
end

#body(context, value = nil, &block) ⇒ Object

Set or retrieve the response body. When a block is given, evaluation is deferred until the body is read with #each.



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/ocular/inputs/http_input.rb', line 378

def body(context, value = nil, &block)
    if block_given?
        def block.each; yield(call) end
        context.response.body = block
    elsif value
        # Rack 2.0 returns a Rack::File::Iterator here instead of
        # Rack::File as it was in the previous API.
        unless context.request.head?
            headers(context).delete 'Content-Length'
        end
        context.response.body = value
    else
        context.response.body
    end
end

#build_signature(pattern, keys, &block) ⇒ Object



266
267
268
# File 'lib/ocular/inputs/http_input.rb', line 266

def build_signature(pattern, keys, &block)
    return [pattern, keys, block]
end

#call(env) ⇒ Object



372
373
374
# File 'lib/ocular/inputs/http_input.rb', line 372

def call(env)
    dup.call!(env)
end

#call!(env) ⇒ Object



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/ocular/inputs/http_input.rb', line 394

def call!(env)
    context = WebRunContext.new

    context.request = Request.new(env)
    context.response = Response.new
    context.env = env
    context.params = indifferent_params(context.request.params)

    context.response['Content-Type'] = nil
    invoke(context) { |context| dispatch(context) }

    unless context.response['Content-Type']
        context.response['Content-Type'] = "text/html"
    end

    context.response.finish
end

#call_block(context) {|context| ... } ⇒ Object

Yields:

  • (context)


450
451
452
# File 'lib/ocular/inputs/http_input.rb', line 450

def call_block(context)
    yield(context)
end

#compile(path) ⇒ Object



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/ocular/inputs/http_input.rb', line 314

def compile(path)
    if path.respond_to? :to_str
        keys = []

        # Split the path into pieces in between forward slashes.
        # A negative number is given as the second argument of path.split
        # because with this number, the method does not ignore / at the end
        # and appends an empty string at the end of the return value.
        #
        segments = path.split('/', -1).map! do |segment|
            ignore = []

            # Special character handling.
            #
            pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]|:(?!\w)/) do |c|
                ignore << escaped(c).join if c.match(/[\.@]/)
                patt = encoded(c)
                patt.gsub(/%[\da-fA-F]{2}/) do |match|
                    match.split(//).map! { |char| char == char.downcase ? char : "[#{char}#{char.downcase}]" }.join
                end
            end

            ignore = ignore.uniq.join

            # Key handling.
            #
            pattern.gsub(/((:\w+)|\*)/) do |match|
                if match == "*"
                    keys << 'splat'
                    "(.*?)"
                else
                    keys << $2[1..-1]
                ignore_pattern = safe_ignore(ignore)

                ignore_pattern
                end
            end
        end

        # Special case handling.
        #
        if last_segment = segments[-1] and last_segment.match(/\[\^\\\./)
            parts = last_segment.rpartition(/\[\^\\\./)
            parts[1] = '[^'
            segments[-1] = parts.join
        end
        [/\A#{segments.join('/')}\z/, keys]
    elsif path.respond_to?(:keys) && path.respond_to?(:match)
        [path, path.keys]
    elsif path.respond_to?(:names) && path.respond_to?(:match)
        [path, path.names]
    elsif path.respond_to? :match
        [path, []]
    else
        raise TypeError, path
    end
end

#dispatch(context) ⇒ Object



412
413
414
415
416
417
418
419
420
421
422
# File 'lib/ocular/inputs/http_input.rb', line 412

def dispatch(context)
    invoke(context) do |context|
        route!(context)
    end
rescue ::Exception => error
    invoke(context) do |context|
        handle_exception!(context, error)
    end
ensure
    
end

#generate_uri_from_names(script_name, path) ⇒ Object



237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/ocular/inputs/http_input.rb', line 237

def generate_uri_from_names(script_name, path)
    if path[0] == "/"
        path = path[1..-1]
    end
    
    if script_name && script_name != ""
        name = script_name + "/" + path
    else
        name = path
    end

    return "/" + name
end

#handle_exception!(context, error) ⇒ Object



438
439
440
441
442
443
444
445
446
447
448
# File 'lib/ocular/inputs/http_input.rb', line 438

def handle_exception!(context, error)
    context.env['error'] = error

    if error.respond_to? :http_status
        context.response.status = error.http_status
    else
        context.response.status = 500
        puts "Internal Server Error: #{error}"
        puts error.backtrace
    end
end

#headers(context, hash = nil) ⇒ Object



514
515
516
517
# File 'lib/ocular/inputs/http_input.rb', line 514

def headers(context, hash = nil)
    context.response.headers.merge! hash if hash
    context.response.headers
end

#indifferent_hashObject

Creates a Hash with indifferent access.



491
492
493
# File 'lib/ocular/inputs/http_input.rb', line 491

def indifferent_hash
    Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
end

#indifferent_params(object) ⇒ Object



496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/ocular/inputs/http_input.rb', line 496

def indifferent_params(object)
    case object
    when Hash
        new_hash = indifferent_hash
        object.each { |key, value| new_hash[key] = indifferent_params(value) }
        new_hash
    when Array
        object.map { |item| indifferent_params(item) }
    else
        object
    end
end

#invoke(context) ⇒ Object



424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/ocular/inputs/http_input.rb', line 424

def invoke(context)
    res = catch(:halt) { yield(context) }

    if Array === res and Fixnum === res.first
        res = res.dup
        status(context, res.shift)
        body(context, res.pop)
        headers(context, *res)
    elsif res.respond_to? :each
        body(context, res)
    end
    nil # avoid double setting the same response tuple twice
end

#process_route(context, pattern, keys, values = []) ⇒ Object



470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
# File 'lib/ocular/inputs/http_input.rb', line 470

def process_route(context, pattern, keys, values = [])
    route = context.request.path_info
    route = '/' if route.empty?
    return unless match = pattern.match(route)
    values += match.captures.map! { |v| URI_INSTANCE.unescape(v) if v }

    if values.any?
        original, @params = context.params, context.params.merge('splat' => [], 'captures' => values)
        keys.zip(values) { |k,v| Array === context.params[k] ? context.params[k] << v : context.params[k] = v if v }
    end

    yield(self, values)

rescue
    context.env['error.params'] = context.params
    raise
ensure
    @params = original if original
end

#route(verb, path, options, proxy, &block) ⇒ Object



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/ocular/inputs/http_input.rb', line 270

def route(verb, path, options, proxy, &block)
    eventbase = Ocular::DSL::EventBase.new(proxy, &block)
    (proxy.events[verb] ||= {})[path] = eventbase

    pattern, keys = compile(path)

    (@routes[verb] ||= []) << build_signature(pattern, keys) do |context|
        context.event_signature = [verb, path]
        response = eventbase.exec(context)
        environment = {
            :path => path,
            :options => options,
            :request => context.request,
            :params => context.params,
            :env => context.env,
            :response => response
        }
        context.log_cause("on#{verb}", environment)

        response
    end
end

#route!(context) ⇒ Object

Raises:



454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# File 'lib/ocular/inputs/http_input.rb', line 454

def route!(context)
    if routes = @routes[context.request.request_method]
        routes.each do |pattern, keys, block|
            process_route(context, pattern, keys) do |*args|
                #env['route'] = block.instance_variable_get(:@route_name)

                #throw :halt, context.exec(&block)
                throw :halt, call_block(context, &block)
            end
        end
    end

    puts "Route missing"
    raise NotFound
end

#safe_ignore(ignore) ⇒ Object



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/ocular/inputs/http_input.rb', line 293

def safe_ignore(ignore)
    unsafe_ignore = []
    ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex|
        unsafe_ignore << hex[1..2]
        ''
    end
    unsafe_patterns = unsafe_ignore.map! do |unsafe|
        chars = unsafe.split(//).map! do |char|
            char == char.downcase ? char : char + char.downcase
        end

        "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])"
    end
    if unsafe_patterns.length > 0
        "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)"
    else
        "([^#{ignore}/?#]+)"
    end
end

#startObject



529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
# File 'lib/ocular/inputs/http_input.rb', line 529

def start()
    
    if @settings[:verbose]
      @app = Rack::CommonLogger.new(@app, STDOUT)
    end

    @thread = Thread.new do
        events_hander = @settings[:silent] ? ::Puma::Events.strings : ::Puma::Events.stdio
        server   = ::Puma::Server.new(self, events_hander)

        server.add_tcp_listener @settings[:host], @settings[:port]
        server.min_threads = 0
        server.max_threads = 16

        server.run
        @stopsignal.pop
        server.stop(true)
    end
end

#status(context, value = nil) ⇒ Object



509
510
511
512
# File 'lib/ocular/inputs/http_input.rb', line 509

def status(context, value = nil)
    context.response.status = value if value
    context.response.status
end

#stopObject



549
550
551
552
# File 'lib/ocular/inputs/http_input.rb', line 549

def stop()
    @stopsignal << "EXIT"
    @thread.join
end