Created
June 15, 2012 03:59
-
-
Save nbibler/2934602 to your computer and use it in GitHub Desktop.
A demo of using a testing beacon to determine body manipulation and state.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rack' | |
module Rack | |
# This would be Rack::Lint. The demonstrable functionality here is to call | |
# up the stack, then inspect the env['rack.linkbeacon'] object to determine | |
# whether or not it was left in an unclosed state. | |
# | |
class LintDemo | |
def initialize(app) | |
@app = app | |
end | |
def call(env) | |
status, headers, body = @app.call(env) | |
if env['rack.lintbeacon'].closed? | |
puts "Good." | |
else | |
puts "OMG FAIL." | |
end | |
[status, headers, body] | |
end | |
end | |
# This basically wraps the body it receives with a proxy that acts as a | |
# beacon. The beacon watches to see if the body is acted upon and attempt | |
# to determine whether the user's middleware (that sits between LintDemo and | |
# LintBeacon) unintentionally modified or left the body object unclosed. | |
# | |
class LintBeacon | |
attr_reader :beacon | |
def initialize(app) | |
@app = app | |
end | |
def call(env) | |
status, headers, body = @app.call(env) | |
@beacon = Beacon.new(body) | |
env['rack.lintbeacon'] = @beacon | |
[status, headers, @beacon] | |
end | |
end | |
# The beacon proxy which wraps the response body. | |
# | |
class Beacon | |
def initialize(body) | |
@closed = false | |
@body = body | |
end | |
def close | |
@closed = true | |
@body.close if @body.respond_to?(:close) | |
end | |
def closed? | |
@closed || !@operated_upon | |
end | |
private | |
def method_missing(method, *args, &block) | |
@closed = false # consider any operation to be a re-open | |
@operated_upon = true | |
@body.send(method, *args, &block) | |
end | |
end | |
end | |
# This is a middleware which does not modify the response objects. | |
# | |
class MyGoodMiddleware | |
def initialize(app) | |
@app = app | |
end | |
def call(env) | |
@app.call(env) | |
end | |
end | |
stack = Rack::Builder.new do | |
use Rack::LintDemo | |
use MyGoodMiddleware | |
use Rack::LintBeacon | |
run ->(env) { [200, {'Content-Type' => 'text/plain'}, ['Success']] } | |
end | |
env = Rack::MockRequest.env_for("http://test.local/") | |
stack.call(env) | |
# => "Good." | |
# This is a middleware which reads the response body without closing it. | |
# | |
class MyBadMiddleware | |
def initialize(app) | |
@app = app | |
end | |
def call(env) | |
status, headers, body = @app.call(env) | |
body.each {} | |
[status, headers, body] | |
end | |
end | |
stack = Rack::Builder.new do | |
use Rack::LintDemo | |
use MyBadMiddleware | |
use Rack::LintBeacon | |
run ->(env) { [200, {'Content-Type' => 'text/plain'}, ['Success']] } | |
end | |
env = Rack::MockRequest.env_for("http://test.local/") | |
stack.call(env) | |
# => "OMG FAIL." | |
# This is a middleware which reads the response body but then closes it before | |
# returning. | |
# | |
class MyAcceptableMiddleware | |
def initialize(app) | |
@app = app | |
end | |
def call(env) | |
status, headers, body = @app.call(env) | |
body.each {} | |
body.close if body.respond_to?(:close) | |
[status, headers, body] | |
end | |
end | |
stack = Rack::Builder.new do | |
use Rack::LintDemo | |
use MyAcceptableMiddleware | |
use Rack::LintBeacon | |
run ->(env) { [200, {'Content-Type' => 'text/plain'}, ['Success']] } | |
end | |
env = Rack::MockRequest.env_for("http://test.local/") | |
stack.call(env) | |
# => "Good." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment