Class: DescribedRoutes::Middleware::Base
- Inherits:
-
Object
- Object
- DescribedRoutes::Middleware::Base
- Includes:
- Rack::RespondTo
- Defined in:
- lib/described_routes/middleware/base.rb
Overview
Abstract Rack middleware for described_routes. It serves ResourceTemplate data at the configured descrbed_routes path and adds link headers to regular requests whose routing matches a ResourceTemplate.
It must be customised to the web framework in use - override #get_resource_templates and #get_resource_routing.
Direct Known Subclasses
Constant Summary collapse
- DEFAULT_OPTIONS =
The default options parameter to #link_headers; controls which links appear in html link elements
{ :describedby => true, :self => false, :up => false, :related => false, :registered_rels => {'edit' => 'edit', 'up' => 'up'}, :described_routes_path => '/described_routes' }
Instance Method Summary collapse
-
#call(env) ⇒ Object
Process a Rack request, either returning ResourceTemplate data fif the request matches the described_routes path, otherwise passing on the request the application and adding a link header to the response.
-
#call_with_link_header(req) ⇒ Object
Passes on a request to the application and adds a link header to the response.
-
#get_resource_routing(req) ⇒ Object
Does nothing - override in framwork-specific middleware to return the ResourceTemplate and params hash matching the request, otherwise a pair of nils.
-
#get_resource_templates ⇒ Object
Does nothing - override in framework-specific middleware to return the top level ResourceTemplates object.
-
#init_from_first_req(req) ⇒ Object
From the first request, initialize @root, @described_routes_uri and @resource_templates.
-
#initialize(app, options = {}) ⇒ Base
constructor
A new instance of Base.
-
#make_link_header(resource_template, params, request_uri) ⇒ Object
Returns a LinkHeader object that represents the required links.
-
#serve_resource_template_data(req, route_name, format) ⇒ Object
Handles requests for ResourceTemplate data.
Constructor Details
#initialize(app, options = {}) ⇒ Base
Returns a new instance of Base.
27 28 29 30 31 32 33 34 35 36 |
# File 'lib/described_routes/middleware/base.rb', line 27 def initialize(app, ={}) @app = app @options = DEFAULT_OPTIONS.merge() DEFAULT_OPTIONS.keys.each do |option| instance_variable_set("@option_#{option}", @options[option]) end @inited = false end |
Instance Method Details
#call(env) ⇒ Object
Process a Rack request, either returning ResourceTemplate data fif the request matches the described_routes path, otherwise passing on the request the application and adding a link header to the response.
75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/described_routes/middleware/base.rb', line 75 def call(env) # puts "\n", "-" * 80, "\n", env.map{|k, v| "#{k} => #{v.inspect}"} req = Rack::Request.new(env) init_from_first_req(req) unless @inited if req.path =~ %r(^#{@option_described_routes_path}(/([^/.]+)?)?(\.([a-z]+))?) serve_resource_template_data(req, $2, $3) else call_with_link_header(req) end end |
#call_with_link_header(req) ⇒ Object
Passes on a request to the application and adds a link header to the response
140 141 142 143 144 145 146 147 148 149 |
# File 'lib/described_routes/middleware/base.rb', line 140 def call_with_link_header(req) status, headers, body = @app.call(req.env) resource_template, params = get_resource_routing(req) if resource_template headers = headers.merge("Link" => make_link_header(resource_template, params, @root + req.fullpath).to_s) end [status, headers, body] end |
#get_resource_routing(req) ⇒ Object
Does nothing - override in framwork-specific middleware to return the ResourceTemplate and params hash matching the request, otherwise a pair of nils
67 68 69 |
# File 'lib/described_routes/middleware/base.rb', line 67 def get_resource_routing(req) [nil, nil] end |
#get_resource_templates ⇒ Object
Does nothing - override in framework-specific middleware to return the top level ResourceTemplates object
59 60 61 |
# File 'lib/described_routes/middleware/base.rb', line 59 def get_resource_templates nil end |
#init_from_first_req(req) ⇒ Object
From the first request, initialize @root, @described_routes_uri and @resource_templates
41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/described_routes/middleware/base.rb', line 41 def init_from_first_req(req) raise "no request host" unless req.host @root = "http://#{req.host}" @root += ":#{req.port}" if req.port && req.port != 80 @root += "#{script_name}" if req.script_name && !req.script_name.empty? @described_routes_uri = @root + @option_described_routes_path @resource_templates = get_resource_templates(@root) raise "get_resource_templates(#{@root.inspect}) failed; no resource templates!" unless @resource_templates @inited = true end |
#make_link_header(resource_template, params, request_uri) ⇒ Object
Returns a LinkHeader object that represents the required links.
Link relation types (“rel” attributes) will contain a standard type (‘self’, ‘up’, ‘describedby’) &/or an extension type in the form “described_route_url(name)#rel”, using the name and rel of the resource template.
The output is filtered by the options hash, with members :self, :describedby, :up, :related.
TODO move this to ResourceTemplate
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/described_routes/middleware/base.rb', line 160 def make_link_header(resource_template, params, request_uri) links = [] type_prefix = @described_routes_uri + '#' # # For the application's root, the link with rel="describedby" has meta="ResourceTemplates" and it refers to a list of all # top level resource templates. Otherwise, rel="describedby" has meta="ResourceTemplate" and it refers to a single resource # template (together with any descendants). # if resource_template.name == 'root' described_by = @described_routes_uri = @resource_templates = "ResourceTemplates" else described_by = @described_routes_uri + "/" + resource_template.name = resource_template.resource_templates = "ResourceTemplate" end # # Add any query parameters to the rel="describedby" link # if params.empty? described_by_with_params = described_by else described_by_with_params = described_by + '?' + params.to_query end # data for rel="self" links << LinkHeader::Link.new(request_uri, [['rel', 'self'], ['role', type_prefix + resource_template.name]]) if @option_self # data for rel="described_by" links << LinkHeader::Link.new(described_by_with_params, [['rel', 'describedby'], ['meta', ]]) if @option_describedby # data for rel="up" # TODO move this to ResourceTemplate if @option_up if resource_template.parent links << LinkHeader::Link.new( resource_template.parent.uri_for(params), [['rel', 'up'], ['role', type_prefix + resource_template.parent.name]]) elsif resource_template.name != 'root' links << LinkHeader::Link.new(@root + '/', [['rel', 'up'], ['role', type_prefix + 'root']]) end end # data for rel="related" if @option_related .(params).each do |l| if l.name != resource_template.name rel = l.rel || l.name rels = [['rel', described_by + '#' + rel]] if l.rel registered_rel = @option_registered_rels[rel] if registered_rel rels.unshift(['rel', registered_rel]) end end links << LinkHeader::Link.new(l.uri, rels + [['role', type_prefix + l.name]]) end end end LinkHeader.new(links) end |
#serve_resource_template_data(req, route_name, format) ⇒ Object
Handles requests for ResourceTemplate data
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 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 |
# File 'lib/described_routes/middleware/base.rb', line 92 def serve_resource_template_data(req, route_name, format) if route_name # /described_routes/{route_name} resource_template = @resource_templates.all_by_name[route_name] unless resource_template return [404, {'Content-Type' => 'text/plain'}, ["No ResourceTemplate named #{route_name.inspect}"]] end target = resource_template rel = "index" # link header will point to the site ResourceTemplates description else rel = "self" # this is the site ResourceTemplates description target = @resource_templates end = target.(req.GET) Rack::RespondTo.env = req.env if format # Format extension overrides any accept header Rack::RespondTo.media_types = [Rack::Mime::MIME_TYPES[format]] else # Supported formats, .text preferred. No html yet! supported_media_types = %w(.text .json .yaml .xml).map{|format| Rack::Mime::MIME_TYPES[format]} # not sure why this is needed - thought Rack::RespondTo would choose for us http_accept = req.env['HTTP_ACCEPT'] if supported_media_types.grep(http_accept).empty? Rack::RespondTo.media_types = supported_media_types else Rack::RespondTo.media_types = [http_accept] end end body = respond_to do |format| format.text {.to_text} format.json {.to_json} format.yaml {.to_yaml} format.xml {.to_xml(Builder::XmlMarkup.new(:indent => 2)).target!} end headers = { 'Link' => %Q(<#{@described_routes_uri}>; rel="#{rel}"; meta="ResourceTemplates"), 'Content-Type' => Rack::RespondTo.selected_media_type } [200, headers, [body]] end |