# frozen_string_literal: true#classRodamoduleRodaPlugins# 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+.moduleTypeRoutingCONFIGURATION={: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}.freezedefself.configure(app,opts={})config=(app.opts[:type_routing]||CONFIGURATION).dup[:use_extension,:use_header,:default_type].eachdo|key|config[key]=opts[key]ifopts.has_key?(key)endtypes=config[:types]=config[:types].dupmimes=config[:mimes]=config[:mimes].dupArray(opts[:exclude]).eachdo|type|types.delete(type)mimes.reject!{|_,v|v==type}endifmapping=opts[:types]types.merge!(mapping)mapping.eachdo|k,v|ifvmimes[v.split(';',2).first]=kendendendtypes.freezemimes.freezetype_keys=config[:types].keysconfig[:extension_regexp]=/(.*?)\.(#{Regexp.union(type_keys.map(&:to_s))})\z/type_keys.eachdo|type|app::RodaRequest.send(:define_method,type)do|&block|on_type(type,&block)endapp::RodaRequest.send(:alias_method,type,type)endapp.opts[:type_routing]=config.freezeendmoduleRequestMethods# Yields if the given +type+ matches the requested data type and halts# the request afterwards, returning the result of the block.defon_type(type,&block)returnunlesstype==requested_typeresponse[RodaResponseHeaders::CONTENT_TYPE]||=@scope.opts[:type_routing][:types][type]always(&block)end# Returns the data type the client requests.defrequested_typereturn@requested_typeifdefined?(@requested_type)opts=@scope.opts[:type_routing]@requested_type=accept_response_typeifopts[:use_header]@requested_type||=opts[:default_type]end# Append the type routing extension back to the path if it was# removed before routing.defreal_remaining_pathifdefined?(@type_routing_extension)"#{super}.#{@type_routing_extension}"elsesuperendendprivate# Removes a trailing file extension from the path, and sets# the requested type if so.def_remaining_path(env)opts=scope.opts[:type_routing]path=superifopts[:use_extension]ifm=opts[:extension_regexp].match(path)@type_routing_extension=@requested_type=m[2].to_sympath=m[1]endendpathend# The response type indicated by the Accept request header.defaccept_response_typemimes=@scope.opts[:type_routing][:mimes]@env['HTTP_ACCEPT'].to_s.split(/\s*,\s*/).mapdo|part|mime,_=part.split(/\s*;\s*/,2)ifsym=mimes[mime]response[RodaResponseHeaders::VARY]=(vary=response[RodaResponseHeaders::VARY])?"#{vary}, Accept":'Accept'returnsymendendnilendendendregister_plugin(:type_routing,TypeRouting)endend