rack-rsi 0.0.1

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.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in rack-rsi.gemspec
4
+ gemspec
data/LICENSE.markdown ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2011 [Ram Singla](https://github.com/ramsingla)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,110 @@
1
+ Rack Side Include
2
+ =================
3
+
4
+ Description
5
+ -----------
6
+
7
+ Rack::RSI is an rack middleware which helps you assemble pages on
8
+ similar lines of ESI without leaving the comfort of Ruby. Rack Side
9
+ Include only support one feature of ESI i.e. <esi:include>. One of
10
+ the key differentiator from esi standard it uses ERB rather than XML
11
+ tags to assemble pages and it does not assemble pages outside the
12
+ Application.
13
+
14
+ Rationale
15
+ ---------
16
+
17
+ A fair bit of what ESI offers, is in the ESI language because Akamais
18
+ customers cannot configure the Akamai edge proxies. While this is
19
+ perfectly sensible for the Akamai business model, it is of little
20
+ relevance for WebApps where the content-provider is in control of the
21
+ servers.
22
+
23
+ Also if you do not want to use a separate tier for ESI, the ESI standard
24
+ is too heavy to implement as a Rack Middleware.
25
+
26
+ ERB is much simpler to render and less CPU intensive interms of XML
27
+ parsing and generating response.
28
+
29
+ Assembling pages inside the applications is chosen deliberately because
30
+ the content for assembly is fetched from within the Rack stack without
31
+ firing any HTTP requests to the server.
32
+
33
+ Potential Scenarios
34
+ -------------------
35
+
36
+ #### Pages Decorated with Short Lived Information
37
+
38
+ Consider a case of high volume news website. Most pages on this website
39
+ contains one article decorated by ads and a "hot news" box. Without the
40
+ assembly the TTL for each of these articles need to be kept low, to keep
41
+ decorations and in particular "hot news" box fresh.
42
+
43
+ Rack Server Include assembly middleware allows you to break the page
44
+ into different fragments which can be cached differently and are
45
+ assembled just before serving the request to the client. In above case
46
+ article can be cached for an infinitely long time, with a directive to
47
+ tell the middle where what and where to include the "hot news" box from.
48
+
49
+ Each part of the section and the page containing the rack side include
50
+ directive can be cached differently.
51
+
52
+ #### Creating Dashboards
53
+
54
+ Consider an admin dashboard of an ecommerce website which shows the new
55
+ orders and new products added on the system.
56
+
57
+ If we are not using assembly middleware you would require to populate
58
+ relevant order and product object in dashboard controller which is not
59
+ well organized. Ideally orders controller should fetch order objects and
60
+ products controller should fetch the products.
61
+
62
+ Rack Server Include assembly lets you achieve this by calling two
63
+ include directives. One to new orders and one to new products which will
64
+ be served by orders and products controller respectively.
65
+
66
+ This can really help you keep you application slim and well-organized.
67
+
68
+ How do I use in Rails 3
69
+ -----------------------
70
+
71
+ in Gemfile
72
+
73
+ gem 'rack-rsi', :require => 'rack/rsi'
74
+
75
+ in config/application.rb
76
+
77
+ # Rack::RSI should be loaded high up the Rack Middleware Stack
78
+ config.middleware.insert_before ActionDispatch::Callbacks, Rack::RSI
79
+
80
+ in controller action
81
+
82
+ def foo_action
83
+ # Notify Rack::RSI to process this request
84
+ headers['rack.rsi'] = '1'
85
+ ...
86
+ end
87
+
88
+ in view template foo_action.html.erb
89
+
90
+ ...
91
+ <%%= rsi_include( '/path/to_include' ) %>
92
+ ...
93
+
94
+ How do I use it with Bare Rack Apps
95
+ -----------------------------------
96
+
97
+ look for hello_world_app.rb in examples fold
98
+
99
+
100
+ Limitations
101
+ ------------
102
+
103
+ * Only GET requests are supported as rack side includes
104
+ * Include requests should not return redirect response
105
+ * There should be no ERB tags other than ones invoking *rsi_include*.
106
+
107
+ License
108
+ -------
109
+
110
+ Copyright &copy; 2011 Ram Singla. Released under MIT License.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ * Write Specs
2
+ * Write More Documentation
3
+ * Create an EM machine version which makes requests in Parallel.
@@ -0,0 +1,13 @@
1
+ require "pathname"
2
+
3
+ $LOAD_PATH.unshift(Pathname(__FILE__).expand_path.dirname)
4
+ $LOAD_PATH.unshift(Pathname(__FILE__).expand_path.dirname.parent.join("lib"))
5
+
6
+ require "rack/rsi"
7
+ require "hello_world_app"
8
+
9
+ use Rack::ShowExceptions
10
+ use Rack::Runtime
11
+ use Rack::RSI
12
+ use Rack::CommonLogger
13
+ run HelloWorldApp.new
@@ -0,0 +1,51 @@
1
+ require "rack"
2
+
3
+ class HelloWorldApp
4
+
5
+ def call(env)
6
+ request = Rack::Request.new(env)
7
+ response = Rack::Response.new
8
+
9
+ if request.path_info == "/"
10
+ action = "index"
11
+ else
12
+ action = request.path_info[1..-1].gsub(/\?.*$/, '')
13
+ end
14
+
15
+ if ACTIONS.include?(action)
16
+ send(action, request, response)
17
+ else
18
+ response.status = 404
19
+ response.write("404 Not Found")
20
+ end
21
+
22
+ response.finish
23
+ end
24
+
25
+ private
26
+
27
+ ACTIONS = %<index header footer>
28
+
29
+ def index(request, response)
30
+ response['rack.rsi'] = '1'
31
+ response['Cache-Control'] = 'max-age=3600'
32
+ response.write(%{
33
+ <title>HelloWorld</title>
34
+ <%= rsi_include( "/header?user=buzzmenot" ) %>
35
+ <p>Hello World!</p>
36
+ <%= rsi_include( "/footer?company=github" ) %>
37
+ }.gsub(/^\s*/, "").strip)
38
+ end
39
+
40
+ def header(request, response)
41
+ response['Cache-Control'] = 'max-age=60'
42
+ response.write( "Here comes header from Header Action exclusively for #{request.params['user']}" )
43
+ end
44
+
45
+ def footer(request, response)
46
+ response['Cache-Control'] = 'max-age=120'
47
+ response.write( "Here comes footer from Footer Action exclusively for #{request.params['company']}" )
48
+ end
49
+
50
+ end
51
+
data/lib/rack/rsi.rb ADDED
@@ -0,0 +1,114 @@
1
+ # Copyright Ram Singla (c) 2011.
2
+ # Released under MIT License
3
+
4
+ require 'rack'
5
+ require 'erb'
6
+ require 'uri'
7
+ require 'digest/md5'
8
+ require 'rack/rsi_version'
9
+
10
+ class Rack::RSI
11
+
12
+ class Error < ::RuntimeError
13
+ end
14
+
15
+ class RsiRender
16
+
17
+ def initialize( app, env, level = 0 )
18
+ @app, @env, @level = app, env, level
19
+ @headers, @body = {}, {}
20
+ end
21
+
22
+ def rack_rsi?
23
+ @headers.values.select{ |x| x && x['rack.esi'] }.any?
24
+ end
25
+
26
+ def cache_control_headers
27
+ @headers.values.collect{ |x| ( x ? x['Cache-Control'] : nil ) || 'max-age=0' }
28
+ end
29
+
30
+ def get_binding
31
+ return binding( )
32
+ end
33
+
34
+ def rsi_include( source )
35
+ uri = URI.parse( source )
36
+ include_env = @env.merge( "PATH_INFO" => uri.path,
37
+ "SCRIPT_NAME" => "",
38
+ "QUERY_STRING" => uri.query,
39
+ "REQUEST_METHOD" => "GET" )
40
+ begin
41
+ include_status, include_headers, include_body = @app.dup.call(include_env)
42
+ @headers[ source ] = include_headers
43
+ @body[ source ] = ( include_status == 200 ? include_body : [] )
44
+ rescue Exception => message
45
+ @body[ source ] = []
46
+ end
47
+ value = ''
48
+ @body[ source ].each{ |part| value << part }
49
+ value
50
+ end
51
+
52
+ end
53
+
54
+ def initialize( app )
55
+ @app = app
56
+ end
57
+
58
+ def call( env )
59
+ assemble_response( env )
60
+ end
61
+
62
+ # Assemble Response
63
+ def assemble_response( env )
64
+ vanilla_env = env.dup
65
+ vanilla_app = @app.dup
66
+
67
+ # calling app and env on orignal request
68
+ status, headers, enumerable_body = original_response = @app.call(env)
69
+
70
+ rack_rsi_flag = headers.delete('rack.rsi')
71
+ return original_response unless rack_rsi_flag
72
+
73
+ body = ""
74
+ enumerable_body.each do |part|
75
+ body << part
76
+ end
77
+
78
+ cache_control_headers = Array( headers.delete( 'Cache-Control' ) || "max-age=0" )
79
+
80
+ # Like Varnish supports upto 5 levels of ESI includes recursively
81
+ level = 0
82
+ while( rack_rsi_flag )
83
+ erb = ERB.new( body, 0 )
84
+ renderer = RsiRender.new( vanilla_app, vanilla_env, level )
85
+ body = erb.result( renderer.get_binding )
86
+ renderer.cache_control_headers.inject( cache_control_headers ){ |s,x| s.push( x ) }
87
+ rack_rsi_flag = renderer.rack_rsi?
88
+ level += 1
89
+ end
90
+
91
+ # Set ETag for the Request
92
+ headers['ETag'] = Digest::MD5.hexdigest( body )
93
+ headers.delete( 'Last-Modified' )
94
+
95
+ # For Assembled Pages Cache-Control to be set as private, with
96
+ # max-age=<minimum max-age of all the requests that are assembled>
97
+ # and should be revalidate on stale
98
+ min_max_age = cache_control_headers.collect{ |x| x.match(/max-age\s*=\s*(\d+)/).to_a[1].to_i }.min
99
+
100
+ headers['Cache-Control'] = "private, max-age=#{min_max_age}, must-revalidate"
101
+ headers.delete( 'Expires' )
102
+ headers['Expires'] = ( Time.now.utc + min_max_age ).strftime("%a, %d %m %Y %T %Z") if min_max_age > 0
103
+
104
+ # Create Correct Content-Length
105
+ headers['Content-Length'] = Rack::Utils.bytesize( body ).to_s
106
+
107
+ # For now whatever headers is set by the original action would be
108
+ # passed on. Expect for Cache-Control, ETag, Expires, Last-Modified
109
+ # Cookies from the original action are passed on.
110
+ [ status, headers, [ body ] ]
111
+ end
112
+
113
+ end
114
+
@@ -0,0 +1,6 @@
1
+ module Rack
2
+ class RSI
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
6
+
data/rack-rsi.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack/rsi_version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-rsi"
7
+ s.version = Rack::RSI::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ram Singla"]
10
+ s.email = ["ram.singla@gmail.com"]
11
+ s.homepage = "https://github.com/ramsingla/rack-rsi"
12
+ s.summary = %q{Rack Middleware: Rack Side Include}
13
+ s.description = %q{Rack Side Include helps you assemble pages like Edge Side Include (ESI) using ERB tags.}
14
+
15
+ s.rubyforge_project = "rack-rsi"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.required_rubygems_version = ">= 1.3.7"
23
+ s.add_dependency "rack"
24
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-rsi
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Ram Singla
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-13 00:00:00 +05:30
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rack
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ description: Rack Side Include helps you assemble pages like Edge Side Include (ESI) using ERB tags.
28
+ email:
29
+ - ram.singla@gmail.com
30
+ executables: []
31
+
32
+ extensions: []
33
+
34
+ extra_rdoc_files: []
35
+
36
+ files:
37
+ - .gitignore
38
+ - Gemfile
39
+ - LICENSE.markdown
40
+ - README.markdown
41
+ - Rakefile
42
+ - TODO
43
+ - examples/config.ru
44
+ - examples/hello_world_app.rb
45
+ - lib/rack/rsi.rb
46
+ - lib/rack/rsi_version.rb
47
+ - rack-rsi.gemspec
48
+ has_rdoc: true
49
+ homepage: https://github.com/ramsingla/rack-rsi
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 1.3.7
69
+ requirements: []
70
+
71
+ rubyforge_project: rack-rsi
72
+ rubygems_version: 1.6.2
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: "Rack Middleware: Rack Side Include"
76
+ test_files: []
77
+