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 +7 -0
- data/README.md +45 -0
- data/lib/active_storage/service/tencent_cos_service.rb +141 -0
- metadata +58 -0
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: []
|