cloudfront_asset_host 1.0.0

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 @@
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