module Sinatra::Helpers
def attachment(filename=nil)
Set the Content-Disposition to "attachment" with the specified filename,
def attachment(filename=nil) response['Content-Disposition'] = 'attachment' if filename params = '; filename="%s"' % File.basename(filename) response['Content-Disposition'] << params end end
def back ; request.referer ; end
def back ; request.referer ; end
def body(value=nil, &block)
Set or retrieve the response body. When a block is given,
def body(value=nil, &block) if block_given? def block.each; yield(call) end response.body = block elsif value response.body = value else response.body end end
def cache_control(*values)
See RFC 2616 / 14.9 for more on standard cache control directives:
=> Cache-Control: public, must-revalidate, max-age=60
cache_control :public, :must_revalidate, :max_age => 60
a Hash of value directives (:max_age, :min_stale, :s_max_age).
:no_store, :must_revalidate, :proxy_revalidate) may be passed along with
Any number of non-value directives (:public, :private, :no_cache,
Specify response freshness policy for HTTP caches (Cache-Control header).
def cache_control(*values) if values.last.kind_of?(Hash) hash = values.pop hash.reject! { |k,v| v == false } hash.reject! { |k,v| values << k if v == true } else hash = {} end values = values.map { |value| value.to_s.tr('_','-') } hash.each do |key, value| key = key.to_s.tr('_', '-') value = value.to_i if key == "max-age" values << [key, value].join('=') end response['Cache-Control'] = values.join(', ') if values.any? end
def content_type(type, params={})
Set the Content-Type of the response body given a media type or file
def content_type(type, params={}) default = params.delete :default mime_type = mime_type(type) || default fail "Unknown media type: %p" % type if mime_type.nil? mime_type = mime_type.dup unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type } params[:charset] = params.delete('charset') || settings.default_encoding end mime_type << ";#{params.map { |kv| kv.join('=') }.join(', ')}" unless params.empty? response['Content-Type'] = mime_type end
def error(code, body=nil)
def error(code, body=nil) code, body = 500, code.to_str if code.respond_to? :to_str response.body = body unless body.nil? halt code end
def etag(value, kind=:strong)
matching etag, execution is immediately halted. If the request method is
When the current request includes an 'If-None-Match' header with a
cache validator.
indicates whether the etag should be used as a :strong (default) or :weak
identifies the current version of the resource. The +kind+ argument
GET matches. The +value+ argument is an identifier that uniquely
Set the response entity tag (HTTP 'ETag' header) and halt if conditional
def etag(value, kind=:strong) raise TypeError, ":strong or :weak expected" if ![:strong,:weak].include?(kind) value = '"%s"' % value value = 'W/' + value if kind == :weak response['ETag'] = value # Conditional GET check if etags = env['HTTP_IF_NONE_MATCH'] etags = etags.split(/\s*,\s*/) halt 304 if etags.include?(value) || etags.include?('*') end end
def expires(amount, *values)
=> Expires: Mon, 08 Jun 2009 08:50:17 GMT
=> Cache-Control: public, must-revalidate, max-age=60
expires 500, :public, :must_revalidate
"values" arguments are passed to the #cache_control helper:
indicating when the response should be considered "stale". The remaining
can be an integer number of seconds in the future or a Time object
Set the Expires header and Cache-Control/max-age directive. Amount
def expires(amount, *values) values << {} unless values.last.kind_of?(Hash) if amount.respond_to?(:to_time) max_age = amount.to_time - Time.now time = amount.to_time else max_age = amount time = Time.now + amount end values.last.merge!(:max_age => max_age) cache_control(*values) response['Expires'] = time.httpdate end
def headers(hash=nil)
def headers(hash=nil) response.headers.merge! hash if hash response.headers end
def last_modified(time)
equal or later than the time specified, execution is immediately halted
When the current request includes an 'If-Modified-Since' header that is
DateTime, or other object that responds to +to_time+.
and halt if conditional GET matches. The +time+ argument is a Time,
Set the last modified time of the resource (HTTP 'Last-Modified' header)
def last_modified(time) return unless time if time.respond_to?(:to_time) time = time.to_time else ## make a best effort to convert something else to a time object ## if this fails, this should throw an ArgumentError, then the # rescue will result in an http 200, which should be safe time = Time.parse(time.to_s) end response['Last-Modified'] = time.httpdate # compare based on seconds since epoch halt 304 if Time.httpdate(request.env['HTTP_IF_MODIFIED_SINCE']).to_i >= time.to_i rescue ArgumentError end
def mime_type(type)
def mime_type(type) Base.mime_type(type) end
def not_found(body=nil)
def not_found(body=nil) error 404, body end
def redirect(uri, *args)
def redirect(uri, *args) if not uri =~ /^https?:\/\// # According to RFC 2616 section 14.30, "the field value consists of a # single absolute URI" abs_uri = "#{request.scheme}://#{request.host}" if request.scheme == 'https' && request.port != 443 || request.scheme == 'http' && request.port != 80 abs_uri << ":#{request.port}" end uri = (abs_uri << uri) end status 302 response['Location'] = uri halt(*args) end
def send_file(path, opts={})
def send_file(path, opts={}) stat = File.stat(path) last_modified stat.mtime if opts[:type] or not response['Content-Type'] content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' end if opts[:disposition] == 'attachment' || opts[:filename] attachment opts[:filename] || path elsif opts[:disposition] == 'inline' response['Content-Disposition'] = 'inline' end file_length = opts[:length] || stat.size sf = StaticFile.open(path, 'rb') if ! sf.parse_ranges(env, file_length) response['Content-Range'] = "bytes */#{file_length}" halt 416 elsif r=sf.range response['Content-Range'] = "bytes #{r.begin}-#{r.end}/#{file_length}" response['Content-Length'] = (r.end - r.begin + 1).to_s halt 206, sf else response['Content-Length'] ||= file_length.to_s halt sf end rescue Errno::ENOENT not_found end
def session
def session request.session end
def status(value=nil)
def status(value=nil) response.status = value if value response.status end