I'm working on a replacement for this gem called accept_headers.
Rack::AcceptHeaders is a suite of tools for Ruby/Rack applications that eases the complexity of building and interpreting the Accept* family of HTTP request headers.
This is a fork of rack-accept. The major addition being accept-extension parameter support.
Some features of the library are:
- Strict adherence to RFC 2616, specifically section 14
- Full support for the Accept, Accept-Charset, Accept-Encoding, and Accept-Language HTTP request headers
- May be used as Rack middleware or standalone
- A comprehensive test suite that covers many edge cases
Add this line to your application's Gemfile:
gem 'rack-accept_headers'
And then execute:
$ bundle
Or install it yourself as:
$ gem install rack-accept_headers
Or install it from a local copy:
$ git clone git://github.com/kamui/rack-accept_headers.git
$ cd rack-accept_headers
$ rake package
$ rake install
Rack::AcceptHeaders implements the Rack middleware interface and may be used with any
Rack-based application. Simply insert the Rack::AcceptHeaders
module in your Rack
middleware pipeline and access the Rack::AcceptHeaders::Request
object in the
rack-accept_headers.request
environment key, as in the following example.
require 'rack/accept_headers'
use Rack::AcceptHeaders
app = lambda do |env|
accept = env['rack-accept_headers.request']
response = Rack::Response.new
if accept.media_type?('text/html')
response['Content-Type'] = 'text/html'
response.write "<p>Hello. You accept text/html!</p>"
else
response['Content-Type'] = 'text/plain'
response.write "Apparently you don't accept text/html. Too bad."
end
response.finish
end
run app
Rack::AcceptHeaders can also construct automatic 406 responses if you set up
the types of media, character sets, encoding, or languages your server is able
to serve ahead of time. If you pass a configuration block to your use
statement it will yield the Rack::AcceptHeaders::Context
object that is used for that
invocation.
require 'rack/accept_headers'
use(Rack::AcceptHeaders) do |context|
# We only ever serve content in English or Japanese from this site, so if
# the user doesn't accept either of these we will respond with a 406.
context.languages = %w< en jp >
end
app = ...
run app
Note: You should think carefully before using Rack::AcceptHeaders in this way. Many user agents are careless about the types of Accept headers they send, and depend on apps not being too picky. Instead of automatically sending a 406, you should probably only send one when absolutely necessary.
Rack::AcceptHeaders supports accept-extension parameter support. Here's an example:
require 'rack/accept_headers'
use Rack::AcceptHeaders
app = lambda do |env|
SUPPORTED_MEDIA_TYPES = {
"text/html" => :html
"application/json" => :json,
"text/xml" => :xml
}
accept = env['rack-accept_headers.request']
response = Rack::Response.new
if accept
media_type = accept.media_type.best_of(SUPPORTED_MEDIA_TYPES.keys)
# Here, I would return a 415 Unsupported media type, if media_type is nil
# The unsupported_media_type call is left unimplemented here
# unsupported_media_type unless media_type
# To output the media_type symbol
# puts SUPPORTED_MEDIA_TYPES[media_type]
# To return a hash of accept-extension params for the given media type
# puts accept.media_type.params(media_type)
response['Content-Type'] = media_type
response.write %Q{{ "message" : "Hello. You accept #{media_type}" }}
else
media_type = "*/*"
response['Content-Type'] = SUPPORTED_MEDIA_TYPES.keys.first
response.write "Defaulting to #{response['Content-Type']}."
end
response.finish
end
run app
So, given this Accept
header:
Accept: application/json;version=1.0;q=0.1
accept = env['rack-accept_headers.request']
params = accept.media_type.params['application/json')
The params
hash will end up with this value:
{
"application/json" : {
"q" : 0.1,
"version" : "1.0"
}
}
Additionally, Rack::AcceptHeaders may be used outside of a Rack context to provide any Ruby app the ability to construct and interpret Accept headers.
require 'rack/accept_headers'
mtype = Rack::AcceptHeaders::MediaType.new
mtype.qvalues = { 'text/html' => 1, 'text/*' => 0.8, '*/*' => 0.5 }
mtype.to_s # => "Accept: text/html, text/*;q=0.8, */*;q=0.5"
cset = Rack::AcceptHeaders::Charset.new('unicode-1-1, iso-8859-5;q=0.8')
cset.best_of(%w< iso-8859-5 unicode-1-1 >) # => "unicode-1-1"
cset.accept?('iso-8859-1') # => true
The very last line in this example may look like a mistake to someone not familiar with the intricacies of the spec, but it's actually correct. It just puts emphasis on the convenience of using this library so you don't have to worry about these kinds of details.