forked from ontoportal/ontologies_api
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'development' into test
- Loading branch information
Showing
5 changed files
with
352 additions
and
173 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,65 +1,55 @@ | ||
require_relative '../test/test_case' | ||
|
||
use Rack::ContentNegotiation | ||
|
||
class DereferenceResourceController < ApplicationController | ||
namespace "/ontologies" do | ||
get "/:acronym/resolve/:uri" do | ||
acronym = params[:acronym] | ||
uri = params[:uri] | ||
|
||
namespace '/dereference_resource' do | ||
|
||
get do | ||
reply "GET: /:acronym/:uri?output_format= OR POST: acronym, uri, output_format parameters" | ||
end | ||
if acronym.blank? || uri.blank? | ||
error 500, "Usage: ontologies/:acronym/resolve/:uri?output_format= OR POST: acronym, uri, output_format parameters" | ||
end | ||
|
||
get "/:acronym/:uri" do | ||
acronym = params[:acronym] | ||
uri = params[:uri] | ||
output_format = params[:output_format].presence || 'jsonld' | ||
process_request(acronym, uri, output_format) | ||
end | ||
output_format = env["format"].presence || params[:output_format].presence || 'application/n-triples' | ||
|
||
process_request(acronym, uri, output_format) | ||
end | ||
|
||
post do | ||
private | ||
|
||
acronym = params[:acronym] | ||
uri = params[:uri] | ||
output_format = params[:output_format].presence || 'jsonld' | ||
process_request(acronym, uri, output_format) | ||
end | ||
def process_request(acronym_param, uri_param, output_format) | ||
acronym = acronym_param | ||
uri = URI.decode_www_form_component(uri_param) | ||
|
||
private | ||
error 500, "INVALID URI" unless valid_url?(uri) | ||
sub = LinkedData::Models::Ontology.find(acronym).first&.latest_submission | ||
|
||
def process_request(acronym_param, uri_param, output_format) | ||
acronym = URI.decode_www_form_component(acronym_param) | ||
uri = URI.decode_www_form_component(uri_param) | ||
unless valid_url?(acronym) && valid_url?(uri) | ||
raise error 500, "INVALID URLs" | ||
return | ||
end | ||
error 500, "Ontology not found" unless sub | ||
|
||
r = Resource.new(acronym, uri) | ||
case output_format | ||
when 'jsonld' | ||
content_type 'application/json' | ||
reply JSON.parse(r.to_json) | ||
when 'json' | ||
content_type 'application/json' | ||
reply JSON.parse(r.to_json) | ||
when 'xml' | ||
content_type 'application/xml' | ||
reply r.to_xml | ||
when 'turtle' | ||
content_type 'text/turtle' | ||
reply r.to_turtle | ||
when 'ntriples' | ||
content_type 'application/n-triples' | ||
reply r.to_ntriples | ||
else | ||
raise error 500, "Invalid output format" | ||
end | ||
r = Resource.new(sub.id, uri) | ||
case output_format | ||
when 'application/ld+json', 'application/json' | ||
r.to_json | ||
when 'application/rdf+xml', 'application/xml' | ||
r.to_xml | ||
when 'text/turtle' | ||
r.to_turtle | ||
when 'application/n-triples' | ||
r.to_ntriples | ||
else | ||
error 500, "Invalid output format, valid format are: application/json, application/ld+json, application/xml, application/rdf+xml, text/turtle and application/n-triples" | ||
end | ||
|
||
end | ||
|
||
def valid_url?(url) | ||
uri = URI.parse(url) | ||
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) | ||
rescue URI::InvalidURIError | ||
false | ||
end | ||
end | ||
|
||
def valid_url?(url) | ||
uri = URI.parse(url) | ||
uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) | ||
rescue URI::InvalidURIError | ||
false | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
module Rack | ||
class ContentNegotiation | ||
DEFAULT_CONTENT_TYPE = "application/n-triples" # N-Triples | ||
VARY = { 'Vary' => 'Accept' }.freeze | ||
ENDPOINTS_FILTER = %r{^/ontologies/[^/]+/resolve/[^/]+$} # Accepted API endpoints to apply content negotiation | ||
|
||
# @return [#call] | ||
attr_reader :app | ||
|
||
# @return [Hash{Symbol => String}] | ||
attr_reader :options | ||
|
||
## | ||
# @param [#call] app | ||
# @param [Hash{Symbol => Object}] options | ||
# Other options passed to writer. | ||
# @option options [String] :default (DEFAULT_CONTENT_TYPE) Specific content type | ||
# @option options [RDF::Format, #to_sym] :format Specific RDF writer format to use | ||
def initialize(app, options = {}) | ||
@app, @options = app, options | ||
@options[:default] = (@options[:default] || DEFAULT_CONTENT_TYPE).to_s | ||
end | ||
|
||
## | ||
# Handles a Rack protocol request. | ||
# Parses Accept header to find appropriate mime-type and sets content_type accordingly. | ||
# | ||
# Inserts ordered content types into the environment as `ORDERED_CONTENT_TYPES` if an Accept header is present | ||
# | ||
# @param [Hash{String => String}] env | ||
# @return [Array(Integer, Hash, #each)] Status, Headers and Body | ||
# @see https://rubydoc.info/github/rack/rack/file/SPEC | ||
def call(env) | ||
if env['PATH_INFO'].match?(ENDPOINTS_FILTER) | ||
if env.has_key?('HTTP_ACCEPT') | ||
accepted_types = parse_accept_header(env['HTTP_ACCEPT']) | ||
if !accepted_types.empty? | ||
env["format"] = accepted_types.first | ||
add_content_type_header(app.call(env), env["format"]) | ||
else | ||
not_acceptable | ||
end | ||
else | ||
env["format"] = options[:default] | ||
add_content_type_header(app.call(env), env["format"]) | ||
end | ||
else | ||
app.call(env) | ||
end | ||
end | ||
|
||
protected | ||
|
||
# Parses an HTTP `Accept` header, returning an array of MIME content types ordered by precedence rules. | ||
# | ||
# @param [String, #to_s] header | ||
# @return [Array<String>] Array of content types sorted by precedence | ||
# @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 | ||
def parse_accept_header(header) | ||
entries = header.to_s.split(',') | ||
parsed_entries = entries.map { |entry| parse_accept_entry(entry) } | ||
sorted_entries = parsed_entries.sort_by { |entry| entry.quality }.reverse | ||
content_types = sorted_entries.map { |entry| entry.content_type } | ||
content_types.flatten.compact | ||
end | ||
|
||
|
||
|
||
# Parses an individual entry from the Accept header. | ||
# | ||
# @param [String] entry An entry from the Accept header | ||
# @return [Entry] An object representing the parsed entry | ||
def parse_accept_entry(entry) | ||
# Represents an entry parsed from the Accept header | ||
entry_struct = Struct.new(:content_type, :quality, :wildcard_count, :param_count) | ||
content_type, *params = entry.split(';').map(&:strip) | ||
quality = 1.0 # Default quality | ||
params.reject! do |param| | ||
if param.start_with?('q=') | ||
quality = param[2..-1].to_f | ||
true | ||
end | ||
end | ||
wildcard_count = content_type.count('*') | ||
entry_struct.new(content_type, quality, wildcard_count, params.size) | ||
end | ||
|
||
|
||
## | ||
# Returns a content type appropriate for the given `media_range`, | ||
# returns `nil` if `media_range` contains a wildcard subtype | ||
# that is not mapped. | ||
# | ||
# @param [String, #to_s] media_range | ||
# @return [String, nil] | ||
def find_content_type_for_media_range(media_range) | ||
case media_range.to_s | ||
when '*/*', 'text/*' | ||
options[:default] | ||
when 'application/n-triples' | ||
'application/n-triples' | ||
when 'text/turtle' | ||
'text/turtle' | ||
when 'application/json', 'application/ld+json', 'application/*' | ||
'application/ld+json' | ||
when 'text/xml', 'text/rdf+xml', 'application/rdf+xml', 'application/xml' | ||
'application/rdf+xml' | ||
else | ||
nil | ||
end | ||
end | ||
|
||
## | ||
# Outputs an HTTP `406 Not Acceptable` response. | ||
# | ||
# @param [String, #to_s] message | ||
# @return [Array(Integer, Hash, #each)] | ||
def not_acceptable(message = nil) | ||
code = 406 | ||
http_status = [code, Rack::Utils::HTTP_STATUS_CODES[code]].join(' ') | ||
message = http_status + (message.nil? ? "\n" : " (#{message})\n") | ||
[code, { 'Content-Type' => "text/plain" }.merge(VARY), [message]] | ||
end | ||
|
||
def add_content_type_header(response, type) | ||
response[1] = response[1].merge(VARY).merge('Content-Type' => type) | ||
response | ||
end | ||
|
||
end | ||
end |
Oops, something went wrong.