esi-for-rack 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. data/README.rdoc +34 -0
  2. data/Rakefile +34 -0
  3. data/VERSION +1 -0
  4. data/lib/esi_for_rack.rb +81 -0
  5. data/lib/esi_for_rack/lookup.rb +105 -0
  6. data/lib/esi_for_rack/node.rb +161 -0
  7. data/spec/http_integration/accept_language_spec.rb +23 -0
  8. data/spec/http_integration/cookie_spec.rb +23 -0
  9. data/spec/http_integration/fixtures/accept_language/1.html +12 -0
  10. data/spec/http_integration/fixtures/cookie/1.html +3 -0
  11. data/spec/http_integration/fixtures/query_string/1.html +3 -0
  12. data/spec/http_integration/fixtures/user_agent/1.html +5 -0
  13. data/spec/http_integration/query_string_spec.rb +22 -0
  14. data/spec/http_integration/user_agent_spec.rb +23 -0
  15. data/spec/spec.opts +7 -0
  16. data/spec/spec_helper.rb +25 -0
  17. data/spec/tags/choose_spec.rb +18 -0
  18. data/spec/tags/comment_spec.rb +9 -0
  19. data/spec/tags/fixtures/choose/simple1.html +8 -0
  20. data/spec/tags/fixtures/choose/simple2.html +11 -0
  21. data/spec/tags/fixtures/choose/simple3.html +8 -0
  22. data/spec/tags/fixtures/comment/simple.html +1 -0
  23. data/spec/tags/fixtures/include/alt.html +3 -0
  24. data/spec/tags/fixtures/include/src.html +3 -0
  25. data/spec/tags/fixtures/include/src_continue.html +3 -0
  26. data/spec/tags/fixtures/nested/complex.html +12 -0
  27. data/spec/tags/fixtures/try/include.html +6 -0
  28. data/spec/tags/fixtures/try/malformed_attempt.html +5 -0
  29. data/spec/tags/fixtures/try/malformed_except.html +3 -0
  30. data/spec/tags/fixtures/vars/simple.html +1 -0
  31. data/spec/tags/include_spec.rb +25 -0
  32. data/spec/tags/nested_spec.rb +13 -0
  33. data/spec/tags/try_spec.rb +28 -0
  34. data/spec/tags/vars_spec.rb +9 -0
  35. metadata +137 -0
data/README.rdoc ADDED
@@ -0,0 +1,34 @@
1
+ = EsiForRack
2
+
3
+ == What is ESI?
4
+
5
+ ESI is a standard way to assemble pages. This spec is supported by several reverse proxies.
6
+
7
+ http://slideshare.net/joshbuddy/to-the-edge-of-web-performance-and-beyond
8
+
9
+ == Usage
10
+
11
+ In your builder, just use it.
12
+
13
+ use EsiForRack
14
+
15
+ In your HTTP responses, just normal ESI tags. If you're working within Rails, give Spackle a try.
16
+
17
+ http://github.com/joshbuddy/spackle
18
+
19
+ Here is an example of a response that would be parsed by EsiForRack
20
+
21
+ <html>
22
+ <body>
23
+ <esi:include src="/helloworld"/>
24
+ </body>
25
+ </html>
26
+
27
+ In this case, a request to <tt>/helloworld</tt> would be made by EsiForRack to fill in the request. If your application sent: "Hey world" as a response to <tt>/helloworld</tt> the above example would be interpolated to:
28
+
29
+ <html>
30
+ <body>
31
+ Hey world
32
+ </body>
33
+ </html>
34
+
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'lib/esi_for_rack'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "esi-for-rack"
7
+ s.description = s.summary = "ESI for Rack"
8
+ s.email = "joshbuddy@gmail.com"
9
+ s.homepage = "http://github.com/joshbuddy/esi_for_rack"
10
+ s.authors = ["Joshua Hull"]
11
+ s.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
12
+ s.add_dependency 'esi_attribute_language'
13
+ s.add_dependency 'nokogiri'
14
+ s.add_dependency 'spy-vs-spy'
15
+ s.add_dependency 'dirge'
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
22
+ require 'spec'
23
+ require 'spec/rake/spectask'
24
+
25
+ task :spec => 'spec:all'
26
+ namespace(:spec) do
27
+ Spec::Rake::SpecTask.new(:all) do |t|
28
+ t.spec_opts ||= []
29
+ t.spec_opts << "-rubygems"
30
+ t.spec_opts << "--options" << "spec/spec.opts"
31
+ t.spec_files = FileList['spec/**/*.rb']
32
+ end
33
+
34
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.4
@@ -0,0 +1,81 @@
1
+ require 'nokogiri'
2
+ require 'set'
3
+
4
+ require 'esi_attribute_language'
5
+ require 'spy_vs_spy'
6
+ require 'dirge'
7
+
8
+ require ~'esi_for_rack/node'
9
+ require ~'esi_for_rack/lookup'
10
+
11
+ class EsiForRack
12
+
13
+ def initialize(app, lookup = nil)
14
+ @app = app
15
+ @lookup = lookup
16
+ end
17
+
18
+ def self.response_body_to_str(response_body)
19
+ if response_body.respond_to? :to_str
20
+ response_body.to_str
21
+ elsif response_body.respond_to?(:each)
22
+ body = ''
23
+ response_body.each { |part|
24
+ body << part.to_s
25
+ }
26
+ body
27
+ else
28
+ raise TypeError, "stringable or iterable required"
29
+ end
30
+ end
31
+
32
+ def call(env)
33
+ @lookup ||= [Lookup::PassThrough.new(@app, env), Lookup::Http.new(@app, env)]
34
+ request = Rack::Request.new(env)
35
+ result = @app.call(env)
36
+ response = Rack::Response.new(result[2], result[0], result[1])
37
+
38
+ if response['Content-Type'] =~ /text\/html/ && (body = EsiForRack.response_body_to_str(result.last)) && body.index('<esi:')
39
+ user_agent_hash = {}
40
+ begin
41
+ user_agent = SOC::SpyVsSpy.new(env['HTTP_USER_AGENT'] || '-')
42
+ user_agent_hash['browser'] = case user_agent.browser
43
+ when 'Firefox' then 'MOZILLA'
44
+ when 'MSIE' then 'MSIE'
45
+ else; 'OTHER'
46
+ end
47
+
48
+ user_agent_hash['version'] = [user_agent.version.major, user_agent.version.minor].compact.join('.')
49
+
50
+ user_agent_hash['os'] = if user_agent.os.windows?
51
+ 'WIN'
52
+ elsif user_agent.os.osx?
53
+ 'MAC'
54
+ else
55
+ 'OTHER'
56
+ end
57
+ rescue
58
+ # error parsing ua
59
+ end
60
+
61
+ binding = {
62
+ :HTTP_ACCEPT_LANGUAGE => Set.new((env['HTTP_ACCEPT_LANGUAGE'] || '').split(',').map{|l| l.gsub(/q=[0-9]\.[0-9]{1,3}/, '').gsub(';','').strip}),
63
+ :HTTP_COOKIE => request.cookies,
64
+ :HTTP_HOST => request.host,
65
+ :HTTP_REFERER => request.referer,
66
+ :HTTP_USER_AGENT => user_agent_hash,
67
+ :QUERY_STRING => request.GET
68
+ }
69
+ context = Node::Context.new(binding, @lookup)
70
+
71
+ parsed_body = context.parse(body).to_s
72
+ response.header['Content-Length'] = parsed_body.size.to_s
73
+
74
+ [response.status, response.headers, [parsed_body]]
75
+ else
76
+ result
77
+ end
78
+
79
+ end
80
+
81
+ end
@@ -0,0 +1,105 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'logger'
4
+
5
+ class EsiForRack
6
+
7
+ class Lookup
8
+
9
+ class Http
10
+
11
+ class RedirectFollower
12
+ class TooManyRedirects < StandardError; end
13
+
14
+ attr_accessor :url, :body, :redirect_limit, :response, :code
15
+
16
+ def initialize(url, limit=5)
17
+ @url, @redirect_limit = url, limit
18
+ logger.level = Logger::INFO
19
+ end
20
+
21
+ def logger
22
+ @logger ||= Logger.new(STDOUT)
23
+ end
24
+
25
+ def resolve
26
+ raise TooManyRedirects if redirect_limit < 0
27
+
28
+ self.response = Net::HTTP.get_response(URI.parse(url))
29
+
30
+ logger.info "redirect limit: #{redirect_limit}"
31
+ logger.info "response code: #{response.code}"
32
+ logger.debug "response body: #{response.body}"
33
+
34
+ if response.kind_of?(Net::HTTPRedirection)
35
+ self.url = redirect_url
36
+ self.redirect_limit -= 1
37
+
38
+ logger.info "redirect found, headed to #{url}"
39
+ resolve
40
+ end
41
+
42
+ self.body = response.body
43
+ self.code = response.code
44
+ self
45
+ end
46
+
47
+ def redirect_url
48
+ if response['location'].nil?
49
+ response.body.match(/<a href=\"([^>]+)\">/i)[1]
50
+ else
51
+ response['location']
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+ def initialize(app, env)
58
+ @app = app
59
+ @env = env
60
+ end
61
+
62
+ def [](path)
63
+ res = RedirectFollower.new(path).resolve
64
+ res.body if res.code == '200'
65
+ end
66
+
67
+ end
68
+
69
+ class PassThrough
70
+
71
+ def initialize(app, env)
72
+ @app = app
73
+ @env = env
74
+ end
75
+
76
+ def [](path)
77
+ return if path[0,4] == 'http'
78
+
79
+ uri = URI(path)
80
+
81
+ request = {
82
+ "REQUEST_METHOD" => "GET",
83
+ "SERVER_NAME" => @env['SERVER_NAME'],
84
+ "SERVER_PORT" => @env['SERVER_PORT'],
85
+ "QUERY_STRING" => uri.query.to_s,
86
+ "PATH_INFO" => (!uri.path || uri.path.empty?) ? "/" : uri.path,
87
+ "rack.url_scheme" => uri.scheme || @env['rack.url_scheme'] || 'http',
88
+ "SCRIPT_NAME" => ""
89
+ }
90
+
91
+ response = @app.call(request)
92
+ if response.first == 200
93
+ EsiForRack.response_body_to_str(response.last)
94
+ else
95
+ nil
96
+ end
97
+
98
+ rescue URI::InvalidURIError
99
+ nil
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+ end
@@ -0,0 +1,161 @@
1
+ class EsiForRack
2
+
3
+ class Node
4
+
5
+ IncludeFailedError = Class.new(RuntimeError)
6
+
7
+ attr_reader :node, :context
8
+
9
+ def init(node, context)
10
+ @node = node
11
+ @context = context
12
+ self
13
+ end
14
+
15
+ def execute_in_place!
16
+ node.replace(Nokogiri::XML::CDATA.new(node.document, execute.to_s))
17
+ end
18
+
19
+ class Include < Node
20
+
21
+ def resolved_src
22
+ EsiAttributeLanguage::SimpleGrammar.parse(@node['src']).execute(context.resolver)
23
+ end
24
+
25
+ def resolved_alt
26
+ EsiAttributeLanguage::SimpleGrammar.parse(@node['alt']).execute(context.resolver) if @node['alt']
27
+ end
28
+
29
+ def continue_on_error?
30
+ node['onerror'] == 'continue'
31
+ end
32
+
33
+ def execute
34
+ context.lookup(resolved_src) or
35
+ (resolved_alt && context.lookup(resolved_alt)) or
36
+ (!continue_on_error? && raise(IncludeFailedError.new)) or nil
37
+ end
38
+
39
+ end
40
+
41
+ class Vars < Node
42
+ def execute
43
+ EsiAttributeLanguage::SimpleGrammar.parse(node.to_str).execute(context.resolver)
44
+ end
45
+ end
46
+
47
+ class Try < Node
48
+
49
+ def execute
50
+ unless @esi_attempt = node.css('esi_attempt')[0]
51
+ raise "no attempt within try"
52
+ end
53
+
54
+ unless @esi_except = node.css('esi_except')[0]
55
+ raise "no except within try"
56
+ end
57
+
58
+ val = ''
59
+ begin
60
+ context.process(@esi_attempt)
61
+ @esi_attempt.inner_html
62
+ rescue IncludeFailedError
63
+ context.process(@esi_except)
64
+ @esi_except.inner_html
65
+ end
66
+ end
67
+ end
68
+
69
+ class Choose < Node
70
+ def execute
71
+ whens = node.css('esi_when').to_a
72
+ raise "choose block contains no when elements" if whens.empty?
73
+
74
+ otherwise = node.css('esi_otherwise')[0]
75
+
76
+ whens.each do |esi_when|
77
+ if EsiAttributeLanguage::Grammar.parse(esi_when['test']).execute(context.resolver).equal?(true)
78
+ context.process(esi_when)
79
+ return esi_when.to_str
80
+ end
81
+ end
82
+ if otherwise
83
+ context.process(otherwise)
84
+ return otherwise.to_str
85
+ end
86
+ nil
87
+ end
88
+ end
89
+
90
+ class Context
91
+
92
+ attr_reader :resolver, :doc
93
+
94
+ def initialize(resolver, lookup)
95
+ @resolver = resolver
96
+ @lookup = lookup.is_a?(Array) ? lookup : [lookup]
97
+ @include = Include.new
98
+ @choose = Choose.new
99
+ @vars = Vars.new
100
+ @try = Try.new
101
+
102
+ end
103
+
104
+ def lookup(url)
105
+ @lookup.each do |l|
106
+ resolved_body = l[url]
107
+ return resolved_body if resolved_body
108
+ end
109
+ nil
110
+ end
111
+
112
+ def parse(document)
113
+ document.gsub!('esi:', 'esi_')
114
+ document.gsub!(/(<\/?esi_[^>]*>)/, ']]>\1<![CDATA[')
115
+ document[0,0] = %|<?xml version="1.0"?>\n<esi_root><![CDATA[|
116
+ document << ']]></esi_root>'
117
+
118
+ @doc = Nokogiri::XML(document)
119
+
120
+ @doc.css('esi_comment').each do |esi_comment|
121
+ esi_comment.replace(Nokogiri::XML::CDATA.new(doc, ''))
122
+ end
123
+
124
+ process(@doc.css('esi_root')[0])
125
+
126
+ result = ''
127
+ @doc.css('esi_root')[0].children.each do |n|
128
+ result << n.to_str
129
+ end
130
+
131
+ result
132
+ end
133
+
134
+ def process(doc_fragment)
135
+ # have to go one at a time because of limitation of .css, its not a live list.
136
+ # ps, i'll only break if i totally have to
137
+ loop do
138
+ should_break = true
139
+ doc_fragment.css('esi_try,esi_choose,esi_vars,esi_include').each do |esi_node|
140
+ case esi_node.name.to_sym
141
+ when :esi_include
142
+ @include.init(esi_node, self).execute_in_place!
143
+ when :esi_choose
144
+ @choose.init(esi_node, self).execute_in_place!
145
+ when :esi_vars
146
+ @vars.init(esi_node, self).execute_in_place!
147
+ when :esi_try
148
+ @try.init(esi_node, self).execute_in_place!
149
+ should_break = false
150
+ break
151
+ end
152
+ end
153
+ break if should_break
154
+ end
155
+ end
156
+
157
+ end
158
+
159
+ end
160
+
161
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi accept language lookups" do
4
+
5
+ it "should lookup a cookie variable" do
6
+
7
+ vars = {'type' => 'user'}
8
+ builder = Rack::Builder.new do
9
+ use EsiForRack, {'/file/WIN' => 'os', '/file/1.5' => 'version', '/file/MOZILLA' => 'browser'}
10
+
11
+ run proc { |env|
12
+ data = IO.read('spec/http_integration/fixtures/accept_language/1.html')
13
+ [200, {'Content-type' => 'text/html', 'Content-length' => data.size.to_s}, [data]]
14
+ }
15
+ end
16
+
17
+ request = Rack::MockRequest.env_for("/?#{Rack::Utils.build_query(vars)}")
18
+ request['HTTP_ACCEPT_LANGUAGE'] = 'da, en-gb;q=0.8, en;q=0.7'
19
+ builder.call(request).last.should == ["<html><body>\nEN\nDA\nEN-GB\n\n</body></html>"]
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi cookie variable lookups" do
4
+
5
+ it "should lookup a cookie variable" do
6
+
7
+ vars = {'type' => 'user'}
8
+ builder = Rack::Builder.new do
9
+ use EsiForRack, {'/file/1' => 'resource'}
10
+
11
+ run proc { |env|
12
+ data = IO.read('spec/http_integration/fixtures/cookie/1.html')
13
+ [200, {'Content-type' => 'text/html', 'Content-length' => data.size.to_s}, [data]]
14
+ }
15
+ end
16
+
17
+ request = Rack::MockRequest.env_for("/?#{Rack::Utils.build_query(vars)}")
18
+ request['HTTP_COOKIE'] = 'id=1'
19
+ builder.call(request).last.should == ["<html><body>\nresource\n</body></html>"]
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,12 @@
1
+ <html><body>
2
+ <esi:choose>
3
+ <esi:when test="$(HTTP_ACCEPT_LANGUAGE{en})">EN</esi:when>
4
+ </esi:choose>
5
+ <esi:choose>
6
+ <esi:when test="$(HTTP_ACCEPT_LANGUAGE{da})">DA</esi:when>
7
+ </esi:choose>
8
+ <esi:choose>
9
+ <esi:when test="$(HTTP_ACCEPT_LANGUAGE{en-gb})">EN-GB</esi:when>
10
+ </esi:choose>
11
+
12
+ </body></html>
@@ -0,0 +1,3 @@
1
+ <html><body>
2
+ <esi:include src="/file/$(HTTP_COOKIE{id})"/>
3
+ </body></html>
@@ -0,0 +1,3 @@
1
+ <html><body>
2
+ <esi:include src="/file/$(QUERY_STRING{id})"/>
3
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <html><body>
2
+ <esi:include src="/file/$(HTTP_USER_AGENT{browser})"/>
3
+ <esi:include src="/file/$(HTTP_USER_AGENT{os})"/>
4
+ <esi:include src="/file/$(HTTP_USER_AGENT{version})"/>
5
+ </body></html>
@@ -0,0 +1,22 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi query string variable lookups" do
4
+
5
+ it "should lookup a query string variable" do
6
+
7
+ vars = {'id' => '1'}
8
+ builder = Rack::Builder.new do
9
+ use EsiForRack, {'/file/1' => 'resource'}
10
+
11
+ run proc { |env|
12
+ data = IO.read('spec/http_integration/fixtures/query_string/1.html')
13
+ [200, {'Content-type' => 'text/html', 'Content-length' => data.size.to_s}, [data]]
14
+ }
15
+ end
16
+
17
+ request = Rack::MockRequest.env_for("/?#{Rack::Utils.build_query(vars)}")
18
+ builder.call(request).last.should == ["<html><body>\nresource\n</body></html>"]
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi user agent variable lookups" do
4
+
5
+ it "should lookup a user agent variable" do
6
+
7
+ vars = {'type' => 'user'}
8
+ builder = Rack::Builder.new do
9
+ use EsiForRack, {'/file/WIN' => 'os', '/file/1.5' => 'version', '/file/MOZILLA' => 'browser'}
10
+
11
+ run proc { |env|
12
+ data = IO.read('spec/http_integration/fixtures/user_agent/1.html')
13
+ [200, {'Content-type' => 'text/html', 'Content-length' => data.size.to_s}, [data]]
14
+ }
15
+ end
16
+
17
+ request = Rack::MockRequest.env_for("/?#{Rack::Utils.build_query(vars)}")
18
+ request['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.0) Gecko/20060308 Firefox/1.5.0'
19
+ builder.call(request).last.should == ["<html><body>\n browser\n os\n version\n</body></html>"]
20
+
21
+ end
22
+
23
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,7 @@
1
+ --colour
2
+ --format
3
+ specdoc
4
+ --loadby
5
+ mtime
6
+ --reverse
7
+ --backtrace
@@ -0,0 +1,25 @@
1
+ require 'rack'
2
+ require 'lib/esi_for_rack'
3
+
4
+ def build_app(file, lookup)
5
+ builder = Rack::Builder.new do
6
+ use EsiForRack, lookup
7
+
8
+ run proc { |env|
9
+ data = if env['PATH_INFO'] == '/'
10
+ IO.read(file)
11
+ else
12
+ lookup[env['PATH_INFO']]
13
+ end
14
+
15
+ if data
16
+ [200, {'Content-type' => 'text/html', 'Content-length' => data.size.to_s}, [data]]
17
+ else
18
+ [404, {}, []]
19
+ end
20
+ }
21
+ end
22
+
23
+ request = Rack::MockRequest.env_for("/?a=b")
24
+ builder.call(request)
25
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi choose" do
4
+
5
+ it "should pick the first available choice" do
6
+ build_app('spec/tags/fixtures/choose/simple1.html', {}).last.should == ["\n Hey you\n "]
7
+ end
8
+
9
+ it "should pick next available choice" do
10
+ build_app('spec/tags/fixtures/choose/simple2.html', {}).last.should == ["\n And you\n "]
11
+ end
12
+
13
+ it "should take the otherwise block if there is no valid choice" do
14
+ build_app('spec/tags/fixtures/choose/simple3.html', {}).last.should == [" \n No good\n "]
15
+ end
16
+
17
+
18
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi comment" do
4
+
5
+ it "should not render a comment" do
6
+ build_app('spec/tags/fixtures/comment/simple.html', {}).last.should == ["This will show up."]
7
+ end
8
+
9
+ end
@@ -0,0 +1,8 @@
1
+ <esi:choose>
2
+ <esi:when test="1==1">
3
+ Hey you
4
+ </esi:when>
5
+ <esi:otherwise>
6
+ No good
7
+ </esi:otherwise>
8
+ </esi:chooise>
@@ -0,0 +1,11 @@
1
+ <esi:choose>
2
+ <esi:when test="2==1">
3
+ Hey you
4
+ </esi:when>
5
+ <esi:when test="1==1">
6
+ And you
7
+ </esi:when>
8
+ <esi:otherwise>
9
+ No good
10
+ </esi:otherwise>
11
+ </esi:chooise>
@@ -0,0 +1,8 @@
1
+ <esi:choose>
2
+ <esi:when test="2==1">
3
+ Hey you
4
+ </esi:when>
5
+ <esi:otherwise>
6
+ No good
7
+ </esi:otherwise>
8
+ </esi:chooise>
@@ -0,0 +1 @@
1
+ This will <esi:comment>This will never show up</esi:comment>show up.
@@ -0,0 +1,3 @@
1
+ <html><body>
2
+ <esi:include src="unavilable resource" alt="/alternate" />
3
+ </body></html>
@@ -0,0 +1,3 @@
1
+ <html><body>
2
+ <esi:include src="/great" />
3
+ </body></html>
@@ -0,0 +1,3 @@
1
+ <html><body>
2
+ <esi:include src="/great" onerror="continue"/>
3
+ </body></html>
@@ -0,0 +1,12 @@
1
+ <esi:try>
2
+ <esi:attempt>
3
+ This is my include: <esi:include src="/include" />
4
+ Choice/<esi:choose>
5
+ <esi:when test="1==2">false</esi:when>
6
+ <esi:when test="1==1">true</esi:when>
7
+ </esi:choose>/Choice
8
+ </esi:attempt>
9
+ <esi:except>
10
+ BLAH!
11
+ </esi:except>
12
+ </esi:try>
@@ -0,0 +1,6 @@
1
+ <esi:try>
2
+ <esi:attempt>
3
+ <esi:include src="/great"/>
4
+ </esi:attempt>
5
+ <esi:except>This is bad</esi:except>
6
+ </esi:try>
@@ -0,0 +1,5 @@
1
+ <esi:try>
2
+ <esi:attempt>
3
+ <esi:include src="/great"/>
4
+ </esi:attempt>
5
+ </esi:try>
@@ -0,0 +1,3 @@
1
+ <esi:try>
2
+ <esi:except>This is bad</esi:except>
3
+ </esi:try>
@@ -0,0 +1 @@
1
+ <esi:vars><img src="http://www.example.com/$(QUERY_STRING{a})/hello.gif"/ ></esi:vars>
@@ -0,0 +1,25 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi include" do
4
+
5
+ it "should include a src" do
6
+ build_app('spec/tags/fixtures/include/src.html', {'/great' => "<p>This is great</p>"}).last.should == ["<html><body>\n<p>This is great</p>\n</body></html>"]
7
+ end
8
+
9
+ it "should include an alt if src is unavilable" do
10
+ build_app('spec/tags/fixtures/include/alt.html', {'/alternate' => "<p>This is great</p>"}).last.should == ["<html><body>\n<p>This is great</p>\n</body></html>"]
11
+ end
12
+
13
+ it "should raise an error if src is unavilable" do
14
+ proc { build_app('spec/tags/fixtures/include/src.html', {}) }.should raise_error
15
+ end
16
+
17
+ it "should raise an error if src and alt is unavilable" do
18
+ proc { build_app('spec/tags/fixtures/include/alt.html', {}) }.should raise_error
19
+ end
20
+
21
+ it "should continue though, if onerror=continue" do
22
+ build_app('spec/tags/fixtures/include/src_continue.html', {}).last.should == ["<html><body>\n\n</body></html>"]
23
+ end
24
+
25
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi nested" do
4
+
5
+ it "nested1" do
6
+ build_app('spec/tags/fixtures/nested/complex.html', {}).last.should == ["\n BLAH!\n "]
7
+ end
8
+
9
+ it "nested2" do
10
+ build_app('spec/tags/fixtures/nested/complex.html', {'/include' => "<p>This is great</p>"}).last.should == ["\n This is my include: <p>This is great</p>\n Choice/true/Choice\n "]
11
+ end
12
+
13
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi try" do
4
+
5
+ it "should raise on try block without except" do
6
+ proc {
7
+ build_app('spec/tags/fixtures/try/malformed_attempt.html', {'/great' => "<p>This is great</p>"})
8
+ }.should raise_error
9
+ end
10
+
11
+ it "should raise on try block without attempt" do
12
+ proc {
13
+ build_app('spec/tags/fixtures/try/malformed_attempt.html', {'/great' => "<p>This is great</p>"})
14
+ }.should raise_error
15
+ end
16
+
17
+ it "should include normally within an attempt block" do
18
+ build_app('spec/tags/fixtures/try/include.html', {'/great' => "<p>This is great</p>"}).last.should ==
19
+ ["\n <p>This is great</p>\n \n"]
20
+ end
21
+
22
+ it "should render the except block when the include fails within an attempt block" do
23
+ build_app('spec/tags/fixtures/try/include.html', {}).last.should ==
24
+ ["This is bad\n"]
25
+ end
26
+
27
+
28
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe "esi vars" do
4
+
5
+ it "should render the content within vars" do
6
+ build_app('spec/tags/fixtures/vars/simple.html', {}).last.should == ["<img src=\"http://www.example.com/b/hello.gif\"/ >"]
7
+ end
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: esi-for-rack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Joshua Hull
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-25 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: esi_attribute_language
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: nokogiri
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: spy-vs-spy
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ - !ruby/object:Gem::Dependency
46
+ name: dirge
47
+ type: :runtime
48
+ version_requirement:
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ description: ESI for Rack
56
+ email: joshbuddy@gmail.com
57
+ executables: []
58
+
59
+ extensions: []
60
+
61
+ extra_rdoc_files:
62
+ - README.rdoc
63
+ files:
64
+ - README.rdoc
65
+ - Rakefile
66
+ - VERSION
67
+ - lib/esi_for_rack.rb
68
+ - lib/esi_for_rack/lookup.rb
69
+ - lib/esi_for_rack/node.rb
70
+ - spec/http_integration/accept_language_spec.rb
71
+ - spec/http_integration/cookie_spec.rb
72
+ - spec/http_integration/fixtures/accept_language/1.html
73
+ - spec/http_integration/fixtures/cookie/1.html
74
+ - spec/http_integration/fixtures/query_string/1.html
75
+ - spec/http_integration/fixtures/user_agent/1.html
76
+ - spec/http_integration/query_string_spec.rb
77
+ - spec/http_integration/user_agent_spec.rb
78
+ - spec/spec.opts
79
+ - spec/spec_helper.rb
80
+ - spec/tags/choose_spec.rb
81
+ - spec/tags/comment_spec.rb
82
+ - spec/tags/fixtures/choose/simple1.html
83
+ - spec/tags/fixtures/choose/simple2.html
84
+ - spec/tags/fixtures/choose/simple3.html
85
+ - spec/tags/fixtures/comment/simple.html
86
+ - spec/tags/fixtures/include/alt.html
87
+ - spec/tags/fixtures/include/src.html
88
+ - spec/tags/fixtures/include/src_continue.html
89
+ - spec/tags/fixtures/nested/complex.html
90
+ - spec/tags/fixtures/try/include.html
91
+ - spec/tags/fixtures/try/malformed_attempt.html
92
+ - spec/tags/fixtures/try/malformed_except.html
93
+ - spec/tags/fixtures/vars/simple.html
94
+ - spec/tags/include_spec.rb
95
+ - spec/tags/nested_spec.rb
96
+ - spec/tags/try_spec.rb
97
+ - spec/tags/vars_spec.rb
98
+ has_rdoc: true
99
+ homepage: http://github.com/joshbuddy/esi_for_rack
100
+ licenses: []
101
+
102
+ post_install_message:
103
+ rdoc_options:
104
+ - --charset=UTF-8
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: "0"
112
+ version:
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: "0"
118
+ version:
119
+ requirements: []
120
+
121
+ rubyforge_project:
122
+ rubygems_version: 1.3.5
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: ESI for Rack
126
+ test_files:
127
+ - spec/http_integration/accept_language_spec.rb
128
+ - spec/http_integration/cookie_spec.rb
129
+ - spec/http_integration/query_string_spec.rb
130
+ - spec/http_integration/user_agent_spec.rb
131
+ - spec/spec_helper.rb
132
+ - spec/tags/choose_spec.rb
133
+ - spec/tags/comment_spec.rb
134
+ - spec/tags/include_spec.rb
135
+ - spec/tags/nested_spec.rb
136
+ - spec/tags/try_spec.rb
137
+ - spec/tags/vars_spec.rb