Run this script locally, and point your browser at http://localhost:3000/said to try it out.
#!/usr/bin/env ruby
require 'rubygems'
require 'mongrel'
class SaidHandler < Mongrel::HttpHandler
def process(request, response)
@request, @params = request, nil
response.start(200) do |head, out|
head["Content-Type"] = "text/html"
out.write action
end
end
def action
if (id = params['cont_id']) && @cont[id]
block, @b = @cont[id]
return block.call
end
said
end
def params
unless @params
@params = @request.class.query_parse(@request.params['QUERY_STRING'])
if @request.params['REQUEST_METHOD'] == "POST"
@request.class.query_parse(@request.body.read).each { |key, value| @params[key] = value }
end
end
@params
end
def arg(key)
(@b || params)[key.to_s]
end
def said
aform(input("foo"), submit) {
w_link("click here") {
"you said: #{arg :foo}"}}
end
def aform(*args, &block)
"<form method=\"post\"><input type=\"hidden\" name=\"cont_id\" value=\"#{make_cont(block)}\">#{args.join}</form>"
end
def input(*args)
"<input name=\"#{args.join}\" />"
end
def w_link(*args, &block)
"<a href=\"?cont_id=#{make_cont(block)}\">#{args.join}</a>"
end
def submit
'<input type="submit" />'
end
def make_cont(block)
@cont ||= {}
id = loop { x = rand(100).to_s; break(x) unless @cont.keys.include?(x) }
@cont[id] = [block, params]
id
end
end
h = Mongrel::HttpServer.new("0.0.0.0", "3000")
h.register("/said", SaidHandler.new)
h.run.join
This can be done more cleanly using true continuations (callcc), but they aren't compatible with threading (used by mongrel). For this example, I have faked it by storing the request params with each proc, rather than the whole stack binding, but true continuations can be used by replacing make_cont:
def make_cont(block)
cc = create_cc { block.call }
@cont ||= {}
id = loop { x = rand(100).to_s; break(x) unless @cont.keys.include?(x) }
@cont[id] = cc
id
end
def create_cc
callcc { |cc| return cc }
yield
end