require 'json'
require 'sinatra/base'
module SequenceServer
# Controller.
class Routes < Sinatra::Base
# See
# http://www.sinatrarb.com/configuration.html
configure do
# We don't need Rack::MethodOverride. Let's avoid the overhead.
disable :method_override
# Ensure exceptions never leak out of the app. Exceptions raised within
# the app must be handled by the app. We do this by attaching error
# blocks to exceptions we know how to handle and attaching to Exception
# as fallback.
disable :show_exceptions, :raise_errors
# Make it a policy to dump to 'rack.errors' any exception raised by the
# app so that error handlers don't have to do it themselves. But for it
# to always work, Exceptions defined by us should not respond to `code`
# or `http_status` methods. Error blocks must explicitly set http
# status, if needed, by calling `status` method.
enable :dump_errors
# We don't want Sinatra do setup any loggers for us. We will use our own.
set :logging, nil
end
# See
# http://www.sinatrarb.com/intro.html#Mime%20Types
configure do
mime_type :fasta, 'text/fasta'
mime_type :xml, 'text/xml'
mime_type :tsv, 'text/tsv'
end
configure do
# Public, and views directory will be found here.
set :root, lambda { SequenceServer.root }
# Allow :frame_options to be configured for Rack::Protection.
#
# By default _any website_ can embed SequenceServer in an iframe. To
# change this, set `:frame_options` config to :deny, :sameorigin, or
# 'ALLOW-FROM uri'.
set :protection, lambda {
frame_options = SequenceServer.config[:frame_options]
frame_options && { :frame_options => frame_options }
}
end
configure :production do
set :public_folder,
lambda { File.join SequenceServer.root, 'public', 'dist' }
end
helpers do
# Render an anchor element from the given Hash.
#
# See links.rb for example of a Hash object that will be rendered.
def a(link)
return unless link[:title] && link[:url]
target = absolute?(link[:url]) && '_blank' || '_self'
a = %()
a << %( ) if link[:icon]
a << "#{link[:title]}"
end
# Is the given URI absolute? (or relative?)
#
# Returns false if nil is passed.
def absolute?(uri)
uri && URI.parse(uri).absolute?
end
# Prettify given data.
def prettify(data)
return prettify_tuple(data) if tuple? data
return prettify_float(data) if float? data
data
end
# Formats float as "a.bc" or "a x b^c". The latter if float is in
# scientific notation. Former otherwise.
def prettify_float(data)
data.to_s.match(/(\d+\.\d+)e?([+-]\d+)?/)
base = Regexp.last_match[1]
power = Regexp.last_match[2]
s = format '%.2f', base
s << " × 10#{power}" if power
s
end
# Formats an array of two elements as "first (last)".
def prettify_tuple(tuple)
"#{tuple.first} (#{tuple.last})"
end
# Is the given value a tuple? (array of length two).
def tuple?(data)
data.is_a?(Array) && data.length == 2
end
def float?(data)
data.is_a?(Float) ||
(data.is_a?(String) && data =~ /(\d+\.\d+)e?([+-]\d+)?/)
end
end
# For any request that hits the app in development mode, log incoming
# params.
before do
logger.debug params
end
# Render the search form.
get '/' do
erb :search, :locals => { :databases => Database.group_by(&:type) }
end
# BLAST search!
post '/' do
erb :result, :locals => { :report => BLAST.run(params) }
end
# @params sequence_ids: whitespace separated list of sequence ids to
# retrieve
# @params database_ids: whitespace separated list of database ids to
# retrieve the sequence from.
# @params download: whether to return raw response or initiate file
# download
#
# Use whitespace to separate entries in sequence_ids (all other chars exist
# in identifiers) and retreival_databases (we don't allow whitespace in a
# database's name, so it's safe).
get '/get_sequence/' do
sequence_ids = params[:sequence_ids].split(/\s/)
database_ids = params[:database_ids].split(/\s/)
sequences = Sequence::Retriever.new(sequence_ids, database_ids,
params[:download])
send_file(sequences.file.path,
:type => sequences.mime,
:filename => sequences.filename) if params[:download]
sequences.to_json
end
# Download BLAST report in various formats.
get '/download/:search_id.:type' do
out = BLAST::Formatter.new(params[:search_id], params[:type])
send_file out.file.path, :filename => out.filename, :type => out.mime
end
# This error block will only ever be hit if the user gives us a funny
# sequence or incorrect advanced parameter. Well, we could hit this block
# if someone is playing around with our HTTP API too.
error BLAST::ArgumentError do
status 400
error = env['sinatra.error']
erb :'400', :locals => { :error => error }
end
# This will catch any unhandled error and some very special errors. Ideally
# we will never hit this block. If we do, there's a bug in SequenceServer
# or something really weird going on. If we hit this error block we show
# the stacktrace to the user requesting them to post the same to our Google
# Group.
error Exception, BLAST::RuntimeError do
status 500
error = env['sinatra.error']
erb :'500', :locals => { :error => error }
end
end
end