public
Rubygem
Description: Merb Core: All you need. None you don't.
Homepage: http://www.merbivore.com
Clone URL: git://github.com/wycats/merb-core.git
Search Repo:
Michael S. Klishin (author)
Sun May 11 14:20:04 -0700 2008
commit  bc1a1b78bace56503c8a567bdc1dfe6e9a14edbe
tree    ee520fb350dced0f2dbb8b989723d58d01faabda
parent  af548c0808576118ffbb43468104146feb7acbf9 parent  ef14f310e859489b2681d3da3198a37d322541c9
merb-core / lib / merb-core / controller / mixins / controller.rb
100644 266 lines (254 sloc) 9.369 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
module Merb
  # Module that is mixed in to all implemented controllers.
  module ControllerMixin
    # Renders the block given as a parameter using chunked encoding.
    #
    # ==== Parameters
    # &blk::
    # A block that, when called, will use send_chunks to send chunks of data
    # down to the server. The chunking will terminate once the block returns.
    #
    # ==== Examples
    # def stream
    # prefix = '<p>'
    # suffix = "</p>\r\n"
    # render_chunked do
    # IO.popen("cat /tmp/test.log") do |io|
    # done = false
    # until done
    # sleep 0.3
    # line = io.gets.chomp
    #
    # if line == 'EOF'
    # done = true
    # else
    # send_chunk(prefix + line + suffix)
    # end
    # end
    # end
    # end
    # end
    def render_chunked(&blk)
      must_support_streaming!
      headers['Transfer-Encoding'] = 'chunked'
      Proc.new { |response|
        @response = response
        response.send_status_no_connection_close('')
        response.send_header
        blk.call
        response.write("0\r\n\r\n")
      }
    end
 
    # Writes a chunk from +render_chunked+ to the response that is sent back to
    # the client. This should only be called within a +render_chunked+ block.
    #
    # ==== Parameters
    # data<String>:: a chunk of data to return.
    def send_chunk(data)
      @response.write('%x' % data.size + "\r\n")
      @response.write(data + "\r\n")
    end
    
    # ==== Parameters
    # &blk::
    # A proc that should get called outside the mutex, and which will return
    # the value to render.
    #
    # ==== Returns
    # Proc::
    # A block that Mongrel can call later, allowing Merb to release the
    # thread lock and render another request.
    def render_deferred(&blk)
      must_support_streaming!
      Proc.new {|response|
        result = blk.call
        response.send_status(result.length)
        response.send_header
        response.write(result)
      }
    end
    
    # Renders the passed in string, then calls the block outside the mutex and
    # after the string has been returned to the client.
    #
    # ==== Parameters
    # str<String>:: A +String+ to return to the client.
    # &blk:: A block that should get called once the string has been returned.
    #
    # ==== Returns
    # Proc::
    # A block that Mongrel can call after returning the string to the user.
    def render_then_call(str, &blk)
      must_support_streaming!
      Proc.new {|response|
        response.send_status(str.length)
        response.send_header
        response.write(str)
        blk.call
      }
    end
        
    # ==== Parameters
    # url<String>::
    # URL to redirect to. It can be either a relative or fully-qualified URL.
    # permanent<Boolean>::
    # When true, return status 301 Moved Permanently
    #
    # ==== Returns
    # String:: Explanation of redirect.
    #
    # ==== Examples
    # redirect("/posts/34")
    # redirect("http://www.merbivore.com/")
    # redirect("http://www.merbivore.com/", true)
    def redirect(url, permanent = false)
      self.status = permanent ? 301 : 302
      Merb.logger.info("Redirecting to: #{url} (#{self.status})")
      headers['Location'] = url
      "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
    end
    
    # Sends a file over HTTP. When given a path to a file, it will set the
    # right headers so that the static file is served directly.
    #
    # ==== Parameters
    # file<String>:: Path to file to send to the client.
    # opts<Hash>:: Options for sending the file (see below).
    #
    # ==== Options (opts)
    # :disposition<String>::
    # The disposition of the file send. Defaults to "attachment".
    # :filename<String>::
    # The name to use for the file. Defaults to the filename of file.
    # :type<String>:: The content type.
    #
    # ==== Returns
    # IO:: An I/O stream for the file.
    def send_file(file, opts={})
      opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
      disposition = opts[:disposition].dup || 'attachment'
      disposition << %(; filename="#{opts[:filename] ? opts[:filename] : File.basename(file)}")
      headers.update(
        'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
        'Content-Disposition' => disposition,
        'Content-Transfer-Encoding' => 'binary'
      )
      File.open(file)
    end
    
    # Send binary data over HTTP to the user as a file download. May set content type,
    # apparent file name, and specify whether to show data inline or download as an attachment.
    #
    # ==== Parameters
    # data<String>:: Path to file to send to the client.
    # opts<Hash>:: Options for sending the data (see below).
    #
    # ==== Options (opts)
    # :disposition<String>::
    # The disposition of the file send. Defaults to "attachment".
    # :filename<String>::
    # The name to use for the file. Defaults to the filename of file.
    # :type<String>:: The content type.
    def send_data(data, opts={})
      opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
      disposition = opts[:disposition].dup || 'attachment'
      disposition << %(; filename="#{opts[:filename]}") if opts[:filename]
      headers.update(
        'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
        'Content-Disposition' => disposition,
        'Content-Transfer-Encoding' => 'binary'
      )
      data
    end
    
    # Streams a file over HTTP.
    #
    # ==== Parameters
    # opts<Hash>:: Options for the file streaming (see below).
    # &stream::
    # A block that, when called, will return an object that responds to
    # +get_lines+ for streaming.
    #
    # ==== Options
    # :disposition<String>::
    # The disposition of the file send. Defaults to "attachment".
    # :type<String>:: The content type.
    # :content_length<Numeric>:: The length of the content to send.
    # :filename<String>:: The name to use for the streamed file.
    #
    # ==== Examples
    # stream_file({ :filename => file_name, :type => content_type,
    # :content_length => content_length }) do |response|
    # AWS::S3::S3Object.stream(user.folder_name + "-" + user_file.unique_id, bucket_name) do |chunk|
    # response.write chunk
    # end
    # end
    def stream_file(opts={}, &stream)
      must_support_streaming!
      opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
      disposition = opts[:disposition].dup || 'attachment'
      disposition << %(; filename="#{opts[:filename]}")
      headers.update(
        'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
        'Content-Disposition' => disposition,
        'Content-Transfer-Encoding' => 'binary',
        'CONTENT-LENGTH' => opts[:content_length]
      )
      Proc.new{|response|
        response.send_status(opts[:content_length])
        response.send_header
        stream.call(response)
      }
    end
 
    # Uses the nginx specific +X-Accel-Redirect+ header to send a file directly
    # from nginx. For more information, see the nginx wiki:
    # http://wiki.codemongers.com/NginxXSendfile
    #
    # ==== Parameters
    # file<String>:: Path to file to send to the client.
    def nginx_send_file(file)
      headers['X-Accel-Redirect'] = File.expand_path(file)
      return
    end
  
    # Sets a cookie to be included in the response.
    #
    # If you need to set a cookie, then use the +cookies+ hash.
    #
    # ==== Parameters
    # name<~to_s>:: A name for the cookie.
    # value<~to_s>:: A value for the cookie.
    # expires<~gmtime:~strftime, Hash>:: An expiration time for the cookie, or a hash of cookie options.
    # ---
    # @public
    def set_cookie(name, value, expires)
      options = expires.is_a?(Hash) ? expires : {:expires => expires}
      cookies.set_cookie(name, value, options)
    end
    
    # Marks a cookie as deleted and gives it an expires stamp in the past. This
    # method is used primarily internally in Merb.
    #
    # Use the +cookies+ hash to manipulate cookies instead.
    #
    # ==== Parameters
    # name<~to_s>:: A name for the cookie to delete.
    def delete_cookie(name)
      set_cookie(name, nil, Merb::Const::COOKIE_EXPIRED_TIME)
    end
    
    # Escapes the string representation of +obj+ and escapes it for use in XML.
    #
    # ==== Parameter
    # obj<~to_s>:: The object to escape for use in XML.
    #
    # ==== Returns
    # String:: The escaped object.
    def escape_xml(obj)
      Erubis::XmlHelper.escape_xml(obj.to_s)
    end
    alias h escape_xml
    alias html_escape escape_xml
    
    private
      # Checks whether streaming is supported by the current Rack adapter.
      #
      # ==== Raises
      # NotImplemented:: The Rack adapter doens't support streaming.
      def must_support_streaming!
        unless request.env['rack.streaming']
          raise(Merb::ControllerExceptions::NotImplemented, "Current Rack adapter does not support streaming")
        end
      end
  end
end