sinatra 0.3.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- data/AUTHORS +40 -0
- data/CHANGES +189 -0
- data/README.rdoc +146 -117
- data/Rakefile +33 -10
- data/{test → compat}/app_test.rb +11 -10
- data/{test → compat}/application_test.rb +10 -5
- data/compat/builder_test.rb +101 -0
- data/{test → compat}/custom_error_test.rb +0 -0
- data/compat/erb_test.rb +136 -0
- data/{test → compat}/events_test.rb +16 -3
- data/compat/filter_test.rb +30 -0
- data/compat/haml_test.rb +233 -0
- data/compat/helper.rb +30 -0
- data/compat/mapped_error_test.rb +72 -0
- data/{test → compat}/pipeline_test.rb +9 -4
- data/{test → compat}/public/foo.xml +0 -0
- data/compat/sass_test.rb +57 -0
- data/{test → compat}/sessions_test.rb +0 -0
- data/{test → compat}/streaming_test.rb +4 -1
- data/{test → compat}/sym_params_test.rb +0 -0
- data/{test → compat}/template_test.rb +0 -0
- data/{test → compat}/use_in_file_templates_test.rb +0 -0
- data/{test → compat}/views/foo.builder +0 -0
- data/{test → compat}/views/foo.erb +0 -0
- data/{test → compat}/views/foo.haml +0 -0
- data/{test → compat}/views/foo.sass +0 -0
- data/{test → compat}/views/foo_layout.erb +0 -0
- data/{test → compat}/views/foo_layout.haml +0 -0
- data/{test → compat}/views/layout_test/foo.builder +0 -0
- data/{test → compat}/views/layout_test/foo.erb +0 -0
- data/{test → compat}/views/layout_test/foo.haml +0 -0
- data/{test → compat}/views/layout_test/foo.sass +0 -0
- data/{test → compat}/views/layout_test/layout.builder +0 -0
- data/{test → compat}/views/layout_test/layout.erb +0 -0
- data/{test → compat}/views/layout_test/layout.haml +0 -0
- data/{test → compat}/views/layout_test/layout.sass +0 -0
- data/{test → compat}/views/no_layout/no_layout.builder +0 -0
- data/{test → compat}/views/no_layout/no_layout.haml +0 -0
- data/lib/sinatra.rb +6 -1484
- data/lib/sinatra/base.rb +838 -0
- data/lib/sinatra/compat.rb +239 -0
- data/{images → lib/sinatra/images}/404.png +0 -0
- data/{images → lib/sinatra/images}/500.png +0 -0
- data/lib/sinatra/main.rb +48 -0
- data/lib/sinatra/test.rb +114 -0
- data/lib/sinatra/test/bacon.rb +17 -0
- data/lib/sinatra/test/rspec.rb +7 -8
- data/lib/sinatra/test/spec.rb +3 -4
- data/lib/sinatra/test/unit.rb +3 -5
- data/sinatra.gemspec +68 -35
- data/test/base_test.rb +68 -0
- data/test/builder_test.rb +50 -87
- data/test/data/reload_app_file.rb +3 -0
- data/test/erb_test.rb +38 -124
- data/test/filter_test.rb +27 -22
- data/test/haml_test.rb +51 -216
- data/test/helper.rb +22 -6
- data/test/helpers_test.rb +361 -0
- data/test/mapped_error_test.rb +137 -49
- data/test/middleware_test.rb +58 -0
- data/test/options_test.rb +97 -0
- data/test/reload_test.rb +61 -0
- data/test/request_test.rb +18 -0
- data/test/result_test.rb +88 -0
- data/test/routing_test.rb +391 -0
- data/test/sass_test.rb +27 -48
- data/test/sinatra_test.rb +13 -0
- data/test/static_test.rb +57 -0
- data/test/templates_test.rb +88 -0
- data/test/views/hello.builder +1 -0
- data/test/views/hello.erb +1 -0
- data/test/views/hello.haml +1 -0
- data/test/views/hello.sass +2 -0
- data/test/views/hello.test +1 -0
- data/test/views/layout2.builder +3 -0
- data/test/views/layout2.erb +2 -0
- data/test/views/layout2.haml +2 -0
- data/test/views/layout2.test +1 -0
- metadata +80 -48
- data/ChangeLog +0 -96
- data/lib/sinatra/test/methods.rb +0 -76
- data/test/event_context_test.rb +0 -15
File without changes
|
@@ -62,6 +62,7 @@ context "Static files (by default)" do
|
|
62
62
|
headers['Last-Modified'].should.equal last_modified.httpdate
|
63
63
|
end
|
64
64
|
|
65
|
+
# Deprecated. Use: ConditionalGet middleware.
|
65
66
|
specify "are not served when If-Modified-Since matches" do
|
66
67
|
last_modified = File.mtime(Sinatra.application.options.public + '/foo.xml')
|
67
68
|
@request = Rack::MockRequest.new(Sinatra.application)
|
@@ -91,7 +92,8 @@ context "SendData" do
|
|
91
92
|
Sinatra.application = nil
|
92
93
|
end
|
93
94
|
|
94
|
-
|
95
|
+
# Deprecated. send_data is going away.
|
96
|
+
xspecify "should send the data with options" do
|
95
97
|
get '/' do
|
96
98
|
send_data 'asdf', :status => 500
|
97
99
|
end
|
@@ -102,6 +104,7 @@ context "SendData" do
|
|
102
104
|
body.should.equal 'asdf'
|
103
105
|
end
|
104
106
|
|
107
|
+
# Deprecated. The Content-Disposition is no longer handled by sendfile.
|
105
108
|
specify "should include a Content-Disposition header" do
|
106
109
|
get '/' do
|
107
110
|
send_file File.dirname(__FILE__) + '/public/foo.xml'
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/lib/sinatra.rb
CHANGED
@@ -1,1486 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require 'uri'
|
4
|
-
require 'rack'
|
1
|
+
libdir = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
5
3
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
elsif ENV['EVENT']
|
10
|
-
require 'swiftcore/evented_mongrel'
|
11
|
-
puts "Using Evented Mongrel"
|
12
|
-
end
|
4
|
+
require 'sinatra/base'
|
5
|
+
require 'sinatra/main'
|
6
|
+
require 'sinatra/compat'
|
13
7
|
|
14
|
-
|
15
|
-
class Rack::File; MIME_TYPES = Rack::Mime::MIME_TYPES; end
|
16
|
-
end
|
17
|
-
|
18
|
-
module Rack #:nodoc:
|
19
|
-
|
20
|
-
class Request #:nodoc:
|
21
|
-
|
22
|
-
# Set of request method names allowed via the _method parameter hack. By
|
23
|
-
# default, all request methods defined in RFC2616 are included, with the
|
24
|
-
# exception of TRACE and CONNECT.
|
25
|
-
POST_TUNNEL_METHODS_ALLOWED = %w( PUT DELETE OPTIONS HEAD )
|
26
|
-
|
27
|
-
# Return the HTTP request method with support for method tunneling using
|
28
|
-
# the POST _method parameter hack. If the real request method is POST and
|
29
|
-
# a _method param is given and the value is one defined in
|
30
|
-
# +POST_TUNNEL_METHODS_ALLOWED+, return the value of the _method param
|
31
|
-
# instead.
|
32
|
-
def request_method
|
33
|
-
if post_tunnel_method_hack?
|
34
|
-
params['_method'].upcase
|
35
|
-
else
|
36
|
-
@env['REQUEST_METHOD']
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def user_agent
|
41
|
-
@env['HTTP_USER_AGENT']
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
# Return truthfully if the request is a valid verb-over-post hack.
|
47
|
-
def post_tunnel_method_hack?
|
48
|
-
@env['REQUEST_METHOD'] == 'POST' &&
|
49
|
-
POST_TUNNEL_METHODS_ALLOWED.include?(self.POST.fetch('_method', '').upcase)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
module Utils
|
54
|
-
extend self
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
|
59
|
-
module Sinatra
|
60
|
-
extend self
|
61
|
-
|
62
|
-
VERSION = '0.3.3'
|
63
|
-
|
64
|
-
class NotFound < RuntimeError
|
65
|
-
def self.code ; 404 ; end
|
66
|
-
end
|
67
|
-
class ServerError < RuntimeError
|
68
|
-
def self.code ; 500 ; end
|
69
|
-
end
|
70
|
-
|
71
|
-
Result = Struct.new(:block, :params, :status) unless defined?(Result)
|
72
|
-
|
73
|
-
def options
|
74
|
-
application.options
|
75
|
-
end
|
76
|
-
|
77
|
-
def application
|
78
|
-
@app ||= Application.new
|
79
|
-
end
|
80
|
-
|
81
|
-
def application=(app)
|
82
|
-
@app = app
|
83
|
-
end
|
84
|
-
|
85
|
-
def port
|
86
|
-
application.options.port
|
87
|
-
end
|
88
|
-
|
89
|
-
def host
|
90
|
-
application.options.host
|
91
|
-
end
|
92
|
-
|
93
|
-
def env
|
94
|
-
application.options.env
|
95
|
-
end
|
96
|
-
|
97
|
-
# Deprecated: use application instead of build_application.
|
98
|
-
alias :build_application :application
|
99
|
-
|
100
|
-
def server
|
101
|
-
options.server ||= defined?(Rack::Handler::Thin) ? "thin" : "mongrel"
|
102
|
-
|
103
|
-
# Convert the server into the actual handler name
|
104
|
-
handler = options.server.capitalize
|
105
|
-
|
106
|
-
# If the convenience conversion didn't get us anything,
|
107
|
-
# fall back to what the user actually set.
|
108
|
-
handler = options.server unless Rack::Handler.const_defined?(handler)
|
109
|
-
|
110
|
-
@server ||= eval("Rack::Handler::#{handler}")
|
111
|
-
end
|
112
|
-
|
113
|
-
def run
|
114
|
-
begin
|
115
|
-
puts "== Sinatra/#{Sinatra::VERSION} has taken the stage on port #{port} for #{env} with backup by #{server.name}"
|
116
|
-
server.run(application, {:Port => port, :Host => host}) do |server|
|
117
|
-
trap(:INT) do
|
118
|
-
server.stop
|
119
|
-
puts "\n== Sinatra has ended his set (crowd applauds)"
|
120
|
-
end
|
121
|
-
end
|
122
|
-
rescue Errno::EADDRINUSE => e
|
123
|
-
puts "== Someone is already performing on port #{port}!"
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
class Event
|
128
|
-
include Rack::Utils
|
129
|
-
|
130
|
-
URI_CHAR = '[^/?:,&#\.]'.freeze unless defined?(URI_CHAR)
|
131
|
-
PARAM = /(:(#{URI_CHAR}+)|\*)/.freeze unless defined?(PARAM)
|
132
|
-
SPLAT = /(.*?)/
|
133
|
-
attr_reader :path, :block, :param_keys, :pattern, :options
|
134
|
-
|
135
|
-
def initialize(path, options = {}, &b)
|
136
|
-
@path = URI.encode(path)
|
137
|
-
@block = b
|
138
|
-
@param_keys = []
|
139
|
-
@options = options
|
140
|
-
splats = 0
|
141
|
-
regex = @path.to_s.gsub(PARAM) do |match|
|
142
|
-
if match == "*"
|
143
|
-
@param_keys << "_splat_#{splats}"
|
144
|
-
splats += 1
|
145
|
-
SPLAT.to_s
|
146
|
-
else
|
147
|
-
@param_keys << $2
|
148
|
-
"(#{URI_CHAR}+)"
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
@pattern = /^#{regex}$/
|
153
|
-
end
|
154
|
-
|
155
|
-
def invoke(request)
|
156
|
-
params = {}
|
157
|
-
if agent = options[:agent]
|
158
|
-
return unless request.user_agent =~ agent
|
159
|
-
params[:agent] = $~[1..-1]
|
160
|
-
end
|
161
|
-
if host = options[:host]
|
162
|
-
return unless host === request.host
|
163
|
-
end
|
164
|
-
return unless pattern =~ request.path_info.squeeze('/')
|
165
|
-
path_params = param_keys.zip($~.captures.map{|s| unescape(s) if s}).to_hash
|
166
|
-
params.merge!(path_params)
|
167
|
-
splats = params.select { |k, v| k =~ /^_splat_\d+$/ }.sort.map(&:last)
|
168
|
-
unless splats.empty?
|
169
|
-
params.delete_if { |k, v| k =~ /^_splat_\d+$/ }
|
170
|
-
params["splat"] = splats
|
171
|
-
end
|
172
|
-
Result.new(block, params, 200)
|
173
|
-
end
|
174
|
-
|
175
|
-
end
|
176
|
-
|
177
|
-
class Error
|
178
|
-
|
179
|
-
attr_reader :type, :block, :options
|
180
|
-
|
181
|
-
def initialize(type, options={}, &block)
|
182
|
-
@type = type
|
183
|
-
@block = block
|
184
|
-
@options = options
|
185
|
-
end
|
186
|
-
|
187
|
-
def invoke(request)
|
188
|
-
Result.new(block, options, code)
|
189
|
-
end
|
190
|
-
|
191
|
-
def code
|
192
|
-
if type.respond_to?(:code)
|
193
|
-
type.code
|
194
|
-
else
|
195
|
-
500
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
end
|
200
|
-
|
201
|
-
class Static
|
202
|
-
include Rack::Utils
|
203
|
-
|
204
|
-
def initialize(app)
|
205
|
-
@app = app
|
206
|
-
end
|
207
|
-
|
208
|
-
def invoke(request)
|
209
|
-
path = @app.options.public + unescape(request.path_info)
|
210
|
-
return unless File.file?(path)
|
211
|
-
block = Proc.new { send_file path, :disposition => nil }
|
212
|
-
Result.new(block, {}, 200)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
# Methods for sending files and streams to the browser instead of rendering.
|
217
|
-
module Streaming
|
218
|
-
DEFAULT_SEND_FILE_OPTIONS = {
|
219
|
-
:type => 'application/octet-stream'.freeze,
|
220
|
-
:disposition => 'attachment'.freeze,
|
221
|
-
:stream => true,
|
222
|
-
:buffer_size => 8192
|
223
|
-
}.freeze
|
224
|
-
|
225
|
-
class MissingFile < RuntimeError; end
|
226
|
-
|
227
|
-
class FileStreamer
|
228
|
-
attr_reader :path, :options
|
229
|
-
|
230
|
-
def initialize(path, options)
|
231
|
-
@path, @options = path, options
|
232
|
-
end
|
233
|
-
|
234
|
-
def to_result(cx, *args)
|
235
|
-
self
|
236
|
-
end
|
237
|
-
|
238
|
-
def each
|
239
|
-
size = options[:buffer_size]
|
240
|
-
File.open(path, 'rb') do |file|
|
241
|
-
while buf = file.read(size)
|
242
|
-
yield buf
|
243
|
-
end
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
protected
|
249
|
-
# Sends the file by streaming it 8192 bytes at a time. This way the
|
250
|
-
# whole file doesn't need to be read into memory at once. This makes
|
251
|
-
# it feasible to send even large files.
|
252
|
-
#
|
253
|
-
# Be careful to sanitize the path parameter if it coming from a web
|
254
|
-
# page. send_file(params[:path]) allows a malicious user to
|
255
|
-
# download any file on your server.
|
256
|
-
#
|
257
|
-
# Options:
|
258
|
-
# * <tt>:filename</tt> - suggests a filename for the browser to use.
|
259
|
-
# Defaults to File.basename(path).
|
260
|
-
# * <tt>:type</tt> - specifies an HTTP content type.
|
261
|
-
# Defaults to 'application/octet-stream'.
|
262
|
-
# * <tt>:disposition</tt> - specifies whether the file will be shown
|
263
|
-
# inline or downloaded. Valid values are 'inline' and 'attachment'
|
264
|
-
# (default). When set to nil, the Content-Disposition and
|
265
|
-
# Content-Transfer-Encoding headers are omitted entirely.
|
266
|
-
# * <tt>:stream</tt> - whether to send the file to the user agent as it
|
267
|
-
# is read (true) or to read the entire file before sending (false).
|
268
|
-
# Defaults to true.
|
269
|
-
# * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used
|
270
|
-
# to stream the file. Defaults to 8192.
|
271
|
-
# * <tt>:status</tt> - specifies the status code to send with the
|
272
|
-
# response. Defaults to '200 OK'.
|
273
|
-
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value
|
274
|
-
# (See Time#httpdate) indicating the last modified time of the file.
|
275
|
-
# If the request includes an If-Modified-Since header that matches this
|
276
|
-
# value exactly, a 304 Not Modified response is sent instead of the file.
|
277
|
-
# Defaults to the file's last modified time.
|
278
|
-
#
|
279
|
-
# The default Content-Type and Content-Disposition headers are
|
280
|
-
# set to download arbitrary binary files in as many browsers as
|
281
|
-
# possible. IE versions 4, 5, 5.5, and 6 are all known to have
|
282
|
-
# a variety of quirks (especially when downloading over SSL).
|
283
|
-
#
|
284
|
-
# Simple download:
|
285
|
-
# send_file '/path/to.zip'
|
286
|
-
#
|
287
|
-
# Show a JPEG in the browser:
|
288
|
-
# send_file '/path/to.jpeg',
|
289
|
-
# :type => 'image/jpeg',
|
290
|
-
# :disposition => 'inline'
|
291
|
-
#
|
292
|
-
# Show a 404 page in the browser:
|
293
|
-
# send_file '/path/to/404.html,
|
294
|
-
# :type => 'text/html; charset=utf-8',
|
295
|
-
# :status => 404
|
296
|
-
#
|
297
|
-
# Read about the other Content-* HTTP headers if you'd like to
|
298
|
-
# provide the user with more information (such as Content-Description).
|
299
|
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
|
300
|
-
#
|
301
|
-
# Also be aware that the document may be cached by proxies and browsers.
|
302
|
-
# The Pragma and Cache-Control headers declare how the file may be cached
|
303
|
-
# by intermediaries. They default to require clients to validate with
|
304
|
-
# the server before releasing cached responses. See
|
305
|
-
# http://www.mnot.net/cache_docs/ for an overview of web caching and
|
306
|
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
|
307
|
-
# for the Cache-Control header spec.
|
308
|
-
def send_file(path, options = {}) #:doc:
|
309
|
-
raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
|
310
|
-
|
311
|
-
options[:length] ||= File.size(path)
|
312
|
-
options[:filename] ||= File.basename(path)
|
313
|
-
options[:type] ||= Rack::File::MIME_TYPES[File.extname(options[:filename])[1..-1]] || 'text/plain'
|
314
|
-
options[:last_modified] ||= File.mtime(path).httpdate
|
315
|
-
options[:stream] = true unless options.key?(:stream)
|
316
|
-
options[:buffer_size] ||= DEFAULT_SEND_FILE_OPTIONS[:buffer_size]
|
317
|
-
send_file_headers! options
|
318
|
-
|
319
|
-
if options[:stream]
|
320
|
-
throw :halt, [options[:status] || 200, FileStreamer.new(path, options)]
|
321
|
-
else
|
322
|
-
File.open(path, 'rb') { |file| throw :halt, [options[:status] || 200, [file.read]] }
|
323
|
-
end
|
324
|
-
end
|
325
|
-
|
326
|
-
# Send binary data to the user as a file download. May set content type,
|
327
|
-
# apparent file name, and specify whether to show data inline or download
|
328
|
-
# as an attachment.
|
329
|
-
#
|
330
|
-
# Options:
|
331
|
-
# * <tt>:filename</tt> - Suggests a filename for the browser to use.
|
332
|
-
# * <tt>:type</tt> - specifies an HTTP content type.
|
333
|
-
# Defaults to 'application/octet-stream'.
|
334
|
-
# * <tt>:disposition</tt> - specifies whether the file will be shown inline
|
335
|
-
# or downloaded. Valid values are 'inline' and 'attachment' (default).
|
336
|
-
# * <tt>:status</tt> - specifies the status code to send with the response.
|
337
|
-
# Defaults to '200 OK'.
|
338
|
-
# * <tt>:last_modified</tt> - an optional RFC 2616 formatted date value (See
|
339
|
-
# Time#httpdate) indicating the last modified time of the response entity.
|
340
|
-
# If the request includes an If-Modified-Since header that matches this
|
341
|
-
# value exactly, a 304 Not Modified response is sent instead of the data.
|
342
|
-
#
|
343
|
-
# Generic data download:
|
344
|
-
# send_data buffer
|
345
|
-
#
|
346
|
-
# Download a dynamically-generated tarball:
|
347
|
-
# send_data generate_tgz('dir'), :filename => 'dir.tgz'
|
348
|
-
#
|
349
|
-
# Display an image Active Record in the browser:
|
350
|
-
# send_data image.data,
|
351
|
-
# :type => image.content_type,
|
352
|
-
# :disposition => 'inline'
|
353
|
-
#
|
354
|
-
# See +send_file+ for more information on HTTP Content-* headers and caching.
|
355
|
-
def send_data(data, options = {}) #:doc:
|
356
|
-
send_file_headers! options.merge(:length => data.size)
|
357
|
-
throw :halt, [options[:status] || 200, [data]]
|
358
|
-
end
|
359
|
-
|
360
|
-
private
|
361
|
-
|
362
|
-
def send_file_headers!(options)
|
363
|
-
options = DEFAULT_SEND_FILE_OPTIONS.merge(options)
|
364
|
-
[:length, :type, :disposition].each do |arg|
|
365
|
-
raise ArgumentError, ":#{arg} option required" unless options.key?(arg)
|
366
|
-
end
|
367
|
-
|
368
|
-
# Send a "304 Not Modified" if the last_modified option is provided and
|
369
|
-
# matches the If-Modified-Since request header value.
|
370
|
-
if last_modified = options[:last_modified]
|
371
|
-
header 'Last-Modified' => last_modified
|
372
|
-
throw :halt, [ 304, '' ] if last_modified == request.env['HTTP_IF_MODIFIED_SINCE']
|
373
|
-
end
|
374
|
-
|
375
|
-
headers(
|
376
|
-
'Content-Length' => options[:length].to_s,
|
377
|
-
'Content-Type' => options[:type].strip # fixes a problem with extra '\r' with some browsers
|
378
|
-
)
|
379
|
-
|
380
|
-
# Omit Content-Disposition and Content-Transfer-Encoding headers if
|
381
|
-
# the :disposition option set to nil.
|
382
|
-
if !options[:disposition].nil?
|
383
|
-
disposition = options[:disposition].dup || 'attachment'
|
384
|
-
disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
|
385
|
-
headers 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary'
|
386
|
-
end
|
387
|
-
|
388
|
-
# Fix a problem with IE 6.0 on opening downloaded files:
|
389
|
-
# If Cache-Control: no-cache is set (which Rails does by default),
|
390
|
-
# IE removes the file it just downloaded from its cache immediately
|
391
|
-
# after it displays the "open/save" dialog, which means that if you
|
392
|
-
# hit "open" the file isn't there anymore when the application that
|
393
|
-
# is called for handling the download is run, so let's workaround that
|
394
|
-
header('Cache-Control' => 'private') if headers['Cache-Control'] == 'no-cache'
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
|
399
|
-
# Helper methods for building various aspects of the HTTP response.
|
400
|
-
module ResponseHelpers
|
401
|
-
|
402
|
-
# Immediately halt response execution by redirecting to the resource
|
403
|
-
# specified. The +path+ argument may be an absolute URL or a path
|
404
|
-
# relative to the site root. Additional arguments are passed to the
|
405
|
-
# halt.
|
406
|
-
#
|
407
|
-
# With no integer status code, a '302 Temporary Redirect' response is
|
408
|
-
# sent. To send a permanent redirect, pass an explicit status code of
|
409
|
-
# 301:
|
410
|
-
#
|
411
|
-
# redirect '/somewhere/else', 301
|
412
|
-
#
|
413
|
-
# NOTE: No attempt is made to rewrite the path based on application
|
414
|
-
# context. The 'Location' response header is set verbatim to the value
|
415
|
-
# provided.
|
416
|
-
def redirect(path, *args)
|
417
|
-
status(302)
|
418
|
-
header 'Location' => path
|
419
|
-
throw :halt, *args
|
420
|
-
end
|
421
|
-
|
422
|
-
# Access or modify response headers. With no argument, return the
|
423
|
-
# underlying headers Hash. With a Hash argument, add or overwrite
|
424
|
-
# existing response headers with the values provided:
|
425
|
-
#
|
426
|
-
# headers 'Content-Type' => "text/html;charset=utf-8",
|
427
|
-
# 'Last-Modified' => Time.now.httpdate,
|
428
|
-
# 'X-UA-Compatible' => 'IE=edge'
|
429
|
-
#
|
430
|
-
# This method also available in singular form (#header).
|
431
|
-
def headers(header = nil)
|
432
|
-
@response.headers.merge!(header) if header
|
433
|
-
@response.headers
|
434
|
-
end
|
435
|
-
alias :header :headers
|
436
|
-
|
437
|
-
# Set the content type of the response body (HTTP 'Content-Type' header).
|
438
|
-
#
|
439
|
-
# The +type+ argument may be an internet media type (e.g., 'text/html',
|
440
|
-
# 'application/xml+atom', 'image/png') or a Symbol key into the
|
441
|
-
# Rack::File::MIME_TYPES table.
|
442
|
-
#
|
443
|
-
# Media type parameters, such as "charset", may also be specified using the
|
444
|
-
# optional hash argument:
|
445
|
-
#
|
446
|
-
# get '/foo.html' do
|
447
|
-
# content_type 'text/html', :charset => 'utf-8'
|
448
|
-
# "<h1>Hello World</h1>"
|
449
|
-
# end
|
450
|
-
#
|
451
|
-
def content_type(type, params={})
|
452
|
-
type = Rack::File::MIME_TYPES[type.to_s] if type.kind_of?(Symbol)
|
453
|
-
fail "Invalid or undefined media_type: #{type}" if type.nil?
|
454
|
-
if params.any?
|
455
|
-
params = params.collect { |kv| "%s=%s" % kv }.join(', ')
|
456
|
-
type = [ type, params ].join(";")
|
457
|
-
end
|
458
|
-
response.header['Content-Type'] = type
|
459
|
-
end
|
460
|
-
|
461
|
-
# Set the last modified time of the resource (HTTP 'Last-Modified' header)
|
462
|
-
# and halt if conditional GET matches. The +time+ argument is a Time,
|
463
|
-
# DateTime, or other object that responds to +to_time+.
|
464
|
-
#
|
465
|
-
# When the current request includes an 'If-Modified-Since' header that
|
466
|
-
# matches the time specified, execution is immediately halted with a
|
467
|
-
# '304 Not Modified' response.
|
468
|
-
#
|
469
|
-
# Calling this method before perfoming heavy processing (e.g., lengthy
|
470
|
-
# database queries, template rendering, complex logic) can dramatically
|
471
|
-
# increase overall throughput with caching clients.
|
472
|
-
def last_modified(time)
|
473
|
-
time = time.to_time if time.respond_to?(:to_time)
|
474
|
-
time = time.httpdate if time.respond_to?(:httpdate)
|
475
|
-
response.header['Last-Modified'] = time
|
476
|
-
throw :halt, 304 if time == request.env['HTTP_IF_MODIFIED_SINCE']
|
477
|
-
time
|
478
|
-
end
|
479
|
-
|
480
|
-
# Set the response entity tag (HTTP 'ETag' header) and halt if conditional
|
481
|
-
# GET matches. The +value+ argument is an identifier that uniquely
|
482
|
-
# identifies the current version of the resource. The +strength+ argument
|
483
|
-
# indicates whether the etag should be used as a :strong (default) or :weak
|
484
|
-
# cache validator.
|
485
|
-
#
|
486
|
-
# When the current request includes an 'If-None-Match' header with a
|
487
|
-
# matching etag, execution is immediately halted. If the request method is
|
488
|
-
# GET or HEAD, a '304 Not Modified' response is sent. For all other request
|
489
|
-
# methods, a '412 Precondition Failed' response is sent.
|
490
|
-
#
|
491
|
-
# Calling this method before perfoming heavy processing (e.g., lengthy
|
492
|
-
# database queries, template rendering, complex logic) can dramatically
|
493
|
-
# increase overall throughput with caching clients.
|
494
|
-
#
|
495
|
-
# ==== See Also
|
496
|
-
# {RFC2616: ETag}[http://w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19],
|
497
|
-
# ResponseHelpers#last_modified
|
498
|
-
def entity_tag(value, strength=:strong)
|
499
|
-
value =
|
500
|
-
case strength
|
501
|
-
when :strong then '"%s"' % value
|
502
|
-
when :weak then 'W/"%s"' % value
|
503
|
-
else raise TypeError, "strength must be one of :strong or :weak"
|
504
|
-
end
|
505
|
-
response.header['ETag'] = value
|
506
|
-
|
507
|
-
# Check for If-None-Match request header and halt if match is found.
|
508
|
-
etags = (request.env['HTTP_IF_NONE_MATCH'] || '').split(/\s*,\s*/)
|
509
|
-
if etags.include?(value) || etags.include?('*')
|
510
|
-
# GET/HEAD requests: send Not Modified response
|
511
|
-
throw :halt, 304 if request.get? || request.head?
|
512
|
-
# Other requests: send Precondition Failed response
|
513
|
-
throw :halt, 412
|
514
|
-
end
|
515
|
-
end
|
516
|
-
|
517
|
-
alias :etag :entity_tag
|
518
|
-
|
519
|
-
end
|
520
|
-
|
521
|
-
module RenderingHelpers
|
522
|
-
|
523
|
-
def render(renderer, template, options={})
|
524
|
-
m = method("render_#{renderer}")
|
525
|
-
result = m.call(resolve_template(renderer, template, options), options)
|
526
|
-
if layout = determine_layout(renderer, template, options)
|
527
|
-
result = m.call(resolve_template(renderer, layout, options), options) { result }
|
528
|
-
end
|
529
|
-
result
|
530
|
-
end
|
531
|
-
|
532
|
-
def determine_layout(renderer, template, options)
|
533
|
-
return if options[:layout] == false
|
534
|
-
layout_from_options = options[:layout] || :layout
|
535
|
-
resolve_template(renderer, layout_from_options, options, false)
|
536
|
-
end
|
537
|
-
|
538
|
-
private
|
539
|
-
|
540
|
-
def resolve_template(renderer, template, options, scream = true)
|
541
|
-
case template
|
542
|
-
when String
|
543
|
-
template
|
544
|
-
when Proc
|
545
|
-
template.call
|
546
|
-
when Symbol
|
547
|
-
if proc = templates[template]
|
548
|
-
resolve_template(renderer, proc, options, scream)
|
549
|
-
else
|
550
|
-
read_template_file(renderer, template, options, scream)
|
551
|
-
end
|
552
|
-
else
|
553
|
-
nil
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
def read_template_file(renderer, template, options, scream = true)
|
558
|
-
path = File.join(
|
559
|
-
options[:views_directory] || Sinatra.application.options.views,
|
560
|
-
"#{template}.#{renderer}"
|
561
|
-
)
|
562
|
-
unless File.exists?(path)
|
563
|
-
raise Errno::ENOENT.new(path) if scream
|
564
|
-
nil
|
565
|
-
else
|
566
|
-
File.read(path)
|
567
|
-
end
|
568
|
-
end
|
569
|
-
|
570
|
-
def templates
|
571
|
-
Sinatra.application.templates
|
572
|
-
end
|
573
|
-
|
574
|
-
end
|
575
|
-
|
576
|
-
module Erb
|
577
|
-
|
578
|
-
def erb(content, options={})
|
579
|
-
require 'erb'
|
580
|
-
render(:erb, content, options)
|
581
|
-
end
|
582
|
-
|
583
|
-
private
|
584
|
-
|
585
|
-
def render_erb(content, options = {})
|
586
|
-
locals_opt = options.delete(:locals) || {}
|
587
|
-
|
588
|
-
locals_code = ""
|
589
|
-
locals_hash = {}
|
590
|
-
locals_opt.each do |key, value|
|
591
|
-
locals_code << "#{key} = locals_hash[:#{key}]\n"
|
592
|
-
locals_hash[:"#{key}"] = value
|
593
|
-
end
|
594
|
-
|
595
|
-
body = ::ERB.new(content).src
|
596
|
-
eval("#{locals_code}#{body}", binding)
|
597
|
-
end
|
598
|
-
|
599
|
-
end
|
600
|
-
|
601
|
-
module Haml
|
602
|
-
|
603
|
-
def haml(content, options={})
|
604
|
-
require 'haml'
|
605
|
-
render(:haml, content, options)
|
606
|
-
end
|
607
|
-
|
608
|
-
private
|
609
|
-
|
610
|
-
def render_haml(content, options = {}, &b)
|
611
|
-
haml_options = (options[:options] || {}).
|
612
|
-
merge(Sinatra.options.haml || {})
|
613
|
-
::Haml::Engine.new(content, haml_options).
|
614
|
-
render(options[:scope] || self, options[:locals] || {}, &b)
|
615
|
-
end
|
616
|
-
|
617
|
-
end
|
618
|
-
|
619
|
-
# Generate valid CSS using Sass (part of Haml)
|
620
|
-
#
|
621
|
-
# Sass templates can be in external files with <tt>.sass</tt> extension
|
622
|
-
# or can use Sinatra's in_file_templates. In either case, the file can
|
623
|
-
# be rendered by passing the name of the template to the +sass+ method
|
624
|
-
# as a symbol.
|
625
|
-
#
|
626
|
-
# Unlike Haml, Sass does not support a layout file, so the +sass+ method
|
627
|
-
# will ignore both the default <tt>layout.sass</tt> file and any parameters
|
628
|
-
# passed in as <tt>:layout</tt> in the options hash.
|
629
|
-
#
|
630
|
-
# === Sass Template Files
|
631
|
-
#
|
632
|
-
# Sass templates can be stored in separate files with a <tt>.sass</tt>
|
633
|
-
# extension under the view path.
|
634
|
-
#
|
635
|
-
# Example:
|
636
|
-
# get '/stylesheet.css' do
|
637
|
-
# header 'Content-Type' => 'text/css; charset=utf-8'
|
638
|
-
# sass :stylesheet
|
639
|
-
# end
|
640
|
-
#
|
641
|
-
# The "views/stylesheet.sass" file might contain the following:
|
642
|
-
#
|
643
|
-
# body
|
644
|
-
# #admin
|
645
|
-
# :background-color #CCC
|
646
|
-
# #main
|
647
|
-
# :background-color #000
|
648
|
-
# #form
|
649
|
-
# :border-color #AAA
|
650
|
-
# :border-width 10px
|
651
|
-
#
|
652
|
-
# And yields the following output:
|
653
|
-
#
|
654
|
-
# body #admin {
|
655
|
-
# background-color: #CCC; }
|
656
|
-
# body #main {
|
657
|
-
# background-color: #000; }
|
658
|
-
#
|
659
|
-
# #form {
|
660
|
-
# border-color: #AAA;
|
661
|
-
# border-width: 10px; }
|
662
|
-
#
|
663
|
-
#
|
664
|
-
# NOTE: Haml must be installed or a LoadError will be raised the first time an
|
665
|
-
# attempt is made to render a Sass template.
|
666
|
-
#
|
667
|
-
# See http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html for comprehensive documentation on Sass.
|
668
|
-
module Sass
|
669
|
-
|
670
|
-
def sass(content, options = {})
|
671
|
-
require 'sass'
|
672
|
-
|
673
|
-
# Sass doesn't support a layout, so we override any possible layout here
|
674
|
-
options[:layout] = false
|
675
|
-
|
676
|
-
render(:sass, content, options)
|
677
|
-
end
|
678
|
-
|
679
|
-
private
|
680
|
-
|
681
|
-
def render_sass(content, options = {})
|
682
|
-
::Sass::Engine.new(content).render
|
683
|
-
end
|
684
|
-
|
685
|
-
end
|
686
|
-
|
687
|
-
# Generating conservative XML content using Builder templates.
|
688
|
-
#
|
689
|
-
# Builder templates can be inline by passing a block to the builder method,
|
690
|
-
# or in external files with +.builder+ extension by passing the name of the
|
691
|
-
# template to the +builder+ method as a Symbol.
|
692
|
-
#
|
693
|
-
# === Inline Rendering
|
694
|
-
#
|
695
|
-
# If the builder method is given a block, the block is called directly with
|
696
|
-
# an +XmlMarkup+ instance and the result is returned as String:
|
697
|
-
# get '/who.xml' do
|
698
|
-
# builder do |xml|
|
699
|
-
# xml.instruct!
|
700
|
-
# xml.person do
|
701
|
-
# xml.name "Francis Albert Sinatra",
|
702
|
-
# :aka => "Frank Sinatra"
|
703
|
-
# xml.email 'frank@capitolrecords.com'
|
704
|
-
# end
|
705
|
-
# end
|
706
|
-
# end
|
707
|
-
#
|
708
|
-
# Yields the following XML:
|
709
|
-
# <?xml version='1.0' encoding='UTF-8'?>
|
710
|
-
# <person>
|
711
|
-
# <name aka='Frank Sinatra'>Francis Albert Sinatra</name>
|
712
|
-
# <email>Frank Sinatra</email>
|
713
|
-
# </person>
|
714
|
-
#
|
715
|
-
# === Builder Template Files
|
716
|
-
#
|
717
|
-
# Builder templates can be stored in separate files with a +.builder+
|
718
|
-
# extension under the view path. An +XmlMarkup+ object named +xml+ is
|
719
|
-
# automatically made available to template.
|
720
|
-
#
|
721
|
-
# Example:
|
722
|
-
# get '/bio.xml' do
|
723
|
-
# builder :bio
|
724
|
-
# end
|
725
|
-
#
|
726
|
-
# The "views/bio.builder" file might contain the following:
|
727
|
-
# xml.instruct! :xml, :version => '1.1'
|
728
|
-
# xml.person do
|
729
|
-
# xml.name "Francis Albert Sinatra"
|
730
|
-
# xml.aka "Frank Sinatra"
|
731
|
-
# xml.aka "Ol' Blue Eyes"
|
732
|
-
# xml.aka "The Chairman of the Board"
|
733
|
-
# xml.born 'date' => '1915-12-12' do
|
734
|
-
# xml.text! "Hoboken, New Jersey, U.S.A."
|
735
|
-
# end
|
736
|
-
# xml.died 'age' => 82
|
737
|
-
# end
|
738
|
-
#
|
739
|
-
# And yields the following output:
|
740
|
-
# <?xml version='1.1' encoding='UTF-8'?>
|
741
|
-
# <person>
|
742
|
-
# <name>Francis Albert Sinatra</name>
|
743
|
-
# <aka>Frank Sinatra</aka>
|
744
|
-
# <aka>Ol' Blue Eyes</aka>
|
745
|
-
# <aka>The Chairman of the Board</aka>
|
746
|
-
# <born date='1915-12-12'>Hoboken, New Jersey, U.S.A.</born>
|
747
|
-
# <died age='82' />
|
748
|
-
# </person>
|
749
|
-
#
|
750
|
-
# NOTE: Builder must be installed or a LoadError will be raised the first
|
751
|
-
# time an attempt is made to render a builder template.
|
752
|
-
#
|
753
|
-
# See http://builder.rubyforge.org/ for comprehensive documentation on
|
754
|
-
# Builder.
|
755
|
-
module Builder
|
756
|
-
|
757
|
-
def builder(content=nil, options={}, &block)
|
758
|
-
options, content = content, nil if content.is_a?(Hash)
|
759
|
-
content = Proc.new { block } if content.nil?
|
760
|
-
render(:builder, content, options)
|
761
|
-
end
|
762
|
-
|
763
|
-
private
|
764
|
-
|
765
|
-
def render_builder(content, options = {}, &b)
|
766
|
-
require 'builder'
|
767
|
-
xml = ::Builder::XmlMarkup.new(:indent => 2)
|
768
|
-
case content
|
769
|
-
when String
|
770
|
-
eval(content, binding, '<BUILDER>', 1)
|
771
|
-
when Proc
|
772
|
-
content.call(xml)
|
773
|
-
end
|
774
|
-
xml.target!
|
775
|
-
end
|
776
|
-
|
777
|
-
end
|
778
|
-
|
779
|
-
class EventContext
|
780
|
-
include Rack::Utils
|
781
|
-
include ResponseHelpers
|
782
|
-
include Streaming
|
783
|
-
include RenderingHelpers
|
784
|
-
include Erb
|
785
|
-
include Haml
|
786
|
-
include Builder
|
787
|
-
include Sass
|
788
|
-
|
789
|
-
attr_accessor :request, :response
|
790
|
-
|
791
|
-
attr_accessor :route_params
|
792
|
-
|
793
|
-
def initialize(request, response, route_params)
|
794
|
-
@params = nil
|
795
|
-
@data = nil
|
796
|
-
@request = request
|
797
|
-
@response = response
|
798
|
-
@route_params = route_params
|
799
|
-
@response.body = nil
|
800
|
-
end
|
801
|
-
|
802
|
-
def status(value=nil)
|
803
|
-
response.status = value if value
|
804
|
-
response.status
|
805
|
-
end
|
806
|
-
|
807
|
-
def body(value=nil)
|
808
|
-
response.body = value if value
|
809
|
-
response.body
|
810
|
-
end
|
811
|
-
|
812
|
-
def params
|
813
|
-
@params ||=
|
814
|
-
begin
|
815
|
-
hash = Hash.new {|h,k| h[k.to_s] if Symbol === k}
|
816
|
-
hash.merge! @request.params
|
817
|
-
hash.merge! @route_params
|
818
|
-
hash
|
819
|
-
end
|
820
|
-
end
|
821
|
-
|
822
|
-
def data
|
823
|
-
@data ||= params.keys.first
|
824
|
-
end
|
825
|
-
|
826
|
-
def stop(*args)
|
827
|
-
throw :halt, args
|
828
|
-
end
|
829
|
-
|
830
|
-
def complete(returned)
|
831
|
-
@response.body || returned
|
832
|
-
end
|
833
|
-
|
834
|
-
def session
|
835
|
-
request.env['rack.session'] ||= {}
|
836
|
-
end
|
837
|
-
|
838
|
-
def reset!
|
839
|
-
@params = nil
|
840
|
-
@data = nil
|
841
|
-
end
|
842
|
-
|
843
|
-
private
|
844
|
-
|
845
|
-
def method_missing(name, *args, &b)
|
846
|
-
if @response.respond_to?(name)
|
847
|
-
@response.send(name, *args, &b)
|
848
|
-
else
|
849
|
-
super
|
850
|
-
end
|
851
|
-
end
|
852
|
-
|
853
|
-
end
|
854
|
-
|
855
|
-
|
856
|
-
# The Application class represents the top-level working area of a
|
857
|
-
# Sinatra app. It provides the DSL for defining various aspects of the
|
858
|
-
# application and implements a Rack compatible interface for dispatching
|
859
|
-
# requests.
|
860
|
-
#
|
861
|
-
# Many of the instance methods defined in this class (#get, #post,
|
862
|
-
# #put, #delete, #layout, #before, #error, #not_found, etc.) are
|
863
|
-
# available at top-level scope. When invoked from top-level, the
|
864
|
-
# messages are forwarded to the "default application" (accessible
|
865
|
-
# at Sinatra::application).
|
866
|
-
class Application
|
867
|
-
|
868
|
-
# Hash of event handlers with request method keys and
|
869
|
-
# arrays of potential handlers as values.
|
870
|
-
attr_reader :events
|
871
|
-
|
872
|
-
# Hash of error handlers with error status codes as keys and
|
873
|
-
# handlers as values.
|
874
|
-
attr_reader :errors
|
875
|
-
|
876
|
-
# Hash of template name mappings.
|
877
|
-
attr_reader :templates
|
878
|
-
|
879
|
-
# Hash of filters with event name keys (:before) and arrays of
|
880
|
-
# handlers as values.
|
881
|
-
attr_reader :filters
|
882
|
-
|
883
|
-
# Array of objects to clear during reload. The objects in this array
|
884
|
-
# must respond to :clear.
|
885
|
-
attr_reader :clearables
|
886
|
-
|
887
|
-
# Object including open attribute methods for modifying Application
|
888
|
-
# configuration.
|
889
|
-
attr_reader :options
|
890
|
-
|
891
|
-
# List of methods available from top-level scope. When invoked from
|
892
|
-
# top-level the method is forwarded to the default application
|
893
|
-
# (Sinatra::application).
|
894
|
-
FORWARD_METHODS = %w[
|
895
|
-
get put post delete head template layout before error not_found
|
896
|
-
configures configure set set_options set_option enable disable use
|
897
|
-
development? test? production?
|
898
|
-
]
|
899
|
-
|
900
|
-
# Create a new Application with a default configuration taken
|
901
|
-
# from the default_options Hash.
|
902
|
-
#
|
903
|
-
# NOTE: A default Application is automatically created the first
|
904
|
-
# time any of Sinatra's DSL related methods is invoked so there
|
905
|
-
# is typically no need to create an instance explicitly. See
|
906
|
-
# Sinatra::application for more information.
|
907
|
-
def initialize
|
908
|
-
@reloading = false
|
909
|
-
@clearables = [
|
910
|
-
@events = Hash.new { |hash, key| hash[key] = [] },
|
911
|
-
@errors = Hash.new,
|
912
|
-
@filters = Hash.new { |hash, key| hash[key] = [] },
|
913
|
-
@templates = Hash.new,
|
914
|
-
@middleware = []
|
915
|
-
]
|
916
|
-
@options = OpenStruct.new(self.class.default_options)
|
917
|
-
load_default_configuration!
|
918
|
-
end
|
919
|
-
|
920
|
-
# Hash of default application configuration options. When a new
|
921
|
-
# Application is created, the #options object takes its initial values
|
922
|
-
# from here.
|
923
|
-
#
|
924
|
-
# Changes to the default_options Hash effect only Application objects
|
925
|
-
# created after the changes are made. For this reason, modifications to
|
926
|
-
# the default_options Hash typically occur at the very beginning of a
|
927
|
-
# file, before any DSL related functions are invoked.
|
928
|
-
def self.default_options
|
929
|
-
return @default_options unless @default_options.nil?
|
930
|
-
app_file = locate_app_file
|
931
|
-
root = File.expand_path(File.dirname(app_file))
|
932
|
-
@default_options = {
|
933
|
-
:run => true,
|
934
|
-
:port => 4567,
|
935
|
-
:host => '0.0.0.0',
|
936
|
-
:env => (ENV['RACK_ENV'] || :development).to_sym,
|
937
|
-
:root => root,
|
938
|
-
:views => root + '/views',
|
939
|
-
:public => root + '/public',
|
940
|
-
:sessions => false,
|
941
|
-
:logging => true,
|
942
|
-
:app_file => app_file,
|
943
|
-
:raise_errors => false
|
944
|
-
}
|
945
|
-
load_default_options_from_command_line!
|
946
|
-
@default_options
|
947
|
-
end
|
948
|
-
|
949
|
-
# Search ARGV for command line arguments and update the
|
950
|
-
# Sinatra::default_options Hash accordingly. This method is
|
951
|
-
# invoked the first time the default_options Hash is accessed.
|
952
|
-
# NOTE: Ignores --name so unit/spec tests can run individually
|
953
|
-
def self.load_default_options_from_command_line! #:nodoc:
|
954
|
-
# fixes issue with: gem install --test sinatra
|
955
|
-
return if ARGV.empty? || File.basename($0) =~ /gem/
|
956
|
-
require 'optparse'
|
957
|
-
OptionParser.new do |op|
|
958
|
-
op.on('-p port') { |port| default_options[:port] = port }
|
959
|
-
op.on('-e env') { |env| default_options[:env] = env.to_sym }
|
960
|
-
op.on('-x') { default_options[:mutex] = true }
|
961
|
-
op.on('-s server') { |server| default_options[:server] = server }
|
962
|
-
end.parse!(ARGV.dup.select { |o| o !~ /--name/ })
|
963
|
-
end
|
964
|
-
|
965
|
-
# Use heuristics to locate the application file.
|
966
|
-
def self.locate_app_file
|
967
|
-
caller[1..-1].reverse.each do |path|
|
968
|
-
path = path.split(':', 2)[0]
|
969
|
-
next if path =~ /sinatra\.rb$/ || path == '(__DSL__)'
|
970
|
-
return path
|
971
|
-
end
|
972
|
-
$0
|
973
|
-
end
|
974
|
-
|
975
|
-
# Determine whether the application is in the process of being
|
976
|
-
# reloaded.
|
977
|
-
def reloading?
|
978
|
-
@reloading == true
|
979
|
-
end
|
980
|
-
|
981
|
-
# Yield to the block for configuration if the current environment
|
982
|
-
# matches any included in the +envs+ list. Always yield to the block
|
983
|
-
# when no environment is specified.
|
984
|
-
#
|
985
|
-
# NOTE: configuration blocks are not executed during reloads.
|
986
|
-
def configures(*envs, &b)
|
987
|
-
return if reloading?
|
988
|
-
yield self if envs.empty? || envs.include?(options.env)
|
989
|
-
end
|
990
|
-
|
991
|
-
alias :configure :configures
|
992
|
-
|
993
|
-
# When both +option+ and +value+ arguments are provided, set the option
|
994
|
-
# specified. With a single Hash argument, set all options specified in
|
995
|
-
# Hash. Options are available via the Application#options object.
|
996
|
-
#
|
997
|
-
# Setting individual options:
|
998
|
-
# set :port, 80
|
999
|
-
# set :env, :production
|
1000
|
-
# set :views, '/path/to/views'
|
1001
|
-
#
|
1002
|
-
# Setting multiple options:
|
1003
|
-
# set :port => 80,
|
1004
|
-
# :env => :production,
|
1005
|
-
# :views => '/path/to/views'
|
1006
|
-
#
|
1007
|
-
def set(option, value=self)
|
1008
|
-
if value == self && option.kind_of?(Hash)
|
1009
|
-
option.each { |key,val| set(key, val) }
|
1010
|
-
else
|
1011
|
-
options.send("#{option}=", value)
|
1012
|
-
end
|
1013
|
-
end
|
1014
|
-
|
1015
|
-
alias :set_option :set
|
1016
|
-
alias :set_options :set
|
1017
|
-
|
1018
|
-
# Enable the options specified by setting their values to true. For
|
1019
|
-
# example, to enable sessions and logging:
|
1020
|
-
# enable :sessions, :logging
|
1021
|
-
def enable(*opts)
|
1022
|
-
opts.each { |key| set(key, true) }
|
1023
|
-
end
|
1024
|
-
|
1025
|
-
# Disable the options specified by setting their values to false. For
|
1026
|
-
# example, to disable logging and automatic run:
|
1027
|
-
# disable :logging, :run
|
1028
|
-
def disable(*opts)
|
1029
|
-
opts.each { |key| set(key, false) }
|
1030
|
-
end
|
1031
|
-
|
1032
|
-
# Define an event handler for the given request method and path
|
1033
|
-
# spec. The block is executed when a request matches the method
|
1034
|
-
# and spec.
|
1035
|
-
#
|
1036
|
-
# NOTE: The #get, #post, #put, and #delete helper methods should
|
1037
|
-
# be used to define events when possible.
|
1038
|
-
def event(method, path, options = {}, &b)
|
1039
|
-
events[method].push(Event.new(path, options, &b)).last
|
1040
|
-
end
|
1041
|
-
|
1042
|
-
# Define an event handler for GET requests.
|
1043
|
-
def get(path, options={}, &b)
|
1044
|
-
event(:get, path, options, &b)
|
1045
|
-
end
|
1046
|
-
|
1047
|
-
# Define an event handler for POST requests.
|
1048
|
-
def post(path, options={}, &b)
|
1049
|
-
event(:post, path, options, &b)
|
1050
|
-
end
|
1051
|
-
|
1052
|
-
# Define an event handler for HEAD requests.
|
1053
|
-
def head(path, options={}, &b)
|
1054
|
-
event(:head, path, options, &b)
|
1055
|
-
end
|
1056
|
-
|
1057
|
-
# Define an event handler for PUT requests.
|
1058
|
-
#
|
1059
|
-
# NOTE: PUT events are triggered when the HTTP request method is
|
1060
|
-
# PUT and also when the request method is POST and the body includes a
|
1061
|
-
# "_method" parameter set to "PUT".
|
1062
|
-
def put(path, options={}, &b)
|
1063
|
-
event(:put, path, options, &b)
|
1064
|
-
end
|
1065
|
-
|
1066
|
-
# Define an event handler for DELETE requests.
|
1067
|
-
#
|
1068
|
-
# NOTE: DELETE events are triggered when the HTTP request method is
|
1069
|
-
# DELETE and also when the request method is POST and the body includes a
|
1070
|
-
# "_method" parameter set to "DELETE".
|
1071
|
-
def delete(path, options={}, &b)
|
1072
|
-
event(:delete, path, options, &b)
|
1073
|
-
end
|
1074
|
-
|
1075
|
-
# Visits and invokes each handler registered for the +request_method+ in
|
1076
|
-
# definition order until a Result response is produced. If no handler
|
1077
|
-
# responds with a Result, the NotFound error handler is invoked.
|
1078
|
-
#
|
1079
|
-
# When the request_method is "HEAD" and no valid Result is produced by
|
1080
|
-
# the set of handlers registered for HEAD requests, an attempt is made to
|
1081
|
-
# invoke the GET handlers to generate the response before resorting to the
|
1082
|
-
# default error handler.
|
1083
|
-
def lookup(request)
|
1084
|
-
method = request.request_method.downcase.to_sym
|
1085
|
-
events[method].eject(&[:invoke, request]) ||
|
1086
|
-
(events[:get].eject(&[:invoke, request]) if method == :head) ||
|
1087
|
-
errors[NotFound].invoke(request)
|
1088
|
-
end
|
1089
|
-
|
1090
|
-
# Define a named template. The template may be referenced from
|
1091
|
-
# event handlers by passing the name as a Symbol to rendering
|
1092
|
-
# methods. The block is executed each time the template is rendered
|
1093
|
-
# and the resulting object is passed to the template handler.
|
1094
|
-
#
|
1095
|
-
# The following example defines a HAML template named hello and
|
1096
|
-
# invokes it from an event handler:
|
1097
|
-
#
|
1098
|
-
# template :hello do
|
1099
|
-
# "h1 Hello World!"
|
1100
|
-
# end
|
1101
|
-
#
|
1102
|
-
# get '/' do
|
1103
|
-
# haml :hello
|
1104
|
-
# end
|
1105
|
-
#
|
1106
|
-
def template(name, &b)
|
1107
|
-
templates[name] = b
|
1108
|
-
end
|
1109
|
-
|
1110
|
-
# Define a layout template.
|
1111
|
-
def layout(name=:layout, &b)
|
1112
|
-
template(name, &b)
|
1113
|
-
end
|
1114
|
-
|
1115
|
-
# Define a custom error handler for the exception class +type+. The block
|
1116
|
-
# is invoked when the specified exception type is raised from an error
|
1117
|
-
# handler and can manipulate the response as needed:
|
1118
|
-
#
|
1119
|
-
# error MyCustomError do
|
1120
|
-
# status 500
|
1121
|
-
# 'So what happened was...' + request.env['sinatra.error'].message
|
1122
|
-
# end
|
1123
|
-
#
|
1124
|
-
# The Sinatra::ServerError handler is used by default when an exception
|
1125
|
-
# occurs and no matching error handler is found.
|
1126
|
-
def error(type=ServerError, options = {}, &b)
|
1127
|
-
errors[type] = Error.new(type, options, &b)
|
1128
|
-
end
|
1129
|
-
|
1130
|
-
# Define a custom error handler for '404 Not Found' responses. This is a
|
1131
|
-
# shorthand for:
|
1132
|
-
# error NotFound do
|
1133
|
-
# ..
|
1134
|
-
# end
|
1135
|
-
def not_found(options={}, &b)
|
1136
|
-
error NotFound, options, &b
|
1137
|
-
end
|
1138
|
-
|
1139
|
-
# Define a request filter. When <tt>type</tt> is <tt>:before</tt>, execute the
|
1140
|
-
# block in the context of each request before matching event handlers.
|
1141
|
-
def filter(type, &b)
|
1142
|
-
filters[type] << b
|
1143
|
-
end
|
1144
|
-
|
1145
|
-
# Invoke the block in the context of each request before invoking
|
1146
|
-
# matching event handlers.
|
1147
|
-
def before(&b)
|
1148
|
-
filter :before, &b
|
1149
|
-
end
|
1150
|
-
|
1151
|
-
# True when environment is :development.
|
1152
|
-
def development? ; options.env == :development ; end
|
1153
|
-
|
1154
|
-
# True when environment is :test.
|
1155
|
-
def test? ; options.env == :test ; end
|
1156
|
-
|
1157
|
-
# True when environment is :production.
|
1158
|
-
def production? ; options.env == :production ; end
|
1159
|
-
|
1160
|
-
# Clear all events, templates, filters, and error handlers
|
1161
|
-
# and then reload the application source file. This occurs
|
1162
|
-
# automatically before each request is processed in development.
|
1163
|
-
def reload!
|
1164
|
-
clearables.each(&:clear)
|
1165
|
-
load_default_configuration!
|
1166
|
-
load_development_configuration! if development?
|
1167
|
-
@pipeline = nil
|
1168
|
-
@reloading = true
|
1169
|
-
Kernel.load options.app_file
|
1170
|
-
@reloading = false
|
1171
|
-
end
|
1172
|
-
|
1173
|
-
# Determine whether the application is in the process of being
|
1174
|
-
# reloaded.
|
1175
|
-
def reloading?
|
1176
|
-
@reloading == true
|
1177
|
-
end
|
1178
|
-
|
1179
|
-
# Mutex instance used for thread synchronization.
|
1180
|
-
def mutex
|
1181
|
-
@@mutex ||= Mutex.new
|
1182
|
-
end
|
1183
|
-
|
1184
|
-
# Yield to the block with thread synchronization
|
1185
|
-
def run_safely
|
1186
|
-
if development? || options.mutex
|
1187
|
-
mutex.synchronize { yield }
|
1188
|
-
else
|
1189
|
-
yield
|
1190
|
-
end
|
1191
|
-
end
|
1192
|
-
|
1193
|
-
# Add a piece of Rack middleware to the pipeline leading to the
|
1194
|
-
# application.
|
1195
|
-
def use(klass, *args, &block)
|
1196
|
-
fail "#{klass} must respond to 'new'" unless klass.respond_to?(:new)
|
1197
|
-
@pipeline = nil
|
1198
|
-
@middleware.push([ klass, args, block ]).last
|
1199
|
-
end
|
1200
|
-
|
1201
|
-
private
|
1202
|
-
|
1203
|
-
# Rack middleware derived from current state of application options.
|
1204
|
-
# These components are plumbed in at the very beginning of the
|
1205
|
-
# pipeline.
|
1206
|
-
def optional_middleware
|
1207
|
-
[
|
1208
|
-
([ Rack::CommonLogger, [], nil ] if options.logging),
|
1209
|
-
([ Rack::Session::Cookie, [], nil ] if options.sessions)
|
1210
|
-
].compact
|
1211
|
-
end
|
1212
|
-
|
1213
|
-
# Rack middleware explicitly added to the application with #use. These
|
1214
|
-
# components are plumbed into the pipeline downstream from
|
1215
|
-
# #optional_middle.
|
1216
|
-
def explicit_middleware
|
1217
|
-
@middleware
|
1218
|
-
end
|
1219
|
-
|
1220
|
-
# All Rack middleware used to construct the pipeline.
|
1221
|
-
def middleware
|
1222
|
-
optional_middleware + explicit_middleware
|
1223
|
-
end
|
1224
|
-
|
1225
|
-
public
|
1226
|
-
|
1227
|
-
# An assembled pipeline of Rack middleware that leads eventually to
|
1228
|
-
# the Application#invoke method. The pipeline is built upon first
|
1229
|
-
# access. Defining new middleware with Application#use or manipulating
|
1230
|
-
# application options may cause the pipeline to be rebuilt.
|
1231
|
-
def pipeline
|
1232
|
-
@pipeline ||=
|
1233
|
-
middleware.inject(method(:dispatch)) do |app,(klass,args,block)|
|
1234
|
-
klass.new(app, *args, &block)
|
1235
|
-
end
|
1236
|
-
end
|
1237
|
-
|
1238
|
-
# Rack compatible request invocation interface.
|
1239
|
-
def call(env)
|
1240
|
-
run_safely do
|
1241
|
-
reload! if development? && (options.reload != false)
|
1242
|
-
pipeline.call(env)
|
1243
|
-
end
|
1244
|
-
end
|
1245
|
-
|
1246
|
-
# Request invocation handler - called at the end of the Rack pipeline
|
1247
|
-
# for each request.
|
1248
|
-
#
|
1249
|
-
# 1. Create Rack::Request, Rack::Response helper objects.
|
1250
|
-
# 2. Lookup event handler based on request method and path.
|
1251
|
-
# 3. Create new EventContext to house event handler evaluation.
|
1252
|
-
# 4. Invoke each #before filter in context of EventContext object.
|
1253
|
-
# 5. Invoke event handler in context of EventContext object.
|
1254
|
-
# 6. Return response to Rack.
|
1255
|
-
#
|
1256
|
-
# See the Rack specification for detailed information on the
|
1257
|
-
# +env+ argument and return value.
|
1258
|
-
def dispatch(env)
|
1259
|
-
request = Rack::Request.new(env)
|
1260
|
-
context = EventContext.new(request, Rack::Response.new([], 200), {})
|
1261
|
-
begin
|
1262
|
-
returned =
|
1263
|
-
catch(:halt) do
|
1264
|
-
filters[:before].each { |f| context.instance_eval(&f) }
|
1265
|
-
result = lookup(context.request)
|
1266
|
-
context.route_params = result.params
|
1267
|
-
context.response.status = result.status
|
1268
|
-
context.reset!
|
1269
|
-
[:complete, context.instance_eval(&result.block)]
|
1270
|
-
end
|
1271
|
-
body = returned.to_result(context)
|
1272
|
-
rescue => e
|
1273
|
-
msg = "#{e.class.name} - #{e.message}:"
|
1274
|
-
msg << "\n #{e.backtrace.join("\n ")}"
|
1275
|
-
request.env['rack.errors'] << msg
|
1276
|
-
|
1277
|
-
request.env['sinatra.error'] = e
|
1278
|
-
status = e.class.code rescue 500
|
1279
|
-
context.status(status)
|
1280
|
-
raise if options.raise_errors && e.class != NotFound
|
1281
|
-
result = (errors[e.class] || errors[ServerError]).invoke(request)
|
1282
|
-
returned =
|
1283
|
-
catch(:halt) do
|
1284
|
-
[:complete, context.instance_eval(&result.block)]
|
1285
|
-
end
|
1286
|
-
body = returned.to_result(context)
|
1287
|
-
end
|
1288
|
-
body = '' unless body.respond_to?(:each)
|
1289
|
-
body = '' if request.env["REQUEST_METHOD"].upcase == 'HEAD'
|
1290
|
-
context.body = body.kind_of?(String) ? [*body] : body
|
1291
|
-
context.finish
|
1292
|
-
end
|
1293
|
-
|
1294
|
-
private
|
1295
|
-
|
1296
|
-
# Called immediately after the application is initialized or reloaded to
|
1297
|
-
# register default events, templates, and error handlers.
|
1298
|
-
def load_default_configuration!
|
1299
|
-
events[:get] << Static.new(self)
|
1300
|
-
configure do
|
1301
|
-
error do
|
1302
|
-
'<h1>Internal Server Error</h1>'
|
1303
|
-
end
|
1304
|
-
not_found { '<h1>Not Found</h1>'}
|
1305
|
-
end
|
1306
|
-
end
|
1307
|
-
|
1308
|
-
# Called before reloading to perform development specific configuration.
|
1309
|
-
def load_development_configuration!
|
1310
|
-
get '/sinatra_custom_images/:image.png' do
|
1311
|
-
content_type :png
|
1312
|
-
File.read(File.dirname(__FILE__) + "/../images/#{params[:image]}.png")
|
1313
|
-
end
|
1314
|
-
|
1315
|
-
not_found do
|
1316
|
-
(<<-HTML).gsub(/^ {8}/, '')
|
1317
|
-
<!DOCTYPE html>
|
1318
|
-
<html>
|
1319
|
-
<head>
|
1320
|
-
<style type="text/css">
|
1321
|
-
body {text-align:center;color:#888;font-family:arial;font-size:22px;margin:20px;}
|
1322
|
-
#content {margin:0 auto;width:500px;text-align:left}
|
1323
|
-
</style>
|
1324
|
-
</head>
|
1325
|
-
<body>
|
1326
|
-
<h2>Sinatra doesn't know this diddy.</h2>
|
1327
|
-
<img src='/sinatra_custom_images/404.png'>
|
1328
|
-
<div id="content">
|
1329
|
-
Try this:
|
1330
|
-
<pre>#{request.request_method.downcase} "#{request.path_info}" do\n .. do something ..\nend<pre>
|
1331
|
-
</div>
|
1332
|
-
</body>
|
1333
|
-
</html>
|
1334
|
-
HTML
|
1335
|
-
end
|
1336
|
-
|
1337
|
-
error do
|
1338
|
-
@error = request.env['sinatra.error']
|
1339
|
-
(<<-HTML).gsub(/^ {8}/, '')
|
1340
|
-
<!DOCTYPE html>
|
1341
|
-
<html>
|
1342
|
-
<head>
|
1343
|
-
<style type="text/css" media="screen">
|
1344
|
-
body {font-family:verdana;color:#333}
|
1345
|
-
#content {width:700px;margin-left:20px}
|
1346
|
-
#content h1 {width:99%;color:#1D6B8D;font-weight:bold}
|
1347
|
-
#stacktrace {margin-top:-20px}
|
1348
|
-
#stacktrace pre {font-size:12px;border-left:2px solid #ddd;padding-left:10px}
|
1349
|
-
#stacktrace img {margin-top:10px}
|
1350
|
-
</style>
|
1351
|
-
</head>
|
1352
|
-
<body>
|
1353
|
-
<div id="content">
|
1354
|
-
<img src="/sinatra_custom_images/500.png">
|
1355
|
-
<div class="info">
|
1356
|
-
Params: <pre>#{params.inspect}</pre>
|
1357
|
-
</div>
|
1358
|
-
<div id="stacktrace">
|
1359
|
-
<h1>#{escape_html(@error.class.name + ' - ' + @error.message.to_s)}</h1>
|
1360
|
-
<pre><code>#{escape_html(@error.backtrace.join("\n"))}</code></pre>
|
1361
|
-
</div>
|
1362
|
-
</div>
|
1363
|
-
</body>
|
1364
|
-
</html>
|
1365
|
-
HTML
|
1366
|
-
end
|
1367
|
-
end
|
1368
|
-
|
1369
|
-
end
|
1370
|
-
|
1371
|
-
end
|
1372
|
-
|
1373
|
-
# Delegate DSLish methods to the currently active Sinatra::Application
|
1374
|
-
# instance.
|
1375
|
-
Sinatra::Application::FORWARD_METHODS.each do |method|
|
1376
|
-
eval(<<-EOS, binding, '(__DSL__)', 1)
|
1377
|
-
def #{method}(*args, &b)
|
1378
|
-
Sinatra.application.#{method}(*args, &b)
|
1379
|
-
end
|
1380
|
-
EOS
|
1381
|
-
end
|
1382
|
-
|
1383
|
-
def helpers(&b)
|
1384
|
-
Sinatra::EventContext.class_eval(&b)
|
1385
|
-
end
|
1386
|
-
|
1387
|
-
def use_in_file_templates!
|
1388
|
-
file = caller.first.sub(/:\d+$/, '')
|
1389
|
-
data = IO.read(file).split('__END__').last
|
1390
|
-
data.gsub! /\r\n/, "\n"
|
1391
|
-
current_template = nil
|
1392
|
-
data.each_line do |line|
|
1393
|
-
if line =~ /^@@\s?(.*)/
|
1394
|
-
current_template = $1.to_sym
|
1395
|
-
Sinatra.application.templates[current_template] = ''
|
1396
|
-
elsif current_template
|
1397
|
-
Sinatra.application.templates[current_template] << line
|
1398
|
-
end
|
1399
|
-
end
|
1400
|
-
end
|
1401
|
-
|
1402
|
-
def mime(ext, type)
|
1403
|
-
Rack::File::MIME_TYPES[ext.to_s] = type
|
1404
|
-
end
|
1405
|
-
|
1406
|
-
### Misc Core Extensions
|
1407
|
-
|
1408
|
-
module Kernel
|
1409
|
-
def silence_warnings
|
1410
|
-
old_verbose, $VERBOSE = $VERBOSE, nil
|
1411
|
-
yield
|
1412
|
-
ensure
|
1413
|
-
$VERBOSE = old_verbose
|
1414
|
-
end
|
1415
|
-
end
|
1416
|
-
|
1417
|
-
class Symbol
|
1418
|
-
def to_proc
|
1419
|
-
Proc.new { |*args| args.shift.__send__(self, *args) }
|
1420
|
-
end
|
1421
|
-
end
|
1422
|
-
|
1423
|
-
class Array
|
1424
|
-
def to_hash
|
1425
|
-
self.inject({}) { |h, (k, v)| h[k] = v; h }
|
1426
|
-
end
|
1427
|
-
def to_proc
|
1428
|
-
Proc.new { |*args| args.shift.__send__(self[0], *(args + self[1..-1])) }
|
1429
|
-
end
|
1430
|
-
end
|
1431
|
-
|
1432
|
-
module Enumerable
|
1433
|
-
def eject(&block)
|
1434
|
-
find { |e| result = block[e] and break result }
|
1435
|
-
end
|
1436
|
-
end
|
1437
|
-
|
1438
|
-
### Core Extension results for throw :halt
|
1439
|
-
|
1440
|
-
class Proc
|
1441
|
-
def to_result(cx, *args)
|
1442
|
-
cx.instance_eval(&self)
|
1443
|
-
args.shift.to_result(cx, *args)
|
1444
|
-
end
|
1445
|
-
end
|
1446
|
-
|
1447
|
-
class String
|
1448
|
-
def to_result(cx, *args)
|
1449
|
-
args.shift.to_result(cx, *args)
|
1450
|
-
self
|
1451
|
-
end
|
1452
|
-
end
|
1453
|
-
|
1454
|
-
class Array
|
1455
|
-
def to_result(cx, *args)
|
1456
|
-
self.shift.to_result(cx, *self)
|
1457
|
-
end
|
1458
|
-
end
|
1459
|
-
|
1460
|
-
class Symbol
|
1461
|
-
def to_result(cx, *args)
|
1462
|
-
cx.send(self, *args)
|
1463
|
-
end
|
1464
|
-
end
|
1465
|
-
|
1466
|
-
class Fixnum
|
1467
|
-
def to_result(cx, *args)
|
1468
|
-
cx.status self
|
1469
|
-
args.shift.to_result(cx, *args)
|
1470
|
-
end
|
1471
|
-
end
|
1472
|
-
|
1473
|
-
class NilClass
|
1474
|
-
def to_result(cx, *args)
|
1475
|
-
''
|
1476
|
-
end
|
1477
|
-
end
|
1478
|
-
|
1479
|
-
at_exit do
|
1480
|
-
raise $! if $!
|
1481
|
-
Sinatra.run if Sinatra.application.options.run
|
1482
|
-
end
|
1483
|
-
|
1484
|
-
mime :xml, 'application/xml'
|
1485
|
-
mime :js, 'application/javascript'
|
1486
|
-
mime :png, 'image/png'
|
8
|
+
use_in_file_templates!
|