rack_after_reply 0.0.1

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 ADDED
@@ -0,0 +1,3 @@
1
+ == 0.0.1 2011-05-10
2
+
3
+ * Hi.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) George Ogata
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,46 @@
1
+ # Rack After Reply
2
+
3
+ A hook for Rack apps which fires after the response has been sent, and
4
+ the socket to the client has been closed.
5
+
6
+ This is the ideal time to perform delayable, non-backgroundable tasks,
7
+ such as garbage collection, stats gathering, flushing logs, etc.
8
+ without affecting response times at all.
9
+
10
+ ## Usage
11
+
12
+ Simply add your callbacks to `env['rack_after_reply.callbacks']`.
13
+
14
+ use Rack::ContentLength
15
+ use Rack::ContentType, 'text/plain'
16
+ run lambda { |env|
17
+ env['rack_after_reply.callbacks'] << lambda { ... }
18
+ [200, {}, ['hi']]
19
+ }
20
+
21
+ ## Support
22
+
23
+ Rack After Request works with these web servers:
24
+
25
+ * [Mongrel](https://github.com/fauna/mongrel)
26
+ * [Passenger](http://www.modrails.com)
27
+ * [Thin](https://github.com/macournoyer/thin)
28
+ * [Unicorn](http://unicorn.bogomips.org)
29
+ * WEBrick (distributed with Ruby)
30
+
31
+ To request support for other web servers, [open a ticket][issues] or
32
+ submit a patch.
33
+
34
+ [issues]: http://github.com/oggy/rack_after_reply/issues
35
+
36
+ ## Contributing
37
+
38
+ * [Bug reports](https://github.com/oggy/rack_after_reply/issues)
39
+ * [Source](https://github.com/oggy/rack_after_reply)
40
+ * Patches: Fork on Github, send pull request.
41
+ * Ensure patch includes tests.
42
+ * Leave the version alone, or bump it in a separate commit.
43
+
44
+ ## Copyright
45
+
46
+ Copyright (c) George Ogata. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'ritual'
@@ -0,0 +1,39 @@
1
+ require 'rack'
2
+
3
+ module RackAfterReply
4
+ CALLBACKS_KEY = 'rack_after_reply.callbacks'.freeze
5
+
6
+ autoload :AppProxy, 'rack_after_reply/app_proxy'
7
+ autoload :Adapter, 'rack_after_reply/adapter'
8
+ autoload :RequestHandler, 'rack_after_reply/request_handler'
9
+
10
+ class << self
11
+ #
12
+ # Apply extensions for all loaded web servers.
13
+ #
14
+ def apply
15
+ Adapter::Thin.apply if defined?(::Thin)
16
+ Adapter::Mongrel.apply if defined?(::Mongrel)
17
+ Adapter::Passenger.apply if defined?(::PhusionPassenger)
18
+ Adapter::WEBrick.apply if defined?(::WEBrick)
19
+ Adapter::Unicorn.apply if defined?(::Unicorn)
20
+ end
21
+
22
+ def freedom_patch(mod, method) # :nodoc:
23
+ # Prevent infinite recursion if we've already done it.
24
+ return if mod.method_defined?("#{method}_without_rack_after_reply")
25
+
26
+ mod.module_eval do
27
+ alias_method "#{method}_without_rack_after_reply", method
28
+ alias_method method, "#{method}_with_rack_after_reply"
29
+ end
30
+ end
31
+
32
+ def freedom_extend(object, method) # :nodoc:
33
+ klass = (class << object; self; end)
34
+ freedom_patch(klass, method)
35
+ end
36
+ end
37
+ end
38
+
39
+ RackAfterReply.apply
@@ -0,0 +1,10 @@
1
+ module RackAfterReply
2
+ module Adapter
3
+ autoload :Base, 'rack_after_reply/adapter/base'
4
+ autoload :Mongrel, 'rack_after_reply/adapter/mongrel'
5
+ autoload :Passenger, 'rack_after_reply/adapter/passenger'
6
+ autoload :Thin, 'rack_after_reply/adapter/thin'
7
+ autoload :Unicorn, 'rack_after_reply/adapter/unicorn'
8
+ autoload :WEBrick, 'rack_after_reply/adapter/webrick'
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module RackAfterReply
2
+ module Adapter
3
+ class Base
4
+ def self.apply
5
+ return if defined?(@applied)
6
+ instance.apply
7
+ @applied = true
8
+ end
9
+
10
+ def self.instance
11
+ @instance ||= new
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ module RackAfterReply
2
+ module Adapter
3
+ class Mongrel < Base
4
+ def apply
5
+ Rack::Handler::Mongrel.module_eval do
6
+ include RackAfterReply::RequestHandler
7
+
8
+ def initialize_with_rack_after_reply(app)
9
+ app = AppProxy.new(self, app)
10
+ initialize_without_rack_after_reply(app)
11
+ end
12
+ RackAfterReply.freedom_patch self, :initialize
13
+
14
+ def process_with_rack_after_reply(request, response)
15
+ process_without_rack_after_reply(request, response)
16
+ ensure
17
+ response.socket.close
18
+ fire_rack_after_reply
19
+ end
20
+ RackAfterReply.freedom_patch self, :process
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module RackAfterReply
2
+ module Adapter
3
+ class Passenger < Base
4
+ def apply
5
+ PhusionPassenger::Rack::RequestHandler.module_eval do
6
+ include RackAfterReply::RequestHandler
7
+
8
+ def initialize_with_rack_after_reply(owner_pipe, app, options = {})
9
+ app = AppProxy.new(self, app)
10
+ initialize_without_rack_after_reply(owner_pipe, app, options)
11
+ end
12
+ RackAfterReply.freedom_patch self, :initialize
13
+
14
+ def accept_and_process_next_request_with_rack_after_reply(socket_wrapper, channel, buffer)
15
+ accept_and_process_next_request_without_rack_after_reply(socket_wrapper, channel, buffer)
16
+ fire_rack_after_reply
17
+ end
18
+ RackAfterReply.freedom_patch self, :accept_and_process_next_request
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module RackAfterReply
2
+ module Adapter
3
+ class Thin < Base
4
+ def apply
5
+ ::Thin::Connection.module_eval do
6
+ def pre_process_with_rack_after_reply
7
+ callbacks = []
8
+ @request.env[RackAfterReply::CALLBACKS_KEY] = callbacks
9
+ EM.next_tick { callbacks.each {|c| c.call} }
10
+ pre_process_without_rack_after_reply
11
+ end
12
+ RackAfterReply.freedom_patch self, :pre_process
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module RackAfterReply
2
+ module Adapter
3
+ class Unicorn < Base
4
+ def apply
5
+ ::Unicorn::HttpServer.module_eval do
6
+ include RackAfterReply::RequestHandler
7
+
8
+ def process_client_with_rack_after_reply(client)
9
+ # We can't install the AppProxy in #initialize, because
10
+ # the HttpServer is already instantiated by the time we
11
+ # typically run. Wrap it here exactly once.
12
+ self.app = AppProxy.new(self, app) unless @rack_after_reply_wrapped
13
+ @rack_after_reply_wrapped = true
14
+
15
+ process_client_without_rack_after_reply(client)
16
+ fire_rack_after_reply
17
+ end
18
+ RackAfterReply.freedom_patch(self, :process_client)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,47 @@
1
+ module RackAfterReply
2
+ module Adapter
3
+ class WEBrick < Base
4
+ def apply
5
+ # Rack::Handler::WEBrick#service returns before the socket is closed,
6
+ # and if we close it ourselves, WEBrick will close it again causing a
7
+ # bomb. We can access the socket through the response argument, though,
8
+ # so we hook into its #close method.
9
+ Rack::Handler::WEBrick.module_eval do
10
+ include RackAfterReply::RequestHandler
11
+
12
+ def initialize_with_rack_after_reply(server, app)
13
+ app = AppProxy.new(self, app)
14
+ initialize_without_rack_after_reply(server, app)
15
+ end
16
+ RackAfterReply.freedom_patch self, :initialize
17
+
18
+ def service_with_rack_after_reply(request, response)
19
+ response.extend ResponseExtension
20
+ response.rack_after_reply_handler = self
21
+ service_without_rack_after_reply(request, response)
22
+ end
23
+ RackAfterReply.freedom_patch self, :service
24
+ end
25
+ end
26
+
27
+ module ResponseExtension
28
+ def send_response(socket)
29
+ socket.extend SocketExtension
30
+ socket.rack_after_reply_handler = rack_after_reply_handler
31
+ super
32
+ end
33
+
34
+ attr_accessor :rack_after_reply_handler
35
+ end
36
+
37
+ module SocketExtension
38
+ def close
39
+ super
40
+ rack_after_reply_handler.fire_rack_after_reply
41
+ end
42
+
43
+ attr_accessor :rack_after_reply_handler
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ module RackAfterReply
2
+ #
3
+ # Wraps a Rack app to intercept the rack environment passed to #call
4
+ # for access by the request handler after the socket is closed.
5
+ #
6
+ class AppProxy
7
+ def initialize(request_handler, app)
8
+ @request_handler = request_handler
9
+ @app = app
10
+ end
11
+
12
+ def call(env)
13
+ callbacks = []
14
+ env[RackAfterReply::CALLBACKS_KEY] = callbacks
15
+ @request_handler.rack_after_reply_callbacks = callbacks
16
+ @app.call(env)
17
+ end
18
+
19
+ def method_missing(name, *args, &block)
20
+ class_eval <<-EOS
21
+ def #{name}(*args, &block)
22
+ @app.#{name}(*args, &block)
23
+ end
24
+ EOS
25
+ send(name, *args, &block)
26
+ end
27
+
28
+ def respond_to?(name, include_private=false)
29
+ super || @app.respond_to?(name, include_private)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,18 @@
1
+ module RackAfterReply
2
+ module RequestHandler
3
+ attr_accessor :rack_after_reply_callbacks
4
+
5
+ def fire_rack_after_reply
6
+ # Ensure we only fire the hook once. Passenger runs its request
7
+ # handler when shutting down, causing an infinite loop if we
8
+ # don't check for this.
9
+ rack_after_reply_callbacks or
10
+ return
11
+
12
+ rack_after_reply_callbacks.each do |callback|
13
+ callback.call
14
+ end
15
+ self.rack_after_reply_callbacks = nil
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ module RackAfterReply
2
+ VERSION = [0, 0, 1]
3
+
4
+ class << VERSION
5
+ include Comparable
6
+
7
+ def to_s
8
+ join('.')
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack_after_reply
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - George Ogata
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-05-10 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email:
23
+ - george.ogata@gmail.com
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - LICENSE
30
+ - README.markdown
31
+ files:
32
+ - lib/rack_after_reply/adapter/base.rb
33
+ - lib/rack_after_reply/adapter/mongrel.rb
34
+ - lib/rack_after_reply/adapter/passenger.rb
35
+ - lib/rack_after_reply/adapter/thin.rb
36
+ - lib/rack_after_reply/adapter/unicorn.rb
37
+ - lib/rack_after_reply/adapter/webrick.rb
38
+ - lib/rack_after_reply/adapter.rb
39
+ - lib/rack_after_reply/app_proxy.rb
40
+ - lib/rack_after_reply/request_handler.rb
41
+ - lib/rack_after_reply/version.rb
42
+ - lib/rack_after_reply.rb
43
+ - LICENSE
44
+ - README.markdown
45
+ - Rakefile
46
+ - CHANGELOG
47
+ has_rdoc: true
48
+ homepage: http://github.com/oggy/rack_after_reply
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --charset=UTF-8
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ segments:
62
+ - 0
63
+ version: "0"
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ segments:
70
+ - 1
71
+ - 3
72
+ - 6
73
+ version: 1.3.6
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.7
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Rack hook which fires after the socket to the client is closed.
81
+ test_files: []
82
+