wakame-vdc-dcmgr 10.11.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +202 -0
- data/NOTICE +1 -0
- data/Rakefile +142 -0
- data/bin/collector +46 -0
- data/config/dcmgr.conf.example +9 -0
- data/config/initializers/isono.rb +43 -0
- data/config/initializers/passenger.rb +6 -0
- data/config/initializers/sequel.rb +21 -0
- data/config/path_resolver.rb +12 -0
- data/lib/dcmgr.rb +115 -0
- data/lib/dcmgr/endpoints/core_api.rb +1004 -0
- data/lib/dcmgr/endpoints/core_api_mock.rb +816 -0
- data/lib/dcmgr/endpoints/errors.rb +55 -0
- data/lib/dcmgr/endpoints/metadata.rb +129 -0
- data/lib/dcmgr/logger.rb +44 -0
- data/lib/dcmgr/models/account.rb +104 -0
- data/lib/dcmgr/models/account_resource.rb +16 -0
- data/lib/dcmgr/models/base.rb +69 -0
- data/lib/dcmgr/models/base_new.rb +371 -0
- data/lib/dcmgr/models/frontend_system.rb +38 -0
- data/lib/dcmgr/models/host_pool.rb +102 -0
- data/lib/dcmgr/models/image.rb +46 -0
- data/lib/dcmgr/models/instance.rb +255 -0
- data/lib/dcmgr/models/instance_netfilter_group.rb +16 -0
- data/lib/dcmgr/models/instance_nic.rb +68 -0
- data/lib/dcmgr/models/instance_spec.rb +21 -0
- data/lib/dcmgr/models/ip_lease.rb +42 -0
- data/lib/dcmgr/models/netfilter_group.rb +88 -0
- data/lib/dcmgr/models/netfilter_rule.rb +21 -0
- data/lib/dcmgr/models/network.rb +32 -0
- data/lib/dcmgr/models/physical_host.rb +67 -0
- data/lib/dcmgr/models/request_log.rb +25 -0
- data/lib/dcmgr/models/ssh_key_pair.rb +55 -0
- data/lib/dcmgr/models/storage_pool.rb +134 -0
- data/lib/dcmgr/models/tag.rb +126 -0
- data/lib/dcmgr/models/tag_mapping.rb +28 -0
- data/lib/dcmgr/models/volume.rb +130 -0
- data/lib/dcmgr/models/volume_snapshot.rb +47 -0
- data/lib/dcmgr/node_modules/hva_collector.rb +134 -0
- data/lib/dcmgr/node_modules/sta_collector.rb +72 -0
- data/lib/dcmgr/scheduler.rb +12 -0
- data/lib/dcmgr/scheduler/find_last.rb +16 -0
- data/lib/dcmgr/scheduler/find_random.rb +16 -0
- data/lib/dcmgr/stm/instance.rb +25 -0
- data/lib/dcmgr/stm/snapshot_context.rb +33 -0
- data/lib/dcmgr/stm/volume_context.rb +65 -0
- data/lib/dcmgr/web/base.rb +21 -0
- data/lib/sinatra/accept_media_types.rb +128 -0
- data/lib/sinatra/lazy_auth.rb +56 -0
- data/lib/sinatra/rabbit.rb +278 -0
- data/lib/sinatra/respond_to.rb +272 -0
- data/lib/sinatra/sequel_transaction.rb +27 -0
- data/lib/sinatra/static_assets.rb +83 -0
- data/lib/sinatra/url_for.rb +44 -0
- data/web/api/config.ru +20 -0
- data/web/api/public/index.html +0 -0
- data/web/metadata/config.ru +20 -0
- data/web/metadata/public/index.html +0 -0
- metadata +326 -0
@@ -0,0 +1,278 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/url_for'
|
3
|
+
require 'sinatra/respond_to'
|
4
|
+
|
5
|
+
module Sinatra
|
6
|
+
module Rabbit
|
7
|
+
|
8
|
+
class DuplicateParamException < Exception; end
|
9
|
+
class DuplicateOperationException < Exception; end
|
10
|
+
class DuplicateCollectionException < Exception; end
|
11
|
+
|
12
|
+
class Operation
|
13
|
+
attr_reader :name, :method
|
14
|
+
|
15
|
+
STANDARD = {
|
16
|
+
:index => { :method => :get, :member => false },
|
17
|
+
:show => { :method => :get, :member => true },
|
18
|
+
:create => { :method => :post, :member => false },
|
19
|
+
:update => { :method => :put, :member => true },
|
20
|
+
:destroy => { :method => :delete, :member => true }
|
21
|
+
}
|
22
|
+
|
23
|
+
def initialize(coll, name, opts, &block)
|
24
|
+
@name = name.to_sym
|
25
|
+
opts = STANDARD[@name].merge(opts) if standard?
|
26
|
+
@collection = coll
|
27
|
+
raise "No method for operation #{name}" unless opts[:method]
|
28
|
+
@method = opts[:method].to_sym
|
29
|
+
@member = opts[:member]
|
30
|
+
@description = ""
|
31
|
+
instance_eval(&block) if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
def standard?
|
35
|
+
STANDARD.keys.include?(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def description(text="")
|
39
|
+
return @description if text.blank?
|
40
|
+
@description = text
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_documentation(app)
|
44
|
+
raise ArgumentError unless app < Sinatra::Base
|
45
|
+
coll, oper = @collection, self
|
46
|
+
app.get("/api/docs/#{@collection.name}/#{@name}") do
|
47
|
+
@collection, @operation = coll, oper
|
48
|
+
respond_to do |format|
|
49
|
+
format.html { haml :'docs/operation' }
|
50
|
+
format.xml { haml :'docs/operation' }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def control(&block)
|
56
|
+
op = self
|
57
|
+
@control = Proc.new do
|
58
|
+
#op.validate(params)
|
59
|
+
instance_eval(&block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def prefix
|
64
|
+
# FIXME: Make the /api prefix configurable
|
65
|
+
"/api"
|
66
|
+
end
|
67
|
+
|
68
|
+
def path(args = {})
|
69
|
+
l_prefix = args[:prefix] ? args[:prefix] : prefix
|
70
|
+
if @member
|
71
|
+
if standard?
|
72
|
+
"#{l_prefix}/#{@collection.name}/:id"
|
73
|
+
else
|
74
|
+
"#{l_prefix}/#{@collection.name}/:id/#{name}"
|
75
|
+
end
|
76
|
+
else
|
77
|
+
"#{l_prefix}/#{@collection.name}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate(app)
|
82
|
+
raise ArgumentError unless app < Sinatra::Base
|
83
|
+
|
84
|
+
app.send(@method, path, {}, &@control)
|
85
|
+
# Set up some Rails-like URL helpers
|
86
|
+
if name == :index
|
87
|
+
gen_route "#{@collection.name}_url", app
|
88
|
+
elsif name == :show
|
89
|
+
gen_route "#{@collection.name.to_s.singularize}_url", app
|
90
|
+
else
|
91
|
+
gen_route "#{name}_#{@collection.name.to_s.singularize}_url", app
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def authz(&blk)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def gen_route(name, app)
|
100
|
+
route_url = path
|
101
|
+
if @member
|
102
|
+
app.send(:define_method, name) do |id, *args|
|
103
|
+
url = query_url(route_url, args[0])
|
104
|
+
url_for url.gsub(/:id/, id.to_s), :full
|
105
|
+
end
|
106
|
+
else
|
107
|
+
app.send(:define_method, name) do |*args|
|
108
|
+
url = query_url(route_url, args[0])
|
109
|
+
url_for url, :full
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class Collection
|
116
|
+
attr_reader :name, :operations
|
117
|
+
|
118
|
+
def initialize(name, &block)
|
119
|
+
@name = name
|
120
|
+
@description = ""
|
121
|
+
@operations = {}
|
122
|
+
instance_eval(&block) if block_given?
|
123
|
+
end
|
124
|
+
|
125
|
+
# Set/Return description for collection
|
126
|
+
# If first parameter is not present, full description will be
|
127
|
+
# returned.
|
128
|
+
def description(text='')
|
129
|
+
return @description if text.blank?
|
130
|
+
@description = text
|
131
|
+
end
|
132
|
+
|
133
|
+
def generate_documentation(app)
|
134
|
+
raise ArgumentError unless app < Sinatra::Base
|
135
|
+
coll, oper, features = self, @operations, driver.features(name)
|
136
|
+
app.get("/api/docs/#{@name}") do
|
137
|
+
@collection, @operations, @features = coll, oper, features
|
138
|
+
respond_to do |format|
|
139
|
+
format.html { haml :'docs/collection' }
|
140
|
+
format.xml { haml :'docs/collection' }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Add a new operation for this collection. For the standard REST
|
146
|
+
# operations :index, :show, :update, and :destroy, we already know
|
147
|
+
# what method to use and whether this is an operation on the URL for
|
148
|
+
# individual elements or for the whole collection.
|
149
|
+
#
|
150
|
+
# For non-standard operations, options must be passed:
|
151
|
+
# :method : one of the HTTP methods
|
152
|
+
# :member : whether this is an operation on the collection or an
|
153
|
+
# individual element (FIXME: custom operations on the
|
154
|
+
# collection will use a nonsensical URL) The URL for the
|
155
|
+
# operation is the element URL with the name of the operation
|
156
|
+
# appended
|
157
|
+
#
|
158
|
+
# This also defines a helper method like show_instance_url that returns
|
159
|
+
# the URL to this operation (in request context)
|
160
|
+
def operation(name, opts = {}, &block)
|
161
|
+
raise DuplicateOperationException if @operations[name]
|
162
|
+
@operations[name] = Operation.new(self, name, opts, &block)
|
163
|
+
end
|
164
|
+
|
165
|
+
def generate(app = ::Sinatra::Application)
|
166
|
+
raise ArgumentError unless app < Sinatra::Base
|
167
|
+
operations.values.each { |op| op.generate(app) }
|
168
|
+
collname = name # Work around Ruby's weird scoping/capture
|
169
|
+
app.send(:define_method, "#{name.to_s.singularize}_url") do |id|
|
170
|
+
url_for "/api/#{collname}/#{id}", :full
|
171
|
+
end
|
172
|
+
|
173
|
+
if index_op = operations[:index]
|
174
|
+
app.send(:define_method, "#{name}_url") do
|
175
|
+
url_for index_op.path.gsub(/\/\?$/,''), :full
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def add_feature_params(features)
|
181
|
+
features.each do |f|
|
182
|
+
f.operations.each do |fop|
|
183
|
+
if cop = operations[fop.name]
|
184
|
+
fop.params.each_key do |k|
|
185
|
+
if cop.params.has_key?(k)
|
186
|
+
raise DuplicateParamException, "Parameter '#{k}' for operation #{fop.name} defined by collection #{@name} and by feature #{f.name}"
|
187
|
+
else
|
188
|
+
cop.params[k] = fop.params[k]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def collections
|
198
|
+
@collections ||= {}
|
199
|
+
end
|
200
|
+
|
201
|
+
# Create a new collection. NAME should be the pluralized name of the
|
202
|
+
# collection.
|
203
|
+
#
|
204
|
+
# Adds a helper method #{name}_url which returns the URL to the :index
|
205
|
+
# operation on this collection.
|
206
|
+
def collection(name, &block)
|
207
|
+
raise DuplicateCollectionException if collections[name]
|
208
|
+
#return unless driver.has_collection?(name.to_sym)
|
209
|
+
collections[name] = Collection.new(name, &block)
|
210
|
+
#collections[name].add_feature_params(driver.features(name))
|
211
|
+
collections[name].generate(self)
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.registered(app)
|
215
|
+
app.register(Sinatra::RespondTo)
|
216
|
+
app.helpers RabbitHelper
|
217
|
+
app.class_eval {
|
218
|
+
# Generate a root route for API docs
|
219
|
+
get '/api/docs\/?' do
|
220
|
+
respond_to do |format|
|
221
|
+
format.html { haml :'docs/index' }
|
222
|
+
format.xml { haml :'docs/index' }
|
223
|
+
end
|
224
|
+
end
|
225
|
+
}
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
module RabbitHelper
|
230
|
+
def query_url(url, params)
|
231
|
+
return url if params.nil? || params.empty?
|
232
|
+
url + "?#{URI.escape(params.collect{|k,v| "#{k}=#{v}"}.join('&'))}"
|
233
|
+
end
|
234
|
+
|
235
|
+
def entry_points
|
236
|
+
collections.values.inject([]) do |m, coll|
|
237
|
+
url = url_for coll.operations[:index].path, :full
|
238
|
+
m << [ coll.name, url ]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
class String
|
246
|
+
# Rails defines this for a number of other classes, including Object
|
247
|
+
# see activesupport/lib/active_support/core_ext/object/blank.rb
|
248
|
+
def blank?
|
249
|
+
self !~ /\S/
|
250
|
+
end
|
251
|
+
|
252
|
+
# Title case.
|
253
|
+
#
|
254
|
+
# "this is a string".titlecase
|
255
|
+
# => "This Is A String"
|
256
|
+
#
|
257
|
+
# CREDIT: Eliazar Parra
|
258
|
+
# Copied from facets
|
259
|
+
def titlecase
|
260
|
+
gsub(/\b\w/){ $`[-1,1] == "'" ? $& : $&.upcase }
|
261
|
+
end
|
262
|
+
|
263
|
+
def pluralize
|
264
|
+
self + "s"
|
265
|
+
end
|
266
|
+
|
267
|
+
def singularize
|
268
|
+
self.gsub(/s$/, '')
|
269
|
+
end
|
270
|
+
|
271
|
+
def underscore
|
272
|
+
gsub(/::/, '/').
|
273
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
274
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
275
|
+
tr("-", "_").
|
276
|
+
downcase
|
277
|
+
end
|
278
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'sinatra/accept_media_types'
|
3
|
+
|
4
|
+
# Accept header parsing was looked at but deemed
|
5
|
+
# too much of an irregularity to deal with. Problems with the header
|
6
|
+
# differences from IE, Firefox, Safari, and every other UA causes
|
7
|
+
# problems with the expected output. The general expected behavior
|
8
|
+
# would be serve html when no extension provided, but most UAs say
|
9
|
+
# they will accept application/xml with out a quality indicator, meaning
|
10
|
+
# you'd get the xml block served insead. Just plain retarded, use the
|
11
|
+
# extension and you'll never be suprised.
|
12
|
+
|
13
|
+
module Sinatra
|
14
|
+
module RespondTo
|
15
|
+
class UnhandledFormat < Sinatra::NotFound; end
|
16
|
+
class MissingTemplate < Sinatra::NotFound
|
17
|
+
def code; 500 end
|
18
|
+
end
|
19
|
+
|
20
|
+
TEXT_MIME_TYPES = [:txt, :html, :js, :json, :xml, :rss, :atom, :css, :asm, :c, :cc, :conf,
|
21
|
+
:csv, :cxx, :diff, :dtd, :f, :f77, :f90, :for, :gemspec, :h, :hh, :htm,
|
22
|
+
:log, :mathml, :mml, :p, :pas, :pl, :pm, :py, :rake, :rb, :rdf, :rtf, :ru,
|
23
|
+
:s, :sgm, :sgml, :sh, :svg, :svgz, :text, :wsdl, :xhtml, :xsl, :xslt, :yaml,
|
24
|
+
:yml, :ics, :png]
|
25
|
+
|
26
|
+
def self.registered(app)
|
27
|
+
app.helpers RespondTo::Helpers
|
28
|
+
|
29
|
+
app.set :default_charset, 'utf-8'
|
30
|
+
app.set :default_content, :html
|
31
|
+
app.set :assume_xhr_is_js, true
|
32
|
+
|
33
|
+
# We remove the trailing extension so routes
|
34
|
+
# don't have to be of the style
|
35
|
+
#
|
36
|
+
# get '/resouce.:format'
|
37
|
+
#
|
38
|
+
# They can instead be of the style
|
39
|
+
#
|
40
|
+
# get '/resource'
|
41
|
+
#
|
42
|
+
# and the format will automatically be available in <tt>format</tt>
|
43
|
+
app.before do
|
44
|
+
# Let through sinatra image urls in development
|
45
|
+
next if self.class.development? && request.path_info =~ %r{/__sinatra__/.*?.png}
|
46
|
+
|
47
|
+
unless options.static? && options.public? && (request.get? || request.head?) && static_file?(request.path_info)
|
48
|
+
rpi = request.path_info.sub(%r{\.([^\./]+)$}, '')
|
49
|
+
|
50
|
+
if (not $1) or ($1 and TEXT_MIME_TYPES.include?($1.to_sym))
|
51
|
+
request.path_info, ext = rpi, nil
|
52
|
+
if ! $1.nil?
|
53
|
+
ext = $1
|
54
|
+
elsif env['HTTP_ACCEPT'].nil? || env['HTTP_ACCEPT'].empty?
|
55
|
+
ext = options.default_content
|
56
|
+
end
|
57
|
+
if ext
|
58
|
+
@mime_types = [ Helpers::mime_type(ext) ]
|
59
|
+
format ext
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
app.configure :development do |dev|
|
66
|
+
dev.error UnhandledFormat do
|
67
|
+
content_type :html, :charset => 'utf-8'
|
68
|
+
|
69
|
+
(<<-HTML).gsub(/^ {10}/, '')
|
70
|
+
<!DOCTYPE html>
|
71
|
+
<html>
|
72
|
+
<head>
|
73
|
+
<style type="text/css">
|
74
|
+
body { text-align:center;font-family:helvetica,arial;font-size:22px;
|
75
|
+
color:#888;margin:20px}
|
76
|
+
#c {margin:0 auto;width:500px;text-align:left}
|
77
|
+
</style>
|
78
|
+
</head>
|
79
|
+
<body>
|
80
|
+
<h2>Sinatra doesn't know this ditty.</h2>
|
81
|
+
<img src='/__sinatra__/404.png'>
|
82
|
+
<div id="c">
|
83
|
+
Try this:
|
84
|
+
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{format} { "Hello World" }\n end\nend</pre>
|
85
|
+
</div>
|
86
|
+
</body>
|
87
|
+
</html>
|
88
|
+
HTML
|
89
|
+
end
|
90
|
+
|
91
|
+
dev.error MissingTemplate do
|
92
|
+
content_type :html, :charset => 'utf-8'
|
93
|
+
response.status = request.env['sinatra.error'].code
|
94
|
+
|
95
|
+
engine = request.env['sinatra.error'].message.split('.').last
|
96
|
+
engine = 'haml' unless ['haml', 'builder', 'erb'].include? engine
|
97
|
+
|
98
|
+
path = File.basename(request.path_info)
|
99
|
+
path = "root" if path.nil? || path.empty?
|
100
|
+
|
101
|
+
format = engine == 'builder' ? 'xml' : 'html'
|
102
|
+
|
103
|
+
layout = case engine
|
104
|
+
when 'haml' then "!!!\n%html\n %body= yield"
|
105
|
+
when 'erb' then "<html>\n <body>\n <%= yield %>\n </body>\n</html>"
|
106
|
+
when 'builder' then ::Sinatra::VERSION =~ /^1.0/ ? "xml << yield" : "builder do |xml|\n xml << yield\nend"
|
107
|
+
end
|
108
|
+
|
109
|
+
layout = "<small>app.#{format}.#{engine}</small>\n<pre>#{escape_html(layout)}</pre>"
|
110
|
+
|
111
|
+
(<<-HTML).gsub(/^ {10}/, '')
|
112
|
+
<!DOCTYPE html>
|
113
|
+
<html>
|
114
|
+
<head>
|
115
|
+
<style type="text/css">
|
116
|
+
body { text-align:center;font-family:helvetica,arial;font-size:22px;
|
117
|
+
color:#888;margin:20px}
|
118
|
+
#c {margin:0 auto;width:500px;text-align:left;}
|
119
|
+
small {float:right;clear:both;}
|
120
|
+
pre {clear:both;}
|
121
|
+
</style>
|
122
|
+
</head>
|
123
|
+
<body>
|
124
|
+
<h2>Sinatra can't find #{request.env['sinatra.error'].message}</h2>
|
125
|
+
<img src='/__sinatra__/500.png'>
|
126
|
+
<div id="c">
|
127
|
+
Try this:<br />
|
128
|
+
#{layout}
|
129
|
+
<small>#{path}.#{format}.#{engine}</small>
|
130
|
+
<pre>Hello World!</pre>
|
131
|
+
<small>application.rb</small>
|
132
|
+
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{engine == 'builder' ? 'xml' : 'html'} { #{engine} :#{path}#{",\n#{' '*32}layout => :app" if layout} }\n end\nend</pre>
|
133
|
+
</div>
|
134
|
+
</body>
|
135
|
+
</html>
|
136
|
+
HTML
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
app.class_eval do
|
142
|
+
private
|
143
|
+
def accept_list
|
144
|
+
@mime_types || Rack::AcceptMediaTypes.new(env['HTTP_ACCEPT'] || '')
|
145
|
+
end
|
146
|
+
|
147
|
+
# Changes in 1.0 Sinatra reuse render for layout so we store
|
148
|
+
# the original value to tell us if this is an automatic attempt
|
149
|
+
# to do a layout call. If it is, it might fail with Errno::ENOENT
|
150
|
+
# and we want to pass that back to sinatra since it isn't a MissingTemplate
|
151
|
+
# error
|
152
|
+
def render_with_format(*args, &block)
|
153
|
+
assumed_layout = args[1] == :layout
|
154
|
+
args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
|
155
|
+
render_without_format *args, &block
|
156
|
+
rescue Errno::ENOENT => e
|
157
|
+
raise MissingTemplate, "#{args[1]}.#{args[0]}" unless assumed_layout
|
158
|
+
raise e
|
159
|
+
end
|
160
|
+
alias_method :render_without_format, :render
|
161
|
+
alias_method :render, :render_with_format
|
162
|
+
|
163
|
+
if ::Sinatra::VERSION =~ /^0\.9/
|
164
|
+
def lookup_layout_with_format(*args)
|
165
|
+
args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
|
166
|
+
lookup_layout_without_format *args
|
167
|
+
end
|
168
|
+
alias_method :lookup_layout_without_format, :lookup_layout
|
169
|
+
alias_method :lookup_layout, :lookup_layout_with_format
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
module Helpers
|
175
|
+
# Patch the content_type function to remember the set type
|
176
|
+
# This helps cut down on time in the format helper so it
|
177
|
+
# doesn't have to do a reverse lookup on the header
|
178
|
+
def self.included(klass)
|
179
|
+
klass.class_eval do
|
180
|
+
def content_type_with_save(*args)
|
181
|
+
content_type_without_save *args
|
182
|
+
@_format = args.first.to_sym
|
183
|
+
response['Content-Type']
|
184
|
+
end
|
185
|
+
alias_method :content_type_without_save, :content_type
|
186
|
+
alias_method :content_type, :content_type_with_save
|
187
|
+
end if ::Sinatra::VERSION =~ /^1.0/
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.mime_type(sym)
|
191
|
+
::Sinatra::Base.respond_to?(:mime_type) && ::Sinatra::Base.mime_type(sym) || ::Sinatra::Base.media_type(sym)
|
192
|
+
end
|
193
|
+
|
194
|
+
def format(val=nil)
|
195
|
+
unless val.nil?
|
196
|
+
mime_type = ::Sinatra::RespondTo::Helpers.mime_type(val)
|
197
|
+
fail "Unknown media type #{val}\nTry registering the extension with a mime type" if mime_type.nil?
|
198
|
+
|
199
|
+
@_format = val.to_sym
|
200
|
+
response['Content-Type'].sub!(/^[^;]+/, mime_type)
|
201
|
+
charset options.default_charset if Sinatra::RespondTo::TEXT_MIME_TYPES.include?(format) and format!=:png
|
202
|
+
end
|
203
|
+
|
204
|
+
@_format
|
205
|
+
end
|
206
|
+
|
207
|
+
# This is mostly just a helper so request.path_info isn't changed when
|
208
|
+
# serving files from the public directory
|
209
|
+
def static_file?(path)
|
210
|
+
public_dir = File.expand_path(options.public)
|
211
|
+
path = File.expand_path(File.join(public_dir, unescape(path)))
|
212
|
+
|
213
|
+
path[0, public_dir.length] == public_dir && File.file?(path)
|
214
|
+
end
|
215
|
+
|
216
|
+
def charset(val=nil)
|
217
|
+
fail "Content-Type must be set in order to specify a charset" if response['Content-Type'].nil?
|
218
|
+
|
219
|
+
if response['Content-Type'] =~ /charset=[^;]+/
|
220
|
+
response['Content-Type'].sub!(/charset=[^;]+/, (val == '' && '') || "charset=#{val}")
|
221
|
+
else
|
222
|
+
response['Content-Type'] += ";charset=#{val}"
|
223
|
+
end unless val.nil?
|
224
|
+
|
225
|
+
response['Content-Type'][/charset=([^;]+)/, 1]
|
226
|
+
end
|
227
|
+
|
228
|
+
def respond_to(&block)
|
229
|
+
wants = Format.new
|
230
|
+
yield wants
|
231
|
+
fmt, type, handler = match_accept_type(accept_list, wants)
|
232
|
+
raise UnhandledFormat if fmt.nil?
|
233
|
+
format fmt
|
234
|
+
handler.nil? ? nil : handler.call
|
235
|
+
end
|
236
|
+
|
237
|
+
def match_accept_type(mime_types, format)
|
238
|
+
selected = []
|
239
|
+
accepted_types = mime_types.map {|type| Regexp.escape(type).gsub(/\\\*/,'.*') }
|
240
|
+
# Fix for Chrome based browsers which returns XML when 'xhtml' is requested.
|
241
|
+
if env['HTTP_USER_AGENT'] =~ /Chrome/ and accepted_types.size>1
|
242
|
+
accepted_types[0], accepted_types[1] = accepted_types[1], accepted_types[0]
|
243
|
+
if accepted_types[0].eql?('application/xhtml\\+xml')
|
244
|
+
accepted_types[0] = 'text/html'
|
245
|
+
end
|
246
|
+
end
|
247
|
+
accepted_types.each do |at|
|
248
|
+
format.each do |fmt, ht, handler|
|
249
|
+
(selected = [fmt, ht, handler]) and break if ht.match(at)
|
250
|
+
end
|
251
|
+
break unless selected.empty?
|
252
|
+
end
|
253
|
+
selected
|
254
|
+
end
|
255
|
+
|
256
|
+
# NOTE Array instead of hash because order matters (wildcard type
|
257
|
+
# matches first handler)
|
258
|
+
class Format < Array #:nodoc:
|
259
|
+
def method_missing(format, *args, &handler)
|
260
|
+
mt = Sinatra::RespondTo::Helpers.mime_type(format)
|
261
|
+
if mt.nil?
|
262
|
+
Sinatra::Base.send(:fail, "Unknown media type for respond_to: #{format}\nTry registering the extension with a mime type")
|
263
|
+
end
|
264
|
+
self << [format.to_s, mt, handler]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
Rack::Mime::MIME_TYPES.merge!({ ".gv" => "text/plain" })
|
272
|
+
Sinatra::Application.register Sinatra::RespondTo
|