deltacloud-core 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/deltacloud.rb +2 -0
- data/lib/deltacloud/base_driver/base_driver.rb +30 -0
- data/lib/deltacloud/base_driver/features.rb +7 -0
- data/lib/deltacloud/base_driver/mock_driver.rb +18 -0
- data/lib/deltacloud/drivers/azure/azure_driver.rb +127 -0
- data/lib/deltacloud/drivers/ec2/ec2_driver.rb +128 -4
- data/lib/deltacloud/drivers/gogrid/gogrid_driver.rb +5 -2
- data/lib/deltacloud/drivers/mock/mock_driver.rb +10 -16
- data/lib/deltacloud/drivers/opennebula/opennebula_driver.rb +1 -1
- data/lib/deltacloud/drivers/rackspace/rackspace_driver.rb +110 -11
- data/lib/deltacloud/drivers/rhevm/rhevm_driver.rb +7 -4
- data/lib/deltacloud/helpers/application_helper.rb +8 -1
- data/lib/deltacloud/helpers/blob_stream.rb +51 -0
- data/lib/deltacloud/models/blob.rb +26 -0
- data/lib/deltacloud/models/bucket.rb +24 -0
- data/lib/drivers.rb +1 -0
- data/lib/sinatra/respond_to.rb +147 -181
- data/lib/sinatra/respond_to_old.rb +253 -0
- data/server.rb +104 -22
- data/tests/instance_states_test.rb +1 -2
- data/tests/instances_test.rb +12 -9
- data/tests/url_for_test.rb +1 -1
- data/views/blobs/show.html.haml +20 -0
- data/views/blobs/show.xml.haml +7 -0
- data/views/buckets/index.html.haml +33 -0
- data/views/buckets/index.xml.haml +10 -0
- data/views/buckets/new.html.haml +13 -0
- data/views/buckets/show.html.haml +19 -0
- data/views/buckets/show.xml.haml +8 -0
- data/views/instance_states/show.html.haml +1 -1
- data/views/instance_states/{show.gv.erb → show.png.erb} +0 -0
- metadata +61 -33
@@ -180,7 +180,7 @@ class OpennebulaDriver < Deltacloud::BaseDriver
|
|
180
180
|
|
181
181
|
imageid = computehash['STORAGE/DISK[@type="disk"]'].attributes['href'].split("/").last
|
182
182
|
|
183
|
-
state = (computehash['STATE'].text ==
|
183
|
+
state = (computehash['STATE'].text == "ACTIVE") ? "RUNNING" : "STOPPED"
|
184
184
|
|
185
185
|
hwp_name = computehash['INSTANCE_TYPE'] || 'small'
|
186
186
|
|
@@ -18,6 +18,7 @@
|
|
18
18
|
|
19
19
|
require 'deltacloud/base_driver'
|
20
20
|
require 'deltacloud/drivers/rackspace/rackspace_client'
|
21
|
+
require 'cloudfiles'
|
21
22
|
|
22
23
|
module Deltacloud
|
23
24
|
module Drivers
|
@@ -27,8 +28,13 @@ class RackspaceDriver < Deltacloud::BaseDriver
|
|
27
28
|
|
28
29
|
feature :instances, :user_name
|
29
30
|
|
31
|
+
def supported_collections
|
32
|
+
DEFAULT_COLLECTIONS + [ :buckets ]
|
33
|
+
end
|
34
|
+
|
30
35
|
def hardware_profiles(credentials, opts = nil)
|
31
36
|
racks = new_client( credentials )
|
37
|
+
results=""
|
32
38
|
safely do
|
33
39
|
results = racks.list_flavors.map do |flav|
|
34
40
|
HardwareProfile.new(flav["id"].to_s) do
|
@@ -43,6 +49,7 @@ class RackspaceDriver < Deltacloud::BaseDriver
|
|
43
49
|
|
44
50
|
def images(credentials, opts=nil)
|
45
51
|
racks = new_client( credentials )
|
52
|
+
results=""
|
46
53
|
safely do
|
47
54
|
results = racks.list_images.map do |img|
|
48
55
|
Image.new( {
|
@@ -75,8 +82,8 @@ class RackspaceDriver < Deltacloud::BaseDriver
|
|
75
82
|
end
|
76
83
|
Instance.new( {
|
77
84
|
:id => id,
|
78
|
-
:state => "
|
79
|
-
:actions => instance_actions_for(
|
85
|
+
:state => "RUNNING",
|
86
|
+
:actions => instance_actions_for( "RUNNING" ),
|
80
87
|
} )
|
81
88
|
end
|
82
89
|
|
@@ -141,6 +148,88 @@ class RackspaceDriver < Deltacloud::BaseDriver
|
|
141
148
|
end
|
142
149
|
|
143
150
|
|
151
|
+
|
152
|
+
|
153
|
+
define_instance_states do
|
154
|
+
start.to( :pending ) .on( :create )
|
155
|
+
|
156
|
+
pending.to( :running ) .automatically
|
157
|
+
|
158
|
+
running.to( :running ) .on( :reboot )
|
159
|
+
running.to( :shutting_down ) .on( :stop )
|
160
|
+
|
161
|
+
shutting_down.to( :stopped ) .automatically
|
162
|
+
|
163
|
+
stopped.to( :finish ) .automatically
|
164
|
+
end
|
165
|
+
|
166
|
+
#--
|
167
|
+
# Buckets
|
168
|
+
#--
|
169
|
+
def buckets(credentials, opts)
|
170
|
+
bucket_list = []
|
171
|
+
cf = cloudfiles_client(credentials)
|
172
|
+
safely do
|
173
|
+
cf.containers.each do |container_name|
|
174
|
+
current = cf.container(container_name)
|
175
|
+
bucket_list << convert_container(current)
|
176
|
+
end #containers.each
|
177
|
+
end #safely
|
178
|
+
bucket_list = filter_on(bucket_list, :id, opts)
|
179
|
+
bucket_list
|
180
|
+
end
|
181
|
+
|
182
|
+
#--
|
183
|
+
# Create Bucket
|
184
|
+
#--
|
185
|
+
def create_bucket(credentials, name, opts)
|
186
|
+
bucket = nil
|
187
|
+
cf = cloudfiles_client(credentials)
|
188
|
+
safely do
|
189
|
+
new_bucket = cf.create_container(name)
|
190
|
+
bucket = convert_container(new_bucket)
|
191
|
+
end
|
192
|
+
bucket
|
193
|
+
end
|
194
|
+
|
195
|
+
#--
|
196
|
+
# Delete Bucket
|
197
|
+
#--
|
198
|
+
def delete_bucket(credentials, name, opts)
|
199
|
+
cf = cloudfiles_client(credentials)
|
200
|
+
safely do
|
201
|
+
cf.delete_container(name)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
#--
|
206
|
+
# Blobs
|
207
|
+
#--
|
208
|
+
def blobs(credentials, opts)
|
209
|
+
cf = cloudfiles_client(credentials)
|
210
|
+
blobs = []
|
211
|
+
safely do
|
212
|
+
cf_container = cf.container(opts['bucket'])
|
213
|
+
cf_container.objects.each do |object_name|
|
214
|
+
blobs << convert_object(cf_container.object(object_name))
|
215
|
+
end
|
216
|
+
end
|
217
|
+
blobs = filter_on(blobs, :id, opts)
|
218
|
+
blobs
|
219
|
+
end
|
220
|
+
|
221
|
+
#-
|
222
|
+
# Blob data
|
223
|
+
#-
|
224
|
+
def blob_data(credentials, bucket_id, blob_id, opts)
|
225
|
+
cf = cloudfiles_client(credentials)
|
226
|
+
cf.container(bucket_id).object(blob_id).data_stream do |chunk|
|
227
|
+
yield chunk
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
private
|
232
|
+
|
144
233
|
def convert_srv_to_instance(srv)
|
145
234
|
inst = Instance.new(:id => srv["id"].to_s,
|
146
235
|
:owner_id => "root",
|
@@ -163,17 +252,27 @@ class RackspaceDriver < Deltacloud::BaseDriver
|
|
163
252
|
end
|
164
253
|
end
|
165
254
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
255
|
+
def convert_container(cf_container)
|
256
|
+
Bucket.new({ :id => cf_container.name,
|
257
|
+
:name => cf_container.name,
|
258
|
+
:size => cf_container.count,
|
259
|
+
:blob_list => cf_container.objects
|
260
|
+
})
|
261
|
+
end
|
173
262
|
|
174
|
-
|
263
|
+
def convert_object(cf_object)
|
264
|
+
Blob.new({ :id => cf_object.name,
|
265
|
+
:bucket => cf_object.container.name,
|
266
|
+
:content_length => cf_object.bytes,
|
267
|
+
:content_type => cf_object.content_type,
|
268
|
+
:last_modified => cf_object.last_modified
|
269
|
+
})
|
270
|
+
end
|
175
271
|
|
176
|
-
|
272
|
+
def cloudfiles_client(credentials)
|
273
|
+
safely do
|
274
|
+
CloudFiles::Connection.new(credentials.user, credentials.password)
|
275
|
+
end
|
177
276
|
end
|
178
277
|
|
179
278
|
def safely(&block)
|
@@ -78,10 +78,13 @@ class RHEVMDriver < Deltacloud::BaseDriver
|
|
78
78
|
|
79
79
|
def statify(state)
|
80
80
|
st = state.nil? ? "" : state.upcase()
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
case st
|
82
|
+
when "UP"
|
83
|
+
"RUNNING"
|
84
|
+
when "DOWN"
|
85
|
+
"STOPPED"
|
86
|
+
when "POWERING UP"
|
87
|
+
"PENDING"
|
85
88
|
end
|
86
89
|
|
87
90
|
define_hardware_profile 'rhevm'
|
@@ -54,6 +54,13 @@ module ApplicationHelper
|
|
54
54
|
return 'password' if driver_has_feature?(:authentication_password)
|
55
55
|
end
|
56
56
|
|
57
|
+
def driver_has_bucket_location_feature?
|
58
|
+
driver.features(:buckets).each do |feat|
|
59
|
+
return true if feat.name == :bucket_location
|
60
|
+
end
|
61
|
+
false
|
62
|
+
end
|
63
|
+
|
57
64
|
def filter_all(model)
|
58
65
|
filter = {}
|
59
66
|
filter.merge!(:id => params[:id]) if params[:id]
|
@@ -100,8 +107,8 @@ module ApplicationHelper
|
|
100
107
|
return redirect(instances_url) if name.eql?(:destroy) or @instance.class!=Instance
|
101
108
|
|
102
109
|
respond_to do |format|
|
103
|
-
format.html { haml :"instances/show" }
|
104
110
|
format.xml { haml :"instances/show" }
|
111
|
+
format.html { haml :"instances/show" }
|
105
112
|
format.json {convert_to_json(:instance, @instance) }
|
106
113
|
end
|
107
114
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright (C) 2010 Red Hat, Inc.
|
2
|
+
#
|
3
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
4
|
+
# contributor license agreements. See the NOTICE file distributed with
|
5
|
+
# this work for additional information regarding copyright ownership. The
|
6
|
+
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
7
|
+
# "License"); you may not use this file except in compliance with the
|
8
|
+
# License. You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
14
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
15
|
+
# License for the specific language governing permissions and limitations
|
16
|
+
# under the License.
|
17
|
+
|
18
|
+
#--
|
19
|
+
# based on the example from http://macournoyer.com/blog/2009/06/04/pusher-and-async-with-thin/
|
20
|
+
#--
|
21
|
+
require 'eventmachine'
|
22
|
+
class BlobStream
|
23
|
+
AsyncResponse = [-1, {}, []].freeze
|
24
|
+
def self.call(env, credentials, params)
|
25
|
+
body = DeferrableBody.new
|
26
|
+
#Get the headers out asap. Don't specify a content-type let
|
27
|
+
#the client guess and if they can't they SHOULD default to
|
28
|
+
#'application/octet-stream' anyway as per:
|
29
|
+
#http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1
|
30
|
+
EM.next_tick { env['async.callback'].call [200, {'Content-Type' => "#{params['content_type']}", 'Content-Length' => "#{params['content_length']}"}, body] }
|
31
|
+
#call the driver from here. the driver method yields for every chunk of blob it receives. We then
|
32
|
+
#use body.call to write that chunk as received.
|
33
|
+
driver.blob_data(credentials, params[:bucket], params[:blob], params) {|chunk| body.call ["#{chunk}"]} #close blob_data block
|
34
|
+
body.succeed
|
35
|
+
AsyncResponse # Tells Thin to not close the connection and continue it's work on other request
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class DeferrableBody
|
40
|
+
include EventMachine::Deferrable
|
41
|
+
|
42
|
+
def call(body)
|
43
|
+
body.each do |chunk|
|
44
|
+
@body_callback.call(chunk)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def each(&blk)
|
49
|
+
@body_callback = blk
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2009 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
5
|
+
# contributor license agreements. See the NOTICE file distributed with
|
6
|
+
# this work for additional information regarding copyright ownership. The
|
7
|
+
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
8
|
+
# "License"); you may not use this file except in compliance with the
|
9
|
+
# License. You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
15
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
16
|
+
# License for the specific language governing permissions and limitations
|
17
|
+
# under the License.
|
18
|
+
|
19
|
+
class Blob < BaseModel
|
20
|
+
#already has an id from basemodel (for the key)
|
21
|
+
attr_accessor :bucket
|
22
|
+
attr_accessor :content_length
|
23
|
+
attr_accessor :content_type
|
24
|
+
attr_accessor :last_modified
|
25
|
+
attr_accessor :content
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2009 Red Hat, Inc.
|
3
|
+
#
|
4
|
+
# Licensed to the Apache Software Foundation (ASF) under one or more
|
5
|
+
# contributor license agreements. See the NOTICE file distributed with
|
6
|
+
# this work for additional information regarding copyright ownership. The
|
7
|
+
# ASF licenses this file to you under the Apache License, Version 2.0 (the
|
8
|
+
# "License"); you may not use this file except in compliance with the
|
9
|
+
# License. You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
15
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
16
|
+
# License for the specific language governing permissions and limitations
|
17
|
+
# under the License.
|
18
|
+
|
19
|
+
class Bucket < BaseModel
|
20
|
+
|
21
|
+
attr_accessor :name
|
22
|
+
attr_accessor :size
|
23
|
+
attr_accessor :blob_list
|
24
|
+
end
|
data/lib/drivers.rb
CHANGED
data/lib/sinatra/respond_to.rb
CHANGED
@@ -1,93 +1,132 @@
|
|
1
|
+
# respond_to (The MIT License)
|
2
|
+
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
4
|
+
# and associated documentation files (the 'Software'), to deal in the Software without restriction,
|
5
|
+
# including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
6
|
+
# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
7
|
+
# furnished to do so, subject to the following conditions:
|
8
|
+
#
|
9
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
10
|
+
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
11
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
12
|
+
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
13
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE
|
14
|
+
|
1
15
|
require 'sinatra/base'
|
2
|
-
require '
|
16
|
+
require 'rack/accept'
|
3
17
|
|
4
|
-
|
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.
|
18
|
+
use Rack::Accept
|
12
19
|
|
13
20
|
module Sinatra
|
14
21
|
module RespondTo
|
15
|
-
|
16
|
-
class MissingTemplate < Sinatra::NotFound
|
17
|
-
|
22
|
+
|
23
|
+
class MissingTemplate < Sinatra::NotFound; end
|
24
|
+
|
25
|
+
# Define all MIME types you want to support here.
|
26
|
+
# This conversion table will be used for auto-negotiation
|
27
|
+
# with browser in sinatra when no 'format' parameter is specified.
|
28
|
+
|
29
|
+
SUPPORTED_ACCEPT_HEADERS = {
|
30
|
+
:xml => [
|
31
|
+
'text/xml',
|
32
|
+
'application/xml'
|
33
|
+
],
|
34
|
+
:html => [
|
35
|
+
'text/html',
|
36
|
+
'application/xhtml+xml'
|
37
|
+
],
|
38
|
+
:json => [
|
39
|
+
'application/json'
|
40
|
+
]
|
41
|
+
}
|
42
|
+
|
43
|
+
# We need to pass array of available response types to
|
44
|
+
# best_media_type method
|
45
|
+
def accept_to_array
|
46
|
+
SUPPORTED_ACCEPT_HEADERS.keys.collect do |key|
|
47
|
+
SUPPORTED_ACCEPT_HEADERS[key]
|
48
|
+
end.flatten
|
18
49
|
end
|
19
50
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
51
|
+
# Then, when we get best media type for response, we need
|
52
|
+
# to know which format to choose
|
53
|
+
def lookup_format_from_mime(mime)
|
54
|
+
SUPPORTED_ACCEPT_HEADERS.keys.each do |format|
|
55
|
+
return format if SUPPORTED_ACCEPT_HEADERS[format].include?(mime)
|
56
|
+
end
|
57
|
+
end
|
25
58
|
|
26
59
|
def self.registered(app)
|
60
|
+
|
27
61
|
app.helpers RespondTo::Helpers
|
28
62
|
|
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
63
|
app.before do
|
44
|
-
|
64
|
+
|
65
|
+
# Skip development error image and static content
|
45
66
|
next if self.class.development? && request.path_info =~ %r{/__sinatra__/.*?.png}
|
67
|
+
next if options.static? && options.public? && (request.get? || request.head?) && static_file?(request.path_info)
|
46
68
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
69
|
+
# Remove extension from URI
|
70
|
+
# Extension will be available as a 'extension' method (extension=='txt')
|
71
|
+
|
72
|
+
extension request.path_info.match(/\.([^\.\/]+)$/).to_a.first
|
73
|
+
|
74
|
+
# If ?format= is present, ignore all Accept negotiations because
|
75
|
+
# we are not dealing with browser
|
76
|
+
if request.params.has_key? 'format'
|
77
|
+
format params['format'].to_sym
|
78
|
+
end
|
79
|
+
|
80
|
+
# Let's make a little exception here to handle
|
81
|
+
# /api/instance_states[.gv/.png] calls
|
82
|
+
if extension.eql?('gv')
|
83
|
+
format :gv
|
84
|
+
elsif extension.eql?('png')
|
85
|
+
format :png
|
62
86
|
end
|
63
|
-
end
|
64
87
|
|
65
|
-
|
66
|
-
|
67
|
-
|
88
|
+
# Get Rack::Accept::Response object and find best possible
|
89
|
+
# mime type to output.
|
90
|
+
# This negotiation works fine with latest rest-client gem:
|
91
|
+
#
|
92
|
+
# RestClient.get 'http://localhost:3001/api', {:accept => :json } =>
|
93
|
+
# 'application/json'
|
94
|
+
# RestClient.get 'http://localhost:3001/api', {:accept => :xml } =>
|
95
|
+
# 'application/xml'
|
96
|
+
#
|
97
|
+
# Also browsers like Firefox (3.6.x) and Chromium reporting
|
98
|
+
# 'application/xml+xhtml' which is recognized as :html reponse
|
99
|
+
# In browser you can force output using ?format=[format] parameter.
|
100
|
+
|
101
|
+
rack_accept = env['rack-accept.request']
|
102
|
+
|
103
|
+
if rack_accept.media_type.to_s.strip.eql?('Accept:')
|
104
|
+
format :xml
|
105
|
+
else
|
106
|
+
format lookup_format_from_mime(rack_accept.best_media_type(accept_to_array))
|
107
|
+
end
|
68
108
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
109
|
+
end
|
110
|
+
|
111
|
+
app.class_eval do
|
112
|
+
# This code was copied from respond_to plugin
|
113
|
+
# http://github.com/cehoffman/sinatra-respond_to
|
114
|
+
# MIT License
|
115
|
+
alias :render_without_format :render
|
116
|
+
def render(*args, &block)
|
117
|
+
assumed_layout = args[1] == :layout
|
118
|
+
args[1] = "#{args[1]}.#{format}".to_sym if args[1].is_a?(::Symbol)
|
119
|
+
render_without_format *args, &block
|
120
|
+
rescue Errno::ENOENT => e
|
121
|
+
raise MissingTemplate, "#{args[1]}.#{args[0]}" unless assumed_layout
|
122
|
+
raise e
|
89
123
|
end
|
124
|
+
private :render
|
125
|
+
end
|
90
126
|
|
127
|
+
# This code was copied from respond_to plugin
|
128
|
+
# http://github.com/cehoffman/sinatra-respond_to
|
129
|
+
app.configure :development do |dev|
|
91
130
|
dev.error MissingTemplate do
|
92
131
|
content_type :html, :charset => 'utf-8'
|
93
132
|
response.status = request.env['sinatra.error'].code
|
@@ -103,7 +142,6 @@ module Sinatra
|
|
103
142
|
layout = case engine
|
104
143
|
when 'haml' then "!!!\n%html\n %body= yield"
|
105
144
|
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
145
|
end
|
108
146
|
|
109
147
|
layout = "<small>app.#{format}.#{engine}</small>\n<pre>#{escape_html(layout)}</pre>"
|
@@ -117,19 +155,16 @@ module Sinatra
|
|
117
155
|
color:#888;margin:20px}
|
118
156
|
#c {margin:0 auto;width:500px;text-align:left;}
|
119
157
|
small {float:right;clear:both;}
|
120
|
-
pre {clear:both;}
|
158
|
+
pre {clear:both;text-align:left;font-size:70%;width:500px;margin:0 auto;}
|
121
159
|
</style>
|
122
160
|
</head>
|
123
161
|
<body>
|
124
162
|
<h2>Sinatra can't find #{request.env['sinatra.error'].message}</h2>
|
125
163
|
<img src='/__sinatra__/500.png'>
|
164
|
+
<pre>#{request.env['sinatra.error'].backtrace.join("\n")}</pre>
|
126
165
|
<div id="c">
|
127
|
-
Try this:<br />
|
128
|
-
#{layout}
|
129
|
-
<small>#{path}.#{format}.#{engine}</small>
|
130
|
-
<pre>Hello World!</pre>
|
131
166
|
<small>application.rb</small>
|
132
|
-
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{
|
167
|
+
<pre>#{request.request_method.downcase} '#{request.path_info}' do\n respond_to do |wants|\n wants.#{format} { #{engine} :#{path} }\n end\nend</pre>
|
133
168
|
</div>
|
134
169
|
</body>
|
135
170
|
</html>
|
@@ -137,75 +172,23 @@ module Sinatra
|
|
137
172
|
end
|
138
173
|
|
139
174
|
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
175
|
end
|
173
176
|
|
174
177
|
module Helpers
|
175
|
-
|
176
|
-
# This
|
177
|
-
#
|
178
|
+
|
179
|
+
# This code was copied from respond_to plugin
|
180
|
+
# http://github.com/cehoffman/sinatra-respond_to
|
178
181
|
def self.included(klass)
|
179
182
|
klass.class_eval do
|
180
|
-
|
183
|
+
alias :content_type_without_save :content_type
|
184
|
+
def content_type(*args)
|
181
185
|
content_type_without_save *args
|
182
186
|
@_format = args.first.to_sym
|
183
187
|
response['Content-Type']
|
184
188
|
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
189
|
end
|
203
|
-
|
204
|
-
@_format
|
205
190
|
end
|
206
191
|
|
207
|
-
# This is mostly just a helper so request.path_info isn't changed when
|
208
|
-
# serving files from the public directory
|
209
192
|
def static_file?(path)
|
210
193
|
public_dir = File.expand_path(options.public)
|
211
194
|
path = File.expand_path(File.join(public_dir, unescape(path)))
|
@@ -213,60 +196,43 @@ module Sinatra
|
|
213
196
|
path[0, public_dir.length] == public_dir && File.file?(path)
|
214
197
|
end
|
215
198
|
|
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
199
|
|
225
|
-
|
200
|
+
# Extension holds trimmed extension. This is extra usefull
|
201
|
+
# when you want to build original URI (with extension)
|
202
|
+
# You can simply call "#{request.env['REQUEST_URI']}.#{extension}"
|
203
|
+
def extension(val=nil)
|
204
|
+
@_extension ||= val
|
205
|
+
@_extension
|
226
206
|
end
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
format fmt
|
234
|
-
handler.nil? ? nil : handler.call
|
207
|
+
|
208
|
+
# This helper will holds current format. Helper should be
|
209
|
+
# accesible from all places in Sinatra
|
210
|
+
def format(val=nil)
|
211
|
+
@_format ||= val
|
212
|
+
@_format
|
235
213
|
end
|
236
214
|
|
237
|
-
def
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
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
|
215
|
+
def respond_to(&block)
|
216
|
+
wants = {}
|
217
|
+
|
218
|
+
def wants.method_missing(type, *args, &handler)
|
219
|
+
self[type] = handler
|
246
220
|
end
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
221
|
+
|
222
|
+
# Set proper content-type and encoding for
|
223
|
+
# text based formats
|
224
|
+
if [:xml, :gv, :html, :json].include?(format)
|
225
|
+
content_type format, :charset => 'utf-8'
|
252
226
|
end
|
253
|
-
|
254
|
-
|
227
|
+
yield wants
|
228
|
+
# Raise this error if requested format is not defined
|
229
|
+
# in respond_to { } block.
|
230
|
+
raise MissingTemplate if wants[format].nil?
|
255
231
|
|
256
|
-
|
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
|
232
|
+
wants[format].call
|
266
233
|
end
|
234
|
+
|
267
235
|
end
|
236
|
+
|
268
237
|
end
|
269
238
|
end
|
270
|
-
|
271
|
-
Rack::Mime::MIME_TYPES.merge!({ ".gv" => "text/plain" })
|
272
|
-
Sinatra::Application.register Sinatra::RespondTo
|