Module: Roda::RodaPlugins::TypeRouting

Defined in:
lib/roda/plugins/type_routing.rb

Overview

This plugin makes it easier to to respond to specific request data types. User agents can request specific data types by either supplying an appropriate Accept request header or by appending it as file extension to the path.

Example:

plugin :type_routing

route do |r|
  r.get 'a' do
    r.html{ "<h1>This is the HTML response</h1>" }
    r.json{ '{"json": "ok"}' }
    r.xml{ "<root>This is the XML response</root>" }
    "Unsupported data type"
  end
end

This application will handle the following paths:

/a.html

HTML response

/a.json

JSON response

/a.xml

XML response

/a

HTML, JSON, or XML response, depending on the Accept header

The response Content-Type header will be set to a suitable value when the r.html, r.json, or r.xml block is matched.

Note that if no match is found, code will continue to execute, which can result in unexpected behaviour. This should only happen if you do not handle all supported/configured types. If you want to simplify handling, you can just place the html handling after the other types, without using a separate block:

route do |r|
  r.get 'a' do
    r.json{ '{"json": "ok"}' }
    r.xml{ "<root>This is the XML response</root>" }

    "<h1>This is the HTML response</h1>"
  end
end

This works correctly because Roda’s default Content-Type is text/html. Note that if you use this approach, the type_routing plugin’s :html content type will not be used for html responses, since you aren’t using an r.html block. Instead, the Content-Type header will be set to Roda’s default (which you can override via the default_headers plugin).

If the type routing is based on the Accept request header and not the file extension, then an appropriate Vary header will be set or appended to, so that HTTP caches do not serve the same result for requests with different Accept headers.

To match custom extensions, use the :types option:

plugin :type_routing, types: {
  yaml: 'application/x-yaml',
  js: 'application/javascript; charset=utf-8'
}

route do |r|
  r.get 'a' do
    r.yaml{ YAML.dump "YAML data" }
    r.js{ "JavaScript code" }
    # or:
    r.on_type(:js){ "JavaScript code" }
    "Unsupported data type"
  end
end

Plugin options

The following plugin options are supported:

:default_type

The default data type to assume if the client did not provide one. Defaults to :html.

:exclude

Exclude one or more types from the default set (default set is :html, :xml, :json).

:types

Mapping from a data type to its MIME-Type. Used both to match incoming requests and to provide Content-Type values. If the value is nil, no Content-Type will be set. The type may contain media type parameters, which will be sent to the client but ignored for request matching.

:use_extension

Whether to take the path extension into account. Default is true.

:use_header

Whether to take the Accept header into account. Default is true.

Defined Under Namespace

Modules: RequestMethods

Constant Summary collapse

CONFIGURATION =
{
  :mimes => {
    'text/json' => :json,
    'application/json' => :json,
    'text/xml' => :xml,
    'application/xml' => :xml,
    'text/html' => :html,
  }.freeze,
  :types => {
    :json => 'application/json'.freeze,
    :xml => 'application/xml'.freeze,
    :html => 'text/html'.freeze,
  }.freeze,
  :use_extension => true,
  :use_header => true,
  :default_type => :html
}.freeze

Class Method Summary collapse

Class Method Details

.configure(app, opts = {}) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/roda/plugins/type_routing.rb', line 110

def self.configure(app, opts = {})
  config = (app.opts[:type_routing] || CONFIGURATION).dup
  [:use_extension, :use_header, :default_type].each do |key|
    config[key] = opts[key] if opts.has_key?(key)
  end

  types = config[:types] = config[:types].dup
  mimes = config[:mimes] = config[:mimes].dup

  Array(opts[:exclude]).each do |type|
    types.delete(type)
    mimes.reject!{|_, v| v == type}
  end

  if mapping = opts[:types]
    types.merge!(mapping)

    mapping.each do |k, v|
      if v
        mimes[v.split(';', 2).first] = k
      end
    end
  end

  types.freeze
  mimes.freeze

  type_keys = config[:types].keys
  config[:extension_regexp] = /(.*?)\.(#{Regexp.union(type_keys.map(&:to_s))})\z/

  type_keys.each do |type|
    app::RodaRequest.send(:define_method, type) do |&block|
      on_type(type, &block)
    end
    app::RodaRequest.send(:alias_method, type, type)
  end

  app.opts[:type_routing] = config.freeze
end