cloud_assets 0.0.2

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 cloud_assets.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ cloud_assets enables a Rails app to make transparent use of
2
+ assets on a remote server in an alternative technology.
3
+
4
+ Principles and modes of operation
5
+ ---------------------------------
6
+
7
+ You can avoid incorporating static assets in your Rails
8
+ app directly, if they exist somewhere else on the internet.
9
+ With cloud_assets you configure your Rails app as the
10
+ front end for assets managed in another internet-connected
11
+ system, e.g. WordPress, Drupal, SharePoint, etc.
12
+
13
+ Instead of returning a 404 Not Found, cloud_assets will
14
+ proxy a pooled request to the remote server, cache the
15
+ content locally, and serve it from Rails.
16
+
17
+ cloud_assets also provides tools and vocabulary for making
18
+ Rails views that make use of HTML originating on the
19
+ remote system, and rewrites URIs in such templates to
20
+ point either to the local Rails system or a CDN, depending
21
+ on configuration and utility.
22
+
23
+ Installation
24
+ ------------
25
+
26
+ 1.
27
+
28
+ Add cloud_assets to your Gemfile and bundle install, then
29
+ define the following in your environment.
30
+
31
+ Required:
32
+ CLOUD_ASSET_ORIGIN - Origin URI for remote assets
33
+
34
+ Optional:
35
+ CLOUD_ASSET_CDN - Content delivery network, if any. This
36
+ is used to rewrite URIs for images and other large static
37
+ assets to the CDN instead of Rails.
38
+
39
+ CLOUD_ASSET_USER, CLOUD_ASSET_PASSWORD - HTTP Basic
40
+ credentials which will be used when retrieving assets from
41
+ the cloud asset source.
42
+
43
+ 2.
44
+
45
+ Put a route at the bottom of your routes.rb:
46
+
47
+ match '*url' => 'cloud_assets#content'
48
+
49
+ This will allow cloud_assets to handle anything Rails
50
+ doesn't recognize.
51
+
52
+ 3.
53
+
54
+ In application_controller.rb:
55
+
56
+ include CloudAssets
57
+
58
+ Additional Configuration
59
+ ------------------------
60
+
61
+ If you are serving any non-trivial amount of remotely
62
+ sourced assets out of your Rails system, you'll want a cache.
63
+ cloud_assets likes memcached via dalli.
64
+
65
+ To enable it, just define $hydra_cache somewhere in one of
66
+ your initializers.
67
+
68
+ Usage
69
+ -----
70
+
71
+ Set a remote layout for an HTML view by defining the URI to
72
+ the layout on the remote system, e.g.
73
+
74
+ set_remote_layout '/templates/foo.html'
75
+
76
+ You can inject additional content into the elements of that
77
+ template, or override the interior of its elements, using CSS
78
+ selectors:
79
+
80
+ inject_into_remote_layout '#notice' => flash[:notice]
81
+ override_remote_layout 'body' => yield
82
+
83
+ Finally, obtain and show the result (complete with rewrites
84
+ of CDN URLs and so on) with:
85
+
86
+ apply_remote_layout
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,34 @@
1
+ class CloudAssetsController < ApplicationController
2
+
3
+ def content
4
+ asset_response = cloud_asset request.fullpath
5
+ if asset_response.success?
6
+ content_type = asset_response.headers_hash['Content-type']
7
+ if content_type.kind_of? Array
8
+ content_type = content_type.pop
9
+ end
10
+ if content_type =~ /text\/html/
11
+ set_remote_layout asset_response
12
+ response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate, max-age=0'
13
+ render :html => ''
14
+ elsif content_type =~ /javascript/
15
+ response.headers['Cache-Control'] = "max-age=#{CloudAssets::javascript_max_age_seconds}"
16
+ # In externally sourced JS, mask the cloud source to point here
17
+ # we want to do this because of JavaScript's same-source restrictions
18
+ body = CloudAssets::fixup_javascript(asset_response.body.gsub CloudAssets::origin,'')
19
+ send_data body, :type => content_type, :disposition => 'inline'
20
+ elsif content_type =~ /css/
21
+ response.headers['Cache-Control'] = "max-age=#{CloudAssets::css_max_age_seconds}"
22
+ # In externally sourced CSS, mask the cloud source to point to the CDN
23
+ body = CloudAssets::fixup_css(asset_response.body.gsub CloudAssets::origin, CloudAssets::cdn)
24
+ send_data body, :type => content_type, :disposition => 'inline'
25
+ else
26
+ response.headers['Cache-Control'] = "max-age=#{CloudAssets::other_max_age_seconds}"
27
+ send_data asset_response.body, :type => content_type, :disposition => 'inline'
28
+ end
29
+ else
30
+ render :status => :not_found
31
+ end
32
+ end
33
+
34
+ end
File without changes
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "cloud_assets/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "cloud_assets"
7
+ s.version = CloudAssets::VERSION
8
+ s.authors = ["Rob Heittman"]
9
+ s.email = ["rob.heittman@solertium.com"]
10
+ s.homepage = "https://github.com/rfc2616/cloud_assets"
11
+ s.summary = %q{
12
+ Enables a Rails app to make transparent use of
13
+ assets on a remote server in an alternative technology.
14
+ }
15
+ s.description = %q{
16
+ This gem is in use in some production sites to provide
17
+ backing for a Rails app using content from WordPress and
18
+ PostLaunch (a Java based CMS), and has specific
19
+ dependencies on Typhoeus, Nokogiri, and dalli, favorites
20
+ we use at Solertium. It's in the early stages of
21
+ being made into a general purpose tool -- cleanup,
22
+ generalized tests (our site-specific ones are stripped
23
+ from the gem) documentation, performance, and decoupling
24
+ from dependencies.
25
+ }
26
+
27
+ s.files = `git ls-files`.split("\n")
28
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
29
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
30
+ s.require_paths = ["lib"]
31
+
32
+ # specify any dependencies here; for example:
33
+ # s.add_development_dependency "rspec"
34
+ s.add_runtime_dependency "typhoeus"
35
+ s.add_runtime_dependency "nokogiri"
36
+ s.add_runtime_dependency "dalli"
37
+ end
@@ -0,0 +1,3 @@
1
+ module CloudAssets
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,223 @@
1
+ require "cloud_assets/version"
2
+
3
+ module CloudAssets
4
+
5
+ def self.setup
6
+ yield self
7
+ end
8
+
9
+ # Monkey-patch this method if you need to hack some fixups into
10
+ # html from your asset source.
11
+ def self.fixup_html(html)
12
+ html
13
+ end
14
+
15
+ # Monkey-patch this method if you need to hack some fixups into
16
+ # javascript from your asset source.
17
+ def self.fixup_javascript(javascript)
18
+ javascript
19
+ end
20
+
21
+ # Monkey-patch this method if you need to hack some fixups into
22
+ # css from your asset source.
23
+ def self.fixup_css(css)
24
+ css
25
+ end
26
+
27
+ mattr_accessor :origin
28
+ @@origin = ENV['CLOUD_ASSET_ORIGIN']
29
+
30
+ mattr_accessor :user
31
+ @@user = ENV['CLOUD_ASSET_USER']
32
+
33
+ mattr_accessor :password
34
+ @@password = ENV['CLOUD_ASSET_PASSWORD']
35
+
36
+ mattr_accessor :cdn
37
+ @@cdn = ENV['CLOUD_ASSET_CDN'] || ''
38
+
39
+ # How long objects from the source can be kept in our local cache
40
+ mattr_accessor :cache_timeout_seconds
41
+ @@cache_timeout_seconds = 604800
42
+
43
+ # How long to allow clients (and CDN) to keep javascript assets
44
+ mattr_accessor :javascript_max_age_seconds
45
+ @@javascript_max_age_seconds = 600
46
+
47
+ # How long to allow clients (and CDN) to keep css assets
48
+ mattr_accessor :css_max_age_seconds
49
+ @@css_max_age_seconds = 600
50
+
51
+ # How long to allow clients (and CDN) to keep other assets
52
+ mattr_accessor :other_max_age_seconds
53
+ @@other_max_age_seconds = 86400
54
+
55
+ mattr_accessor :verbose
56
+ @@verbose = false
57
+
58
+ class Engine < Rails::Engine
59
+
60
+ module ControllerMethods
61
+
62
+ require 'typhoeus'
63
+ require 'nokogiri'
64
+
65
+ def cloud_asset(path)
66
+ p = "#{CloudAssets::origin}#{path}"
67
+ hydra = Typhoeus::Hydra.new
68
+ unless $dalli_cache.nil?
69
+ hydra.cache_getter do |request|
70
+ $dalli_cache.get(request.cache_key) rescue nil
71
+ end
72
+ hydra.cache_setter do |request|
73
+ $dalli_cache.set(request.cache_key, request.response, request.cache_timeout)
74
+ end
75
+ end
76
+ options = {
77
+ :follow_location => true,
78
+ :max_redirects => 3,
79
+ :cache_timeout => CloudAssets::cache_timeout_seconds
80
+ }
81
+ unless CloudAssets::user.nil?
82
+ options[:username] = CloudAssets::user
83
+ options[:password] = CloudAssets::password
84
+ options[:auth_method] = :basic
85
+ end
86
+ if CloudAssets.verbose
87
+ puts "Retrieving remote asset #{p}"
88
+ end
89
+ request = Typhoeus::Request.new p, options
90
+ asset_response = nil
91
+ request.on_complete do |hydra_response|
92
+ asset_response = hydra_response
93
+ end
94
+ hydra.queue request
95
+ hydra.run
96
+ asset_response
97
+ end
98
+
99
+ def optimize_uri(src)
100
+ return nil if src.nil?
101
+ o = CloudAssets::cdn || ''
102
+ src.gsub!(CloudAssets::origin,'')
103
+ return src if src =~ /^http:/
104
+ "#{o}#{src}"
105
+ end
106
+
107
+ def correct_uri(src)
108
+ src.gsub(CloudAssets::origin,'')
109
+ end
110
+
111
+ def optimized_html_for(asset_response)
112
+ doc = Nokogiri::HTML(asset_response.body)
113
+
114
+ { 'img' => 'src',
115
+ 'link' => 'href' }.each do |tag,attribute|
116
+ doc.css(tag).each do |e|
117
+ if tag == 'link' and e['rel'] != 'stylesheet'
118
+ next
119
+ end
120
+ unless e[attribute].nil?
121
+ e[attribute] = optimize_uri(e[attribute])
122
+ end
123
+ end
124
+ end
125
+
126
+ { 'a' => 'href',
127
+ 'script' => 'src',
128
+ 'link' => 'href' }.each do |tag,attribute|
129
+ doc.css(tag).each do |e|
130
+ if tag == 'link' and e['rel'] == 'stylesheet'
131
+ next
132
+ end
133
+ unless e[attribute].nil?
134
+ e[attribute] = correct_uri(e[attribute])
135
+ end
136
+ end
137
+ end
138
+
139
+ doc
140
+ end
141
+
142
+ def inject_into_remote_layout(hash)
143
+ if @injections.nil?
144
+ @injections = {}
145
+ end
146
+ @injections.merge! hash
147
+ end
148
+
149
+ def override_remote_layout(hash)
150
+ if @overrides.nil?
151
+ @overrides = {}
152
+ end
153
+ @overrides.merge! hash
154
+ end
155
+
156
+ def set_remote_layout(layout)
157
+ @remote_layout = layout
158
+ end
159
+
160
+ def set_default_remote_layout(layout)
161
+ if @remote_layout.nil?
162
+ @remote_layout = layout
163
+ end
164
+ end
165
+
166
+ def apply_remote_layout
167
+ begin
168
+ if @remote_layout.nil?
169
+ raise <<-ERR
170
+ No remote layout is defined. Use set_remote_layout or
171
+ set_default_remote_layout in your views prior to calling
172
+ apply_remote_layout.
173
+ ERR
174
+ end
175
+ if @remote_layout.kind_of? String
176
+ doc = optimized_html_for cloud_asset "#{@remote_layout}"
177
+ else
178
+ doc = optimized_html_for @remote_layout
179
+ end
180
+ unless @overrides.nil?
181
+ @overrides.each do |key, value|
182
+ begin
183
+ doc.at_css(key).inner_html = value
184
+ rescue
185
+ puts "Failed to override template element: #{key}"
186
+ end
187
+ end
188
+ end
189
+ unless @injections.nil?
190
+ @injections.each do |key, value|
191
+ begin
192
+ doc.at_css(key).add_child(value)
193
+ rescue
194
+ puts "Failed to inject data into template element: #{key}"
195
+ end
196
+ end
197
+ end
198
+ # We don't know what in-doc references might be to, so we have to make
199
+ # them local and can't optimize them to the CDN -- at least not without
200
+ # some serious guessing which we are not ready to do
201
+ CloudAssets::fixup_html(doc.to_s.gsub CloudAssets::origin, '')
202
+ rescue => e
203
+ puts e.inspect
204
+ puts e.backtrace
205
+ raise e
206
+ end
207
+ end
208
+
209
+ end
210
+
211
+ initializer 'cloud_assets.app_controller' do |app|
212
+ ActiveSupport.on_load(:action_controller) do
213
+ include ControllerMethods
214
+ helper_method :inject_into_remote_layout
215
+ helper_method :override_remote_layout
216
+ helper_method :set_remote_layout
217
+ helper_method :set_default_remote_layout
218
+ helper_method :apply_remote_layout
219
+ end
220
+ end
221
+ end
222
+
223
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloud_assets
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rob Heittman
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-26 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: typhoeus
16
+ requirement: &70144157060340 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70144157060340
25
+ - !ruby/object:Gem::Dependency
26
+ name: nokogiri
27
+ requirement: &70144157059580 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70144157059580
36
+ - !ruby/object:Gem::Dependency
37
+ name: dalli
38
+ requirement: &70144157058760 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70144157058760
47
+ description: ! "\n This gem is in use in some production sites to provide\n backing
48
+ for a Rails app using content from WordPress and\n PostLaunch (a Java based CMS),
49
+ and has specific\n dependencies on Typhoeus, Nokogiri, and dalli, favorites\n
50
+ \ we use at Solertium. It's in the early stages of\n being made into a general
51
+ purpose tool -- cleanup,\n generalized tests (our site-specific ones are stripped\n
52
+ \ from the gem) documentation, performance, and decoupling\n from dependencies.\n
53
+ \ "
54
+ email:
55
+ - rob.heittman@solertium.com
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - .gitignore
61
+ - Gemfile
62
+ - README.md
63
+ - Rakefile
64
+ - app/controllers/cloud_assets_controller.rb
65
+ - app/views/cloud_assets/content.html.erb
66
+ - cloud_assets.gemspec
67
+ - lib/cloud_assets.rb
68
+ - lib/cloud_assets/version.rb
69
+ homepage: https://github.com/rfc2616/cloud_assets
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.17
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Enables a Rails app to make transparent use of assets on a remote server
93
+ in an alternative technology.
94
+ test_files: []