esi-for-rack 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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