class Opener::Webservice::Server

The meat of the webservices: the actual Sinatra application. Components should extend this class and configure it (e.g. to specify what component class to use).

Constants

INPUT_FIELDS

List of fields that can contain input to process.

@return [Array]

Public Class Methods

accepted_params() click to toggle source

Returns the accepted component parameters.

@return [Array]

# File lib/opener/webservice/server.rb, line 31
def self.accepted_params
  return @accepted_params ||= []
end
accepted_params=(params) click to toggle source

Sets the accepted component parameters. Parameter names are always stored as symbols.

@param [Array] params

# File lib/opener/webservice/server.rb, line 22
def self.accepted_params=(params)
  @accepted_params = params.map(&:to_sym)
end
text_processor() click to toggle source

Returns the text processor to use.

@return [Class]

# File lib/opener/webservice/server.rb, line 49
def self.text_processor
  return @text_processor
end
text_processor=(processor) click to toggle source

Sets the text processor to use.

@param [Class] processor

# File lib/opener/webservice/server.rb, line 40
def self.text_processor=(processor)
  @text_processor = processor
end

Public Instance Methods

/() click to toggle source

Shows a form that allows users to submit data directly from their browser.

# File lib/opener/webservice/server.rb, line 83
get '/' do
  erb :index
end
add_transaction_parameters(options) click to toggle source

@param [Hash] options

# File lib/opener/webservice/server.rb, line 277
def add_transaction_parameters(options)
  # If raw input is given we'll trim it so the payload isn't too large for
  # Rollbar/New Relic. This uses Hash#merge so we don't modify the
  # original options variable.
  if options['input']
    options = options.merge(
      'input' => options['input'].byteslice(0, 256)
    )
  end

  Transaction.current.add_parameters(options)
end
analyze(options) click to toggle source

Analyzes the input and returns an Array containing the output and content type.

@param [Hash] options @return [Array]

# File lib/opener/webservice/server.rb, line 191
def analyze(options)
  add_transaction_parameters(options)

  comp_options = InputSanitizer.new.whitelist_options(
    options,
    self.class.accepted_params
  )

  input     = InputExtractor.new.extract(options)
  processor = self.class.text_processor.new(comp_options)
  output    = processor.run(input)

  if processor.respond_to?(:output_type)
    type = processor.output_type
  else
    type = :xml
  end

  return output, type
end
analyze_async(options, request_id) click to toggle source

Analyzes the input asynchronously.

@param [Hash] options @param [String] request_id

# File lib/opener/webservice/server.rb, line 218
def analyze_async(options, request_id)
  output, _ = analyze(options)

  submit_output(output, request_id, options)

# Submit the error to the error callback, re-raise so Rollbar can also
# report it.
rescue Exception => error
  ErrorHandler.new.submit(error, request_id) if options['error_callback']

  raise error
end
async() { || ... } click to toggle source

Runs the block in a separate thread. When running a test environment the block is instead yielded normally.

# File lib/opener/webservice/server.rb, line 328
def async
  if self.class.environment == :test
    yield
  else
    Thread.new { yield }
  end
end
authenticate!() click to toggle source

Authenticates the current request.

# File lib/opener/webservice/server.rb, line 312
def authenticate!
  token  = Configuration.authentication_token
  secret = Configuration.authentication_secret
  creds  = {token => params[token], secret => params[secret]}

  response = HTTPClient.get(Configuration.authentication_endpoint, creds)

  unless response.ok?
    halt(403, "Authentication failed: #{response.body}")
  end
end
json_input?() click to toggle source

Returns `true` if the input data is in JSON, false otherwise

@return [TrueClass|FalseClass]

# File lib/opener/webservice/server.rb, line 305
def json_input?
  return request.content_type == 'application/json'
end
params_from_json() click to toggle source

Returns a Hash containing the parameters from a JSON payload. The keys of this Hash are returned as strings to prevent Symbol DOS attacks.

@return [Hash]

# File lib/opener/webservice/server.rb, line 296
def params_from_json
  return JSON.load(request.body.read)
end
process_async(options) click to toggle source

Processes a request asynchronously, results are submitted to the next callback URL.

@param [Hash] options @return [Hash]

# File lib/opener/webservice/server.rb, line 165
def process_async(options)
  request_id = options['request_id'] || SecureRandom.hex
  final_url  = options['callbacks'].last

  Core::Syslog.info(
    "Processing asynchronous request with final URL #{final_url}",
    :request_id => request_id
  )

  async { analyze_async(options, request_id) }

  content_type :json

  return JSON.dump(
    :request_id => request_id,
    :output_url => "#{final_url}/#{request_id}"
  )
end
process_sync(options) click to toggle source

Processes a request synchronously, results are sent as the response upon completion.

@param [Hash] options @return [String]

# File lib/opener/webservice/server.rb, line 148
def process_sync(options)
  output, ctype = analyze(options)

  content_type(ctype)

  Transaction.reset_current

  return output
end
submit_output(output, request_id, options) click to toggle source

Submits the output to the next callback URL.

@param [String] output @param [String] request_id @param [Hash] options

# File lib/opener/webservice/server.rb, line 238
def submit_output(output, request_id, options)
  callbacks = options['callbacks'].dup
  next_url  = callbacks.shift

  # Re-use the old payload so that any extra data (e.g. metadata) is kept
  # in place.
  new_payload = options.merge(
    'callbacks'  => callbacks,
    'request_id' => request_id
  )

  # Make sure we don't re-send this to the next component.
  new_payload.delete('input')

  if Configuration.output_bucket
    Core::Syslog.info(
      "Uploading output to s3://#{Configuration.output_bucket}",
      :request_id => request_id
    )

    uploader = Uploader.new
    object   = uploader.upload(request_id, output, options['metadata'])

    new_payload['input_url'] = object.url_for(:read, :expires => 3600)
  else
    new_payload['input'] = output
  end

  Core::Syslog.info(
    "Submitting output to #{next_url}",
    :request_id => request_id
  )

  CallbackHandler.new.post(next_url, new_payload)
end