Class: Rack::SPARQL::ContentNegotiation
- 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
- #app ⇒ #call readonly
- #options ⇒ Hash{Symbol => Object} readonly
Instance Method Summary collapse
- #accept_entry(entry) ⇒ Object protected
-
#call(env) ⇒ Array(Integer, Hash, #each)
Handles a Rack protocol request.
-
#http_error(code, message = nil, headers = {}) ⇒ Array(Integer, Hash, #each)
protected
Outputs an HTTP
4xx
or5xx
response. -
#http_status(code) ⇒ String
protected
Returns the standard HTTP status message for the given status
code
. -
#initialize(app, options = {}) ⇒ ContentNegotiation
constructor
A new instance of ContentNegotiation.
-
#not_acceptable(message = nil) ⇒ Array(Integer, Hash, #each)
protected
Outputs an HTTP
406 Not Acceptable
response. -
#parse_accept_header(header) ⇒ Array<String>
protected
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. -
#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.
Constructor Details
#initialize(app, options = {}) ⇒ ContentNegotiation
Returns a new instance of ContentNegotiation.
32 33 34 |
# File 'lib/rack/sparql/conneg.rb', line 32 def initialize(app, = {}) @app, @options = app, end |
Instance Attribute Details
Instance Method Details
#accept_entry(entry) ⇒ Object (protected)
119 120 121 122 123 124 |
# File 'lib/rack/sparql/conneg.rb', line 119 def accept_entry(entry) type, * = entry.delete(' ').split(';') quality = 0 # we sort smallest first .delete_if { |e| quality = 1 - e[2..-1].to_f if e.start_with? 'q=' } [type, [quality, type.count('*'), 1 - .size]] end |
#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 |
#http_error(code, message = nil, headers = {}) ⇒ Array(Integer, Hash, #each) (protected)
Outputs an HTTP 4xx
or 5xx
response.
142 143 144 145 |
# File 'lib/rack/sparql/conneg.rb', line 142 def http_error(code, = nil, headers = {}) = http_status(code) + (.nil? ? "\n" : " (#{})\n") [code, {'Content-Type' => 'text/plain; charset=utf-8'}.merge(headers), []] end |
#http_status(code) ⇒ String (protected)
Returns the standard HTTP status message for the given status code
.
152 153 154 |
# File 'lib/rack/sparql/conneg.rb', line 152 def http_status(code) [code, Rack::Utils::HTTP_STATUS_CODES[code]].join(' ') end |
#not_acceptable(message = nil) ⇒ Array(Integer, Hash, #each) (protected)
Outputs an HTTP 406 Not Acceptable
response.
131 132 133 |
# File 'lib/rack/sparql/conneg.rb', line 131 def not_acceptable( = nil) http_error(406, , VARY) end |
#parse_accept_header(header) ⇒ Array<String> (protected)
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.
114 115 116 117 |
# File 'lib/rack/sparql/conneg.rb', line 114 def parse_accept_header(header) entries = header.to_s.split(',') entries.map { |e| accept_entry(e) }.sort_by(&:last).map(&:first) 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.
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 = {} [:content_types] = env['ORDERED_CONTENT_TYPES'] if env['ORDERED_CONTENT_TYPES'] .merge!(@options) results = ::SPARQL.serialize_results(body, **) 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., headers.merge(VARY)) end end |