Class: Rack::SPARQL::ContentNegotiation

Inherits:
Object
  • Object
show all
Defined in:
lib/rack/sparql/conneg.rb

Overview

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 #initialize.

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

Constant Summary collapse

VARY =
{'Vary' => 'Accept'}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app, options = {}) ⇒ ContentNegotiation

Returns a new instance of ContentNegotiation.

Options Hash (options):

  • :format (RDF::Format, #to_sym)

    Specific RDF writer format to use



32
33
34
# File 'lib/rack/sparql/conneg.rb', line 32

def initialize(app, options = {})
  @app, @options = app, options
end

Instance Attribute Details

#app#call (readonly)



21
22
23
# File 'lib/rack/sparql/conneg.rb', line 21

def app
  @app
end

#optionsHash{Symbol => Object} (readonly)



25
26
27
# File 'lib/rack/sparql/conneg.rb', line 25

def options
  @options
end

Instance Method Details

#call(env) ⇒ Array(Integer, Hash, #each)

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.

Normalizes ‘application/x-www-form-urlencoded` to either `application/sparql-query` or `application/sparql-update` forms.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/rack/sparql/conneg.rb', line 49

def call(env)
  env['ORDERED_CONTENT_TYPES'] = parse_accept_header(env['HTTP_ACCEPT']) if env.has_key?('HTTP_ACCEPT')
  # Normalize application/x-www-form-urlencoded to application/sparql-query or application/sparql-update
  if env['REQUEST_METHOD'] == 'POST' && env.fetch('CONTENT_TYPE', 'application/x-www-form-urlencoded').to_s.start_with?('application/x-www-form-urlencoded')
    content = env['rack.input'].read
    params = Rack::Utils.parse_query(content)
    if query = params.delete('query')
      return [406, {"Content-Type" => "text/plain"}, ["Multiple query parameters"]] unless query.is_a?(String)
      env['rack.input'] = StringIO.new(query)
      env['CONTENT_TYPE'] = 'application/sparql-query'
      env['QUERY_STRING'] = Rack::Utils.build_query(params)
    elsif update = params.delete('update')
      return [406, {"Content-Type" => "text/plain"}, ["Multiple update parameters"]] unless update.is_a?(String)
      env['rack.input'] = StringIO.new(update)
      env['CONTENT_TYPE'] = 'application/sparql-update'
      env['QUERY_STRING'] = Rack::Utils.build_query(params)
    else
      env['rack.input'].rewind # never mind
    end
  end
  response = app.call(env)
  body = response[2].respond_to?(:body) ? response[2].body : response[2]
  body = body.first if body.is_a?(Array) && body.length == 1 && body.first.is_a?(RDF::Literal::Boolean)
  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) ⇒ Array(Integer, Hash, #each)

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

Raises:

  • (RDF::WriterError)

    when no results are generated



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/rack/sparql/conneg.rb', line 90

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