Harbor Documentation

Constants

Attributes

  • status [RW] (Not documented)
  • headers [RW] (Not documented)
  • errors [RW] (Not documented)

Public Class Methods

new(request)

      # File lib/harbor/response.rb, line 15
15:     def initialize(request)
16:       @request = request
17:       @headers = {}
18:       @headers["Content-Type"] = "text/html"
19:       @status = 200
20:       @errors = Harbor::Errors.new
21:     end

Public Instance Methods

[](key)

       # File lib/harbor/response.rb, line 312
312:     def [](key)
313:       headers[key]
314:     end

[]=(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

flush()

      # File lib/harbor/response.rb, line 27
27:     def flush
28:       @io = nil
29:     end

headers()

      # File lib/harbor/response.rb, line 23
23:     def headers
24:       @headers
25:     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

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)

Harbor::Response#send_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

unauthorized()

       # File lib/harbor/response.rb, line 247
247:     def unauthorized
248:       self.status = 401
249:     end

unauthorized!()

       # File lib/harbor/response.rb, line 251
251:     def unauthorized!
252:       abort!(401)
253:     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

string()

       # File lib/harbor/response.rb, line 393
393:     def string
394:       @io ||= StringIO.new("")
395:     end