1 | const fs = require('fs')
|
2 | const crypto = require('crypto')
|
3 | const urlParser = require('url')
|
4 | const http = require('http')
|
5 | const https = require('https')
|
6 | const { Transform, Readable } = require('stream')
|
7 | const fileType = require('file-type')
|
8 |
|
9 | const Img = require('./img')
|
10 | const { getVal, compose } = require('./util')
|
11 | const { HTTPError } = require('./error')
|
12 |
|
13 |
|
14 | const USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36(KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'
|
15 |
|
16 | const ACCEPT = 'image/jpg,image/png,image/webp,image/gif,image/bmp,*/*;q=0.8'
|
17 |
|
18 | class AliImg {
|
19 |
|
20 | constructor (options) {
|
21 | this.options = options
|
22 | return this.createImg.bind(this)
|
23 | }
|
24 |
|
25 | |
26 |
|
27 |
|
28 |
|
29 |
|
30 | putObject (fullpath, objectName) {
|
31 | return this.putObjectFromStream(fs.createReadStream(fullpath), objectName)
|
32 | }
|
33 |
|
34 | |
35 |
|
36 |
|
37 |
|
38 |
|
39 | putObjectFromStream(readable, objectName) {
|
40 | return new Promise((resolve, reject) => {
|
41 | let writable = null
|
42 | readable.on('error', reject).on('data', (chunk) => {
|
43 | if (!writable) {
|
44 |
|
45 | let headers = {'Content-Type': fileType(chunk).mime}
|
46 |
|
47 | writable = this.putObj(objectName, headers)
|
48 | writable.on('error', reject).on('response', compose(resolve, reject))
|
49 | }
|
50 |
|
51 | writable.write(chunk)
|
52 | }).on('end', function () {
|
53 | writable.end()
|
54 | })
|
55 | })
|
56 | }
|
57 |
|
58 | |
59 |
|
60 |
|
61 |
|
62 |
|
63 | putObjectFromUrl (url, objectName) {
|
64 |
|
65 | let opt = urlParser.parse(url)
|
66 |
|
67 | opt.headers = { Accept: ACCEPT, 'User-Agent': USER_AGENT }
|
68 |
|
69 |
|
70 | let agent = opt.protocol === 'https:' ? https : http
|
71 |
|
72 | return new Promise((resolve, reject) => {
|
73 | agent.get(opt).on('error', reject).on('response', (res) => {
|
74 | let { statusCode } = res
|
75 |
|
76 | if (statusCode === 301 || statusCode === 302 || statusCode === 307) {
|
77 | let key = 'Location'
|
78 |
|
79 | let url = getVal(res.headers, key)
|
80 |
|
81 | this.putObjectFromUrl(url, objectName).then(resolve).catch(reject)
|
82 | } else if (statusCode < 200 || statusCode >= 300) {
|
83 |
|
84 | let buf = []
|
85 | res.on('data', Array.prototype.push.bind(buf)).on('end', function () {
|
86 |
|
87 | let body = Buffer.concat(buf).toString()
|
88 | reject(new HTTPError(statusCode, body))
|
89 | })
|
90 | } else {
|
91 |
|
92 | let writable = this.putObj(objectName, res.headers)
|
93 | writable.on('error', reject).on('response', compose(resolve, reject))
|
94 |
|
95 | res.pipe(writable)
|
96 | }
|
97 | })
|
98 | })
|
99 | }
|
100 |
|
101 | createImg (path) {
|
102 | const img = new Img(path)
|
103 | img.client = this
|
104 | img.toBuffer = this.toBuffer
|
105 | img.stream = this.stream
|
106 | img.save = this.save
|
107 | img.write = this.write
|
108 | img.putObjects = this.putObjects
|
109 | return img
|
110 | }
|
111 |
|
112 | |
113 |
|
114 |
|
115 |
|
116 | save (objectName) {
|
117 | return new Promise((resolve, reject) => {
|
118 |
|
119 | let readable = this.stream()
|
120 | readable.on('error', reject).on('headers', headers => {
|
121 |
|
122 | let writable = this.client.putObj(objectName, headers)
|
123 |
|
124 | let url = this.client.getUrl(objectName)
|
125 | writable.on('error', reject).on('response', compose(resolve.bind(null, url), reject))
|
126 |
|
127 | readable.pipe(writable)
|
128 | })
|
129 | })
|
130 | }
|
131 |
|
132 | |
133 |
|
134 |
|
135 | toBuffer () {
|
136 | return new Promise((resolve, reject) => {
|
137 | let buf = []
|
138 | this.stream().on('error', reject).on('data', Array.prototype.push.bind(buf)).on('end', function() {
|
139 |
|
140 | let data = Buffer.concat(buf)
|
141 | resolve(data)
|
142 | })
|
143 | })
|
144 | }
|
145 |
|
146 | |
147 |
|
148 |
|
149 | stream () {
|
150 |
|
151 | const transform = new Transform({
|
152 | transform(chunk, encoding, callback) {
|
153 | this.push(chunk)
|
154 | callback()
|
155 | }
|
156 | })
|
157 |
|
158 |
|
159 | this.putObjects().then(() => {
|
160 |
|
161 | let readable = this.client.getObj(this.toString())
|
162 |
|
163 |
|
164 |
|
165 | readable.on('error', transform.emit.bind(transform, 'error')).on('headers', transform.emit.bind(transform, 'headers'))
|
166 | readable.pipe(transform)
|
167 |
|
168 | }).catch(transform.emit.bind(transform, 'error'))
|
169 |
|
170 | return transform
|
171 | }
|
172 |
|
173 | |
174 |
|
175 |
|
176 |
|
177 | write (fullpath) {
|
178 | return new Promise((resolve, reject) => {
|
179 |
|
180 | let writable = fs.createWriteStream(fullpath)
|
181 |
|
182 | writable.on('error', reject).on('finish', resolve)
|
183 | this.stream().on('error', reject).pipe(writable)
|
184 | })
|
185 | }
|
186 |
|
187 | |
188 |
|
189 |
|
190 | putObjects () {
|
191 | let list = this.child.map((item) => {
|
192 | if (item.path instanceof Readable) {
|
193 | return this.client.putObjectFromStream(item.path, item.objectName)
|
194 | } else if (/https?:\/\//i.test(item.path)) {
|
195 | return this.client.putObjectFromUrl(item.path, item.objectName)
|
196 | } else {
|
197 | return this.client.putObject(item.path, item.objectName)
|
198 | }
|
199 | })
|
200 |
|
201 | return Promise.all(list)
|
202 | }
|
203 |
|
204 | |
205 |
|
206 |
|
207 |
|
208 | getObj(objectName) {
|
209 | let method = 'GET'
|
210 |
|
211 | let headers = { Date: this.getDate() }
|
212 |
|
213 | headers.Authorization = this.getAuthorization({method,headers,objectName})
|
214 |
|
215 | let url = this.getUrl(objectName)
|
216 | let opt = urlParser.parse(url)
|
217 | Object.assign(opt, { headers })
|
218 |
|
219 | let agent = opt.protocol === 'https:' ? https : http
|
220 |
|
221 | let transform = new Transform({
|
222 | transform(chunk, encoding, cb) {
|
223 | this.push(chunk)
|
224 | cb()
|
225 | }
|
226 | })
|
227 |
|
228 | agent.get(opt).on('error', transform.emit.bind(transform, 'error')).on('response', function (res) {
|
229 | let { statusCode, headers } = res
|
230 |
|
231 | if (statusCode < 200 || statusCode >= 300) {
|
232 | let buf = []
|
233 | res.on('data', Array.prototype.push.bind(buf)).on('end', function () {
|
234 |
|
235 | let body = Buffer.concat(buf).toString()
|
236 |
|
237 | transform.emit('error', new HTTPError(statusCode, body))
|
238 | })
|
239 | } else {
|
240 |
|
241 | transform.emit('headers', headers)
|
242 | res.on('error', transform.emit.bind(transform, 'error')).pipe(transform)
|
243 | }
|
244 | })
|
245 |
|
246 | return transform
|
247 | }
|
248 |
|
249 | |
250 |
|
251 |
|
252 |
|
253 |
|
254 | putObj(objectName, headers) {
|
255 | let method = 'PUT'
|
256 | let key = 'Date'
|
257 | if (!getVal(headers, key)) {
|
258 | headers.Date = this.getDate()
|
259 | }
|
260 |
|
261 | headers.Authorization = this.getAuthorization({method,headers,objectName})
|
262 |
|
263 | let url = this.getUrl(objectName)
|
264 | let opt = urlParser.parse(url)
|
265 | Object.assign(opt, { method, headers })
|
266 |
|
267 | let agent = opt.protocol === 'https:' ? https : http
|
268 |
|
269 | return agent.request(opt)
|
270 | }
|
271 |
|
272 |
|
273 | getUrl (objectName) {
|
274 | return 'http://' + [this.options.bucket, this.options.region, 'aliyuncs.com'].join('.') + '/' + objectName
|
275 | }
|
276 |
|
277 |
|
278 | getAuthorization (opt) {
|
279 | return 'OSS ' + this.options.accessKeyId + ':' + this.getSignature(opt)
|
280 | }
|
281 |
|
282 |
|
283 | getSignature (options) {
|
284 | const method = options.method ? options.method.toUpperCase() : 'GET'
|
285 | const type = getVal(options.headers, 'content-type') || options.type || ''
|
286 | const headers = options.headers ? this.canonicalizedOssHeaders(options.headers) : ''
|
287 | const md5str = getVal(options.headers, 'content-md5') || (options.body ? this.getMD5(options.body) : '')
|
288 | const datestr = this.getDate()
|
289 | const query = options.query || {}
|
290 | const objectName = options.objectName || '/'
|
291 | const resoure = this.canonicalizedResource(objectName, query)
|
292 | const str = [method, md5str, type, datestr, headers].join('\n') + resoure
|
293 | return this.getHmac(str)
|
294 | }
|
295 |
|
296 | getMD5 (text) {
|
297 | return crypto.createHash('md5').update(text).digest('base64')
|
298 | }
|
299 |
|
300 | getHmac (text) {
|
301 | return crypto.createHmac('sha1', Buffer.from(this.options.accessKeySecret)).update(text).digest('base64')
|
302 | }
|
303 |
|
304 | getDate () {
|
305 | return new Date().toUTCString()
|
306 | }
|
307 |
|
308 | canonicalizedOssHeaders (obj) {
|
309 | const arr = []
|
310 | for (let key in obj) {
|
311 | if (/^x-oss-/i.test(key)) {
|
312 | arr.push(key.toLowerCase() + ':' + obj[key])
|
313 | }
|
314 | }
|
315 | if (arr.length === 0) {
|
316 | return ''
|
317 | }
|
318 | arr.sort()
|
319 | return arr.join('\n') + '\n'
|
320 | }
|
321 |
|
322 | canonicalizedResource (objectName, query) {
|
323 | const arr = []
|
324 | for (let key in query) {
|
325 | arr.push(key + '=' + query[key])
|
326 | }
|
327 | if (arr.length === 0) {
|
328 | return `/${this.options.bucket}/${objectName}`
|
329 | }
|
330 | arr.sort()
|
331 | return objectName + '?' + arr.join('&')
|
332 | }
|
333 | }
|
334 |
|
335 | module.exports = AliImg |
\ | No newline at end of file |