Harbor::Response
Parent
Methods
- ::new
- #[]
- #[]=
- #abort!
- #buffer
- #cache
- #content_type
- #content_type=
- #delete_cookie
- #escape_filename_for_http_header
- #flush
- #headers
- #inspect
- #message
- #messages
- #no_stat!
- #not_modified!
- #puts
- #redirect
- #redirect!
- #render
- #send_file
- #send_files
- #set_cookie
- #size
- #size=
- #status=
- #stream_file
- #string
- #to_a
- #unauthorized
- #unauthorized!
Constants
Attributes
Public Class Methods
Public Instance Methods
[]=(key, value)
# File lib/harbor/response.rb, line 316 316: def []=(key, value) 317: headers[key] = value 318: end
abort!(code)
# File lib/harbor/response.rb, line 238 238: def abort!(code) 239: if Harbor::View.exists?("exceptions/#{code}.html.erb") 240: render "exceptions/#{code}.html.erb" 241: end 242: 243: self.status = code 244: throw(:abort_request) 245: end
buffer()
# File lib/harbor/response.rb, line 57 57: def buffer 58: if @io.is_a?(StringIO) 59: @io.string 60: else 61: @io || "" 62: end 63: end
cache(key, last_modified, ttl = nil, max_age = nil)
# File lib/harbor/response.rb, line 156 156: def cache(key, last_modified, ttl = nil, max_age = nil) 157: raise ArgumentError.new("You must provide a block of code to cache.") unless block_given? 158: 159: store = nil 160: if key && (ttl || max_age) 161: store = Harbor::View.cache 162: 163: unless store 164: raise ArgumentError.new("Cache Store Not Defined. Please set Harbor::View.cache to your desired cache store.") 165: end 166: 167: key = "page-#{key}" 168: end 169: 170: last_modified = last_modified.httpdate 171: @headers["Last-Modified"] = last_modified 172: @headers["Cache-Control"] = "max-age=#{ttl}, must-revalidate" if ttl 173: 174: modified_since = @request.env["HTTP_IF_MODIFIED_SINCE"] 175: 176: if modified_since == last_modified && (!store || store.get(key)) 177: not_modified! 178: elsif store && item = store.get(key) 179: return puts(item.content) unless item.content.nil? 180: end 181: 182: yield self 183: store.put(key, buffer, ttl, max_age) if store 184: end
content_type()
# File lib/harbor/response.rb, line 43 43: def content_type 44: @headers["Content-Type"] 45: end
content_type=(content_type)
# File lib/harbor/response.rb, line 39 39: def content_type=(content_type) 40: @headers["Content-Type"] = content_type 41: end
inspect()
# File lib/harbor/response.rb, line 274 274: def inspect 275: "<#{self.class} headers=#{headers.inspect} content_type=#{content_type.inspect} status=#{status.inspect} body=#{buffer.inspect}>" 276: end
message(key, message, use_session=true)
Calling reponse.message forces a session to load. The reasoning is as follows: 1) This will eliminate the majority of ugly query-string messages. 2) Calling response.message in an action assumes a human receiver and thus the
use of a session is valid
Nonetheless, control is left to app. Use use_session = false to use query-string based messages instead.
# File lib/harbor/response.rb, line 291 291: def message(key, message, use_session=true) 292: @request.session if use_session 293: messages[key] = message 294: end
messages()
# File lib/harbor/response.rb, line 278 278: def messages 279: @messages ||= @request.messages 280: end
no_stat!()
# File lib/harbor/contrib/stats/response.rb, line 7 7: def no_stat! 8: @headers[STATS_HEADER] = NO_STAT 9: end
not_modified!()
# File lib/harbor/response.rb, line 268 268: def not_modified! 269: NOT_MODIFIED_OMIT_HEADERS.each { |name| headers.delete(name) } 270: self.status = 304 271: throw(:abort_request) 272: end
print(value)
# File lib/harbor/response.rb, line 52 52: def print(value) 53: string.print(value) 54: self.size = string.length 55: end
puts(value)
# File lib/harbor/response.rb, line 47 47: def puts(value) 48: string.puts(value) 49: self.size = string.length 50: end
redirect(url, params = nil)
# File lib/harbor/response.rb, line 209 209: def redirect(url, params = nil) 210: url = URI.parse(url) 211: params ||= {} 212: 213: if url.query 214: params.merge!(Rack::Utils.parse_query(url.query)) 215: url.query = nil 216: end 217: 218: if @request && !@request.session? && !messages.empty? && !messages.expired? 219: messages.each { |key, value| params["messages[#{key}]"] = value } 220: end 221: 222: url.query = Rack::Utils::build_query(params) if params && params.any? 223: 224: self.status = 303 225: self.headers.merge!({ 226: "Location" => url.to_s, 227: "Content-Type" => "text/html" 228: }) 229: HEADER_BLACKLIST.each{|banned_header| self.headers.delete(banned_header)} 230: self.flush 231: self 232: end
redirect!(url, params = nil)
# File lib/harbor/response.rb, line 234 234: def redirect!(url, params = nil) 235: redirect(url, params) and throw(:abort_request) 236: end
render(view, context = {})
# File lib/harbor/response.rb, line 186 186: def render(view, context = {}) 187: if context[:layout].is_a?(Array) 188: warn "Passing multiple layouts to response.render has been deprecated. See Harbor::Layouts." 189: context[:layout] = context[:layout].first 190: end 191: 192: case view 193: when View 194: view.context.merge(context) 195: else 196: view = View.new(view, context.merge({ :request => @request, :response => self })) 197: end 198: 199: self.content_type = view.content_type 200: 201: if context.has_key?(:layout) || @request.xhr? 202: puts view.to_s(context[:layout]) 203: else 204: puts view.to_s(:search) 205: end 206: end
send_file(name, path_or_io, content_type = nil)
# File lib/harbor/response.rb, line 91 91: def send_file(name, path_or_io, content_type = nil) 92: stream_file(path_or_io, content_type) 93: 94: @headers["Content-Disposition"] = "attachment; filename=\"#{escape_filename_for_http_header(name)}\"" 95: nil 96: end
send_files(name, files)
name: filename presented to the browser for the download
files: Enumerable of Harbor::File instances. The files are expected to
exist on disk.
If Nginx sends a HTTP_MOD_ZIP_ENABLED header, build a list of files compatible with the format specified @ github.com/evanmiller/mod_zip:
1034ab38 428 /foo.txt My Document1.txt 83e8110b 100339 /bar.txt My Other Document1.txt
Where the components are, in order: CRC32 (in hexadecimal), uncompressed file size, path or URL to file that can be found by Nginx, and filename (with optional relative path information to be used when building the zip file). The mod_zip documentation claims that the CRC32 is optional, but in practice, zip files generated w/out the CRC value on Ubuntu won’t open on at least Mac OSX 10.6.
If no HTTP_MOD_ZIP_ENABLED is sent, use the ZippedIO class to generate the zip file. This is extremely inefficient and should never be used in a produciton environment.
# File lib/harbor/response.rb, line 122 122: def send_files(name, files) 123: if @request.env["HTTP_MOD_ZIP_ENABLED"] 124: filenames = [] 125: files.each do |file| 126: path = ::File.expand_path(file.path) 127: filename = file.name 128: while filenames.include? filename 129: extname = ::File.extname(filename) 130: basename = ::File.basename(filename, extname) 131: if basename =~ /-(\d+)$/ 132: counter = $1.to_i + 1 133: else 134: counter = 2 135: end 136: filename = "#{basename}-#{counter}#{extname}" 137: end 138: filenames << filename 139: if file.respond_to?(:checksum) 140: puts("#{file.checksum(:pkzip)} #{::File.size(path)} #{path} #{filename}") 141: else 142: puts("#{Harbor::File.new(path).checksum(:pkzip)} #{::File.size(path)} #{path} #{filename}") 143: end 144: end 145: headers["X-Archive-Files"] = "zip" 146: self.content_type = "application/zip" 147: @headers["Content-Disposition"] = "attachment; filename=\"#{escape_filename_for_http_header(name)}\"" 148: else 149: @io = ZippedIO.new(files) 150: self.size = @io.size 151: self.content_type = "application/zip" 152: @headers["Content-Disposition"] = "attachment; filename=\"#{escape_filename_for_http_header(name)}\"" 153: end 154: end
size()
# File lib/harbor/response.rb, line 35 35: def size 36: (@headers["Content-Length"] || buffer.size).to_i 37: end
size=(size)
# File lib/harbor/response.rb, line 31 31: def size=(size) 32: @headers["Content-Length"] = size.to_s 33: end
status=(new_status)
# File lib/harbor/response.rb, line 382 382: def status=(new_status) 383: @status = new_status 384: if @status == 204 || @status == 304 385: @headers.delete "Content-Type" 386: @headers.delete "Content-Length" 387: string.truncate(0) 388: end 389: end
stream_file(path_or_io, content_type = nil)
# File lib/harbor/response.rb, line 65 65: def stream_file(path_or_io, content_type = nil) 66: io = BlockIO.new(path_or_io) 67: 68: if io.path && (header = @request.env["HTTP_X_SENDFILE_TYPE"]) 69: case header 70: when "X-Sendfile" 71: @headers["X-Sendfile"] = io.path 72: when "X-Accel-Redirect" 73: if mapping = @request.env['HTTP_X_ACCEL_MAPPING'] 74: internal, external = mapping.split('=', 2).map { |p| p.strip } 75: @headers["X-Accel-Redirect"] = io.path.sub(/^#{Regexp::escape(internal)}/i, external) 76: else 77: @headers["X-Accel-Redirect"] = io.path 78: end 79: else 80: raise UnsupportedSendfileTypeError.new(header) 81: end 82: else 83: @io = io 84: end 85: 86: self.size = io.size 87: self.content_type = content_type || Harbor::Mime.mime_type(::File.extname(io.path.to_s)) 88: nil 89: end
to_a()
# File lib/harbor/response.rb, line 296 296: def to_a 297: messages.clear if messages.expired? 298: 299: if @request.session? 300: session = @request.session 301: set_cookie(session.key, session.save) 302: end 303: 304: # headers cannot be arrays 305: self.headers.each_pair do |key, value| 306: self.headers[key] = value.join("\n") if value.is_a?(Array) 307: end 308: 309: [self.status, self.headers, self.buffer] 310: end
Private Instance Methods
escape_filename_for_http_header(filename)
# File lib/harbor/response.rb, line 398 398: def escape_filename_for_http_header(filename) 399: # This would work great if IE6 could unescape the Content-Disposition filename field properly, 400: # but it can't, so we use the terribly weak version instead, until IE6 dies off... 401: #filename.gsub(/["\\\x0]/,'\\\\\0') 402: 403: filename.gsub(/[^\w\.]/, '_') 404: end