cloud_assets 0.0.2

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 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: []