rack-rsi 0.0.1

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