Skip to content

Instantly share code, notes, and snippets.

@karmi
Created June 3, 2010 10:57
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save karmi/423744 to your computer and use it in GitHub Desktop.
Save karmi/423744 to your computer and use it in GitHub Desktop.
Code for Rack middleware talks
.DS_Store
*.log
# OMG! Changing !core! Rails class!
#
class ActionDispatch::Response
# Patch Response object so it behaves like an Array
def << part
body_parts << part
end unless defined? ActionDispatch::Response.<<
# Redefine +to_s+ so it returns body, not #<ActionDispatch::Response:0x...>
def to_s
body_parts.join('')
end
end
require 'rubygems'
require 'sinatra'
module Rack
# Put response time information into the response body
# See also http://github.com/ryanb/railscasts-episodes/blob/master/episode-151/store/lib/response_timer.rb
#
class MyTimingMiddleware
def initialize(app)
@app = app
end
def call(env)
started = Time.now
status, headers, body = @app.call(env)
p "BEFORE", [status, headers, body]
# THIS IS OUR MIDDLEWARE ---------------------------------------------->
body << "<p><small>Processed in: #{Time.now-started} seconds<small></p>"
headers['Content-Length'] = Rack::Utils.bytesize(body.to_s).to_s
# <---------------------------------------------------------------------
p "AFTER", [status, headers, body]
[status, headers, body]
end
end
# Return 503 every odd second
#
class MyBusyMiddleware
def initialize(app)
@app = app
end
def call(env)
if Time.now.sec % 2 == 0
@app.call(env)
else
# THIS IS OUR MIDDLEWARE ----------------------------------------------------->
[503, { 'Content-Type' => 'text/html' }, ["I am busy on odd seconds, dude..."]]
# <----------------------------------------------------------------------------
end
end
end
end
# Try to swap the `use ...` lines
use Rack::MyBusyMiddleware
use Rack::MyTimingMiddleware
get '/' do
sleep 0.5 # Processing...
"Hello, World!" # ...response
end
module Rack
class MyTimingMiddleware
def initialize(app)
@app = app
end
def call(env)
started = Time.now
status, headers, body = @app.call(env)
p "BEFORE", [status, headers, body]
# THIS IS OUR MIDDLEWARE ---------------------------------------------->
body << "<p><small>Processed in: #{Time.now-started} seconds<small></p>"
headers['Content-Length'] = Rack::Utils.bytesize(body.to_s).to_s
# <---------------------------------------------------------------------
p "AFTER", [status, headers, body]
[status, headers, body]
end
end
end
require 'rubygems'
require 'rack'
# Run this file and try `curl -i http://localhost:8080`
app = proc { |env|
[
200,
{'Content-Type' => 'text/plain'},
["Hello World!"]
# 1/0 # Boom! (UNCOMMENT TO SHOW EXCEPTION)
]
}
# Build the middleware stack
# See http://rack.rubyforge.org/doc/Rack/Builder.html for more info.
stack = Rack::Builder.app do
use Rack::CommonLogger
use Rack::ShowExceptions
use Rack::ETag
run app
end
Rack::Handler::Thin.run stack
require 'rubygems'
require 'rack'
# A valid Rack application is anything which responds to `call` method, which takes a Hash
# as an argument and returns an array of `[status, headers, body]`.
#
# See http://rack.rubyforge.org/doc/SPEC.html for more info.
#
# Run this file and try `curl -i http://localhost:8080`
app = proc { |env|
[
200, # Status
{'Content-Type' => 'text/plain'}, # Headers
["Hello World!"] # Body
]
}
# See http://github.com/rack/rack/blob/master/lib/rack/handler/thin.rb
Rack::Handler::Thin.run app
# Source:
#
# http://gist.github.com/235097
# http://coderack.org/users/techiferous/middlewares/89-rackspellcheck
#
#
# If you'd like this packaged up as a gem, send me a note. You can get
# in touch with me at http://www.techiferous.com/about
require 'nokogiri'
require 'ispell'
module Rack
class SpellCheck
def initialize(app, options = {})
@app = app
@options = options
# The way ispell parses words means that it's hard to skip URLs.
# Ignoring these "words" eases the pain somewhat.
@ignore = ["www", "com", "http", "org", "html"]
if options[:ignore].is_a? Array
@ignore += options[:ignore].map(&:downcase)
end
end
def call(env)
@doc = nil
@request = Rack::Request.new(env)
status, @headers, @body = @app.call(env)
if html?
find_spelling_errors
highlight_spelling_errors
update_content_length
end
[status, @headers, @body]
end
private
def find_spelling_errors
@speller = Ispell.new('ispell', 'english') # note: we are forking a process
doc.at_css("body").traverse do |node|
if node.text?
node.content = spellcheck(node.content)
end
end
@speller.destroy! # stop the process
@body = doc.to_html
end
def highlight_spelling_errors
style = "background-color: yellow; color: red; font-weight: bold;"
@body.gsub!('changethistobeginningtaglater', "<span style=\"#{style}\">")
@body.gsub!('changethistoendingtaglater', '</span>')
end
def spellcheck(text)
results = @speller.spellcheck(text)
new_text = text
results.each do |res|
case res.type
when :miss, :guess, :none
unless @ignore.include?(res.original.downcase)
new_text.gsub!(res.original,
"changethistobeginningtaglater#{res.original}changethistoendingtaglater")
end
end
end
new_text
end
def html?
@headers["Content-Type"] && @headers["Content-Type"].include?("text/html")
end
def doc
@doc ||= Nokogiri::HTML(body_to_string)
end
def body_to_string
s = ""
@body.each { |x| s << x }
s
end
def update_content_length
@headers['Content-Length'] = Rack::Utils.bytesize(@body).to_s
end
end
end
# Run this file with `rails myapp -m rails-middleware-example-template.rb`
# A new Rails app is generated in the same directory
# Set up .gitignore files
log 'creating', '.gitignore files'
run "touch tmp/.gitignore log/.gitignore vendor/.gitignore"
file '.gitignore', <<-END
.DS_Store
*/.DS_Store
log/*
!log/.gitignore
tmp/**/*
!tmp/.gitignore
config/database.yml
db/*.sqlite3
doc/**/*
public/system/**/*
END
log 'initializing', 'Git repository'
git :init
git :add => '.'
git :commit => "-a -m 'Initial commit -- Blank Rails application'"
log 'adding', 'Middleware'
run 'cp ../my_timing_middleware.rb lib/'
run 'cp ../actiondispatch-response-patch.rb lib/'
git :add => '.'
git :commit => "-m 'Added MyTimingMiddleware and ActionDispatch::Response patch'"
log 'configuring', 'Middleware'
inject_into_file "config/environments/development.rb", :after => "Application.configure do\n" do
<<-CONFIG
require 'lib/actiondispatch-response-patch'
require 'lib/my_timing_middleware'
config.middleware.use Rack::MyTimingMiddleware
CONFIG
end
git :add => '.'
git :commit => "-m 'Configured MyTimingMiddleware for development'"
generate(:scaffold, "person", "name:string", "address:text")
git :add => '.'
git :commit => "-m 'Added Person resource'"
rake "db:create"
rake "db:migrate"
log ''
log ' *** ALL DONE ***'
require 'rubygems'
require 'rack-debug/tasks'
require 'rubygems'
require 'sinatra'
# UNCOMMENT FOR DEMO ---------------------------------------------->
# require 'rack-spell_check' # Require the middleware file
# use Rack::SpellCheck # Insert the middleware into stack
# <-----------------------------------------------------------------
get '/' do
"I jsut cnat spell some wrods correctly..."
end
require 'rubygems'
require 'sinatra'
set :clean_trace, false
use Rack::ShowExceptions
# http://github.com/ddollar/rack-debug
require 'rack/debug'
use Rack::Debug, :socket_path => '/tmp/rack-debug'
get '/' do
@one = 1
@two = 2
debugger
"#{@one} is not #{@two}"
end
# = INSTRUCTIONS =
#
# 1. Run the app normally (`ruby sinatra-debugger.rb`)
# 2. Launch the console with Rake task: SOCKET_PATH=/tmp/rack-debug rake debug
# 3. Load http://localhost:4567/
# 4. Watch the app hang
# 5. Now you can inspect your app (`l`, `n`, etc), print out variables (`p @one`),
# eval some code (`eval @one=2`), or continue the execution (`c`) when you're finished
# 6. Type `help` or check out http://pivotallabs.com/users/chad/blog/articles/366-ruby-debug-in-30-seconds-we-don-t-need-no-stinkin-gui-
# for info about ruby-debug console shortcuts
require 'rubygems'
require 'sinatra'
use Rack::Auth::Basic do |username, password|
[username, password] == ['admin', 'admin']
end
get '/' do
"Welcome, admin!"
end
require 'rubygems'
require 'sinatra'
# http://tomayko.com/src/rack-cache
# http://www.mnot.net/cache_docs/#CACHE-CONTROL
require 'rack/cache'
use Rack::Cache, :verbose => true,
:metastore => "heap:/",
:entitystore => "heap:/"
# Also: file:, redis: with http://github.com/jodosha/redis-store
# Set expiration header
after do
expires 15, :public
end
get '/' do
puts " !!! APP HIT !!! "
"Welcome!"
end
require 'rubygems'
require 'sinatra'
enable :sessions
# Simple request counter to keep track of number of requests
# HOMEWORK: Why we can't set this in Sinatra app before filter?
class RequestCounter
def initialize(app); @app=app; end;
def call(env)
env['rack.session'][:counter] ||= 0
env['rack.session'][:counter] += 1
@app.call(env)
end
end
use RequestCounter
# http://github.com/datagraph/rack-throttle
require 'rack/throttle'
use Rack::Throttle::Hourly, :max => 10
use Rack::Throttle::Interval, :min => 2
get '/' do
puts " !!! APP HIT !!! "
"You already did #{session[:counter]} successful request(s)"
end
require 'rubygems'
require 'sinatra'
use Rack::ShowExceptions
get '/' do
raise "Ooops!"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment