Skip to content
Browse files

vendor rack-esi

  • Loading branch information...
1 parent 9d1b115 commit 51e1d33524f036e587fa0c529c41dfb27ef18461 @bmizerany committed
View
20 vendor/rack-esi/COPYING.txt
@@ -0,0 +1,20 @@
+Rack::ESI
+
+Copyright (c) 2008 Christoffer Sawicki <christoffer.sawicki@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
32 vendor/rack-esi/README.markdown
@@ -0,0 +1,32 @@
+[ESI]: http://www.w3.org/TR/esi-lang
+[Rack::Cache]: http://tomayko.com/src/rack-cache/
+
+TODO: Improve this text.
+
+# Rack::ESI
+
+Rack::ESI is an implementation of a small (but still very useful!) subset of [ESI][].
+
+It allows you to _easily_ cache everything but the user-customized parts of your dynamic pages without leaving the comfortable world of Ruby when used together with [Ryan Tomayko's Rack::Cache][Rack::Cache].
+
+Development of Rails::ESI has just begun and it is not yet ready for anything but exploration.
+
+## Currently Supported Expressions
+
+* `<esi:include src="..."/>` where `src` is a relative URL to be handled by the Rack application.
+* `<esi:remove>...</esi:remove>`
+* `<esi:comment text="..."/>`
+
+## Examples
+
+ rackup examples/basic_example_application.ru
+
+With [Rack::Cache][]:
+
+ rackup examples/basic_example_application_with_caching.ru
+
+## TODOs and FIXMEs
+
+ rake tasks # Show TODOs and FIXMEs
+ rake tasks:fixme # Show FIXMEs
+ rake tasks:todo # Show TODOs
View
21 vendor/rack-esi/Rakefile
@@ -0,0 +1,21 @@
+namespace("tasks") do
+ desc("Show TODOs")
+ task("todo") do
+ system("ack TODO:")
+ end
+
+ desc("Show FIXMEs")
+ task("fixme") do
+ system("ack FIXME:")
+ end
+end
+
+desc("Show TODOs and FIXMEs")
+task("tasks" => ["tasks:todo", "tasks:fixme"])
+
+require "rake/testtask"
+
+Rake::TestTask.new do |t|
+ t.test_files = FileList["test/test*.rb"]
+ t.verbose = true
+end
View
36 vendor/rack-esi/examples/basic_example_application.rb
@@ -0,0 +1,36 @@
+require "rack"
+
+class BasicExampleApplication
+ def initialize
+ @map = initialize_map
+ end
+
+ def call(env)
+ @map.call(env)
+ end
+
+ private
+
+ def initialize_map
+ Rack::URLMap.new({
+ "/" => method(:index),
+ "/header" => method(:header)
+ })
+ end
+
+ def index(env)
+ sleep(2) # A heavy computation...
+
+ body = %{
+ <title>BasicExampleApplication</title>
+ <esi:include src="/header"/>
+ <p>Welcome!</p>
+ }.gsub(/^\s*/, "").strip
+ [200, {"Content-Type" => "text/html", "Cache-Control" => "public, max-age=10"}, [body]]
+ end
+
+ def header(env)
+ body = %{<p>#{Time.now} &ndash; You're not logged in. <a href="#">Click here to login!</a></p>}
+ [200, {"Content-Type" => "text/html", "Cache-Control" => "private, max-age=0, must-revalidate"}, [body]]
+ end
+end
View
14 vendor/rack-esi/examples/basic_example_application.ru
@@ -0,0 +1,14 @@
+require "pathname"
+
+$LOAD_PATH.unshift(Pathname(__FILE__).expand_path.dirname)
+$LOAD_PATH.unshift(Pathname(__FILE__).expand_path.dirname.parent.join("lib"))
+
+require "rack/esi"
+require "basic_example_application"
+
+use Rack::ShowExceptions
+use Rack::ESI
+use Rack::CommonLogger
+
+use Rack::ContentLength
+run BasicExampleApplication.new
View
20 vendor/rack-esi/examples/basic_example_application_with_caching.ru
@@ -0,0 +1,20 @@
+require "pathname"
+
+$LOAD_PATH.unshift(Pathname(__FILE__).expand_path.dirname)
+$LOAD_PATH.unshift(Pathname(__FILE__).expand_path.dirname.parent.join("lib"))
+
+require "rack/esi"
+require "rack/cache"
+require "basic_example_application"
+
+use Rack::ShowExceptions
+use Rack::ESI
+use Rack::CommonLogger
+
+use Rack::Cache,
+ :verbose => true,
+ :metastore => "heap:/",
+ :entitystore => "heap:/"
+
+use Rack::ContentLength
+run BasicExampleApplication.new
View
52 vendor/rack-esi/lib/rack/esi.rb
@@ -0,0 +1,52 @@
+require "rack"
+require "hpricot"
+
+class Rack::ESI
+ class Error < ::RuntimeError
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ status, headers, enumerable_body = original_response = @app.call(env)
+
+ return original_response unless headers["Content-Type"].to_s.match(/(ht|x)ml/) # FIXME: Use another pattern
+
+ body = process_body(enumerable_body)
+
+ return original_response unless body.include?("<esi:")
+
+ xml = Hpricot.XML(body)
+
+ xml.search("esi:include") do |include_element|
+ if src = include_element["src"]
+ path_info = src # TODO: Rewrite the URL to allow more than absolute paths
+ inclusion_env = env.merge("PATH_INFO" => path_info) # TODO: Do something with SCRIPT_NAME/REQUEST_PATH/REQUEST_URI
+ data = process_body(@app.call(inclusion_env)[2]) # FIXME: Check the status
+ new_element = Hpricot::Text.new(data)
+ include_element.parent.replace_child(include_element, new_element)
+ else
+ raise Error, "<esi:include .../> element without @src"
+ end
+ end
+
+ xml.search("esi:remove").remove
+
+ xml.search("esi:comment").remove
+
+ processed_body = xml.to_s
+ processed_headers = headers.merge("Content-Length" => processed_body.size.to_s)
+
+ [status, processed_headers, [processed_body]]
+ end
+
+ private
+
+ def process_body(enumerable_body)
+ parts = []
+ enumerable_body.each { |part| parts << part }
+ return parts.join("")
+ end
+end
View
102 vendor/rack-esi/test/test_rack_esi.rb
@@ -0,0 +1,102 @@
+require "pathname"
+
+$LOAD_PATH.unshift(Pathname(__FILE__).expand_path.dirname)
+$LOAD_PATH.unshift(Pathname(__FILE__).expand_path.dirname.parent.join("lib"))
+
+require "test/unit"
+require "rack/esi"
+
+class TestRackESI < Test::Unit::TestCase
+ def test_response_passthrough
+ mock_app = const([200, {}, ["Hei!"]])
+ esi_app = Rack::ESI.new(mock_app)
+
+ assert_same_response(mock_app, esi_app)
+ end
+
+ def test_xml_response_passthrough
+ mock_app = const([200, {"Content-Type" => "text/xml"}, ["<p>Hei!</p>"]])
+ esi_app = Rack::ESI.new(mock_app)
+
+ assert_same_response(mock_app, esi_app)
+ end
+
+ def test_respect_for_content_type
+ mock_app = const([200, {"Content-Type" => "application/x-y-z"}, ["<esi:include src='/header'/><p>Hei!</p>"]])
+ esi_app = Rack::ESI.new(mock_app)
+
+ assert_same_response(mock_app, esi_app)
+ end
+
+ def test_include
+ app = Rack::URLMap.new({
+ "/" => const([200, {"Content-Type" => "text/xml"}, ["<esi:include src='/header'/>, Index"]]),
+ "/header" => const([200, {"Content-Type" => "text/xml"}, ["Header"]])
+ })
+
+ esi_app = Rack::ESI.new(app)
+
+ expected_body = ["Header, Index"]
+
+ actual_body = esi_app.call("SCRIPT_NAME" => "", "PATH_INFO" => "/")[2]
+
+ assert_equal(expected_body, actual_body)
+ end
+
+ def test_invalid_include_element_exception
+ mock_app = const([200, {"Content-Type" => "text/xml"}, ["<esi:include/>"]])
+ esi_app = Rack::ESI.new(mock_app)
+
+ assert_raise Rack::ESI::Error do
+ esi_app.call({})
+ end
+ end
+
+ def test_remove
+ mock_app = const([200, {"Content-Type" => "text/xml"}, ["<p>Hei! <esi:remove>Hei! </esi:remove>Hei!</p>"]])
+
+ esi_app = Rack::ESI.new(mock_app)
+
+ expected_body = ["<p>Hei! Hei!</p>"]
+
+ actual_body = esi_app.call("SCRIPT_NAME" => "", "PATH_INFO" => "/")[2]
+
+ assert_equal(expected_body, actual_body)
+ end
+
+ def test_comment
+ mock_app = const([200, {"Content-Type" => "text/xml"}, ["<p>(<esi:comment text='*'/>)</p>"]])
+
+ esi_app = Rack::ESI.new(mock_app)
+
+ expected_body = ["<p>()</p>"]
+
+ actual_body = esi_app.call("SCRIPT_NAME" => "", "PATH_INFO" => "/")[2]
+
+ assert_equal(expected_body, actual_body)
+ end
+
+ def test_setting_of_content_length
+ mock_app = const([200, {"Content-Type" => "text/html"}, ["Osameli. <esi:comment text='*'/>"]])
+
+ esi_app = Rack::ESI.new(mock_app)
+
+ response = esi_app.call("SCRIPT_NAME" => "", "PATH_INFO" => "/")
+
+ assert_equal("9", response[1]["Content-Length"])
+ end
+
+ private
+
+ def const(value)
+ lambda { |*_| value }
+ end
+
+ def assert_same_response(a, b)
+ x = a.call({})
+ y = b.call({})
+
+ assert_equal(x, y)
+ assert_equal(x.object_id, y.object_id)
+ end
+end

0 comments on commit 51e1d33

Please sign in to comment.
Something went wrong with that request. Please try again.