activestorage-tencent_cos 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 348b2ef87c38baa18adc32994643d32a16ca7d1394bd5b44cd164163eb8e2646
4
+ data.tar.gz: e36d6cd2d493beaea1dd58188ed252e33dd9473a9797a564a42b9326fa27ae3e
5
+ SHA512:
6
+ metadata.gz: 0c8e24008019e8543bd391ec8eeff8d1ff2928c8b6ddf063751ce61757ed2dc5acbc1a0d5846ee70f7239b40cb64f8cab7c7e6cd85c8f43b96ab55053c68437f
7
+ data.tar.gz: 1043a7284e7d81856d0f1066a029fca8003b14fd0a84fa22bbbdf9badb26e51d1e095d2e8b4f24167d6390a50836e114a4add6e68d3ac1a15144ac00960154c6
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # ActiveStorage::Service::TencentCOSService
2
+ Active Storage Serivce for [Tencent COS](https://cloud.tencent.com/product/cos)
3
+
4
+
5
+ ## Notes
6
+ It is important to note that this library is still in early stages and is currently being implemented according to my needs
7
+ - only public read bucket is support(HTTP caching purpose)
8
+ - direct upload only
9
+ - delete variants have not yet been implemented
10
+ - download have not yet been implemented
11
+
12
+
13
+ ## Usage
14
+ Enable CORS on your bucket(for direct upload)
15
+ https://cloud.tencent.com/document/product/436/13318
16
+
17
+ Add to your Gemfile
18
+ ```
19
+ gem 'activestorage-tencent_cos'
20
+ ```
21
+
22
+ Edit `config/storage.yml`
23
+ ```
24
+ tencent:
25
+ service: TencentCOS
26
+ secret_id: your_secret_id
27
+ secret_key: your_secret_key
28
+ app_id: your_app_id
29
+ bucket: your_bucket
30
+ region: ap-guangzhou # https://cloud.tencent.com/document/product/436/6224
31
+ endpoint: https://assets.example.com # your cdn url
32
+ max_age: 3600 # http cache time
33
+ ```
34
+
35
+ Edit `config/environments/production.rb`
36
+ ```
37
+ config.active_storage.service = :tencent
38
+ ```
39
+
40
+
41
+ ## Tips
42
+ Use cdn url directly
43
+ ```
44
+ <%= image_tag book.cover.service_url %>
45
+ ```
@@ -0,0 +1,141 @@
1
+ # tencent cos does not have sdk for ruby
2
+ # https://cloud.tencent.com/document/product/436/6474
3
+ # TODO wrap cos code as a client lib?
4
+ require 'net/http'
5
+
6
+ module ActiveStorage
7
+ class Service::TencentCOSService < Service
8
+ def initialize(**config)
9
+ @config = config
10
+ end
11
+
12
+ def upload(key, io, checksum: nil)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def download(key)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def download_chunk(key, range)
21
+ instrument :download_chunk, key: key, range: range do
22
+ uri = URI(url_for(key))
23
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client|
24
+ client.get(uri, Range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body
25
+ end
26
+ end
27
+ end
28
+
29
+ def delete(key)
30
+ instrument :delete, key: key do
31
+ uri = URI(url_for(key))
32
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client|
33
+ res = client.delete(uri, Authorization: authorization_for(key, 'delete'))
34
+ # res.code == '204'
35
+ res.kind_of?(Net::HTTPSuccess)
36
+ end
37
+ end
38
+ end
39
+
40
+ def delete_prefixed(prefix)
41
+ # TODO
42
+ # https://cloud.tencent.com/document/product/436/7734
43
+ # https://cloud.tencent.com/document/product/436/14120
44
+
45
+ # prevent destory error
46
+ # raise NotImplementedError
47
+ end
48
+
49
+ def exist?(key)
50
+ instrument :exist, key: key do |payload|
51
+ uri = URI(url_for(key))
52
+ answer = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client|
53
+ res = client.head(uri, Authorization: authorization_for(key, 'head'))
54
+ res.kind_of?(Net::HTTPSuccess)
55
+ end
56
+
57
+ payload[:exist] = answer
58
+ answer
59
+ end
60
+ end
61
+
62
+ def url(key, expires_in:, filename:, content_type:, disposition:)
63
+ instrument :url, key: key do |payload|
64
+ # TODO url?response-content-disposition=disposition
65
+ generated_url = endpoint_url_for(key)
66
+ payload[:url] = generated_url
67
+ generated_url
68
+ end
69
+ end
70
+
71
+ def url_for_direct_upload(key, **)
72
+ instrument :url, key: key do |payload|
73
+ generated_url = url_for(key)
74
+ payload[:url] = generated_url
75
+ generated_url
76
+ end
77
+ end
78
+
79
+ def headers_for_direct_upload(key, checksum:, content_type:, content_length:, **)
80
+ {
81
+ 'Authorization': authorization_for(key, 'put'),
82
+ 'Content-Type': content_type,
83
+ # 'Content-Length': content_length, # refused by browser
84
+ 'Content-MD5': checksum,
85
+ 'Cache-Control': "max-age=#{max_age}",
86
+ }
87
+ end
88
+
89
+ private
90
+ attr_reader :config
91
+
92
+ def max_age
93
+ config.fetch(:max_age, 60)
94
+ end
95
+
96
+ def path_for(key)
97
+ "/#{key}"
98
+ end
99
+
100
+ def host
101
+ bucket, app_id, region = config.fetch_values(:bucket, :app_id, :region)
102
+ "#{bucket}-#{app_id}.cos.#{region}.myqcloud.com"
103
+ end
104
+
105
+ def url_for(key)
106
+ "https://#{host}#{path_for(key)}"
107
+ end
108
+
109
+ def endpoint_url_for(key)
110
+ endpoint = config.fetch(:endpoint)
111
+ if endpoint.present?
112
+ "#{endpoint}#{path_for(key)}"
113
+ else
114
+ url_for(key)
115
+ end
116
+ end
117
+
118
+ # https://cloud.tencent.com/document/product/436/7778#signature
119
+ def authorization_for(key, method, expires_in=url_expires_in)
120
+ secret_id, secret_key = config.fetch_values(:secret_id, :secret_key)
121
+
122
+ time = "#{Time.now.to_i};#{expires_in.after.to_i}"
123
+ # URI.encode(HttpHeaders)?
124
+ sign_key = OpenSSL::HMAC.hexdigest('sha1', secret_key, time)
125
+ http_string = "#{method}\n#{path_for(key)}\n\nhost=#{host}\n"
126
+ string_to_sign = "sha1\n#{time}\n#{OpenSSL::Digest::SHA1.hexdigest(http_string)}\n"
127
+ sign = OpenSSL::HMAC.hexdigest('sha1', sign_key, string_to_sign)
128
+
129
+ {
130
+ 'q-sign-algorithm': 'sha1',
131
+ 'q-ak': secret_id,
132
+ 'q-sign-time': time,
133
+ 'q-key-time': time,
134
+ 'q-header-list': 'host',
135
+ 'q-url-param-list': '',
136
+ 'q-signature': sign,
137
+ }.map {|k, v| "#{k}=#{v}"}.join('&')
138
+ # value should not escaped, `to_query` is not working here
139
+ end
140
+ end
141
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activestorage-tencent_cos
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - ''
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-11-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activestorage
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5.2'
27
+ description:
28
+ email:
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - README.md
34
+ - lib/active_storage/service/tencent_cos_service.rb
35
+ homepage:
36
+ licenses: []
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.7.6
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: ''
58
+ test_files: []