Run this script locally, and point your browser at http://localhost:3000/said to try it out.

Download Code


#!/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