asset_oss 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Richard Taylor
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.md ADDED
@@ -0,0 +1,44 @@
1
+ Asset OSS - 上传Rails项目静态文件到Aliyun OSS
2
+ ===
3
+
4
+ 关于
5
+ ---
6
+
7
+ 基于[asset_id](https://github.com/moocode/asset_id),[aset_sync](https://github.com/rumblelabs/asset_sync)也许是更好的选择
8
+
9
+ 一个简单的上传Rails assets目录里静态文件到Aliyun OSS工具
10
+
11
+ 使用和配置
12
+ ---
13
+
14
+ 添加`gem "asset_oss"`到你的Gemfile
15
+
16
+ 修改`config/environments/production.rb`文件, `config.action_controller.asset_host = "http://my_live_bucket.oss.aliyuncs.com"`
17
+
18
+ 新建一个`config/asset_oss.yml`文件
19
+ ```
20
+ production:
21
+ host: 'oss.aliyuncs.com'
22
+ access_key_id: 'MY_ACCESS_KEY'
23
+ cret_access_key: 'MY_ACCESS_SECRET'
24
+ bucket: "my_live_bucket"
25
+ ```
26
+
27
+ 创建rake任务, `lib/tasks/asset_oss.rake`
28
+ ```
29
+ namespace :asset do
30
+ namespace :oss do
31
+
32
+ desc "uploads the current assets to aliyun oss with stamped ids"
33
+ task :upload do
34
+ AssetOSS::Asset.asset_paths += ['assets'] # Configure additional asset paths
35
+ AssetOSS::OSS.upload
36
+ end
37
+
38
+ end
39
+ end
40
+ ```
41
+
42
+ 其它
43
+ ---
44
+ 也许可以通过修改Rails `config.assets.prefix`实现缓存过期,记得prefix要是assets开头,同时修改`AssetOSS::Asset.asset_paths`
data/README_EN.textile ADDED
@@ -0,0 +1,90 @@
1
+ h1. Asset OSS - Asset stamping and uploading to Aliyun OSS for Rails
2
+
3
+ h2. About
4
+
5
+ 基于asset_id修改,aset_sync也许是更好的选择
6
+
7
+ Uploads static assets to Aliyun OSS with unique identifiers encoded into the path of the asset.
8
+
9
+ Only works with Rails 3.x because Rails 3 makes doing this sort of thing a lot easier and
10
+ that is all I needed it for.
11
+
12
+ This library takes standard Rails asset paths such as <code>/stylesheets/main.css?1288717704</code> and converts them
13
+ into <code>http://my_bucket.oss.aliyuncs.com/stylesheets/main-id-95df8d9bf5237ad08df3115ee74dcb10.css</code>.
14
+
15
+ It uses an MD5 hash of the file contents to produce the id so the same asset will always have the same ID.
16
+
17
+ In my quest to achieve a Google Page Speed score of 100, this library achieves the following:
18
+
19
+ * Assets served from a cookie-less domain
20
+ * Unique identifier is not encoded into a query parameter (so it is cacheable by proxy servers)
21
+ * All assets have far-future expires headers for caching
22
+ * Assets have the Cache-Control: public header for caching
23
+ * CSS and javascript is GZipped and the correct headers are added
24
+
25
+ As an added bonus, all your assets are available over <code>https://</code> as well.
26
+
27
+ h2. Usage
28
+
29
+ Add the gem to your <code>Gemfile</code>
30
+
31
+ <code>gem "asset_oss"</code>
32
+
33
+ Configure <code>config/environments/production.rb</code> to use Aliyun OSS as the asset host
34
+ and to use the id-stamped asset paths in helpers
35
+
36
+ <pre><code>config.action_controller.asset_host = Proc.new do |source|
37
+ 'http://my_bucket.oss.aliyuncs.com'
38
+ end
39
+ config.action_controller.asset_path = Proc.new do |source|
40
+ AssetOSS::Asset.fingerprint(source)
41
+ end
42
+ </code></pre>
43
+
44
+ Add your Aliyun OSS configuration details to <code>config/asset_oss.yml</code>
45
+
46
+ <pre><code>production:
47
+ access_key_id: 'MY_ACCESS_KEY'
48
+ secret_access_key: 'MY_ACCESS_SECRET'
49
+ bucket: "my_live_bucket"
50
+ </code></pre>
51
+
52
+ Optionally create a rake task in <code>lib/tasks/asset_oss.rake</code> to
53
+ perform the upload for use in your deploy scripts
54
+
55
+ <pre><code>namespace :asset do
56
+ namespace :oss do
57
+
58
+ desc "uploads the current assets to aliyun oss with stamped ids"
59
+ task :upload do
60
+ Aliyun::OSS::DEFAULT_HOST.replace "oss-internal.aliyuncs.com"
61
+ AssetOSS::Asset.asset_paths += ['assets'] # Configure additional asset paths
62
+ AssetOSS::OSS.upload
63
+ end
64
+
65
+ end
66
+ end
67
+ </code></pre>
68
+
69
+ h2. SSL configuration
70
+
71
+ If you want to use the SSL host in your configuration you can do so in <code>config/environments/production.rb</code>
72
+
73
+ <pre><code>config.action_controller.asset_host = Proc.new do |source|
74
+ request.ssl? 'https://my_bucket.oss.aliyuncs.com' : 'http://my_bucket.oss.aliyuncs.com'
75
+ end
76
+ </code></pre>
77
+
78
+ h2. CSS Images
79
+
80
+ By default any relative CSS images that match files on the filesystem are converted to AssetOSS paths as well.
81
+
82
+ For aliyun oss, if you don't specify a <code>prefix</code> it will use the <code>http://</code> bucket URL by default. You can override this in <code>config/asset_oss.yml</code>. For example if you wanted to use the <code>https://</code> url:
83
+
84
+ <pre><code>production:
85
+ host: 'oss-internal.aliyuncs.com'
86
+ access_key_id: 'MY_ACCESS_KEY'
87
+ secret_access_key: 'MY_ACCESS_SECRET'
88
+ bucket: "my_live_bucket"
89
+ prefix: "https://my_bucket.oss.aliyuncs.com"
90
+ </code></pre>
data/lib/asset_oss.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'asset_oss/asset'
2
+ require 'asset_oss/cache'
3
+ require 'asset_oss/backend/oss'
@@ -0,0 +1,168 @@
1
+ require 'digest/md5'
2
+ require 'mime/types'
3
+ require 'time'
4
+ require 'zlib'
5
+
6
+ module AssetOSS
7
+ class Asset
8
+
9
+ DEFAULT_ASSET_PATHS = ['favicon.ico', 'images', 'javascripts', 'stylesheets']
10
+ @@asset_paths = DEFAULT_ASSET_PATHS
11
+
12
+ #DEFAULT_GZIP_TYPES = ['text/css', 'application/javascript']
13
+ DEFAULT_GZIP_TYPES = []
14
+ @@gzip_types = DEFAULT_GZIP_TYPES
15
+
16
+ @@debug = false
17
+ @@nocache = false
18
+ @@nofingerprint = false
19
+
20
+ def self.init(options)
21
+ @@debug = options[:debug] if options[:debug]
22
+ @@nocache = options[:nocache] if options[:nocache]
23
+ @@nofingerprint = options[:nofingerprint] if options[:nofingerprint]
24
+ @@nofingerprint ||= []
25
+ end
26
+
27
+ def self.asset_paths
28
+ @@asset_paths
29
+ end
30
+
31
+ def self.gzip_types
32
+ @@gzip_types
33
+ end
34
+
35
+ def self.asset_paths=(paths)
36
+ @@asset_paths = paths
37
+ end
38
+
39
+ def self.gzip_types=(types)
40
+ @@gzip_types = types
41
+ end
42
+
43
+ def self.find(paths=Asset.asset_paths)
44
+ paths.inject([]) {|assets, path|
45
+ path = File.join Rails.root, 'public', path
46
+ a = Asset.new(path)
47
+ #TODO 暂时不上传gz文件,相好解决ie6 gzip的问题再说
48
+ assets << a if a.is_file? and !a.cache_hit? and !a.gz_file?
49
+ assets += Dir.glob(path+'/**/*').inject([]) {|m, file|
50
+ a = Asset.new(file); m << a if a.is_file? and !a.cache_hit? and !a.gz_file?; m
51
+ }
52
+ }
53
+ end
54
+
55
+ def self.fingerprint(path)
56
+ asset = Asset.new(path)
57
+ hit = Cache.get(asset)
58
+ return hit[:fingerprint] if hit
59
+ return asset.fingerprint
60
+ end
61
+
62
+ attr_reader :path
63
+
64
+ def initialize(path)
65
+ @path = path
66
+ @path = absolute_path
67
+ end
68
+
69
+ def path_prefix
70
+ File.join Rails.root, 'public'
71
+ end
72
+
73
+ def absolute_path
74
+ path =~ /#{path_prefix}/ ? path : File.join(path_prefix, path)
75
+ end
76
+
77
+ def relative_path
78
+ path.gsub(path_prefix, '')
79
+ end
80
+
81
+ def gzip_type?
82
+ Asset.gzip_types.include? mime_type
83
+ end
84
+
85
+ def data
86
+ @data ||= File.read(path)
87
+ end
88
+
89
+ def md5
90
+ @digest ||= Digest::MD5.hexdigest(data)
91
+ end
92
+
93
+ def fingerprint
94
+ p = relative_path
95
+ return p if relative_path =~ /^\/assets.*\//
96
+ File.join File.dirname(p), "#{File.basename(p, File.extname(p))}-id-#{md5}#{File.extname(p)}"
97
+ end
98
+
99
+ def mime_type
100
+ MIME::Types.of(path).first.to_s
101
+ end
102
+
103
+ def css?
104
+ mime_type == 'text/css'
105
+ end
106
+
107
+ def replace_css_images!(options={})
108
+ options[:prefix] ||= ''
109
+ # adapted from https://github.com/blakink/asset_id
110
+ data.gsub! /url\((?:"([^"]*)"|'([^']*)'|([^)]*))\)/mi do |match|
111
+ begin
112
+ # $1 is the double quoted string, $2 is single quoted, $3 is no quotes
113
+ uri = ($1 || $2 || $3).to_s.strip
114
+ uri.gsub!(/^\.\.\//, '/')
115
+
116
+ # if the uri appears to begin with a protocol then the asset isn't on the local filesystem
117
+ if uri =~ /[a-z]+:\/\//i
118
+ "url(#{uri})"
119
+ else
120
+ asset = Asset.new(uri)
121
+ # TODO: Check the referenced asset is in the asset_paths
122
+ puts " - Changing CSS URI #{uri} to #{options[:prefix]}#{asset.fingerprint}" if @@debug
123
+ "url(#{options[:prefix]}#{asset.fingerprint})"
124
+ end
125
+ rescue Errno::ENOENT => e
126
+ puts " - Warning: #{uri} not found" if @@debug
127
+ "url(#{uri})"
128
+ end
129
+ end
130
+ end
131
+
132
+ def gzip!
133
+ # adapted from https://github.com/blakink/asset_id
134
+ @data = returning StringIO.open('', 'w') do |gz_data|
135
+ gz = Zlib::GzipWriter.new(gz_data, nil, nil)
136
+ gz.write(data)
137
+ gz.close
138
+ end.string
139
+ end
140
+
141
+ def expiry_date
142
+ @expiry_date ||= (Time.now + (60*60*24*365)).httpdate
143
+ end
144
+
145
+ def cache_headers
146
+ {'Expires' => expiry_date, 'Cache-Control' => 'public'} # 1 year expiry
147
+ end
148
+
149
+ def gzip_headers
150
+ {'Content-Encoding' => 'gzip', 'Vary' => 'Accept-Encoding'}
151
+ end
152
+
153
+ def is_file?
154
+ File.exists? absolute_path and !File.directory? absolute_path
155
+ end
156
+
157
+ def cache_hit?
158
+ return false if @@nocache or Cache.miss? self
159
+ puts "AssetOSS: #{relative_path} - Cache Hit"
160
+ return true
161
+ end
162
+
163
+ def gz_file?
164
+ path =~ /\.gz$/
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,87 @@
1
+ require 'aliyun/oss'
2
+
3
+ module AssetOSS
4
+ class OSS
5
+
6
+ def self.oss_config
7
+ @@config ||= YAML.load_file(File.join(Rails.root, "config/asset_oss.yml"))[Rails.env] rescue nil || {}
8
+ end
9
+
10
+ def self.connect_to_oss
11
+ Aliyun::OSS::Base.establish_connection!(
12
+ :server => oss_config['host'] || Aliyun::OSS::DEFAULT_HOST,
13
+ :access_key_id => oss_config['access_key_id'],
14
+ :secret_access_key => oss_config['secret_access_key']
15
+ )
16
+ end
17
+
18
+ def self.oss_permissions
19
+ :public_read
20
+ end
21
+
22
+ def self.oss_bucket
23
+ oss_config['bucket']
24
+ end
25
+
26
+ def self.oss_folder
27
+ oss_config['folder']
28
+ end
29
+
30
+ def self.oss_prefix
31
+ oss_config['prefix'] || oss_bucket_url
32
+ end
33
+
34
+ def self.oss_bucket_url
35
+ "http://#{oss_bucket}.oss.aliyuncs.com#{oss_folder ? "/#{oss_folder}" : '' }"
36
+ end
37
+
38
+ def self.full_path(asset)
39
+ oss_folder ? "/#{oss_folder}#{asset.fingerprint}" : asset.fingerprint
40
+ end
41
+
42
+ def self.upload(options={})
43
+ Asset.init(:debug => options[:debug], :nofingerprint => options[:nofingerprint])
44
+
45
+ assets = Asset.find
46
+ return if assets.empty?
47
+
48
+ connect_to_oss
49
+
50
+ Aliyun::OSS::Bucket.create(oss_bucket, :access => oss_permissions)
51
+
52
+ assets.each do |asset|
53
+
54
+ puts "AssetOSS: #{asset.relative_path}" if options[:debug]
55
+
56
+ headers = {
57
+ :content_type => asset.mime_type,
58
+ }.merge(asset.cache_headers)
59
+
60
+ asset.replace_css_images!(:prefix => oss_prefix) if asset.css?
61
+
62
+ if asset.gzip_type?
63
+ headers.merge!(asset.gzip_headers)
64
+ asset.gzip!
65
+ end
66
+
67
+ if options[:debug]
68
+ puts " - Uploading: #{full_path(asset)} [#{asset.data.size} bytes]"
69
+ puts " - Headers: #{headers.inspect}"
70
+ end
71
+
72
+ unless options[:dry_run]
73
+ res = Aliyun::OSS::OSSObject.store(
74
+ full_path(asset),
75
+ asset.data,
76
+ oss_bucket,
77
+ headers
78
+ )
79
+ puts " - Response: #{res.inspect}" if options[:debug]
80
+ end
81
+ end
82
+
83
+ Cache.save! unless options[:dry_run]
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,37 @@
1
+ require 'yaml'
2
+
3
+ module AssetOSS
4
+ class Cache
5
+
6
+ def self.empty
7
+ @cache = {}
8
+ end
9
+
10
+ def self.cache
11
+ @cache ||= YAML.load_file(cache_path) rescue {}
12
+ end
13
+
14
+ def self.cache_path
15
+ File.join(Rails.root, 'log', 'asset_oss_cache.yml')
16
+ end
17
+
18
+ def self.get(asset)
19
+ cache[asset.relative_path]
20
+ end
21
+
22
+ def self.hit?(asset)
23
+ return true if cache[asset.relative_path] and cache[asset.relative_path][:fingerprint] == asset.fingerprint
24
+ cache[asset.relative_path] = {:expires => asset.expiry_date.to_s, :fingerprint => asset.fingerprint}
25
+ false
26
+ end
27
+
28
+ def self.miss?(asset)
29
+ !hit?(asset)
30
+ end
31
+
32
+ def self.save!
33
+ File.open(cache_path, 'w') {|f| f.write(YAML.dump(cache))}
34
+ end
35
+
36
+ end
37
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: asset_oss
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Richard Taylor
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-05-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mime-types
16
+ requirement: !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: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: aliyun-oss
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: asset_oss is a library for uploading static assets to Amazon S3.
47
+ email: moomerman@gmail.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files: []
51
+ files:
52
+ - LICENSE
53
+ - README.md
54
+ - README_EN.textile
55
+ - lib/asset_oss.rb
56
+ - lib/asset_oss/cache.rb
57
+ - lib/asset_oss/asset.rb
58
+ - lib/asset_oss/backend/oss.rb
59
+ homepage: http://github.com/mangege/asset_oss
60
+ licenses: []
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --inline-source
64
+ - --charset=UTF-8
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 1.8.24
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: asset_oss is a library for uploading static assets to Aliyun OSS.
85
+ test_files: []