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