wycats-merb-core 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +992 -0
- data/CONTRIBUTORS +94 -0
- data/LICENSE +20 -0
- data/PUBLIC_CHANGELOG +142 -0
- data/README +21 -0
- data/Rakefile +458 -0
- data/TODO +0 -0
- data/bin/merb +11 -0
- data/bin/merb-specs +5 -0
- data/lib/merb-core.rb +598 -0
- data/lib/merb-core/autoload.rb +31 -0
- data/lib/merb-core/bootloader.rb +717 -0
- data/lib/merb-core/config.rb +305 -0
- data/lib/merb-core/constants.rb +45 -0
- data/lib/merb-core/controller/abstract_controller.rb +568 -0
- data/lib/merb-core/controller/exceptions.rb +315 -0
- data/lib/merb-core/controller/merb_controller.rb +256 -0
- data/lib/merb-core/controller/mime.rb +107 -0
- data/lib/merb-core/controller/mixins/authentication.rb +123 -0
- data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
- data/lib/merb-core/controller/mixins/controller.rb +319 -0
- data/lib/merb-core/controller/mixins/render.rb +513 -0
- data/lib/merb-core/controller/mixins/responder.rb +469 -0
- data/lib/merb-core/controller/template.rb +254 -0
- data/lib/merb-core/core_ext.rb +9 -0
- data/lib/merb-core/core_ext/hash.rb +7 -0
- data/lib/merb-core/core_ext/kernel.rb +340 -0
- data/lib/merb-core/dispatch/cookies.rb +130 -0
- data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
- data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
- data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
- data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
- data/lib/merb-core/dispatch/dispatcher.rb +176 -0
- data/lib/merb-core/dispatch/request.rb +729 -0
- data/lib/merb-core/dispatch/router.rb +151 -0
- data/lib/merb-core/dispatch/router/behavior.rb +566 -0
- data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
- data/lib/merb-core/dispatch/router/resources.rb +191 -0
- data/lib/merb-core/dispatch/router/route.rb +511 -0
- data/lib/merb-core/dispatch/session.rb +222 -0
- data/lib/merb-core/dispatch/session/container.rb +74 -0
- data/lib/merb-core/dispatch/session/cookie.rb +173 -0
- data/lib/merb-core/dispatch/session/memcached.rb +68 -0
- data/lib/merb-core/dispatch/session/memory.rb +99 -0
- data/lib/merb-core/dispatch/session/store_container.rb +150 -0
- data/lib/merb-core/dispatch/worker.rb +28 -0
- data/lib/merb-core/gem_ext/erubis.rb +77 -0
- data/lib/merb-core/logger.rb +203 -0
- data/lib/merb-core/plugins.rb +67 -0
- data/lib/merb-core/rack.rb +25 -0
- data/lib/merb-core/rack/adapter.rb +44 -0
- data/lib/merb-core/rack/adapter/ebb.rb +25 -0
- data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
- data/lib/merb-core/rack/adapter/irb.rb +118 -0
- data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/runner.rb +28 -0
- data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/thin.rb +39 -0
- data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
- data/lib/merb-core/rack/adapter/webrick.rb +36 -0
- data/lib/merb-core/rack/application.rb +32 -0
- data/lib/merb-core/rack/handler/mongrel.rb +97 -0
- data/lib/merb-core/rack/middleware.rb +20 -0
- data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
- data/lib/merb-core/rack/middleware/content_length.rb +18 -0
- data/lib/merb-core/rack/middleware/csrf.rb +73 -0
- data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
- data/lib/merb-core/rack/middleware/profiler.rb +19 -0
- data/lib/merb-core/rack/middleware/static.rb +45 -0
- data/lib/merb-core/rack/middleware/tracer.rb +20 -0
- data/lib/merb-core/server.rb +284 -0
- data/lib/merb-core/tasks/audit.rake +68 -0
- data/lib/merb-core/tasks/gem_management.rb +229 -0
- data/lib/merb-core/tasks/merb.rb +1 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
- data/lib/merb-core/tasks/stats.rake +71 -0
- data/lib/merb-core/test.rb +11 -0
- data/lib/merb-core/test/helpers.rb +9 -0
- data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
- data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
- data/lib/merb-core/test/helpers/request_helper.rb +393 -0
- data/lib/merb-core/test/helpers/route_helper.rb +39 -0
- data/lib/merb-core/test/helpers/view_helper.rb +121 -0
- data/lib/merb-core/test/matchers.rb +9 -0
- data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
- data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
- data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
- data/lib/merb-core/test/run_specs.rb +49 -0
- data/lib/merb-core/test/tasks/spectasks.rb +68 -0
- data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
- data/lib/merb-core/test/test_ext/object.rb +14 -0
- data/lib/merb-core/test/test_ext/string.rb +14 -0
- data/lib/merb-core/vendor/facets.rb +2 -0
- data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
- data/lib/merb-core/vendor/facets/inflect.rb +342 -0
- data/lib/merb-core/version.rb +3 -0
- metadata +253 -0
@@ -0,0 +1,107 @@
|
|
1
|
+
module Merb
|
2
|
+
class << self
|
3
|
+
|
4
|
+
# ==== Returns
|
5
|
+
# Hash:: The available mime types.
|
6
|
+
def available_mime_types
|
7
|
+
ResponderMixin::TYPES
|
8
|
+
end
|
9
|
+
|
10
|
+
# Any specific outgoing headers should be included here. These are not
|
11
|
+
# the content-type header but anything in addition to it.
|
12
|
+
# +transform_method+ should be set to a symbol of the method used to
|
13
|
+
# transform a resource into this mime type.
|
14
|
+
# For example for the :xml mime type an object might be transformed by
|
15
|
+
# calling :to_xml, or for the :js mime type, :to_json.
|
16
|
+
# If there is no transform method, use nil.
|
17
|
+
#
|
18
|
+
# ==== Autogenerated Methods
|
19
|
+
# Adding a mime-type adds a render_type method that sets the content
|
20
|
+
# type and calls render.
|
21
|
+
#
|
22
|
+
# By default this does: def render_all, def render_yaml, def render_text,
|
23
|
+
# def render_html, def render_xml, def render_js, and def render_yaml
|
24
|
+
#
|
25
|
+
# ==== Parameters
|
26
|
+
# key<Symbol>:: The name of the mime-type. This is used by the provides API
|
27
|
+
# transform_method<~to_s>::
|
28
|
+
# The associated method to call on objects to convert them to the
|
29
|
+
# appropriate mime-type. For instance, :json would use :to_json as its
|
30
|
+
# transform_method.
|
31
|
+
# mimes<Array[String]>::
|
32
|
+
# A list of possible values sent in the Accept header, such as text/html,
|
33
|
+
# that should be associated with this content-type.
|
34
|
+
# new_response_headers<Hash>::
|
35
|
+
# The response headers to set for the the mime type. For example:
|
36
|
+
# 'Content-Type' => 'application/json; charset=utf-8'; As a shortcut for
|
37
|
+
# the common charset option, use :charset => 'utf-8', which will be
|
38
|
+
# correctly appended to the mimetype itself.
|
39
|
+
# &block:: a block which recieves the current controller when the format
|
40
|
+
# is set (in the controller's #content_type method)
|
41
|
+
def add_mime_type(key, transform_method, mimes, new_response_headers = {}, default_quality = 1, &block)
|
42
|
+
enforce!(key => Symbol, mimes => Array)
|
43
|
+
|
44
|
+
content_type = new_response_headers["Content-Type"] || mimes.first
|
45
|
+
|
46
|
+
if charset = new_response_headers.delete(:charset)
|
47
|
+
content_type += "; charset=#{charset}"
|
48
|
+
end
|
49
|
+
|
50
|
+
ResponderMixin::TYPES.update(key =>
|
51
|
+
{:accepts => mimes,
|
52
|
+
:transform_method => transform_method,
|
53
|
+
:content_type => content_type,
|
54
|
+
:response_headers => new_response_headers,
|
55
|
+
:default_quality => default_quality,
|
56
|
+
:response_block => block })
|
57
|
+
|
58
|
+
mimes.each do |mime|
|
59
|
+
ResponderMixin::MIMES.update(mime => key)
|
60
|
+
end
|
61
|
+
|
62
|
+
Merb::RenderMixin.class_eval <<-EOS, __FILE__, __LINE__
|
63
|
+
def render_#{key}(thing = nil, opts = {})
|
64
|
+
self.content_type = :#{key}
|
65
|
+
render thing, opts
|
66
|
+
end
|
67
|
+
EOS
|
68
|
+
end
|
69
|
+
|
70
|
+
# Removes a MIME-type from the mime-type list.
|
71
|
+
#
|
72
|
+
# ==== Parameters
|
73
|
+
# key<Symbol>:: The key that represents the mime-type to remove.
|
74
|
+
#
|
75
|
+
# ==== Notes
|
76
|
+
# :all is the key for */*; It can't be removed.
|
77
|
+
def remove_mime_type(key)
|
78
|
+
return false if key == :all
|
79
|
+
ResponderMixin::TYPES.delete(key)
|
80
|
+
end
|
81
|
+
|
82
|
+
# ==== Parameters
|
83
|
+
# key<Symbol>:: The key that represents the mime-type.
|
84
|
+
#
|
85
|
+
# ==== Returns
|
86
|
+
# Symbol:: The transform method for the mime type, e.g. :to_json.
|
87
|
+
#
|
88
|
+
# ==== Raises
|
89
|
+
# ArgumentError:: The requested mime type is not valid.
|
90
|
+
def mime_transform_method(key)
|
91
|
+
raise ArgumentError, ":#{key} is not a valid MIME-type" unless ResponderMixin::TYPES.key?(key)
|
92
|
+
ResponderMixin::TYPES[key][:transform_method]
|
93
|
+
end
|
94
|
+
|
95
|
+
# The mime-type for a particular inbound Accepts header.
|
96
|
+
#
|
97
|
+
# ==== Parameters
|
98
|
+
# header<String>:: The name of the header to find the mime-type for.
|
99
|
+
#
|
100
|
+
# ==== Returns
|
101
|
+
# Hash:: The mime type information.
|
102
|
+
def mime_by_request_header(header)
|
103
|
+
available_mime_types.find {|key,info| info[:accepts].include?(header)}.first
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Merb::AuthenticationMixin
|
2
|
+
|
3
|
+
# Attempts to authenticate the user via HTTP Basic authentication. Takes a
|
4
|
+
# block with the username and password, if the block yields false the
|
5
|
+
# authentication is not accepted and :halt is thrown.
|
6
|
+
#
|
7
|
+
# If no block is passed, +basic_authentication+, the +request+ and +authenticate+
|
8
|
+
# methods can be chained. These can be used to independently request authentication
|
9
|
+
# or confirm it, if more control is desired.
|
10
|
+
#
|
11
|
+
# ==== Parameters
|
12
|
+
# realm<~to_s>:: The realm to authenticate against. Defaults to 'Application'.
|
13
|
+
# &authenticator:: A block to check if the authentication is valid.
|
14
|
+
#
|
15
|
+
# ==== Examples
|
16
|
+
# class Application < Merb::Controller
|
17
|
+
#
|
18
|
+
# before :authenticate
|
19
|
+
#
|
20
|
+
# protected
|
21
|
+
#
|
22
|
+
# def authenticate
|
23
|
+
# basic_authentication("My App") do |username, password|
|
24
|
+
# password == "secret"
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# class Application < Merb::Controller
|
31
|
+
#
|
32
|
+
# before :authenticate
|
33
|
+
#
|
34
|
+
# def authenticate
|
35
|
+
# user = basic_authentication.authenticate do |username, password|
|
36
|
+
# User.authenticate(username, password)
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# if user
|
40
|
+
# @current_user = user
|
41
|
+
# else
|
42
|
+
# basic_authentication.request
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# If you need to request basic authentication inside an action you need to use the request! method.
|
49
|
+
#
|
50
|
+
# ====Example
|
51
|
+
#
|
52
|
+
# class Sessions < Application
|
53
|
+
#
|
54
|
+
# def new
|
55
|
+
# case content_type
|
56
|
+
# when :html
|
57
|
+
# render
|
58
|
+
# else
|
59
|
+
# basic_authentication.request!
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
#---
|
66
|
+
# @public
|
67
|
+
def basic_authentication(realm = "Application", &authenticator)
|
68
|
+
@_basic_authentication ||= BasicAuthentication.new(self, realm, &authenticator)
|
69
|
+
end
|
70
|
+
|
71
|
+
class BasicAuthentication
|
72
|
+
# So we can have access to the status codes
|
73
|
+
include Merb::ControllerExceptions
|
74
|
+
|
75
|
+
def initialize(controller, realm = "Application", &authenticator)
|
76
|
+
@controller = controller
|
77
|
+
@realm = realm
|
78
|
+
@auth = Rack::Auth::Basic::Request.new(@controller.request.env)
|
79
|
+
authenticate_or_request(&authenticator) if authenticator
|
80
|
+
end
|
81
|
+
|
82
|
+
def authenticate(&authenticator)
|
83
|
+
if @auth.provided? and @auth.basic?
|
84
|
+
authenticator.call(*@auth.credentials)
|
85
|
+
else
|
86
|
+
false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def request
|
91
|
+
request!
|
92
|
+
throw :halt, @controller.render("HTTP Basic: Access denied.\n", :status => Unauthorized.status, :layout => false)
|
93
|
+
end
|
94
|
+
|
95
|
+
# This is a special case for use outside a before filter. Use this if you need to
|
96
|
+
# request basic authenticaiton as part of an action
|
97
|
+
def request!
|
98
|
+
@controller.status = Unauthorized.status
|
99
|
+
@controller.headers['WWW-Authenticate'] = 'Basic realm="%s"' % @realm
|
100
|
+
end
|
101
|
+
|
102
|
+
# Checks to see if there has been any basic authentication credentials provided
|
103
|
+
def provided?
|
104
|
+
@auth.provided?
|
105
|
+
end
|
106
|
+
|
107
|
+
def username
|
108
|
+
provided? ? @auth.credentials.first : nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def password
|
112
|
+
provided? ? @auth.credentials.last : nil
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
116
|
+
|
117
|
+
def authenticate_or_request(&authenticator)
|
118
|
+
authenticate(&authenticator) || request
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# Provides conditional get support in Merb core.
|
2
|
+
# Conditional get support is intentionally
|
3
|
+
# simple and does not do fancy stuff like making
|
4
|
+
# ETag value from Ruby objects for you.
|
5
|
+
#
|
6
|
+
# The most interesting method for end user is
|
7
|
+
# +request_fresh?+ that is used after setting of
|
8
|
+
# last modification time or ETag:
|
9
|
+
#
|
10
|
+
# ==== Example
|
11
|
+
#
|
12
|
+
# def show
|
13
|
+
# self.etag = Digest::SHA1.hexdigest(calculate_cache_key(params))
|
14
|
+
#
|
15
|
+
# if request_fresh?
|
16
|
+
# self.status = 304
|
17
|
+
# return ''
|
18
|
+
# else
|
19
|
+
# @product = Product.get(params[:id])
|
20
|
+
# display @product
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
module Merb::ConditionalGetMixin
|
24
|
+
|
25
|
+
# Sets ETag response header by calling
|
26
|
+
# #to_s on the argument.
|
27
|
+
#
|
28
|
+
# ==== Parameters
|
29
|
+
# tag<~to_s>::
|
30
|
+
# value of ETag header enclosed in double quotes
|
31
|
+
# as required by the RFC
|
32
|
+
def etag=(tag)
|
33
|
+
headers[Merb::Const::ETAG] = %("#{tag}")
|
34
|
+
end
|
35
|
+
|
36
|
+
# ==== Returns
|
37
|
+
# <String>::
|
38
|
+
# Value of ETag response header or nil if it's not set.
|
39
|
+
def etag
|
40
|
+
headers[Merb::Const::ETAG]
|
41
|
+
end
|
42
|
+
|
43
|
+
# ==== Returns
|
44
|
+
# <Boolean>::
|
45
|
+
# true if ETag response header equals If-None-Match request header,
|
46
|
+
# false otherwise
|
47
|
+
def etag_matches?(tag = self.etag)
|
48
|
+
tag == self.request.if_none_match
|
49
|
+
end
|
50
|
+
|
51
|
+
# Sets Last-Modified response header.
|
52
|
+
#
|
53
|
+
# ==== Parameters
|
54
|
+
# tag<Time>::
|
55
|
+
# resource modification timestamp converted into format
|
56
|
+
# required by the RFC
|
57
|
+
def last_modified=(time)
|
58
|
+
headers[Merb::Const::LAST_MODIFIED] = time.httpdate
|
59
|
+
end
|
60
|
+
|
61
|
+
# ==== Returns
|
62
|
+
# <String>::
|
63
|
+
# Value of Last-Modified response header or nil if it's not set.
|
64
|
+
def last_modified
|
65
|
+
Time.rfc2822(headers[Merb::Const::LAST_MODIFIED])
|
66
|
+
end
|
67
|
+
|
68
|
+
# ==== Returns
|
69
|
+
# <Boolean>::
|
70
|
+
# true if Last-Modified response header is < than
|
71
|
+
# If-Modified-Since request header value, false otherwise.
|
72
|
+
def not_modified?(time = self.last_modified)
|
73
|
+
request.if_modified_since && time && time <= request.if_modified_since
|
74
|
+
end
|
75
|
+
|
76
|
+
# ==== Returns
|
77
|
+
# <Boolean>::
|
78
|
+
# true if either ETag matches or entity is not modified,
|
79
|
+
# so request is fresh; false otherwise
|
80
|
+
def request_fresh?
|
81
|
+
etag_matches?(self.etag) || not_modified?(self.last_modified)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,319 @@
|
|
1
|
+
module Merb
|
2
|
+
# Module that is mixed in to all implemented controllers.
|
3
|
+
module ControllerMixin
|
4
|
+
|
5
|
+
# Enqueu a block to run in a background thread outside of the request
|
6
|
+
# response dispatch
|
7
|
+
#
|
8
|
+
# ==== Parameters
|
9
|
+
# takes a block to run later
|
10
|
+
#
|
11
|
+
# ==== Example
|
12
|
+
# run_later do
|
13
|
+
# SomeBackgroundTask.run
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
def run_later(&blk)
|
17
|
+
Merb::Dispatcher.work_queue << blk
|
18
|
+
end
|
19
|
+
|
20
|
+
# Renders the block given as a parameter using chunked encoding.
|
21
|
+
#
|
22
|
+
# ==== Parameters
|
23
|
+
# &blk::
|
24
|
+
# A block that, when called, will use send_chunks to send chunks of data
|
25
|
+
# down to the server. The chunking will terminate once the block returns.
|
26
|
+
#
|
27
|
+
# ==== Examples
|
28
|
+
# def stream
|
29
|
+
# prefix = '<p>'
|
30
|
+
# suffix = "</p>\r\n"
|
31
|
+
# render_chunked do
|
32
|
+
# IO.popen("cat /tmp/test.log") do |io|
|
33
|
+
# done = false
|
34
|
+
# until done
|
35
|
+
# sleep 0.3
|
36
|
+
# line = io.gets.chomp
|
37
|
+
#
|
38
|
+
# if line == 'EOF'
|
39
|
+
# done = true
|
40
|
+
# else
|
41
|
+
# send_chunk(prefix + line + suffix)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
def render_chunked(&blk)
|
48
|
+
must_support_streaming!
|
49
|
+
headers['Transfer-Encoding'] = 'chunked'
|
50
|
+
Proc.new { |response|
|
51
|
+
@response = response
|
52
|
+
response.send_status_no_connection_close('')
|
53
|
+
response.send_header
|
54
|
+
blk.call
|
55
|
+
response.write("0\r\n\r\n")
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Writes a chunk from +render_chunked+ to the response that is sent back to
|
60
|
+
# the client. This should only be called within a +render_chunked+ block.
|
61
|
+
#
|
62
|
+
# ==== Parameters
|
63
|
+
# data<String>:: a chunk of data to return.
|
64
|
+
def send_chunk(data)
|
65
|
+
@response.write('%x' % data.size + "\r\n")
|
66
|
+
@response.write(data + "\r\n")
|
67
|
+
end
|
68
|
+
|
69
|
+
# ==== Parameters
|
70
|
+
# &blk::
|
71
|
+
# A proc that should get called outside the mutex, and which will return
|
72
|
+
# the value to render.
|
73
|
+
#
|
74
|
+
# ==== Returns
|
75
|
+
# Proc::
|
76
|
+
# A block that Mongrel can call later, allowing Merb to release the
|
77
|
+
# thread lock and render another request.
|
78
|
+
def render_deferred(&blk)
|
79
|
+
must_support_streaming!
|
80
|
+
Proc.new {|response|
|
81
|
+
result = blk.call
|
82
|
+
response.send_status(result.length)
|
83
|
+
response.send_header
|
84
|
+
response.write(result)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
# Renders the passed in string, then calls the block outside the mutex and
|
89
|
+
# after the string has been returned to the client.
|
90
|
+
#
|
91
|
+
# ==== Parameters
|
92
|
+
# str<String>:: A +String+ to return to the client.
|
93
|
+
# &blk:: A block that should get called once the string has been returned.
|
94
|
+
#
|
95
|
+
# ==== Returns
|
96
|
+
# Proc::
|
97
|
+
# A block that Mongrel can call after returning the string to the user.
|
98
|
+
def render_then_call(str, &blk)
|
99
|
+
must_support_streaming!
|
100
|
+
Proc.new {|response|
|
101
|
+
response.send_status(str.length)
|
102
|
+
response.send_header
|
103
|
+
response.write(str)
|
104
|
+
blk.call
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
# ==== Parameters
|
109
|
+
# url<String>::
|
110
|
+
# URL to redirect to. It can be either a relative or fully-qualified URL.
|
111
|
+
# opts<Hash>:: An options hash (see below)
|
112
|
+
#
|
113
|
+
# ==== Options (opts)
|
114
|
+
# :message<Hash>::
|
115
|
+
# Messages to pass in url query string as value for "_message"
|
116
|
+
# :permanent<Boolean>::
|
117
|
+
# When true, return status 301 Moved Permanently
|
118
|
+
#
|
119
|
+
# ==== Returns
|
120
|
+
# String:: Explanation of redirect.
|
121
|
+
#
|
122
|
+
# ==== Examples
|
123
|
+
# redirect("/posts/34")
|
124
|
+
# redirect("/posts/34", :message => { :notice => 'Post updated successfully!' })
|
125
|
+
# redirect("http://www.merbivore.com/")
|
126
|
+
# redirect("http://www.merbivore.com/", :permanent => true)
|
127
|
+
def redirect(url, opts = {})
|
128
|
+
default_redirect_options = { :message => nil, :permanent => false }
|
129
|
+
opts = default_redirect_options.merge(opts)
|
130
|
+
if opts[:message]
|
131
|
+
notice = Merb::Request.escape([Marshal.dump(opts[:message])].pack("m"))
|
132
|
+
url = url =~ /\?/ ? "#{url}&_message=#{notice}" : "#{url}?_message=#{notice}"
|
133
|
+
end
|
134
|
+
self.status = opts[:permanent] ? 301 : 302
|
135
|
+
Merb.logger.info("Redirecting to: #{url} (#{self.status})")
|
136
|
+
headers['Location'] = url
|
137
|
+
"<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
|
138
|
+
end
|
139
|
+
|
140
|
+
def message
|
141
|
+
@_message = defined?(@_message) ? @_message : request.message
|
142
|
+
end
|
143
|
+
|
144
|
+
# Sends a file over HTTP. When given a path to a file, it will set the
|
145
|
+
# right headers so that the static file is served directly.
|
146
|
+
#
|
147
|
+
# ==== Parameters
|
148
|
+
# file<String>:: Path to file to send to the client.
|
149
|
+
# opts<Hash>:: Options for sending the file (see below).
|
150
|
+
#
|
151
|
+
# ==== Options (opts)
|
152
|
+
# :disposition<String>::
|
153
|
+
# The disposition of the file send. Defaults to "attachment".
|
154
|
+
# :filename<String>::
|
155
|
+
# The name to use for the file. Defaults to the filename of file.
|
156
|
+
# :type<String>:: The content type.
|
157
|
+
#
|
158
|
+
# ==== Returns
|
159
|
+
# IO:: An I/O stream for the file.
|
160
|
+
def send_file(file, opts={})
|
161
|
+
opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
|
162
|
+
disposition = opts[:disposition].dup || 'attachment'
|
163
|
+
disposition << %(; filename="#{opts[:filename] ? opts[:filename] : File.basename(file)}")
|
164
|
+
headers.update(
|
165
|
+
'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
|
166
|
+
'Content-Disposition' => disposition,
|
167
|
+
'Content-Transfer-Encoding' => 'binary'
|
168
|
+
)
|
169
|
+
File.open(file, 'rb')
|
170
|
+
end
|
171
|
+
|
172
|
+
# Send binary data over HTTP to the user as a file download. May set content type,
|
173
|
+
# apparent file name, and specify whether to show data inline or download as an attachment.
|
174
|
+
#
|
175
|
+
# ==== Parameters
|
176
|
+
# data<String>:: Path to file to send to the client.
|
177
|
+
# opts<Hash>:: Options for sending the data (see below).
|
178
|
+
#
|
179
|
+
# ==== Options (opts)
|
180
|
+
# :disposition<String>::
|
181
|
+
# The disposition of the file send. Defaults to "attachment".
|
182
|
+
# :filename<String>::
|
183
|
+
# The name to use for the file. Defaults to the filename of file.
|
184
|
+
# :type<String>:: The content type.
|
185
|
+
def send_data(data, opts={})
|
186
|
+
opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
|
187
|
+
disposition = opts[:disposition].dup || 'attachment'
|
188
|
+
disposition << %(; filename="#{opts[:filename]}") if opts[:filename]
|
189
|
+
headers.update(
|
190
|
+
'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
|
191
|
+
'Content-Disposition' => disposition,
|
192
|
+
'Content-Transfer-Encoding' => 'binary'
|
193
|
+
)
|
194
|
+
data
|
195
|
+
end
|
196
|
+
|
197
|
+
# Streams a file over HTTP.
|
198
|
+
#
|
199
|
+
# ==== Parameters
|
200
|
+
# opts<Hash>:: Options for the file streaming (see below).
|
201
|
+
# &stream::
|
202
|
+
# A block that, when called, will return an object that responds to
|
203
|
+
# +get_lines+ for streaming.
|
204
|
+
#
|
205
|
+
# ==== Options
|
206
|
+
# :disposition<String>::
|
207
|
+
# The disposition of the file send. Defaults to "attachment".
|
208
|
+
# :type<String>:: The content type.
|
209
|
+
# :content_length<Numeric>:: The length of the content to send.
|
210
|
+
# :filename<String>:: The name to use for the streamed file.
|
211
|
+
#
|
212
|
+
# ==== Examples
|
213
|
+
# stream_file({ :filename => file_name, :type => content_type,
|
214
|
+
# :content_length => content_length }) do |response|
|
215
|
+
# AWS::S3::S3Object.stream(user.folder_name + "-" + user_file.unique_id, bucket_name) do |chunk|
|
216
|
+
# response.write chunk
|
217
|
+
# end
|
218
|
+
# end
|
219
|
+
def stream_file(opts={}, &stream)
|
220
|
+
must_support_streaming!
|
221
|
+
opts.update(Merb::Const::DEFAULT_SEND_FILE_OPTIONS.merge(opts))
|
222
|
+
disposition = opts[:disposition].dup || 'attachment'
|
223
|
+
disposition << %(; filename="#{opts[:filename]}")
|
224
|
+
headers.update(
|
225
|
+
'Content-Type' => opts[:type].strip, # fixes a problem with extra '\r' with some browsers
|
226
|
+
'Content-Disposition' => disposition,
|
227
|
+
'Content-Transfer-Encoding' => 'binary',
|
228
|
+
# Rack specification requires header values to respond to :each
|
229
|
+
'CONTENT-LENGTH' => opts[:content_length].to_s
|
230
|
+
)
|
231
|
+
Proc.new{|response|
|
232
|
+
response.send_status(opts[:content_length])
|
233
|
+
response.send_header
|
234
|
+
stream.call(response)
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
# Uses the nginx specific +X-Accel-Redirect+ header to send a file directly
|
239
|
+
# from nginx. For more information, see the nginx wiki:
|
240
|
+
# http://wiki.codemongers.com/NginxXSendfile
|
241
|
+
#
|
242
|
+
# and the following sample gist:
|
243
|
+
# http://gist.github.com/11225
|
244
|
+
#
|
245
|
+
# there's also example application up on GitHub:
|
246
|
+
#
|
247
|
+
# http://github.com/michaelklishin/nginx-x-accel-redirect-example-application/tree/master
|
248
|
+
#
|
249
|
+
# Unless Content-Disposition is set before calling this method,
|
250
|
+
# it is set to attachment with streamed file name.
|
251
|
+
#
|
252
|
+
# ==== Parameters
|
253
|
+
# path<String>:: Path to file to send to the client.
|
254
|
+
# content_type<String>:: content type header value. By default is set to empty string to let
|
255
|
+
# Nginx detect it.
|
256
|
+
#
|
257
|
+
# ==== Return
|
258
|
+
# One space string.
|
259
|
+
def nginx_send_file(path, content_type = "")
|
260
|
+
# Let Nginx detect content type unless it is explicitly set
|
261
|
+
headers['Content-Type'] = content_type
|
262
|
+
headers["Content-Disposition"] ||= "attachment; filename=#{path.split('/').last}"
|
263
|
+
|
264
|
+
headers['X-Accel-Redirect'] = path
|
265
|
+
|
266
|
+
return ' '
|
267
|
+
end
|
268
|
+
|
269
|
+
# Sets a cookie to be included in the response.
|
270
|
+
#
|
271
|
+
# If you need to set a cookie, then use the +cookies+ hash.
|
272
|
+
#
|
273
|
+
# ==== Parameters
|
274
|
+
# name<~to_s>:: A name for the cookie.
|
275
|
+
# value<~to_s>:: A value for the cookie.
|
276
|
+
# expires<~gmtime:~strftime, Hash>:: An expiration time for the cookie, or a hash of cookie options.
|
277
|
+
# ---
|
278
|
+
# @public
|
279
|
+
def set_cookie(name, value, expires)
|
280
|
+
options = expires.is_a?(Hash) ? expires : {:expires => expires}
|
281
|
+
cookies.set_cookie(name, value, options)
|
282
|
+
end
|
283
|
+
|
284
|
+
# Marks a cookie as deleted and gives it an expires stamp in the past. This
|
285
|
+
# method is used primarily internally in Merb.
|
286
|
+
#
|
287
|
+
# Use the +cookies+ hash to manipulate cookies instead.
|
288
|
+
#
|
289
|
+
# ==== Parameters
|
290
|
+
# name<~to_s>:: A name for the cookie to delete.
|
291
|
+
def delete_cookie(name)
|
292
|
+
set_cookie(name, nil, Merb::Const::COOKIE_EXPIRED_TIME)
|
293
|
+
end
|
294
|
+
|
295
|
+
# Escapes the string representation of +obj+ and escapes it for use in XML.
|
296
|
+
#
|
297
|
+
# ==== Parameter
|
298
|
+
# obj<~to_s>:: The object to escape for use in XML.
|
299
|
+
#
|
300
|
+
# ==== Returns
|
301
|
+
# String:: The escaped object.
|
302
|
+
def escape_xml(obj)
|
303
|
+
Erubis::XmlHelper.escape_xml(obj.to_s)
|
304
|
+
end
|
305
|
+
alias h escape_xml
|
306
|
+
alias escape_html escape_xml
|
307
|
+
|
308
|
+
private
|
309
|
+
# Checks whether streaming is supported by the current Rack adapter.
|
310
|
+
#
|
311
|
+
# ==== Raises
|
312
|
+
# NotImplemented:: The Rack adapter doens't support streaming.
|
313
|
+
def must_support_streaming!
|
314
|
+
unless request.env['rack.streaming']
|
315
|
+
raise(Merb::ControllerExceptions::NotImplemented, "Current Rack adapter does not support streaming")
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|