cloud_assets 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +86 -0
- data/Rakefile +1 -0
- data/app/controllers/cloud_assets_controller.rb +34 -0
- data/app/views/cloud_assets/content.html.erb +0 -0
- data/cloud_assets.gemspec +37 -0
- data/lib/cloud_assets/version.rb +3 -0
- data/lib/cloud_assets.rb +223 -0
- metadata +94 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
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
|
data/lib/cloud_assets.rb
ADDED
@@ -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: []
|