class Rack::SPARQL::ContentNegotiation

Rack middleware for SPARQL content negotiation.

Uses HTTP Content Negotiation to find an appropriate RDF format to serialize any result with a body being `RDF::Enumerable`.

Override content negotiation by setting the :format option to {Rack::SPARQL#initialize}.

This endpoint also serves the fuction of Rack::LinkedData, as it will serialize SPARQL results, which may be RDF Graphs

Constants

VARY

Attributes

app[R]

@!attribute [r] app @return [#call]

options[R]

@!attribute [r] options @return [Hash{Symbol => Object}]

Public Class Methods

new(app, options = {}) click to toggle source

@param [#call] app @param [Hash{Symbol => Object}] options

Other options passed to writer.

@option options [RDF::Format, to_sym] :format Specific RDF writer format to use

# File lib/rack/sparql/conneg.rb, line 32
def initialize(app, options = {})
  @app, @options = app, options
end

Public Instance Methods

call(env) click to toggle source

Handles a Rack protocol request. Parses Accept header to find appropriate mime-type and sets content_type accordingly.

If result is `RDF::Literal::Boolean`, `RDF::Query::Results`, or `RDF::Enumerable` The result is serialized using {SPARQL::Results}

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)] @see www.rubydoc.info/github/rack/rack/Rack/Runtime#call-instance_method

# File lib/rack/sparql/conneg.rb, line 47
def call(env)
  env['ORDERED_CONTENT_TYPES'] = parse_accept_header(env['HTTP_ACCEPT']) if env.has_key?('HTTP_ACCEPT')
  response = app.call(env)
  body = response[2].respond_to?(:body) ? response[2].body : response[2]
  case body
  when RDF::Enumerable, RDF::Query::Solutions, RDF::Literal::Boolean
    response[2] = body  # Put it back in the response, it might have been a proxy
    serialize(env, *response)
  else response
  end
end
serialize(env, status, headers, body) click to toggle source

Serializes a SPARQL query result into a Rack protocol response using HTTP content negotiation rules or a specified Content-Type.

@param [Hash{String => String}] env @param [Integer] status @param [Hash{String => Object}] headers @param [RDF::Enumerable] body @return [Array(Integer, Hash, each)] @raise [RDF::WriterError] when no results are generated

# File lib/rack/sparql/conneg.rb, line 69
def serialize(env, status, headers, body)
  begin
    serialize_options = {}
    serialize_options[:content_types] = env['ORDERED_CONTENT_TYPES'] if env['ORDERED_CONTENT_TYPES']
    serialize_options.merge!(@options)
    results = ::SPARQL.serialize_results(body, **serialize_options)
    raise RDF::WriterError, "can't serialize results" unless results
    headers = headers.merge(VARY).merge('Content-Type' => results.content_type) # FIXME: don't overwrite existing Vary headers
    [status, headers, [results]]
  rescue RDF::WriterError => e
    # Use this instead of not_acceptable so that headers are not lost.
    http_error(406, e.message, headers.merge(VARY))
  end
end

Protected Instance Methods

accept_entry(entry) click to toggle source
# File lib/rack/sparql/conneg.rb, line 98
def accept_entry(entry)
  type, *options = entry.delete(' ').split(';')
  quality = 0 # we sort smallest first
  options.delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' }
  [type, [quality, type.count('*'), 1 - options.size]]
end
http_error(code, message = nil, headers = {}) click to toggle source

Outputs an HTTP `4xx` or `5xx` response.

@param [Integer, to_i] code @param [String, to_s] message @param [Hash{String => String}] headers @return [Array(Integer, Hash, each)]

# File lib/rack/sparql/conneg.rb, line 121
def http_error(code, message = nil, headers = {})
  message = http_status(code) + (message.nil? ? "\n" : " (#{message})\n")
  [code, {'Content-Type' => 'text/plain; charset=utf-8'}.merge(headers), [message]]
end
http_status(code) click to toggle source

Returns the standard HTTP status message for the given status `code`.

@param [Integer, to_i] code @return [String]

# File lib/rack/sparql/conneg.rb, line 131
def http_status(code)
  [code, Rack::Utils::HTTP_STATUS_CODES[code]].join(' ')
end
not_acceptable(message = nil) click to toggle source

Outputs an HTTP `406 Not Acceptable` response.

@param [String, to_s] message @return [Array(Integer, Hash, each)]

# File lib/rack/sparql/conneg.rb, line 110
def not_acceptable(message = nil)
  http_error(406, message, VARY)
end
parse_accept_header(header) click to toggle source

Parses an HTTP `Accept` header, returning an array of MIME content types ordered by the precedence rules defined in HTTP/1.1 Section 14.1.

@param [String, to_s] header @return [Array<String>] @see www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1

# File lib/rack/sparql/conneg.rb, line 93
def parse_accept_header(header)
  entries = header.to_s.split(',')
  entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first)
end