class Riddl::Server

Attributes

riddl_log[R]
riddl_method[R]
riddl_pinfo[R]
riddl_status[R]

Public Class Methods

new(riddl,opts={},&blk) click to toggle source
# File lib/ruby/riddl/server.rb, line 48
def initialize(riddl,opts={},&blk)# {{{
  @riddl_opts = DAEMONISM_DEFAULT_OPTS.merge({
    :bind         => '0.0.0.0',
    :host         => 'localhost',
    :port         => 9292,
    :secure       => false,
    :verbose      => false,
    :http_only    => false,
    :runtime_opts => [
      ["--port [PORT]", "-p [PORT]", "Specify http port.", ->(p){
        @riddl_opts[:port] = p.to_i
        @riddl_opts[:pidfile] = @riddl_opts[:pidfile].gsub(/\.pid/,'') + '-' + @riddl_opts[:port].to_s + '.pid'
      }],
      ["--http-only", "-s", "Only http, no other protocols.", ->(){ @riddl_opts[:http_only] = true }]
    ],
    :runtime_cmds => [],
    :runtime_proc => Proc.new { |opts|
      @riddl_opts[:cmdl_info] = (@riddl_opts[:secure] ? 'https://' : 'http://') + @riddl_opts[:host] + ':' + @riddl_opts[:port].to_s
      @riddl_opts[:url] ||= @riddl_opts[:cmdl_info]
    }
  }).merge(opts)

  @riddl_logger             = nil
  @riddl_process_out        = true
  @riddl_cross_site_xhr     = false
  @accessible_description   = false
  @riddl_description_string = ''
  @riddl_paths              = []

  @riddl_at_exit            = nil

  @riddl_interfaces         = {}

  daemonism @riddl_opts, &blk

  @riddl = Riddl::Wrapper.new(riddl,@accessible_description)
  if @riddl.description?
    raise SpecificationError, 'RIDDL description does not conform to specification' unless @riddl.validate!
    @riddl_description_string = @riddl.description.xml
  elsif @riddl.declaration?
    raise SpecificationError, 'RIDDL declaration does not conform to specification' unless @riddl.validate!
    @riddl_description_string = @riddl.declaration.description_xml
  else
    raise SpecificationError, 'Not a RIDDL file'
  end

  @riddl.load_necessary_handlers!
  @riddl_paths = @riddl.paths
end

Public Instance Methods

__call() click to toggle source
# File lib/ruby/riddl/server.rb, line 174
def __call #{{{
  @riddl_message = @riddl.io_messages(@riddl_matching_path[0],@riddl_method,@riddl_parameters,@riddl_headers)
  if @riddl_message.nil?
    if @riddl_info[:env].has_key?('HTTP_ORIGIN') && @riddl_cross_site_xhr && @riddl_method == 'options'
      @riddl_res['Access-Control-Allow-Origin'] = '*'
      @riddl_res['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, DELETE, OPTIONS'
      @riddl_res['Access-Control-Allow-Headers'] = @riddl_info[:env]['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] if @riddl_info[:env]['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']
      @riddl_res['Access-Control-Max-Age'] = '0'
      @riddl_res['Content-Length'] = '0'
      @riddl_status = 200
    else
      @riddl_log.write "501: a #{@riddl_method} with the these parameters is not part in the description (xml).\n"
      @riddl_status = 501 # not implemented?!
    end
  else
    if !@riddl_message.nil? && @accessible_description && @riddl_message.in.name == 'riddl-description-request' && @riddl_method == 'get' &&  '/' + @riddl_info[:s].join('/') == '/'
      run Riddl::Utils::Description::RDR, @riddl_description_string
    elsif !@riddl_message.nil? && @accessible_description && @riddl_message.in.name == 'riddl-resource-description-request' && @riddl_method == 'get'
      @riddl_path = File.dirname('/' + @riddl_info[:s].join('/')).gsub(/\/+/,'/')
      on resource File.basename('/' + @riddl_info[:s].join('/')).gsub(/\/+/,'/') do
        run Riddl::Utils::Description::RDR, @riddl.resource_description(@riddl_matching_path[0])
      end
    else
      if @riddl.description?
        instance_exec(@riddl_info, &@riddl_interfaces[nil])
      elsif @riddl.declaration?
        mess = @riddl_message
        @riddl_message.route_to_a.each do |m|
          @riddl_message = m
          @riddl_path = '/'
          if m.interface.base.nil?
            if @riddl_interfaces.key? m.interface.name
              @riddl_info[:r] = m.interface.real_path(@riddl_pinfo).sub(/^\//,'').split('/')
              @riddl_info[:h]['RIDDL_DECLARATION_PATH'] = @riddl_pinfo
              @riddl_info[:h]['RIDDL_DECLARATION_RESOURCE'] = m.interface.top
              @riddl_info[:s] = m.interface.sub.sub(/\//,'').split('/')
              @riddl_info.merge!(:match => matching_path)
              instance_exec(@riddl_info, &@riddl_interfaces[m.interface.name])
            else
              @riddl_log.write "501: not implemented (for remote: add @location in declaration; for local: add to Riddl::Server).\n"
              @riddl_status = 501 # not implemented?!
              break
            end
          else
            run Riddl::Utils::Description::Call, @riddl_exe, @riddl_pinfo, m.interface.top, m.interface.base, m.interface.real_path(@riddl_pinfo)
          end
          break if @riddl_status < 200 || @riddl_status >= 300
          @riddl_info.merge!(:h => @riddl_exe.headers, :p => @riddl_exe.response)
        end
        @riddl_message = mess
      end
    end
    if @riddl_info[:env].has_key?('HTTP_ORIGIN') && @riddl_cross_site_xhr
      @riddl_res['Access-Control-Allow-Origin'] = '*'
      @riddl_res['Access-Control-Max-Age'] = '0'
    end
  end
end
__http_call(env) click to toggle source
# File lib/ruby/riddl/server.rb, line 233
def __http_call(env) #{{{
  @riddl_env = env
  @riddl_env['rack.logger'] =  @riddl_logger if @riddl_logger
  @riddl_log = @riddl_logger || @riddl_env['rack.errors']
  @riddl_res = Rack::Response.new
  @riddl_status = 404

  @riddl_pinfo = Riddl::Protocols::Utils::unescape(@riddl_env["PATH_INFO"].gsub(/\/+/,'/'))
  @riddl_matching_path = @riddl_paths.find{ |e| @riddl_pinfo.match(e[1]).to_s.length == @riddl_pinfo.length }

  if @riddl_matching_path
    @riddl_query_string = @riddl_env['QUERY_STRING']
    @riddl_raw = @riddl_env['rack.input']

    @riddl_headers = {}
    @riddl_env.each do |h,v|
      @riddl_headers[$1] = v if h =~ /^HTTP_(.*)$/
    end
    @riddl_parameters = Protocols::HTTP::Parser.new(
      @riddl_query_string,
      @riddl_raw,
      @riddl_env['CONTENT_TYPE'],
      @riddl_env['CONTENT_LENGTH'],
      @riddl_env['HTTP_CONTENT_DISPOSITION'],
      @riddl_env['HTTP_CONTENT_ID'],
      @riddl_env['HTTP_RIDDL_TYPE']
    ).params
    if @riddl_opts[:http_debug]
      pp @riddl_parameters
    end

    @riddl_method = @riddl_env['REQUEST_METHOD'].downcase
    @riddl_path = '/'
    @riddl_info = {
      :h => @riddl_headers,
      :p => @riddl_parameters,
      :r => @riddl_pinfo.sub(/^\//,'').split('/').map{|e|Protocols::Utils::unescape(e)},
      :s => @riddl_matching_path[0].sub(/\//,'').split('/'),
      :m => @riddl_method,
      :env => @riddl_env.reject{|k,v| k =~ /^rack\./}.merge({'riddl.transport' => 'http', 'custom_protocol' => @riddl_opts[:custom_protocol]}),
      :match => []
    }

    if @riddl_info[:env]['HTTP_CONNECTION'] =~ /Upgrade/ && @riddl_info[:env]['HTTP_UPGRADE'] =~ /\AWebSocket\z/i
      # TODO raise error when declaration and route or (not route and non-local interface)
      # raise SpecificationError, 'RIDDL description does not conform to specification' unless @riddl.validate!
      @riddl_info[:m] = @riddl_method = 'websocket'
      @riddl_message = @riddl.io_messages(@riddl_matching_path[0],'websocket',@riddl_parameters,@riddl_headers)
      if @riddl.description?
        instance_exec(@riddl_info, &@riddl_interfaces[nil])
      elsif @riddl.declaration?
        # one ws connection, no overlay
        unless @riddl_message.nil?
          if @riddl_interfaces.key? @riddl_message.interface.name
            @riddl_info[:r] = @riddl_message.interface.real_path(@riddl_pinfo).sub(/^\//,'').split('/')
            @riddl_info[:h]['RIDDL_DECLARATION_PATH'] = @riddl_pinfo
            @riddl_info[:h]['RIDDL_DECLARATION_RESOURCE'] = @riddl_message.interface.top
            @riddl_info[:s] = @riddl_message.interface.sub.sub(/\//,'').split('/')
            @riddl_info.merge!(:match => matching_path)
            instance_exec(@riddl_info, &@riddl_interfaces[@riddl_message.interface.name])
          end
        end
      end
      throw :async
    elsif @riddl_info[:env]['HTTP_ACCEPT'] == 'text/event-stream'
      @riddl_info[:m] = @riddl_method = 'sse'
      @riddl_message = @riddl.io_messages(@riddl_matching_path[0],'sse',@riddl_parameters,@riddl_headers)
      if @riddl.description?
        instance_exec(@riddl_info, &@riddl_interfaces[nil])
      elsif @riddl.declaration?
        # one ws connection, no overlay
        unless @riddl_message.nil?
          if @riddl_interfaces.key? @riddl_message.interface.name
            @riddl_info[:r] = @riddl_message.interface.real_path(@riddl_pinfo).sub(/^\//,'').split('/')
            @riddl_info[:h]['RIDDL_DECLARATION_PATH'] = @riddl_pinfo
            @riddl_info[:h]['RIDDL_DECLARATION_RESOURCE'] = @riddl_message.interface.top
            @riddl_info[:s] = @riddl_message.interface.sub.sub(/\//,'').split('/')
            @riddl_info.merge!(:match => matching_path)
            instance_exec(@riddl_info, &@riddl_interfaces[@riddl_message.interface.name])
          end
        end
      end
      throw :async
    else
      __call
    end
  else
    @riddl_log.write "404: this resource for sure does not exist.\n"
    @riddl_status = 404 # client requests wrong path
  end
  if @riddl_exe
    @riddl_res.write Protocols::HTTP::Generator.new(@riddl_exe.response,@riddl_res).generate.read
    @riddl_exe.headers.each do |n,h|
      @riddl_res[n] = h
    end
  end
  @riddl_res.status = @riddl_status
  @riddl_res.finish
end
accessible_description(ad) click to toggle source
# File lib/ruby/riddl/server.rb, line 342
def accessible_description(ad)# {{{
  @accessible_description = ad
end
call(env) click to toggle source
# File lib/ruby/riddl/server.rb, line 170
def call(env)# {{{
  dup.__http_call(env)
end
cleanup(&blk) click to toggle source
# File lib/ruby/riddl/server.rb, line 166
def cleanup(&blk)
  @riddl_opts[:cleanup] = blk
end
cross_site_xhr(csxhr) click to toggle source
# File lib/ruby/riddl/server.rb, line 336
def cross_site_xhr(csxhr)# {{{
  @riddl_cross_site_xhr = csxhr
end
declaration_path() click to toggle source
# File lib/ruby/riddl/server.rb, line 433
def declaration_path #{{{
  @riddl_info[:h]['RIDDL_DECLARATION_PATH']
end
declaration_resource() click to toggle source
# File lib/ruby/riddl/server.rb, line 436
def declaration_resource #{{{
  @riddl_info[:h]['RIDDL_DECLARATION_RESOURCE']
end
delete(min='*') click to toggle source
# File lib/ruby/riddl/server.rb, line 422
def delete(min='*'); return false if @riddl_message.nil?; @riddl_path == '/' + @riddl_info[:s].join('/') && @riddl_message.in && min == @riddl_message.in.name && @riddl_method == 'delete'    end
get(min='*') click to toggle source
# File lib/ruby/riddl/server.rb, line 421
def get(min='*');    return false if @riddl_message.nil?; @riddl_path == '/' + @riddl_info[:s].join('/') && @riddl_message.in && min == @riddl_message.in.name && @riddl_method == 'get'       end
interface(name,&block) click to toggle source
# File lib/ruby/riddl/server.rb, line 345
def interface(name,&block) #{{{
  @riddl_interfaces[name] = block
end
logger(lgr) click to toggle source
# File lib/ruby/riddl/server.rb, line 339
def logger(lgr)# {{{
  @riddl_logger = lgr
end
loop!() click to toggle source
# File lib/ruby/riddl/server.rb, line 98
def loop! #{{{
  app = Rack::Builder.new self
  unless @riddl_logger.nil?
    app.use Rack::CommonLogger, @riddl_logger
  end

  server = Rack::Server.new(
    :app => app,
    :Host => @riddl_opts[:bind],
    :Port => @riddl_opts[:port],
    :environment => @riddl_opts[:verbose] ? 'deployment' : 'none',
    :server => 'thin',
    :signals => false
  )
  @riddl_opts[:startup].call if @riddl_opts[:startup]
  if @riddl_opts[:custom_protocol] && !@riddl_opts[:http_only]
    @riddl_opts[:custom_protocol] = @riddl_opts[:custom_protocol].new(@riddl_opts)
    puts @riddl_opts[:custom_protocol].support if @riddl_opts[:custom_protocol].support
  end
  begin
    EM.run do
      if @riddl_opts[:secure]
        server.start do |srv|
          srv.ssl = true
          srv.ssl_options = @riddl_opts[:secure_options]
        end
      else
        server.start
      end

      if @riddl_opts[:custom_protocol] && !@riddl_opts[:http_only]
        @riddl_opts[:custom_protocol].start
      end

      [:INT, :TERM].each do |signal|
        Signal.trap(signal) do
          if @riddl_opts[:cleanup]
            @riddl_opts[:cleanup].call
          end
          EM.stop
        end
      end
      [:HUP].each do |signal|
        Signal.trap(signal) do
          EM.stop
        end
      end

      if @riddl_opts[:parallel]
        EM.defer do
          @riddl_opts[:parallel].call
        end
      end
    end
  rescue => e
    if @riddl_opts[:custom_protocol] && !@riddl_opts[:http_only]
      @riddl_opts[:custom_protocol].error_handling(e)
    end
    puts "Server (#{@riddl_opts[:cmdl_info]}) stopped due to connection error (PID:#{Process.pid})"
  end
end
matching_path() click to toggle source
# File lib/ruby/riddl/server.rb, line 429
def matching_path #{{{
  @riddl_path.sub(/\//,'').split('/')
end
method(what) click to toggle source
# File lib/ruby/riddl/server.rb, line 412
def method(what)# {{{
  if !@riddl_message.nil? && what.class == Hash && what.length == 1
    met, min = what.first
    @riddl_path == @riddl_matching_path[0] && min == @riddl_message.in.name && @riddl_method == met.to_s.downcase
  else
    false
  end
end
on(resource, &block) click to toggle source
# File lib/ruby/riddl/server.rb, line 349
def on(resource, &block)# {{{
  if @riddl_paths.empty? # default interface, when a description and "on" syntax in server
    @riddl_interfaces[nil] = block
    return
  end

  @riddl_path << (@riddl_path == '/' ? resource : '/' + resource)

  ### only descend when there is a possibility that it holds the right path
  rp = @riddl_path.sub(/\//,'').split('/')

  block.call(@riddl_info.merge!(:match => matching_path)) if @riddl_info[:s][rp.length-1] == rp.last
  @riddl_path = File.dirname(@riddl_path).gsub(/\/+/,'/')
end
parallel(&blk) click to toggle source
# File lib/ruby/riddl/server.rb, line 160
def parallel(&blk)
  @riddl_opts[:parallel] = blk
end
patch(min='*') click to toggle source
# File lib/ruby/riddl/server.rb, line 424
def patch(min='*');  return false if @riddl_message.nil?; @riddl_path == '/' + @riddl_info[:s].join('/') && @riddl_message.in && min == @riddl_message.in.name && @riddl_method == 'patch'     end
post(min='*') click to toggle source
# File lib/ruby/riddl/server.rb, line 420
def post(min='*');   return false if @riddl_message.nil?; @riddl_path == '/' + @riddl_info[:s].join('/') && @riddl_message.in && min == @riddl_message.in.name && @riddl_method == 'post'      end
process_out(pout) click to toggle source
# File lib/ruby/riddl/server.rb, line 333
def process_out(pout)# {{{
  @riddl_process_out = pout
end
put(min='*') click to toggle source
# File lib/ruby/riddl/server.rb, line 423
def put(min='*');    return false if @riddl_message.nil?; @riddl_path == '/' + @riddl_info[:s].join('/') && @riddl_message.in && min == @riddl_message.in.name && @riddl_method == 'put'       end
resource(rname=nil) click to toggle source
# File lib/ruby/riddl/server.rb, line 427
def resource(rname=nil); return rname.nil? ? '{}' : rname end
run(what,*args) click to toggle source
# File lib/ruby/riddl/server.rb, line 368
def run(what,*args)# {{{
  return if @riddl_path == ''
  if what.class == Class && what.superclass == Riddl::SSEImplementation
    data = Riddl::Protocols::SSE::ParserData.new
    data.request_path = @riddl_pinfo
    data.request_url = @riddl_pinfo + '?' + @riddl_query_string
    data.query_string = @riddl_query_string
    data.http_method = @riddl_env['REQUEST_METHOD']
    data.body = @riddl_env['rack.input'].read
    data.headers = Hash[
      @riddl_headers.map { |key, value|  [key.downcase.gsub('_','-'), value] }
    ]
    w = what.new(@riddl_info.merge!(:a => args, :match => matching_path))
    w.io = Riddl::Protocols::SSE.new(w, @riddl_env)
    w.io.dispatch(data, @riddl_cross_site_xhr)
  end
  if what.class == Class && what.superclass == Riddl::WebSocketImplementation
    data = Riddl::Protocols::WebSocket::ParserData.new
    data.request_path = @riddl_pinfo
    data.request_url = @riddl_pinfo + '?' + @riddl_query_string
    data.query_string = @riddl_query_string
    data.http_method = @riddl_env['REQUEST_METHOD']
    data.body = @riddl_env['rack.input'].read
    data.headers = Hash[
      @riddl_headers.map { |key, value|  [key.downcase.gsub('_','-'), value] }
    ]
    w = what.new(@riddl_info.merge!(:a => args, :version => @riddl_env['HTTP_SEC_WEBSOCKET_VERSION'], :match => matching_path))
    w.io = Riddl::Protocols::WebSocket.new(w, @riddl_env['thin.connection'])
    w.io.dispatch(data)
  end
  if what.class == Class && what.superclass == Riddl::Implementation
    w = what.new(@riddl_info.merge!(:a => args, :match => matching_path))
    @riddl_exe = Riddl::Server::Execution.new(w.response,w.headers)
    @riddl_status = w.status
    if @riddl_process_out && @riddl_status >= 200 && @riddl_status < 300
      unless @riddl.check_message(@riddl_exe.response,@riddl_exe.headers,@riddl_message.out)
        @riddl_log.write "500: the return for the #{@riddl_method} is not matching anything in the description.\n"
        @riddl_status = 500
        return
      end
    end
  end
end
sse() click to toggle source
# File lib/ruby/riddl/server.rb, line 426
def sse;             return false if @riddl_message.nil?; @riddl_path == '/' + @riddl_info[:s].join('/')                                                       && @riddl_method == 'sse'       end
startup(&blk) click to toggle source
# File lib/ruby/riddl/server.rb, line 163
def startup(&blk)
  @riddl_opts[:startup] = blk
end
use(blk,*args) click to toggle source
# File lib/ruby/riddl/server.rb, line 364
def use(blk,*args)# {{{
  instance_eval(&blk)
end
websocket() click to toggle source
# File lib/ruby/riddl/server.rb, line 425
def websocket;       return false if @riddl_message.nil?; @riddl_path == '/' + @riddl_info[:s].join('/')                                                       && @riddl_method == 'websocket' end