class HaveAPI::Server
Attributes
action_state[RW]
auth_chain[R]
default_version[RW]
extensions[R]
module_name[R]
root[R]
routes[R]
versions[R]
Public Class Methods
new(module_name = HaveAPI.module_name)
click to toggle source
# File lib/haveapi/server.rb, line 184 def initialize(module_name = HaveAPI.module_name) @module_name = module_name @allowed_headers = ['Content-Type'] @auth_chain = HaveAPI::Authentication::Chain.new(self) @extensions = [] end
Public Instance Methods
add_auth_module(v, name, mod, prefix: '')
click to toggle source
# File lib/haveapi/server.rb, line 618 def add_auth_module(v, name, mod, prefix: '') @routes[v] ||= { authentication: { name => { resources: {} } } } HaveAPI.get_version_resources(mod, v).each do |r| mount_resource("#{@root}_auth/#{prefix}/", v, r, @routes[v][:authentication][name][:resources]) end end
add_auth_routes(v, provider, prefix: '')
click to toggle source
@param v [String] API version @param provider [Authentication::Base] @param prefix [String]
# File lib/haveapi/server.rb, line 614 def add_auth_routes(v, provider, prefix: '') provider.register_routes(@sinatra, "#{@root}_auth/#{prefix}") end
allow_header(name)
click to toggle source
# File lib/haveapi/server.rb, line 626 def allow_header(name) @allowed_headers << name unless @allowed_headers.include?(name) @allowed_headers_str = nil end
allowed_headers()
click to toggle source
# File lib/haveapi/server.rb, line 631 def allowed_headers return @allowed_headers_str if @allowed_headers_str @allowed_headers_str = @allowed_headers.join(',') end
app()
click to toggle source
# File lib/haveapi/server.rb, line 637 def app @sinatra end
describe(context)
click to toggle source
# File lib/haveapi/server.rb, line 565 def describe(context) context.version = @default_version ret = { default_version: @default_version, versions: { default: describe_version(context) } } @versions.each do |v| context.version = v ret[:versions][v] = describe_version(context) end ret end
describe_resource(r, hash, context)
click to toggle source
# File lib/haveapi/server.rb, line 603 def describe_resource(r, hash, context) r.describe(hash, context) end
describe_version(context)
click to toggle source
# File lib/haveapi/server.rb, line 581 def describe_version(context) ret = { authentication: @auth_chain.describe(context), resources: {}, meta: Metadata.describe, help: version_prefix(context.version) } # puts JSON.pretty_generate(@routes) @routes[context.version][:resources].each do |resource, children| r_name = resource.resource_name.underscore r_desc = describe_resource(resource, children, context) unless r_desc[:actions].empty? && r_desc[:resources].empty? ret[:resources][r_name] = r_desc end end ret end
mount(prefix = '/')
click to toggle source
Load routes for all resource from included API versions. All routes are mounted under prefix ‘path`. If no default version is set, the last included version is used.
# File lib/haveapi/server.rb, line 216 def mount(prefix = '/') @root = prefix @sinatra = Sinatra.new do # Preload template engine for .md -- without this, tilt will not search # for markdown files with extension .md, only .markdown Tilt[:md] set :views, "#{settings.root}/views" set :public_folder, "#{settings.root}/public" set :bind, '0.0.0.0' if settings.development? set :dump_errors, true set :raise_errors, true set :show_exceptions, false end helpers Sinatra::Cookies helpers ServerHelpers helpers DocHelpers before do if request.env['HTTP_ORIGIN'] headers 'access-control-allow-origin' => '*', 'access-control-allow-credentials' => 'false' end end not_found do setup_formatter report_error(404, {}, 'Action not found') unless @halted end after do if Object.const_defined?(:ActiveRecord) ActiveRecord::Base.connection_handler.clear_active_connections! end end end @sinatra.set(:api_server, self) @routes = {} @default_version ||= @versions.last # Mount root @sinatra.get @root do authenticated?(settings.api_server.default_version) @api = settings.api_server.describe(Context.new( settings.api_server, user: current_user, params: )) content_type 'text/html' erb :index, layout: :main_layout end @sinatra.options @root do setup_formatter access_control authenticated?(settings.api_server.default_version) ret = nil ret = case params[:describe] when 'versions' { versions: settings.api_server.versions, default: settings.api_server.default_version } when 'default' settings.api_server.describe_version(Context.new( settings.api_server, version: settings.api_server.default_version, user: current_user, params: )) else settings.api_server.describe(Context.new( settings.api_server, user: current_user, params: )) end @formatter.format(true, ret) end # Doc @sinatra.get "#{@root}doc" do content_type 'text/html' erb :main_layout do doc(:index) end end @sinatra.get "#{@root}doc/readme" do content_type 'text/html' erb :main_layout do GitHub::Markdown.render(File.new("#{settings.views}/../../../README.md").read) end end @sinatra.get "#{@root}doc/json-schema" do content_type 'text/html' erb :doc_layout, layout: :main_layout do @content = File.read(File.join(settings.root, '../../doc/json-schema.html')) @sidebar = erb :'doc_sidebars/json-schema' end end @sinatra.get %r{#{@root}doc/([^\.]+)(\.md)?} do |f, _| content_type 'text/html' erb :doc_layout, layout: :main_layout do begin @content = doc(f) rescue Errno::ENOENT halt 404 end @sidebar = erb :"doc_sidebars/#{f}" end end # Login/logout links @sinatra.get "#{root}_login" do if current_user redirect back else authenticate!(settings.api_server.default_version) # FIXME end end @sinatra.get "#{root}_logout" do require_auth! end @auth_chain << HaveAPI.default_authenticate if @auth_chain.empty? @auth_chain.setup(@versions) @extensions.each { |e| e.enabled(self) } call_hooks_for(:pre_mount, args: [self, @sinatra]) # Mount default version first mount_version(@root, @default_version) @versions.each do |v| mount_version(version_prefix(v), v) end call_hooks_for(:post_mount, args: [self, @sinatra]) end
mount_action(v, route)
click to toggle source
# File lib/haveapi/server.rb, line 467 def mount_action(v, route) @sinatra.method(route.http_method).call(route.sinatra_path) do setup_formatter if route.action.auth authenticate!(v) else authenticated?(v) end begin body = request.body.read body = if body.empty? nil else JSON.parse(body, symbolize_names: true) end rescue StandardError => e report_error(400, {}, 'Bad JSON syntax') end action = route.action.new(request, v, params, body, Context.new( settings.api_server, version: v, request: self, action: route.action, path: route.path, params:, user: current_user, endpoint: true, resource_path: route.resource_path )) unless action.authorized?(current_user) report_error(403, {}, 'Access denied. Insufficient permissions.') end status, reply, errors, http_status = action.safe_exec @halted = true [ http_status || 200, @formatter.format( status, status ? reply : nil, status ? nil : reply, errors, version: false ) ] end @sinatra.options route.sinatra_path do |*args| setup_formatter access_control route_method = route.http_method.to_s.upcase pass if params[:method] && params[:method] != route_method if route.action.auth authenticate!(v) else authenticated?(v) end ctx = Context.new( settings.api_server, version: v, request: self, action: route.action, path: route.path, args:, params:, user: current_user, endpoint: true, resource_path: route.resource_path ) begin desc = route.action.describe(ctx) unless desc report_error(403, {}, 'Access denied. Insufficient permissions.') end rescue StandardError => e tmp = settings.api_server.call_hooks_for(:description_exception, args: [ctx, e]) report_error( tmp[:http_status] || 500, {}, tmp[:message] || 'Server error occured' ) end @formatter.format(true, desc) end end
mount_nested_resource(v, routes)
click to toggle source
# File lib/haveapi/server.rb, line 451 def mount_nested_resource(v, routes) ret = { resources: {}, actions: {} } routes.each do |route| if route.is_a?(Hash) ret[:resources][route.keys.first] = mount_nested_resource(v, route.values.first) else ret[:actions][route.action] = route.path mount_action(v, route) end end ret end
mount_resource(prefix, v, resource, hash)
click to toggle source
# File lib/haveapi/server.rb, line 434 def mount_resource(prefix, v, resource, hash) hash[resource] = { resources: {}, actions: {} } resource.routes(prefix).each do |route| if route.is_a?(Hash) hash[resource][:resources][route.keys.first] = mount_nested_resource( v, route.values.first ) else hash[resource][:actions][route.action] = route.path mount_action(v, route) end end end
mount_version(prefix, v)
click to toggle source
# File lib/haveapi/server.rb, line 374 def mount_version(prefix, v) @routes[v] ||= {} @routes[v][:resources] = {} @sinatra.get prefix do authenticated?(v) @v = v @help = settings.api_server.describe_version(Context.new( settings.api_server, version: v, user: current_user, params: )) content_type 'text/html' erb :doc_layout, layout: :main_layout do @content = erb :version_page @sidebar = erb :version_sidebar end end @sinatra.options prefix do setup_formatter access_control authenticated?(v) @formatter.format(true, settings.api_server.describe_version(Context.new( settings.api_server, version: v, user: current_user, params: ))) end # Register blocking resource HaveAPI.get_version_resources(@module_name, v).each do |resource| mount_resource(prefix, v, resource, @routes[v][:resources]) end if action_state mount_resource( prefix, v, HaveAPI::Resources::ActionState, @routes[v][:resources] ) end validate_resources(@routes[v][:resources]) end
start!()
click to toggle source
# File lib/haveapi/server.rb, line 641 def start! @sinatra.run! end
use_version(v, default: false)
click to toggle source
Include specific version ‘v` of API.
‘default` is set only when including concrete version. Use {set_default_version} otherwise.
@param v [:all, Array<String>, String]
# File lib/haveapi/server.rb, line 197 def use_version(v, default: false) @versions ||= [] if v == :all @versions = HaveAPI.versions(@module_name) elsif v.is_a?(Array) @versions += v @versions.uniq! else @versions << v @default_version = v if default end end
validate_resources(resources)
click to toggle source
# File lib/haveapi/server.rb, line 426 def validate_resources(resources) resources.each_value do |r| r[:actions].each_key(&:validate_build) validate_resources(r[:resources]) end end
version_prefix(v)
click to toggle source
# File lib/haveapi/server.rb, line 607 def version_prefix(v) "#{@root}v#{v}/" end
Private Instance Methods
do_authenticate(v, request)
click to toggle source
# File lib/haveapi/server.rb, line 647 def do_authenticate(v, request) @auth_chain.authenticate(v, request) end