cloudfront_asset_host 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Menno van der Sman
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,61 @@
1
+ # CloudFront AssetHost
2
+
3
+ Easy deployment of your assets on CloudFront or S3. When enabled in production, your assets will be served from Cloudfront/S3 which will result in a speedier front-end.
4
+
5
+ ## Installing
6
+
7
+ Include the gem in your app's `environment.rb` or Gemfile. It is hosted at Gemcutter[http://gemcutter.org/gems/cloudfront_asset_host]
8
+
9
+ ### Dependencies
10
+
11
+ Make sure your s3-credentials are stored in _config/s3.yml_ like this:
12
+
13
+ access_key_id: 'access_key'
14
+ secret_access_key: 'secret'
15
+
16
+ The gem relies on +openssl md5+ and +gzip+ utilities. Make sure they are available locally and on your servers.
17
+
18
+ ### Configuration
19
+ Create an initializer to configure the plugin _config/initializers/cloudfront_asset_host.rb_
20
+
21
+ # Simple configuration
22
+ CloudfrontAssetHost.configure do |config|
23
+ config.bucket = "bucketname" # required
24
+ config.enabled = true if Rails.env.production? # only enable in production
25
+ end
26
+
27
+ # Extended configuration
28
+ CloudfrontAssetHost.configure do |config|
29
+ config.bucket = "bucketname" # required
30
+ config.cname = "assets.domain.com" # if you have a cname configured for your distribution or bucket
31
+ config.key_prefix = "app/" # if you share the bucket and want to keep things separated
32
+ config.s3_config = "#{RAILS_ROOT}/config/s3.yml" # Alternative location of your s3-config file
33
+
34
+ # gzip related configuration
35
+ config.gzip = true # enable gzipped assets (defaults to true)
36
+ config.gzip_extensions = ['js', 'css'] # only gzip javascript or css (defaults to %w(js css))
37
+ config.gzip_prefix = "gz" # prefix for gzipped bucket (defaults to "gz")
38
+
39
+ config.enabled = true if Rails.env.production? # only enable in production
40
+ end
41
+
42
+ ## Usage
43
+
44
+ ### Uploading your assets
45
+ Run `CloudfrontAssetHost::Uploader.upload!(:verbose => true, :dryrun => false)` before your deployment. Put it for example in your Rakefile or capistrano-recipe. Verbose output will include information about which keys are being uploaded. Enabling _dryrun_ will skip the actual upload if you're just interested to see what will be uploaded.
46
+
47
+ ### Hooks
48
+ If the plugin is enabled. Rails' internal `asset_host` and `asset_id` functionality will be overridden to point to the location of the assets on Cloudfront.
49
+
50
+ ### Other plugins
51
+ When using in combination with SASS and/or asset_packager it is recommended to generate the css-files and package your assets before uploading them to Cloudfront. For example, call `Sass::Plugin.update_stylesheets` and `Synthesis::AssetPackage.build_all` first.
52
+
53
+ ## Compatibility
54
+
55
+ Tested on Rails 2.3.5 with SASS and AssetPackager plugins
56
+
57
+ ## Copyright
58
+
59
+ Created at Wakoopa
60
+
61
+ Copyright (c) 2010 Menno van der Sman, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the cloudfront_asset_host plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.libs << 'test'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the cloudfront_asset_host plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'CloudfrontAssetHost'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ begin
26
+ require 'jeweler'
27
+ Jeweler::Tasks.new do |gemspec|
28
+ gemspec.name = "cloudfront_asset_host"
29
+ gemspec.summary = "Rails plugin to easily and efficiently deploy your assets on Amazon's S3 or CloudFront"
30
+ gemspec.description = "Easy deployment of your assets on CloudFront or S3 using a simple rake-task. When enabled in production, the application's asset_host and public_paths will point to the correct location."
31
+ gemspec.email = "menno@wakoopa.com"
32
+ gemspec.homepage = "http://github.com/menno/cloudfront_asset_host"
33
+ gemspec.authors = ["Menno van der Sman"]
34
+ gemspec.add_dependency 'right_aws'
35
+ end
36
+ Jeweler::GemcutterTasks.new
37
+ rescue LoadError
38
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
39
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
@@ -0,0 +1,64 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{cloudfront_asset_host}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Menno van der Sman"]
12
+ s.date = %q{2010-03-18}
13
+ s.description = %q{Easy deployment of your assets on CloudFront or S3 using a simple rake-task. When enabled in production, the application's asset_host and public_paths will point to the correct location.}
14
+ s.email = %q{menno@wakoopa.com}
15
+ s.extra_rdoc_files = [
16
+ "README.markdown"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "MIT-LICENSE",
21
+ "README.markdown",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "cloudfront_asset_host.gemspec",
25
+ "lib/cloudfront_asset_host.rb",
26
+ "lib/cloudfront_asset_host/asset_tag_helper_ext.rb",
27
+ "lib/cloudfront_asset_host/css_rewriter.rb",
28
+ "lib/cloudfront_asset_host/mime_types.yml",
29
+ "lib/cloudfront_asset_host/uploader.rb",
30
+ "test/app/config/s3.yml",
31
+ "test/app/public/images/image.png",
32
+ "test/app/public/javascripts/application.js",
33
+ "test/app/public/stylesheets/style.css",
34
+ "test/cloudfront_asset_host_test.rb",
35
+ "test/css_rewriter_test.rb",
36
+ "test/test_helper.rb",
37
+ "test/uploader_test.rb"
38
+ ]
39
+ s.homepage = %q{http://github.com/menno/cloudfront_asset_host}
40
+ s.rdoc_options = ["--charset=UTF-8"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = %q{1.3.6}
43
+ s.summary = %q{Rails plugin to easily and efficiently deploy your assets on Amazon's S3 or CloudFront}
44
+ s.test_files = [
45
+ "test/cloudfront_asset_host_test.rb",
46
+ "test/css_rewriter_test.rb",
47
+ "test/test_helper.rb",
48
+ "test/uploader_test.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ s.add_runtime_dependency(%q<right_aws>, [">= 0"])
57
+ else
58
+ s.add_dependency(%q<right_aws>, [">= 0"])
59
+ end
60
+ else
61
+ s.add_dependency(%q<right_aws>, [">= 0"])
62
+ end
63
+ end
64
+
@@ -0,0 +1,103 @@
1
+ require 'cloudfront_asset_host/asset_tag_helper_ext'
2
+
3
+ module CloudfrontAssetHost
4
+
5
+ autoload :Uploader, 'cloudfront_asset_host/uploader'
6
+ autoload :CssRewriter, 'cloudfront_asset_host/css_rewriter'
7
+
8
+ # Bucket that will be used to store all the assets (required)
9
+ mattr_accessor :bucket
10
+
11
+ # CNAME that is configured for the bucket or CloudFront distribution
12
+ mattr_accessor :cname
13
+
14
+ # Prefix keys
15
+ mattr_accessor :key_prefix
16
+
17
+ # Path to S3 config. Expects an +access_key_id+ and +secret_access_key+
18
+ mattr_accessor :s3_config
19
+
20
+ # Indicates whether the plugin should be enabled
21
+ mattr_accessor :enabled
22
+
23
+ # Upload gzipped assets and serve those assets when applicable
24
+ mattr_accessor :gzip
25
+
26
+ # Which extensions to serve as gzip
27
+ mattr_accessor :gzip_extensions
28
+
29
+ # Key-prefix under which to store gzipped assets
30
+ mattr_accessor :gzip_prefix
31
+
32
+ class << self
33
+
34
+ def configure
35
+ # default configuration
36
+ self.bucket = nil
37
+ self.cname = nil
38
+ self.key_prefix = ""
39
+ self.s3_config = "#{RAILS_ROOT}/config/s3.yml"
40
+ self.enabled = false
41
+
42
+ self.gzip = true
43
+ self.gzip_extensions = %w(js css)
44
+ self.gzip_prefix = "gz"
45
+
46
+ yield(self)
47
+
48
+ if properly_configured?
49
+ enable!
50
+ end
51
+ end
52
+
53
+ def asset_host(source = nil, request = nil)
54
+ host = cname.present? ? "http://#{self.cname}" : "http://#{self.bucket_host}"
55
+
56
+ if source && request && CloudfrontAssetHost.gzip
57
+ gzip_allowed = CloudfrontAssetHost.gzip_allowed_for_source?(source)
58
+ gzip_accepted = !(request.headers['User-Agent'].to_s =~ /(Mozilla\/4\.0[678])|(MSIE\s[1-6])/) && request.headers['Accept-Encoding'].to_s.include?('gzip')
59
+
60
+ if gzip_accepted && gzip_allowed
61
+ host << "/#{CloudfrontAssetHost.gzip_prefix}"
62
+ end
63
+ end
64
+
65
+ host
66
+ end
67
+
68
+ def bucket_host
69
+ "#{self.bucket}.s3.amazonaws.com"
70
+ end
71
+
72
+ def enable!
73
+ if enabled
74
+ ActionController::Base.asset_host = Proc.new { |source, request| CloudfrontAssetHost.asset_host(source, request) }
75
+ ActionView::Helpers::AssetTagHelper.send(:alias_method_chain, :rewrite_asset_path, :cloudfront)
76
+ ActionView::Helpers::AssetTagHelper.send(:alias_method_chain, :rails_asset_id, :cloudfront)
77
+ end
78
+ end
79
+
80
+ def key_for_path(path)
81
+ key_prefix + md5sum(path)[0..8]
82
+ end
83
+
84
+ def gzip_allowed_for_source?(source)
85
+ extension = source.split('.').last
86
+ CloudfrontAssetHost.gzip_extensions.include?(extension)
87
+ end
88
+
89
+ private
90
+
91
+ def properly_configured?
92
+ raise "You'll need to specify a bucket" if bucket.blank?
93
+ raise "Could not find S3-configuration" unless File.exists?(s3_config)
94
+ true
95
+ end
96
+
97
+ def md5sum(path)
98
+ `openssl md5 #{path}`.split(/\s/)[1].to_s
99
+ end
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1,37 @@
1
+ module ActionView
2
+ module Helpers
3
+ module AssetTagHelper
4
+
5
+ private
6
+
7
+ # Override asset_id so it calculates the key by md5 instead of modified-time
8
+ def rails_asset_id_with_cloudfront(source)
9
+ if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
10
+ asset_id
11
+ else
12
+ path = File.join(ASSETS_DIR, source)
13
+ asset_id = File.exist?(path) ? CloudfrontAssetHost.key_for_path(path) : ''
14
+
15
+ if @@cache_asset_timestamps
16
+ @@asset_timestamps_cache_guard.synchronize do
17
+ @@asset_timestamps_cache[source] = asset_id
18
+ end
19
+ end
20
+
21
+ asset_id
22
+ end
23
+ end
24
+
25
+ # Override asset_path so it prepends the asset_id
26
+ def rewrite_asset_path_with_cloudfront(source)
27
+ asset_id = rails_asset_id(source)
28
+ if asset_id.blank?
29
+ source
30
+ else
31
+ "/#{asset_id}#{source}"
32
+ end
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,65 @@
1
+ require 'tempfile'
2
+
3
+ module CloudfrontAssetHost
4
+ module CssRewriter
5
+
6
+ # Location of the stylesheets directory
7
+ mattr_accessor :stylesheets_dir
8
+ self.stylesheets_dir = File.join(Rails.public_path, 'stylesheets')
9
+
10
+ class << self
11
+ # matches optional quoted url(<path>)
12
+ ReplaceRexeg = /url\(["']?([^\)"']+)["']?\)/i
13
+
14
+ # Returns the path to the temporary file that contains the
15
+ # rewritten stylesheet
16
+ def rewrite_stylesheet(path)
17
+ contents = File.read(path)
18
+ contents.gsub!(ReplaceRexeg) do |match|
19
+ rewrite_asset_link(match, path)
20
+ end
21
+
22
+ tmp = Tempfile.new("cfah-css")
23
+ tmp.write(contents)
24
+ tmp.flush
25
+ tmp
26
+ end
27
+
28
+ private
29
+
30
+ def rewrite_asset_link(asset_link, stylesheet_path)
31
+ url = asset_link.match(ReplaceRexeg)[1]
32
+ if url
33
+ path = path_for_url(url, stylesheet_path)
34
+
35
+ if path.present? && File.exists?(path)
36
+ key = CloudfrontAssetHost.key_for_path(path) + path.gsub(Rails.public_path, '')
37
+ "url(#{CloudfrontAssetHost.asset_host}/#{key})"
38
+ else
39
+ puts "Could not extract path: #{path}"
40
+ asset_link
41
+ end
42
+ else
43
+ puts "Could not find url in #{asset_link}"
44
+ asset_link
45
+ end
46
+ end
47
+
48
+ def path_for_url(url, stylesheet_path)
49
+ if url.starts_with?('/')
50
+ # absolute to public path
51
+ File.expand_path(File.join(Rails.public_path, url))
52
+ else
53
+ # relative to stylesheet_path
54
+ File.expand_path(File.join(File.dirname(stylesheet_path), url))
55
+ end
56
+ end
57
+
58
+ def stylesheets_to_rewrite
59
+ Dir.glob("#{stylesheets_dir}/**/*.css")
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,151 @@
1
+ "text/html":
2
+ - html
3
+ - htm
4
+ - shtml
5
+ "text/css":
6
+ - css
7
+ "text/xml":
8
+ - xml
9
+ - rss
10
+ "image/gif":
11
+ - gif
12
+ "image/jpeg":
13
+ - jpeg
14
+ - jpg
15
+ "application/x-javascript":
16
+ - js
17
+ "application/atom+xml":
18
+ - atom
19
+ "text/mathml":
20
+ - mml
21
+ "text/plain":
22
+ - txt
23
+ "text/vnd.sun.j2me.app-descriptor":
24
+ - jad
25
+ "text/vnd.wap.wml":
26
+ - wml
27
+ "text/x-component":
28
+ - htc
29
+ "image/png":
30
+ - png
31
+ "image/tiff":
32
+ - tif
33
+ - tiff
34
+ "image/vnd.wap.wbmp":
35
+ - wbmp
36
+ "image/x-icon":
37
+ - ico
38
+ "image/x-jng":
39
+ - jng
40
+ "image/x-ms-bmp":
41
+ - bmp
42
+ "image/svg+xml":
43
+ - svg
44
+ "application/java-archive":
45
+ - jar
46
+ - war
47
+ - ear
48
+ "application/mac-binhex40":
49
+ - hqx
50
+ "application/msword":
51
+ - doc
52
+ "application/pdf":
53
+ - pdf
54
+ "application/postscript":
55
+ - ps
56
+ - eps
57
+ - ai
58
+ "application/rtf":
59
+ - rtf
60
+ "application/vnd.ms-excel":
61
+ - xls
62
+ "application/vnd.ms-powerpoint":
63
+ - ppt
64
+ "application/vnd.wap.wmlc":
65
+ - wmlc
66
+ "application/vnd.wap.xhtml+xml":
67
+ - xhtml
68
+ "application/x-cocoa":
69
+ - cco
70
+ "application/x-java-archive-diff":
71
+ - jardiff
72
+ "application/x-java-jnlp-file":
73
+ - jnlp
74
+ "application/x-makeself":
75
+ - run
76
+ "application/x-perl":
77
+ - pl
78
+ - pm
79
+ "application/x-pilot":
80
+ - prc
81
+ - pdb
82
+ "application/x-rar-compressed":
83
+ - rar
84
+ "application/x-redhat-package-manager":
85
+ - rpm
86
+ "application/x-sea":
87
+ - sea
88
+ "application/x-shockwave-flash":
89
+ - swf
90
+ "application/x-stuffit":
91
+ - sit
92
+ "application/x-tcl":
93
+ - tcl
94
+ - tk
95
+ "application/x-x509-ca-cert":
96
+ - der
97
+ - pem
98
+ - crt
99
+ "application/x-xpinstall":
100
+ - xpi
101
+ "application/zip":
102
+ - zip
103
+ "application/octet-stream":
104
+ - bin
105
+ - exe
106
+ - dll
107
+ "application/octet-stream":
108
+ - deb
109
+ "application/octet-stream":
110
+ - dmg
111
+ "application/octet-stream":
112
+ - eot
113
+ "application/octet-stream":
114
+ - iso
115
+ - img
116
+ "application/octet-stream":
117
+ - msi
118
+ - msp
119
+ - msm
120
+ "audio/midi":
121
+ - mid
122
+ - midi
123
+ - kar
124
+ "audio/mpeg":
125
+ - mp3
126
+ "audio/x-realaudio":
127
+ - ra
128
+ "video/3gpp":
129
+ - 3gpp
130
+ - 3gp
131
+ "video/mpeg":
132
+ - mpeg
133
+ - mpg
134
+ "video/quicktime":
135
+ - mov
136
+ "video/x-flv":
137
+ - flv
138
+ "video/x-mng":
139
+ - mng
140
+ "video/x-ms-asf":
141
+ - asx
142
+ - asf
143
+ "video/x-ms-wmv":
144
+ - wmv
145
+ "video/x-msvideo":
146
+ - avi
147
+ "text/plain":
148
+ - py
149
+ - php
150
+ - config
151
+ - txt
@@ -0,0 +1,124 @@
1
+ require 'right_aws'
2
+ require 'tempfile'
3
+
4
+ module CloudfrontAssetHost
5
+ module Uploader
6
+
7
+ class << self
8
+
9
+ def upload!(options = {})
10
+ dryrun = options.delete(:dryrun) || false
11
+ verbose = options.delete(:verbose) || false
12
+
13
+ puts "-- Updating uncompressed files" if verbose
14
+ upload_keys_with_paths(keys_with_paths, dryrun, verbose, false)
15
+
16
+ if CloudfrontAssetHost.gzip
17
+ puts "-- Updating compressed files" if verbose
18
+ upload_keys_with_paths(gzip_keys_with_paths, dryrun, verbose, true)
19
+ end
20
+
21
+ @existing_keys = nil
22
+ end
23
+
24
+ def upload_keys_with_paths(keys_paths, dryrun, verbose, gzip)
25
+ keys_paths.each do |key, path|
26
+ if existing_keys.include?(key)
27
+ puts "= #{key}" if verbose
28
+ else
29
+ puts "+ #{key}" if verbose
30
+
31
+ extension = File.extname(path)[1..-1]
32
+
33
+ path = rewritten_css_path(path)
34
+
35
+ data_path = gzip ? gzipped_path(path) : path
36
+ bucket.put(key, File.read(data_path), {}, 'public-read', headers_for_path(extension, gzip)) unless dryrun
37
+
38
+ File.unlink(data_path) if gzip && File.exists?(data_path)
39
+ end
40
+ end
41
+ end
42
+
43
+ def gzipped_path(path)
44
+ tmp = Tempfile.new("cfah-gz")
45
+ `gzip #{path} -q -c > #{tmp.path}`
46
+ tmp.path
47
+ end
48
+
49
+ def rewritten_css_path(path)
50
+ if File.extname(path) == '.css'
51
+ tmp = CloudfrontAssetHost::CssRewriter.rewrite_stylesheet(path)
52
+ tmp.path
53
+ else
54
+ path
55
+ end
56
+ end
57
+
58
+ def keys_with_paths
59
+ current_paths.inject({}) do |result, path|
60
+ key = CloudfrontAssetHost.key_for_path(path) + path.gsub(Rails.public_path, '')
61
+
62
+ result[key] = path
63
+ result
64
+ end
65
+ end
66
+
67
+ def gzip_keys_with_paths
68
+ current_paths.inject({}) do |result, path|
69
+ source = path.gsub(Rails.public_path, '')
70
+
71
+ if CloudfrontAssetHost.gzip_allowed_for_source?(source)
72
+ key = "#{CloudfrontAssetHost.gzip_prefix}/" << CloudfrontAssetHost.key_for_path(path) << source
73
+ result[key] = path
74
+ end
75
+
76
+ result
77
+ end
78
+ end
79
+
80
+ def existing_keys
81
+ @existing_keys ||= begin
82
+ keys = []
83
+ keys.concat bucket.keys('prefix' => CloudfrontAssetHost.key_prefix).map { |key| key.name }
84
+ keys.concat bucket.keys('prefix' => CloudfrontAssetHost.gzip_prefix).map { |key| key.name }
85
+ keys
86
+ end
87
+ end
88
+
89
+ def current_paths
90
+ @current_paths ||= Dir.glob("#{Rails.public_path}/{images,javascripts,stylesheets}/**/*").reject { |path| File.directory?(path) }
91
+ end
92
+
93
+ def headers_for_path(extension, gzip = false)
94
+ mime = ext_to_mime[extension] || 'application/octet-stream'
95
+ headers = {
96
+ 'Content-Type' => mime,
97
+ 'Cache-Control' => "max-age=#{10.years.to_i}",
98
+ 'Expires' => 1.year.from_now.utc.to_s
99
+ }
100
+ headers['Content-Encoding'] = 'gzip' if gzip
101
+
102
+ headers
103
+ end
104
+
105
+ def ext_to_mime
106
+ @ext_to_mime ||= Hash[ *( YAML::load_file(File.join(File.dirname(__FILE__), "mime_types.yml")).collect { |k,vv| vv.collect{ |v| [v,k] } }.flatten ) ]
107
+ end
108
+
109
+ def bucket
110
+ @bucket ||= s3.bucket(CloudfrontAssetHost.bucket)
111
+ end
112
+
113
+ def s3
114
+ @s3 ||= RightAws::S3.new(config['access_key_id'], config['secret_access_key'])
115
+ end
116
+
117
+ def config
118
+ @config ||= YAML::load_file(CloudfrontAssetHost.s3_config)
119
+ end
120
+
121
+ end
122
+
123
+ end
124
+ end
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ access_key_id: 'access_key'
3
+ secret_access_key: 'secret'
4
+ options:
5
+ use_ssl: true
File without changes
@@ -0,0 +1 @@
1
+ // Javascripts here
@@ -0,0 +1,4 @@
1
+ body { background-image: url(../images/image.png); }
2
+ body { background-image: url(/images/image.png); }
3
+ body { background-image: url('/images/image.png'); }
4
+ body { background-image: url("/images/image.png"); }
@@ -0,0 +1,104 @@
1
+ require 'test_helper'
2
+
3
+ class CloudfrontAssetHostTest < Test::Unit::TestCase
4
+
5
+ context "A configured plugin" do
6
+ setup do
7
+ CloudfrontAssetHost.configure do |config|
8
+ config.cname = "assethost.com"
9
+ config.bucket = "bucketname"
10
+ config.key_prefix = ""
11
+ config.enabled = false
12
+ end
13
+ end
14
+
15
+ should "add methods to asset-tag-helper" do
16
+ assert ActionView::Helpers::AssetTagHelper.private_method_defined?('rails_asset_id_with_cloudfront')
17
+ assert ActionView::Helpers::AssetTagHelper.private_method_defined?('rewrite_asset_path_with_cloudfront')
18
+ end
19
+
20
+ should "not enable itself by default" do
21
+ assert_equal false, CloudfrontAssetHost.enabled
22
+ assert_equal "", ActionController::Base.asset_host
23
+ end
24
+
25
+ should "return key for path" do
26
+ assert_equal "8ed41cb87", CloudfrontAssetHost.key_for_path(File.join(RAILS_ROOT, 'public', 'javascripts', 'application.js'))
27
+ end
28
+
29
+ should "prepend prefix to key" do
30
+ CloudfrontAssetHost.key_prefix = "prefix/"
31
+ assert_equal "prefix/8ed41cb87", CloudfrontAssetHost.key_for_path(File.join(RAILS_ROOT, 'public', 'javascripts', 'application.js'))
32
+ end
33
+
34
+ context "asset-host" do
35
+
36
+ setup do
37
+ @gzip_request = stub(:headers => {'Accept-Encoding' => 'gzip, compress'})
38
+ @request = stub(:headers => {})
39
+ @img_source = "/images/logo.png"
40
+ @js_source = "/javascripts/application.js"
41
+ end
42
+
43
+ should "use cname for asset_host" do
44
+ assert_equal "http://assethost.com", CloudfrontAssetHost.asset_host(@img_source, @request)
45
+ end
46
+
47
+ should "use bucket_host when cname is not present" do
48
+ CloudfrontAssetHost.cname = nil
49
+ assert_equal "http://bucketname.s3.amazonaws.com", CloudfrontAssetHost.asset_host(@img_source, @request)
50
+ end
51
+
52
+ should "add gz to asset_host when applicable" do
53
+ assert_equal "http://assethost.com/gz", CloudfrontAssetHost.asset_host(@js_source, @gzip_request)
54
+ end
55
+
56
+ end
57
+ end
58
+
59
+ context "An enabled and configured plugin" do
60
+ setup do
61
+ CloudfrontAssetHost.configure do |config|
62
+ config.enabled = true
63
+ config.cname = "assethost.com"
64
+ config.bucket = "bucketname"
65
+ config.key_prefix = ""
66
+ end
67
+ end
68
+
69
+ should "set the asset_host" do
70
+ assert ActionController::Base.asset_host.is_a?(Proc)
71
+ end
72
+
73
+ should "alias methods in asset-tag-helper" do
74
+ assert ActionView::Helpers::AssetTagHelper.private_method_defined?('rails_asset_id_without_cloudfront')
75
+ assert ActionView::Helpers::AssetTagHelper.private_method_defined?('rewrite_asset_path_without_cloudfront')
76
+ assert ActionView::Helpers::AssetTagHelper.private_method_defined?('rails_asset_id')
77
+ assert ActionView::Helpers::AssetTagHelper.private_method_defined?('rewrite_asset_path')
78
+ end
79
+ end
80
+
81
+ context "An improperly configured plugin" do
82
+ should "complain about bucket not being set" do
83
+ assert_raise(RuntimeError) {
84
+ CloudfrontAssetHost.configure do |config|
85
+ config.enabled = false
86
+ config.cname = "assethost.com"
87
+ config.bucket = nil
88
+ end
89
+ }
90
+ end
91
+
92
+ should "complain about missing s3-config" do
93
+ assert_raise(RuntimeError) {
94
+ CloudfrontAssetHost.configure do |config|
95
+ config.enabled = false
96
+ config.cname = "assethost.com"
97
+ config.bucket = "bucketname"
98
+ config.s3_config = "bogus"
99
+ end
100
+ }
101
+ end
102
+ end
103
+
104
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ class CssRewriterTest < Test::Unit::TestCase
4
+
5
+ context "The CssRewriter" do
6
+
7
+ setup do
8
+ CloudfrontAssetHost.configure do |config|
9
+ config.cname = "assethost.com"
10
+ config.bucket = "bucketname"
11
+ config.key_prefix = ""
12
+ config.enabled = false
13
+ end
14
+
15
+ @stylesheet_path = File.join(Rails.public_path, 'stylesheets', 'style.css')
16
+ end
17
+
18
+ should "rewrite a single css file" do
19
+ tmp = CloudfrontAssetHost::CssRewriter.rewrite_stylesheet(@stylesheet_path)
20
+ contents = File.read(tmp.path)
21
+ contents.split("\n").each do |line|
22
+ assert_equal "body { background-image: url(http://assethost.com/d41d8cd98/images/image.png); }", line
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,26 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+
4
+ require 'action_controller'
5
+ require 'right_aws'
6
+
7
+ require 'shoulda'
8
+ require 'mocha'
9
+ begin require 'redgreen'; rescue LoadError; end
10
+ begin require 'turn'; rescue LoadError; end
11
+
12
+ RAILS_ROOT = File.expand_path(File.join(File.dirname(__FILE__), 'app'))
13
+
14
+ module Rails
15
+ class << self
16
+ def root
17
+ RAILS_ROOT
18
+ end
19
+
20
+ def public_path
21
+ File.join(RAILS_ROOT, 'public')
22
+ end
23
+ end
24
+ end
25
+
26
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'cloudfront_asset_host')
@@ -0,0 +1,113 @@
1
+ require 'test_helper'
2
+
3
+ class UploaderTest < Test::Unit::TestCase
4
+
5
+ context "A configured uploader" do
6
+ setup do
7
+ CloudfrontAssetHost.configure do |config|
8
+ config.cname = "assethost.com"
9
+ config.bucket = "bucketname"
10
+ config.key_prefix = ""
11
+ config.s3_config = "#{RAILS_ROOT}/config/s3.yml"
12
+ config.enabled = false
13
+ end
14
+ end
15
+
16
+ should "be able to retrieve s3-config" do
17
+ config = CloudfrontAssetHost::Uploader.config
18
+ assert_equal 'access_key', config['access_key_id']
19
+ assert_equal 'secret', config['secret_access_key']
20
+ end
21
+
22
+ should "be able to instantiate s3-interface" do
23
+ RightAws::S3.expects(:new).with('access_key', 'secret').returns(mock)
24
+ assert_not_nil CloudfrontAssetHost::Uploader.s3
25
+ end
26
+
27
+ should "glob current files" do
28
+ assert_equal 3, CloudfrontAssetHost::Uploader.current_paths.length
29
+ end
30
+
31
+ should "calculate keys for paths" do
32
+ keys_with_paths = CloudfrontAssetHost::Uploader.keys_with_paths
33
+ assert_equal 3, keys_with_paths.length
34
+ assert_match %r{/test/app/public/javascripts/application\.js$}, keys_with_paths["8ed41cb87/javascripts/application.js"]
35
+ end
36
+
37
+ should "calculate gzip keys for paths" do
38
+ gz_keys_with_paths = CloudfrontAssetHost::Uploader.gzip_keys_with_paths
39
+ assert_equal 2, gz_keys_with_paths.length
40
+ assert_match %r{/test/app/public/javascripts/application\.js$}, gz_keys_with_paths["gz/8ed41cb87/javascripts/application.js"]
41
+ assert_match %r{/test/app/public/stylesheets/style\.css$}, gz_keys_with_paths["gz/bd258f13d/stylesheets/style.css"]
42
+ end
43
+
44
+ should "return a mimetype for an extension" do
45
+ assert_equal 'application/x-javascript', CloudfrontAssetHost::Uploader.ext_to_mime['js']
46
+ end
47
+
48
+ should "construct the headers for a path" do
49
+ headers = CloudfrontAssetHost::Uploader.headers_for_path('js')
50
+ assert_equal 'application/x-javascript', headers['Content-Type']
51
+ assert_match /max-age=\d+/, headers['Cache-Control']
52
+ assert DateTime.parse(headers['Expires'])
53
+ end
54
+
55
+ should "add gzip-header" do
56
+ headers = CloudfrontAssetHost::Uploader.headers_for_path('js', true)
57
+ assert_equal 'application/x-javascript', headers['Content-Type']
58
+ assert_equal 'gzip', headers['Content-Encoding']
59
+ assert_match /max-age=\d+/, headers['Cache-Control']
60
+ assert DateTime.parse(headers['Expires'])
61
+ end
62
+
63
+ should "retrieve existing keys" do
64
+ bucket_mock = mock
65
+ bucket_mock.expects(:keys).with({'prefix' => ''}).returns([stub(:name => "keyname")])
66
+ bucket_mock.expects(:keys).with({'prefix' => 'gz'}).returns([stub(:name => "gz/keyname")])
67
+
68
+ CloudfrontAssetHost::Uploader.expects(:bucket).times(2).returns(bucket_mock)
69
+ assert_equal ["keyname", "gz/keyname"], CloudfrontAssetHost::Uploader.existing_keys
70
+ end
71
+
72
+ should "upload files when there are no existing keys" do
73
+ bucket_mock = mock
74
+ bucket_mock.expects(:put).times(5)
75
+ CloudfrontAssetHost::Uploader.stubs(:bucket).returns(bucket_mock)
76
+ CloudfrontAssetHost::Uploader.stubs(:existing_keys).returns([])
77
+
78
+ CloudfrontAssetHost::Uploader.upload!
79
+ end
80
+
81
+ should "not re-upload existing keys" do
82
+ CloudfrontAssetHost::Uploader.expects(:bucket).never
83
+ CloudfrontAssetHost::Uploader.stubs(:existing_keys).returns(
84
+ ["gz/8ed41cb87/javascripts/application.js", "8ed41cb87/javascripts/application.js",
85
+ "d41d8cd98/images/image.png",
86
+ "bd258f13d/stylesheets/style.css", "gz/bd258f13d/stylesheets/style.css"]
87
+ )
88
+
89
+ CloudfrontAssetHost::Uploader.upload!
90
+ end
91
+
92
+ should "correctly gzip files" do
93
+ path = File.join(RAILS_ROOT, 'public', 'javascripts', 'application.js')
94
+ contents = File.read(path)
95
+
96
+ gz_path = CloudfrontAssetHost::Uploader.gzipped_path(path)
97
+ gunzip_contents = `gunzip #{gz_path} -q -c`
98
+
99
+ assert_equal contents, gunzip_contents
100
+ end
101
+
102
+ should "correctly rewrite css files" do
103
+ path = File.join(RAILS_ROOT, 'public', 'stylesheets', 'style.css')
104
+ css_path = CloudfrontAssetHost::Uploader.rewritten_css_path(path)
105
+
106
+ File.read(css_path).split("\n").each do |line|
107
+ assert_equal "body { background-image: url(http://assethost.com/d41d8cd98/images/image.png); }", line
108
+ end
109
+ end
110
+
111
+ end
112
+
113
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cloudfront_asset_host
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Menno van der Sman
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-18 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: right_aws
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ description: Easy deployment of your assets on CloudFront or S3 using a simple rake-task. When enabled in production, the application's asset_host and public_paths will point to the correct location.
33
+ email: menno@wakoopa.com
34
+ executables: []
35
+
36
+ extensions: []
37
+
38
+ extra_rdoc_files:
39
+ - README.markdown
40
+ files:
41
+ - .gitignore
42
+ - MIT-LICENSE
43
+ - README.markdown
44
+ - Rakefile
45
+ - VERSION
46
+ - cloudfront_asset_host.gemspec
47
+ - lib/cloudfront_asset_host.rb
48
+ - lib/cloudfront_asset_host/asset_tag_helper_ext.rb
49
+ - lib/cloudfront_asset_host/css_rewriter.rb
50
+ - lib/cloudfront_asset_host/mime_types.yml
51
+ - lib/cloudfront_asset_host/uploader.rb
52
+ - test/app/config/s3.yml
53
+ - test/app/public/images/image.png
54
+ - test/app/public/javascripts/application.js
55
+ - test/app/public/stylesheets/style.css
56
+ - test/cloudfront_asset_host_test.rb
57
+ - test/css_rewriter_test.rb
58
+ - test/test_helper.rb
59
+ - test/uploader_test.rb
60
+ has_rdoc: true
61
+ homepage: http://github.com/menno/cloudfront_asset_host
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options:
66
+ - --charset=UTF-8
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ requirements: []
84
+
85
+ rubyforge_project:
86
+ rubygems_version: 1.3.6
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: Rails plugin to easily and efficiently deploy your assets on Amazon's S3 or CloudFront
90
+ test_files:
91
+ - test/cloudfront_asset_host_test.rb
92
+ - test/css_rewriter_test.rb
93
+ - test/test_helper.rb
94
+ - test/uploader_test.rb