rack-conneg 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +38 -0
- data/lib/rack/conneg.rb +149 -0
- metadata +66 -0
data/README.rdoc
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
= Description
|
2
|
+
Content negotiation for Rack applications, including a Rails-style respond_to block.
|
3
|
+
|
4
|
+
= Features
|
5
|
+
|
6
|
+
* Allows both file-extension-based and Accept: header-based content negotiation
|
7
|
+
* Allows certain routes to pass through without negotiation (useful for static files, etc.)
|
8
|
+
* Falls back to a pre-set type if negotiation fails
|
9
|
+
* Injects a respond_to method with default handler into the application's namespace
|
10
|
+
* Injects negotiated_type and negotiated_ext methods both into the application and into Rack::Request
|
11
|
+
|
12
|
+
= Sinatra Example
|
13
|
+
|
14
|
+
require 'sinatra'
|
15
|
+
require 'rack/conneg'
|
16
|
+
|
17
|
+
use(Rack::Conneg) { |conneg|
|
18
|
+
conneg.set :accept_all_extensions, false
|
19
|
+
conneg.set :fallback, :html
|
20
|
+
conneg.ignore('/public/')
|
21
|
+
conneg.provide([:json, :xml])
|
22
|
+
}
|
23
|
+
|
24
|
+
before do
|
25
|
+
content_type negotiated_type
|
26
|
+
end
|
27
|
+
|
28
|
+
get '/hello' do
|
29
|
+
response = { :message => 'Hello, World!' }
|
30
|
+
respond_to do |wants|
|
31
|
+
wants.json { response.to_json }
|
32
|
+
wants.xml { response.to_xml }
|
33
|
+
wants.other {
|
34
|
+
content_type 'text/plain'
|
35
|
+
error 406, "Not Acceptable"
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
data/lib/rack/conneg.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'rack'
|
2
|
+
require 'rack/mime'
|
3
|
+
|
4
|
+
module Rack #:nodoc:#
|
5
|
+
|
6
|
+
class Conneg
|
7
|
+
|
8
|
+
VERSION = '0.1.2'
|
9
|
+
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
@ignores = []
|
13
|
+
@opts = {
|
14
|
+
:accept_all_extensions => false,
|
15
|
+
:fallback => 'text/html'
|
16
|
+
}
|
17
|
+
@types = []
|
18
|
+
|
19
|
+
@app.class.module_eval {
|
20
|
+
def negotiated_ext ; @rack_conneg_ext ; end #:nodoc:#
|
21
|
+
def negotiated_type ; @rack_conneg_type ; end #:nodoc:#
|
22
|
+
def respond_to
|
23
|
+
wants = { '*/*' => Proc.new { raise TypeError, "No handler for #{@rack_conneg_type}" } }
|
24
|
+
def wants.method_missing(ext, *args, &handler)
|
25
|
+
type = ext == :other ? '*/*' : Rack::Mime::MIME_TYPES[".#{ext.to_s}"]
|
26
|
+
self[type] = handler
|
27
|
+
end
|
28
|
+
|
29
|
+
yield wants
|
30
|
+
|
31
|
+
(wants[@rack_conneg_type] || wants['*/*']).call
|
32
|
+
end
|
33
|
+
}
|
34
|
+
|
35
|
+
if block_given?
|
36
|
+
yield self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def call(env)
|
41
|
+
extension = nil
|
42
|
+
path_info = env['PATH_INFO']
|
43
|
+
unless @ignores.find { |ignore| ignore.match(path_info) }
|
44
|
+
# First, check to see if there's an explicit type requested
|
45
|
+
# via the file extension
|
46
|
+
mime_type = Rack::Mime.mime_type(::File.extname(path_info),nil)
|
47
|
+
if mime_type
|
48
|
+
env['PATH_INFO'] = path_info.sub!(/(\..+?)$/,'')
|
49
|
+
extension = $1
|
50
|
+
if !(accept_all_extensions? || @types.include?(mime_type))
|
51
|
+
mime_type = nil
|
52
|
+
end
|
53
|
+
else
|
54
|
+
# Create an array of types out of the HTTP_ACCEPT header, sorted
|
55
|
+
# by q value and original order
|
56
|
+
accept_types = env['HTTP_ACCEPT'].split(/,/)
|
57
|
+
accept_types.each_with_index { |t,i|
|
58
|
+
(accept_type,weight) = t.split(/;/)
|
59
|
+
weight = weight.nil? ? 1.0 : weight.split(/\=/).last.to_f
|
60
|
+
accept_types[i] = { :type => accept_type, :weight => weight, :order => i }
|
61
|
+
}
|
62
|
+
accept_types.sort! { |a,b|
|
63
|
+
ord = b[:weight] <=> a[:weight]
|
64
|
+
if ord == 0
|
65
|
+
ord = a[:order] <=> b[:order]
|
66
|
+
end
|
67
|
+
ord
|
68
|
+
}
|
69
|
+
|
70
|
+
# Find the first item in accept_types that matches a registered
|
71
|
+
# content type
|
72
|
+
accept_types.find { |t|
|
73
|
+
re = %r{^#{t[:type].gsub(/\*/,'.+')}$}
|
74
|
+
@types.find { |type| re.match(type) ? mime_type = type : nil }
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
mime_type ||= fallback
|
79
|
+
@app.instance_variable_set('@rack_conneg_ext',env['rack.conneg.ext'] = extension)
|
80
|
+
@app.instance_variable_set('@rack_conneg_type',env['rack.conneg.type'] = mime_type)
|
81
|
+
end
|
82
|
+
@app.call(env) unless @app.nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
# Should content negotiation accept any file extention passed as part of the URI path,
|
86
|
+
# even if it's not one of the registered provided types?
|
87
|
+
def accept_all_extensions?
|
88
|
+
@opts[:accept_all_extensions] ? true : false
|
89
|
+
end
|
90
|
+
|
91
|
+
# What MIME type should be used as a fallback if negotiation fails? Defaults to 'text/html'
|
92
|
+
# since that's what's used to deliver most error message content.
|
93
|
+
def fallback
|
94
|
+
find_mime_type(@opts[:fallback])
|
95
|
+
end
|
96
|
+
|
97
|
+
# Specifies a route prefix or Regexp that should be ignored by the content negotiator. Use
|
98
|
+
# for static files or any other route that should be passed through unaltered.
|
99
|
+
def ignore(route)
|
100
|
+
route_re = route.kind_of?(Regexp) ? route : %r{^#{route}}
|
101
|
+
@ignores << route_re
|
102
|
+
end
|
103
|
+
|
104
|
+
# Register one or more content types that the application offers. Can be a content type string,
|
105
|
+
# a file extension, or a symbol (e.g., 'application/xml', '.xml', and :xml are all equivalent).
|
106
|
+
def provide(*args)
|
107
|
+
args.flatten.each { |type|
|
108
|
+
mime_type = find_mime_type(type)
|
109
|
+
@types << mime_type
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
113
|
+
# Set a content negotiation option. Valid options are:
|
114
|
+
# * :accept_all_extensions - true if all file extensions should be mapped to MIME types whether
|
115
|
+
# or not their associated types are specifically provided
|
116
|
+
# * :fallback - a content type string, file extention, or symbol representing the MIME type to
|
117
|
+
# fall back on if negotiation fails
|
118
|
+
def set(key, value)
|
119
|
+
opt_key = key.to_sym
|
120
|
+
if !@opts.include?(opt_key)
|
121
|
+
raise IndexError, "Unknown option: #{key.to_s}"
|
122
|
+
end
|
123
|
+
@opts[opt_key] = value
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
def find_mime_type(type)
|
128
|
+
valid_types = Rack::Mime::MIME_TYPES.values
|
129
|
+
mime_type = nil
|
130
|
+
if type =~ %r{^[^/]+/[^/]+}
|
131
|
+
mime_type = type
|
132
|
+
else
|
133
|
+
ext = type.to_s
|
134
|
+
ext = ".#{ext}" unless ext =~ /^\./
|
135
|
+
mime_type = Rack::Mime.mime_type(ext,nil)
|
136
|
+
end
|
137
|
+
unless valid_types.include?(mime_type)
|
138
|
+
raise ValueError, "Unknown MIME type: #{mime_type}"
|
139
|
+
end
|
140
|
+
return mime_type
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class Request
|
145
|
+
def negotiated_ext ; @env['rack.conneg.ext'] ; end
|
146
|
+
def negotiated_type ; @env['rack.conneg.type'] ; end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
metadata
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-conneg
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael B. Klein
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-02-10 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rack
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "1.0"
|
24
|
+
version:
|
25
|
+
description: Middleware that provides both file extension and HTTP_ACCEPT-type content negotiation for Rack applications
|
26
|
+
email: Michael.Klein@oregonstate.edu
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
files:
|
34
|
+
- README.rdoc
|
35
|
+
- lib/rack/conneg.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage:
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options:
|
42
|
+
- --main
|
43
|
+
- README.rdoc
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.3.5
|
62
|
+
signing_key:
|
63
|
+
specification_version: 3
|
64
|
+
summary: Content Negotiation middleware for Rack applications
|
65
|
+
test_files: []
|
66
|
+
|