rack-conneg 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|