qiniu
Advanced tools
Comparing version 7.9.0 to 7.10.0
## CHANGE LOG | ||
## 7.10.0 | ||
- 对象存储,上传支持双活 | ||
- 对象存储,上传回调支持 Promise 风格 | ||
- 对象存储,修复分片上传v2在创建 uploadId 失败后仍尝试上传 | ||
## 7.9.0 | ||
@@ -3,0 +8,0 @@ - 对象存储,修复无法对 key 为空字符串的对象进行操作 |
301
index.d.ts
@@ -146,2 +146,7 @@ /** | ||
zoneExpire?: number; | ||
/** | ||
* @default null | ||
*/ | ||
regionsProvider?: httpc.RegionsProvider; | ||
} | ||
@@ -165,2 +170,7 @@ class Config implements ConfigOptions { | ||
export declare namespace form_up { | ||
type UploadResult = { | ||
data: any; | ||
resp: IncomingMessage; | ||
} | ||
class FormUploader { | ||
@@ -175,7 +185,13 @@ conf: conf.Config; | ||
* @param key | ||
* @param rsStream | ||
* @param fsStream | ||
* @param putExtra | ||
* @param callback | ||
*/ | ||
putStream(uploadToken: string, key: string | null, rsStream: NodeJS.ReadableStream, putExtra: PutExtra | null, callback: callback): void; | ||
putStream( | ||
uploadToken: string, | ||
key: string | null, | ||
fsStream: NodeJS.ReadableStream, | ||
putExtra: PutExtra | null, | ||
callback: callback | ||
): Promise<UploadResult>; | ||
@@ -190,3 +206,9 @@ /** | ||
*/ | ||
put(uploadToken: string, key: string | null, body: any, putExtra: PutExtra | null, callback: callback): void; | ||
put( | ||
uploadToken: string, | ||
key: string | null, | ||
body: any, | ||
putExtra: PutExtra | null, | ||
callback: callback | ||
): Promise<UploadResult>; | ||
@@ -200,3 +222,8 @@ /** | ||
*/ | ||
putWithoutKey(uploadToken: string, body: any, putExtra: PutExtra | null, callback: callback): void; | ||
putWithoutKey( | ||
uploadToken: string, | ||
body: any, | ||
putExtra: PutExtra | null, | ||
callback: callback | ||
): Promise<UploadResult>; | ||
@@ -211,3 +238,9 @@ /** | ||
*/ | ||
putFile(uploadToken: string, key: string | null, localFile: string, putExtra: PutExtra | null, callback: callback): void; | ||
putFile( | ||
uploadToken: string, | ||
key: string | null, | ||
localFile: string, | ||
putExtra: PutExtra | null, | ||
callback: callback | ||
): Promise<UploadResult>; | ||
@@ -221,3 +254,8 @@ /** | ||
*/ | ||
putFileWithoutKey(uploadToken: string, localFile: string, putExtra: PutExtra | null, callback: callback): void; | ||
putFileWithoutKey( | ||
uploadToken: string, | ||
localFile: string, | ||
putExtra: PutExtra | null, | ||
callback: callback | ||
): Promise<UploadResult>; | ||
} | ||
@@ -234,3 +272,3 @@ | ||
*/ | ||
params: any; | ||
params: Record<string, string>; | ||
@@ -271,2 +309,7 @@ /** | ||
export declare namespace resume_up { | ||
type UploadResult = { | ||
data: any; | ||
resp: IncomingMessage; | ||
} | ||
class ResumeUploader { | ||
@@ -286,3 +329,10 @@ config: conf.Config; | ||
*/ | ||
putStream(uploadToken: string, key: string | null, rsStream: NodeJS.ReadableStream, rsStreamLen: number, putExtra: PutExtra | null, callback: callback): void; | ||
putStream( | ||
uploadToken: string, | ||
key: string | null, | ||
rsStream: NodeJS.ReadableStream, | ||
rsStreamLen: number, | ||
putExtra: PutExtra | null, | ||
callback: callback | ||
): Promise<UploadResult>; | ||
@@ -297,3 +347,9 @@ /** | ||
*/ | ||
putFile(uploadToken: string, key: string | null, localFile: string, putExtra: PutExtra | null, callback: callback): void; | ||
putFile( | ||
uploadToken: string, | ||
key: string | null, | ||
localFile: string, | ||
putExtra: PutExtra | null, | ||
callback: callback | ||
): Promise<UploadResult>; | ||
@@ -307,3 +363,8 @@ /** | ||
*/ | ||
putFileWithoutKey(uploadToken: string, localFile: string, putExtra: PutExtra | null, callback: callback): void; | ||
putFileWithoutKey( | ||
uploadToken: string, | ||
localFile: string, | ||
putExtra: PutExtra | null, | ||
callback: callback | ||
): Promise<UploadResult>; | ||
} | ||
@@ -445,3 +506,3 @@ | ||
interface RespWrapperOptions<T = any> { | ||
interface ResponseWrapperOptions<T = any> { | ||
data: T; | ||
@@ -451,6 +512,7 @@ resp: IncomingMessage; | ||
class RespWrapper<T = any> { | ||
// responseWrapper.js | ||
class ResponseWrapper<T = any> { | ||
data: T; | ||
resp: IncomingMessage; | ||
constructor(options: RespWrapperOptions); | ||
constructor(options: ResponseWrapperOptions); | ||
ok(): boolean; | ||
@@ -460,2 +522,3 @@ needRetry(): boolean; | ||
// middleware package | ||
namespace middleware { | ||
@@ -465,4 +528,4 @@ interface Middleware { | ||
request: ReqOpts<T>, | ||
next: (reqOpts: ReqOpts<T>) => Promise<RespWrapper<T>> | ||
): Promise<RespWrapper<T>>; | ||
next: (reqOpts: ReqOpts<T>) => Promise<ResponseWrapper<T>> | ||
): Promise<ResponseWrapper<T>>; | ||
} | ||
@@ -477,3 +540,3 @@ | ||
middlewares: Middleware[], | ||
handler: (reqOpts: ReqOpts<T>) => Promise<RespWrapper<T>> | ||
handler: (reqOpts: ReqOpts<T>) => Promise<ResponseWrapper<T>> | ||
); | ||
@@ -488,4 +551,4 @@ | ||
request: httpc.ReqOpts<T>, | ||
next: (reqOpts: httpc.ReqOpts<T>) => Promise<httpc.RespWrapper<T>> | ||
): Promise<httpc.RespWrapper<T>>; | ||
next: (reqOpts: httpc.ReqOpts<T>) => Promise<httpc.ResponseWrapper<T>> | ||
): Promise<httpc.ResponseWrapper<T>>; | ||
} | ||
@@ -534,4 +597,4 @@ | ||
request: httpc.ReqOpts<T>, | ||
next: (reqOpts: httpc.ReqOpts<T>) => Promise<httpc.RespWrapper<T>> | ||
): Promise<httpc.RespWrapper<T>>; | ||
next: (reqOpts: httpc.ReqOpts<T>) => Promise<httpc.ResponseWrapper<T>> | ||
): Promise<httpc.ResponseWrapper<T>>; | ||
@@ -547,3 +610,3 @@ /** | ||
err: Error | null, | ||
respWrapper: RespWrapper<T>, | ||
respWrapper: ResponseWrapper<T>, | ||
reqOpts: ReqOpts<T> | ||
@@ -554,2 +617,3 @@ ): boolean; | ||
// client.js | ||
interface HttpClientOptions { | ||
@@ -581,7 +645,194 @@ httpAgent?: HttpAgent; | ||
constructor(options: HttpClientOptions) | ||
sendRequest(requestOptions: ReqOpts): Promise<RespWrapper> | ||
get(getOptions: GetOptions): Promise<RespWrapper> | ||
post(postOptions: PostOptions): Promise<RespWrapper> | ||
put(putOptions: PutOptions): Promise<RespWrapper> | ||
sendRequest(requestOptions: ReqOpts): Promise<ResponseWrapper> | ||
get(getOptions: GetOptions): Promise<ResponseWrapper> | ||
post(postOptions: PostOptions): Promise<ResponseWrapper> | ||
put(putOptions: PutOptions): Promise<ResponseWrapper> | ||
} | ||
// endpoint.js | ||
interface EndpointOptions { | ||
defaultScheme?: string; | ||
} | ||
interface EndpointPersistInfo { | ||
host: string; | ||
defaultScheme: string; | ||
} | ||
class Endpoint { | ||
static fromPersistInfo(persistInfo: EndpointPersistInfo): Endpoint; | ||
host: string; | ||
defaultScheme: string; | ||
constructor(host: string, options?: EndpointOptions); | ||
getValue(options?: {scheme?: string}): string; | ||
get persistInfo(): EndpointPersistInfo; | ||
} | ||
// region.js | ||
enum SERVICE_NAME { | ||
UC = 'uc', | ||
UP = 'up', | ||
IO = 'io', | ||
RS = 'rs', | ||
RSF = 'rsf', | ||
API = 'api', | ||
S3 = 's3' | ||
} | ||
interface RegionOptions { | ||
regionId?: string; | ||
s3RegionId?: string; | ||
services?: Record<string, Endpoint[]>; | ||
ttl?: number; | ||
createTime?: Date; | ||
} | ||
interface RegionFromZoneOptions { | ||
regionId?: string; | ||
s3RegionId?: string; | ||
ttl?: number; | ||
isPreferCdnHost?: boolean; | ||
} | ||
interface RegionFromRegionIdOptions { | ||
s3RegionId?: string; | ||
ttl?: number; | ||
createTime?: Date; | ||
extendedServices?: Record<SERVICE_NAME | string, Endpoint[]> | ||
} | ||
interface RegionPersistInfo { | ||
regionId?: string; | ||
s3RegionId?: string; | ||
services: Record<SERVICE_NAME | string, EndpointPersistInfo[]>; | ||
ttl: number; | ||
createTime: number; | ||
} | ||
interface QueryRegionsRespData { | ||
region: string; | ||
ttl: number; | ||
s3: { | ||
domains: string[]; | ||
region_alias: string; | ||
}; | ||
uc: { | ||
domains: string[]; | ||
}; | ||
up: { | ||
domains: string[]; | ||
}; | ||
io: { | ||
domains: string[]; | ||
}; | ||
rs: { | ||
domains: string[]; | ||
}; | ||
rsf: { | ||
domains: string[]; | ||
}; | ||
api: { | ||
domains: string[]; | ||
}; | ||
} | ||
class Region { | ||
static fromZone(zone: conf.Zone, options?: RegionFromZoneOptions): Region; | ||
static fromRegionId(regionId: string, options?: RegionFromRegionIdOptions): Region; | ||
static fromPersistInfo(persistInfo: RegionPersistInfo): Region; | ||
static fromQueryData(data: QueryRegionsRespData): Region; | ||
// non-unique | ||
regionId?: string; | ||
s3RegionId?: string; | ||
services: Record<SERVICE_NAME | string, Endpoint[]> | ||
ttl: number; | ||
createTime: Date; | ||
constructor(options: RegionOptions); | ||
get isLive(): boolean; | ||
get persistInfo(): RegionPersistInfo; | ||
} | ||
// endpointProvider.js | ||
interface EndpointsProvider { | ||
getEndpoints(): Promise<Endpoint[]> | ||
} | ||
interface MutableEndpointsProvider extends EndpointsProvider { | ||
setEndpoints(endpoints: Endpoint[]): Promise<void> | ||
} | ||
class StaticEndpointsProvider implements EndpointsProvider { | ||
static fromRegion(region: Region, serviceName: SERVICE_NAME | string): StaticEndpointsProvider; | ||
constructor(endpoints: Endpoint[]); | ||
getEndpoints(): Promise<Endpoint[]>; | ||
} | ||
// regionsProvider.js | ||
interface RegionsProvider { | ||
getRegions(): Promise<Region[]> | ||
} | ||
interface MutableRegionsProvider extends RegionsProvider { | ||
setRegions(regions: Region[]): Promise<void> | ||
} | ||
// StaticRegionsProvider | ||
class StaticRegionsProvider implements RegionsProvider { | ||
regions: Region[]; | ||
constructor(regions: Region[]); | ||
getRegions(): Promise<Region[]>; | ||
} | ||
// CachedRegionsProviderOptions | ||
interface CachedRegionsProviderOptions { | ||
cacheKey: string; | ||
baseRegionsProvider: RegionsProvider; | ||
persistPath?: string; | ||
shrinkInterval?: number; // ms | ||
} | ||
class CachedRegionsProvider implements MutableRegionsProvider { | ||
cacheKey: string; | ||
baseRegionsProvider: RegionsProvider; | ||
lastShrinkAt: Date; | ||
shrinkInterval: number; | ||
constructor( | ||
options: CachedRegionsProviderOptions | ||
); | ||
setRegions(regions: Region[]): Promise<void>; | ||
getRegions(): Promise<Region[]>; | ||
} | ||
// QueryRegionsProvider | ||
interface QueryRegionsProviderOptions { | ||
accessKey: string; | ||
bucketName: string; | ||
endpointsProvider: EndpointsProvider; | ||
} | ||
class QueryRegionsProvider implements RegionsProvider { | ||
accessKey: string; | ||
bucketName: string; | ||
endpointsProvider: EndpointsProvider; | ||
constructor(options: QueryRegionsProviderOptions); | ||
getRegions(): Promise<Region[]>; | ||
} | ||
} | ||
@@ -588,0 +839,0 @@ |
12
index.js
@@ -14,3 +14,11 @@ module.exports = { | ||
HttpClient: require('./qiniu/httpc/client').HttpClient, | ||
ResponseWrapper: require('./qiniu/httpc/responseWrapper').ResponseWrapper | ||
ResponseWrapper: require('./qiniu/httpc/responseWrapper').ResponseWrapper, | ||
Endpoint: require('./qiniu/httpc/endpoint').Endpoint, | ||
StaticEndpointsProvider: require('./qiniu/httpc/endpointsProvider').StaticEndpointsProvider, | ||
SERVICE_NAME: require('./qiniu/httpc/region').SERVICE_NAME, | ||
Region: require('./qiniu/httpc/region').Region, | ||
StaticRegionsProvider: require('./qiniu/httpc/regionsProvider').StaticRegionsProvider, | ||
CachedRegionsProvider: require('./qiniu/httpc/regionsProvider').CachedRegionsProvider, | ||
QueryRegionsProvider: require('./qiniu/httpc/regionsProvider').QueryRegionsProvider, | ||
ChainedRegionsProvider: require('./qiniu/httpc/regionsProvider').ChainedRegionsProvider | ||
}, | ||
@@ -24,4 +32,4 @@ rpc: require('./qiniu/rpc.js'), | ||
sms: { | ||
message: require('./qiniu/sms/message.js'), | ||
message: require('./qiniu/sms/message.js') | ||
} | ||
}; |
{ | ||
"name": "qiniu", | ||
"version": "7.9.0", | ||
"version": "7.10.0", | ||
"description": "Node wrapper for Qiniu Resource (Cloud) Storage API", | ||
@@ -62,3 +62,3 @@ "main": "index.js", | ||
"tunnel-agent": "^0.6.0", | ||
"urllib": "^2.34.1" | ||
"urllib": "^2.41.0" | ||
}, | ||
@@ -65,0 +65,0 @@ "devDependencies": { |
@@ -57,3 +57,3 @@ const os = require('os'); | ||
this.useHttpsDomain = !!(options.useHttpsDomain || false); | ||
// use cdn accerlated domains | ||
// use cdn accerlated domains, this is not work with auto query region | ||
this.useCdnDomain = !!(options.useCdnDomain && true); | ||
@@ -64,2 +64,4 @@ // zone of the bucket | ||
this.zoneExpire = options.zoneExpire || -1; | ||
// only available with upload for now | ||
this.regionsProvider = options.regionsProvider || null; | ||
}; | ||
@@ -66,0 +68,0 @@ |
@@ -53,9 +53,2 @@ const http = require('http'); | ||
/** | ||
* Wrapped result of request | ||
* @typedef {Object} RespWrapper | ||
* @property {*} data | ||
* @property {http.IncomingMessage} resp | ||
*/ | ||
/** | ||
* | ||
@@ -62,0 +55,0 @@ * @param {ReqOpts} requestOptions |
@@ -22,4 +22,4 @@ /** | ||
* @param {Middleware[]} middlewares | ||
* @param {function(ReqOpts):Promise<RespWrapper>} handler | ||
* @return {function(ReqOpts):Promise<RespWrapper>} | ||
* @param {function(ReqOpts):Promise<ResponseWrapper>} handler | ||
* @return {function(ReqOpts):Promise<ResponseWrapper>} | ||
*/ | ||
@@ -26,0 +26,0 @@ exports.composeMiddlewares = function (middlewares, handler) { |
const middleware = require('./base'); | ||
const URL = require('url').URL; | ||
/** | ||
@@ -9,3 +11,3 @@ * @class | ||
* @param {number} [retryDomainsOptions.maxRetryTimes] | ||
* @param {function(Error || null, RespWrapper || null, ReqOpts):boolean} [retryDomainsOptions.retryCondition] | ||
* @param {function(Error || null, ResponseWrapper || null, ReqOpts):boolean} [retryDomainsOptions.retryCondition] | ||
* @constructor | ||
@@ -27,3 +29,3 @@ */ | ||
* @param {Error || null} err | ||
* @param {RespWrapper || null} respWrapper | ||
* @param {ResponseWrapper || null} respWrapper | ||
* @param {ReqOpts} reqOpts | ||
@@ -44,3 +46,3 @@ * @return {boolean} | ||
* @param {ReqOpts} reqOpts | ||
* @param {function(ReqOpts):Promise<RespWrapper>} next | ||
* @param {function(ReqOpts):Promise<ResponseWrapper>} next | ||
* @return {Promise<RespWrapper>} | ||
@@ -62,3 +64,6 @@ */ | ||
this._retriedTimes = 0; | ||
url.hostname = domains.shift(); | ||
const domain = domains.shift(); | ||
const [hostname, port] = domain.split(':'); | ||
url.hostname = hostname; | ||
url.port = port || url.port; | ||
reqOpts.url = url.toString(); | ||
@@ -65,0 +70,0 @@ return true; |
@@ -1,26 +0,56 @@ | ||
const conf = require('../conf'); | ||
const util = require('../util'); | ||
const rpc = require('../rpc'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const Readable = require('stream').Readable; | ||
const getCrc32 = require('crc32'); | ||
const path = require('path'); | ||
const mime = require('mime'); | ||
const Readable = require('stream').Readable; | ||
const formstream = require('formstream'); | ||
const conf = require('../conf'); | ||
const util = require('../util'); | ||
const rpc = require('../rpc'); | ||
const { | ||
prepareRegionsProvider, | ||
doWorkWithRetry, | ||
ChangeEndpointRetryPolicy, | ||
ChangeRegionRetryPolicy | ||
} = require('./internal'); | ||
exports.FormUploader = FormUploader; | ||
exports.PutExtra = PutExtra; | ||
/** | ||
* @class | ||
* @param {conf.Config} [config] | ||
* @constructor | ||
*/ | ||
function FormUploader (config) { | ||
this.config = config || new conf.Config(); | ||
// RetryPolicy API sign isn't stable not export to user | ||
// Internal usage only | ||
this.retryPolicies = [ | ||
new ChangeEndpointRetryPolicy(), | ||
new ChangeRegionRetryPolicy() | ||
]; | ||
} | ||
// 上传可选参数 | ||
// @params fname 请求体中的文件的名称 | ||
// @params params 额外参数设置,参数名称必须以x:开头 | ||
// @param mimeType 指定文件的mimeType | ||
// @param crc32 指定文件的crc32值 | ||
// @param checkCrc 指定是否检测文件的crc32值 | ||
// @param metadata 元数据设置,参数名称必须以 x-qn-meta-${name}: 开头 | ||
function PutExtra (fname, params, mimeType, crc32, checkCrc, metadata) { | ||
/** | ||
* 上传可选参数 | ||
* @param {string} [fname] 请求体中的文件的名称 | ||
* @param {Object} [params] 额外参数设置,参数名称必须以x:开头 | ||
* @param {string} [mimeType] 指定文件的mimeType | ||
* @param {string} [crc32] 指定文件的crc32值 | ||
* @param {number | boolean} [checkCrc] 指定是否检测文件的crc32值 | ||
* @param {Object} [metadata] 元数据设置,参数名称必须以 x-qn-meta-${name}: 开头 | ||
*/ | ||
function PutExtra ( | ||
fname, | ||
params, | ||
mimeType, | ||
crc32, | ||
checkCrc, | ||
metadata | ||
) { | ||
this.fname = fname || ''; | ||
@@ -34,78 +64,187 @@ this.params = params || {}; | ||
FormUploader.prototype.putStream = function (uploadToken, key, fsStream, | ||
putExtra, callbackFunc) { | ||
putExtra = putExtra || new PutExtra(); | ||
if (!putExtra.mimeType) { | ||
putExtra.mimeType = 'application/octet-stream'; | ||
} | ||
/** | ||
* @callback reqCallback | ||
* | ||
* @param { Error } err | ||
* @param { Object } ret | ||
* @param { http.IncomingMessage } info | ||
*/ | ||
if (!putExtra.fname) { | ||
putExtra.fname = key || 'fname'; | ||
} | ||
/** | ||
* @typedef UploadResult | ||
* @property {any} data | ||
* @property {http.IncomingMessage} resp | ||
*/ | ||
/** | ||
* @param {string} uploadToken | ||
* @param {string | null} key | ||
* @param {stream.Readable} fsStream | ||
* @param {PutExtra | null} putExtra | ||
* @param {reqCallback} callbackFunc | ||
* @returns {Promise<UploadResult>} | ||
*/ | ||
FormUploader.prototype.putStream = function ( | ||
uploadToken, | ||
key, | ||
fsStream, | ||
putExtra, | ||
callbackFunc | ||
) { | ||
const preferScheme = this.config.useHttpsDomain ? 'https' : 'http'; | ||
// PutExtra | ||
putExtra = getDefaultPutExtra( | ||
putExtra, | ||
{ | ||
key | ||
} | ||
); | ||
fsStream.on('error', function (err) { | ||
// callbackFunc | ||
callbackFunc(err, null, null); | ||
}); | ||
var accessKey = util.getAKFromUptoken(uploadToken); | ||
var bucket = util.getBucketFromUptoken(uploadToken); | ||
// RegionsProvider | ||
return prepareRegionsProvider({ | ||
config: this.config, | ||
bucketName: util.getBucketFromUptoken(uploadToken), | ||
accessKey: util.getAKFromUptoken(uploadToken) | ||
}) | ||
.then(regionsProvider => { | ||
return doWorkWithRetry({ | ||
workFn: sendPutReq, | ||
util.prepareZone(this, accessKey, bucket, function (err, ctx) { | ||
if (err) { | ||
callbackFunc(err, null, null); | ||
return; | ||
} | ||
createMultipartForm(uploadToken, key, fsStream, putExtra, function (postForm) { | ||
putReq(ctx.config, postForm, callbackFunc); | ||
callbackFunc, | ||
regionsProvider, | ||
// stream not support retry | ||
retryPolicies: [] | ||
}); | ||
}); | ||
}); | ||
}; | ||
function putReq (config, postForm, callbackFunc) { | ||
// set up hosts order | ||
var upHosts = []; | ||
function sendPutReq (endpoint) { | ||
const endpointValue = endpoint.getValue({ | ||
scheme: preferScheme | ||
}); | ||
if (config.useCdnDomain) { | ||
if (config.zone.cdnUpHosts) { | ||
config.zone.cdnUpHosts.forEach(function (host) { | ||
upHosts.push(host); | ||
}); | ||
} | ||
config.zone.srcUpHosts.forEach(function (host) { | ||
upHosts.push(host); | ||
const postForm = createMultipartForm( | ||
uploadToken, | ||
key, | ||
fsStream, | ||
putExtra | ||
); | ||
return new Promise(resolve => { | ||
putReq( | ||
endpointValue, | ||
postForm, | ||
(err, ret, info) => resolve({ err, ret, info }) | ||
); | ||
}); | ||
} else { | ||
config.zone.srcUpHosts.forEach(function (host) { | ||
upHosts.push(host); | ||
}); | ||
config.zone.cdnUpHosts.forEach(function (host) { | ||
upHosts.push(host); | ||
}); | ||
} | ||
}; | ||
var scheme = config.useHttpsDomain ? 'https://' : 'http://'; | ||
var upDomain = scheme + upHosts[0]; | ||
/** | ||
* @param {string} upDomain | ||
* @param {formstream} postForm | ||
* @param {reqCallback} callbackFunc | ||
*/ | ||
function putReq (upDomain, postForm, callbackFunc) { | ||
rpc.postMultipart(upDomain, postForm, callbackFunc); | ||
} | ||
// 上传字节 | ||
// | ||
FormUploader.prototype.put = function (uploadToken, key, body, putExtra, | ||
callbackFunc) { | ||
var fsStream = new Readable(); | ||
fsStream.push(body); | ||
fsStream.push(null); | ||
/** | ||
* 上传字节 | ||
* @param {string} uploadToken | ||
* @param {string | null} key | ||
* @param {any} body | ||
* @param {PutExtra | null} putExtra | ||
* @param {reqCallback} callbackFunc | ||
* @returns {Promise<UploadResult>} | ||
*/ | ||
FormUploader.prototype.put = function ( | ||
uploadToken, | ||
key, | ||
body, | ||
putExtra, | ||
callbackFunc | ||
) { | ||
const preferScheme = this.config.useHttpsDomain ? 'https' : 'http'; | ||
putExtra = putExtra || new PutExtra(); | ||
return this.putStream(uploadToken, key, fsStream, putExtra, callbackFunc); | ||
// initial PutExtra | ||
putExtra = getDefaultPutExtra( | ||
putExtra, | ||
{ | ||
key | ||
} | ||
); | ||
// initial RegionsProvider | ||
return prepareRegionsProvider({ | ||
config: this.config, | ||
bucketName: util.getBucketFromUptoken(uploadToken), | ||
accessKey: util.getAKFromUptoken(uploadToken) | ||
}) | ||
.then(regionsProvider => { | ||
return doWorkWithRetry({ | ||
workFn: sendPutReq, | ||
callbackFunc, | ||
regionsProvider, | ||
retryPolicies: this.retryPolicies | ||
}); | ||
}); | ||
function sendPutReq (endpoint) { | ||
const fsStream = new Readable(); | ||
fsStream.push(body); | ||
fsStream.push(null); | ||
const endpointValue = endpoint.getValue({ | ||
scheme: preferScheme | ||
}); | ||
const postForm = createMultipartForm( | ||
uploadToken, | ||
key, | ||
fsStream, | ||
putExtra | ||
); | ||
return new Promise(resolve => { | ||
putReq( | ||
endpointValue, | ||
postForm, | ||
(err, ret, info) => { | ||
resolve({ err, ret, info }); | ||
} | ||
); | ||
}); | ||
} | ||
}; | ||
FormUploader.prototype.putWithoutKey = function (uploadToken, body, putExtra, | ||
callbackFunc) { | ||
/** | ||
* @param {string} uploadToken | ||
* @param {any} body | ||
* @param {PutExtra | null} putExtra | ||
* @param {reqCallback} callbackFunc | ||
* @returns {Promise<UploadResult>} | ||
*/ | ||
FormUploader.prototype.putWithoutKey = function ( | ||
uploadToken, | ||
body, | ||
putExtra, | ||
callbackFunc | ||
) { | ||
return this.put(uploadToken, null, body, putExtra, callbackFunc); | ||
}; | ||
function createMultipartForm (uploadToken, key, fsStream, putExtra, callbackFunc) { | ||
var postForm = formstream(); | ||
/** | ||
* @param {string} uploadToken | ||
* @param {string | null} key | ||
* @param {stream.Readable} fsStream | ||
* @param {PutExtra | null} putExtra | ||
* @returns {formstream} | ||
*/ | ||
function createMultipartForm (uploadToken, key, fsStream, putExtra) { | ||
const postForm = formstream(); | ||
postForm.field('token', uploadToken); | ||
@@ -115,6 +254,11 @@ if (key != null) { | ||
} | ||
postForm.stream('file', fsStream, putExtra.fname, putExtra.mimeType); | ||
postForm.stream( | ||
'file', | ||
fsStream, | ||
putExtra.fname, | ||
putExtra.mimeType | ||
); | ||
// putExtra params | ||
for (var k in putExtra.params) { | ||
for (const k in putExtra.params) { | ||
if (k.startsWith('x:')) { | ||
@@ -126,3 +270,3 @@ postForm.field(k, putExtra.params[k].toString()); | ||
// putExtra metadata | ||
for (var metadataKey in putExtra.metadata) { | ||
for (const metadataKey in putExtra.metadata) { | ||
if (metadataKey.startsWith('x-qn-meta-')) { | ||
@@ -133,3 +277,3 @@ postForm.field(metadataKey, putExtra.metadata[metadataKey].toString()); | ||
var fileBody = []; | ||
let fileBody = []; | ||
fsStream.on('data', function (data) { | ||
@@ -139,8 +283,8 @@ fileBody.push(data); | ||
fsStream.on('end', function() { | ||
fsStream.on('end', function () { | ||
if (putExtra.checkCrc) { | ||
if (putExtra.crc32 == null) { | ||
fileBody = Buffer.concat(fileBody); | ||
var bodyCrc32 = parseInt('0x' + getCrc32(fileBody)); | ||
postForm.field('crc32', bodyCrc32); | ||
const bodyCrc32 = parseInt('0x' + getCrc32(fileBody)); | ||
postForm.field('crc32', bodyCrc32.toString()); | ||
} else { | ||
@@ -151,16 +295,25 @@ postForm.field('crc32', putExtra.crc32); | ||
}); | ||
callbackFunc(postForm); | ||
return postForm; | ||
} | ||
// 上传本地文件 | ||
// @params uploadToken 上传凭证 | ||
// @param key 目标文件名 | ||
// @param localFile 本地文件路径 | ||
// @param putExtra 额外选项 | ||
// @param callbackFunc 回调函数 | ||
FormUploader.prototype.putFile = function (uploadToken, key, localFile, putExtra, | ||
callbackFunc) { | ||
/** 上传本地文件 | ||
* @param {string} uploadToken 上传凭证 | ||
* @param {string | null} key 目标文件名 | ||
* @param {string} localFile 本地文件路径 | ||
* @param {PutExtra | null} putExtra 额外选项 | ||
* @param callbackFunc 回调函数 | ||
* @returns {Promise<UploadResult>} | ||
*/ | ||
FormUploader.prototype.putFile = function ( | ||
uploadToken, | ||
key, | ||
localFile, | ||
putExtra, | ||
callbackFunc | ||
) { | ||
const preferScheme = this.config.useHttpsDomain ? 'https' : 'http'; | ||
// initial PutExtra | ||
putExtra = putExtra || new PutExtra(); | ||
var fsStream = fs.createReadStream(localFile); | ||
if (!putExtra.mimeType) { | ||
@@ -174,8 +327,81 @@ putExtra.mimeType = mime.getType(localFile); | ||
return this.putStream(uploadToken, key, fsStream, putExtra, callbackFunc); | ||
putExtra = getDefaultPutExtra( | ||
putExtra, | ||
{ | ||
key | ||
} | ||
); | ||
// initial RegionsProvider | ||
return prepareRegionsProvider({ | ||
config: this.config, | ||
bucketName: util.getBucketFromUptoken(uploadToken), | ||
accessKey: util.getAKFromUptoken(uploadToken) | ||
}) | ||
.then(regionsProvider => { | ||
return doWorkWithRetry({ | ||
workFn: sendPutReq, | ||
callbackFunc, | ||
regionsProvider, | ||
retryPolicies: this.retryPolicies | ||
}); | ||
}); | ||
function sendPutReq (endpoint) { | ||
const fsStream = fs.createReadStream(localFile); | ||
const endpointValue = endpoint.getValue({ | ||
scheme: preferScheme | ||
}); | ||
const postForm = createMultipartForm( | ||
uploadToken, | ||
key, | ||
fsStream, | ||
putExtra | ||
); | ||
return new Promise(resolve => { | ||
putReq( | ||
endpointValue, | ||
postForm, | ||
(err, ret, info) => { | ||
resolve({ err, ret, info }); | ||
} | ||
); | ||
}); | ||
} | ||
}; | ||
FormUploader.prototype.putFileWithoutKey = function (uploadToken, localFile, | ||
putExtra, callbackFunc) { | ||
/** 上传本地文件 | ||
* @param {string} uploadToken 上传凭证 | ||
* @param {string} localFile 本地文件路径 | ||
* @param {PutExtra | null} putExtra 额外选项 | ||
* @param callbackFunc 回调函数 | ||
* @returns {Promise<UploadResult>} | ||
*/ | ||
FormUploader.prototype.putFileWithoutKey = function ( | ||
uploadToken, | ||
localFile, | ||
putExtra, | ||
callbackFunc | ||
) { | ||
return this.putFile(uploadToken, null, localFile, putExtra, callbackFunc); | ||
}; | ||
/** | ||
* @param {PutExtra} putExtra | ||
* @param {Object} options | ||
* @param {string} options.key | ||
* @return {PutExtra} | ||
*/ | ||
function getDefaultPutExtra (putExtra, options) { | ||
putExtra = putExtra || new PutExtra(); | ||
if (!putExtra.mimeType) { | ||
putExtra.mimeType = 'application/octet-stream'; | ||
} | ||
if (!putExtra.fname) { | ||
putExtra.fname = options.key || 'fname'; | ||
} | ||
return putExtra; | ||
} |
@@ -1,7 +0,5 @@ | ||
const conf = require('../conf'); | ||
const util = require('../util'); | ||
const rpc = require('../rpc'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const mime = require('mime'); | ||
const fs = require('fs'); | ||
const getCrc32 = require('crc32'); | ||
@@ -11,7 +9,35 @@ const destroy = require('destroy'); | ||
const conf = require('../conf'); | ||
const util = require('../util'); | ||
const rpc = require('../rpc'); | ||
const { | ||
prepareRegionsProvider, | ||
doWorkWithRetry, | ||
TokenExpiredRetryPolicy, | ||
ChangeEndpointRetryPolicy, | ||
ChangeRegionRetryPolicy | ||
} = require('./internal'); | ||
const { StaticEndpointsProvider } = require('../httpc/endpointsProvider'); | ||
const { Endpoint } = require('../httpc/endpoint'); | ||
exports.ResumeUploader = ResumeUploader; | ||
exports.PutExtra = PutExtra; | ||
/** | ||
* @param {conf.Config} [config] | ||
* @constructor | ||
*/ | ||
function ResumeUploader (config) { | ||
this.config = config || new conf.Config(); | ||
/** | ||
* Internal usage only for now. | ||
* @readonly | ||
*/ | ||
this.retryPolicies = [ | ||
new TokenExpiredRetryPolicy(), | ||
new ChangeEndpointRetryPolicy(), | ||
new ChangeRegionRetryPolicy() | ||
]; | ||
} | ||
@@ -22,5 +48,5 @@ | ||
* | ||
* @param { Error } err | ||
* @param { Object } ret | ||
* @param { http.IncomingMessage } info | ||
* @param {Error} err | ||
* @param {Object} ret | ||
* @param {http.IncomingMessage} info | ||
*/ | ||
@@ -31,17 +57,17 @@ | ||
* | ||
* @param { number } uploadBytes | ||
* @param { number } totalBytes | ||
* @param {number} uploadBytes | ||
* @param {number} totalBytes | ||
*/ | ||
/** | ||
* 上传可选参数 | ||
* @param { string } fname 请求体中的文件的名称 | ||
* @param { Object } params 额外参数设置,参数名称必须以x:开头 | ||
* @param { string | null } mimeType 指定文件的mimeType | ||
* @param { string | null } resumeRecordFile 断点续传的已上传的部分信息记录文件路径 | ||
* @param { progressCallback } progressCallback 上传进度回调,回调参数为 (uploadBytes, totalBytes) | ||
* @param { number } partSize 分片上传v2必传字段 默认大小为4MB 分片大小范围为1 MB - 1 GB | ||
* @param { 'v1' | 'v2' } version 分片上传版本 目前支持v1/v2版本 默认v1 | ||
* @param { Object } metadata 元数据设置,参数名称必须以 x-qn-meta-${name}: 开头 | ||
*/ | ||
* 上传可选参数 | ||
* @param {string} [fname] 请求体中的文件的名称 | ||
* @param {Object} [params] 额外参数设置,参数名称必须以x:开头 | ||
* @param {string | null} [mimeType] 指定文件的mimeType | ||
* @param {string | null} [resumeRecordFile] 断点续传的已上传的部分信息记录文件路径 | ||
* @param {function(number, number):void} [progressCallback] 上传进度回调,回调参数为 (uploadBytes, totalBytes) | ||
* @param {number} [partSize] 分片上传v2必传字段 默认大小为4MB 分片大小范围为1 MB - 1 GB | ||
* @param {'v1' | 'v2'} [version] 分片上传版本 目前支持v1/v2版本 默认v1 | ||
* @param {Object} [metadata] 元数据设置,参数名称必须以 x-qn-meta-${name}: 开头 | ||
*/ | ||
function PutExtra ( | ||
@@ -67,2 +93,17 @@ fname, | ||
/** | ||
* @typedef UploadResult | ||
* @property {any} data | ||
* @property {http.IncomingMessage} resp | ||
*/ | ||
/** | ||
* @param {string} uploadToken | ||
* @param {string | null} key | ||
* @param {stream.Readable} rsStream | ||
* @param {number} rsStreamLen | ||
* @param {PutExtra} putExtra | ||
* @param {reqCallback} callbackFunc | ||
* @return {Promise<UploadResult>} | ||
*/ | ||
ResumeUploader.prototype.putStream = function ( | ||
@@ -76,57 +117,96 @@ uploadToken, | ||
) { | ||
putExtra = putExtra || new PutExtra(); | ||
if (!putExtra.mimeType) { | ||
putExtra.mimeType = 'application/octet-stream'; | ||
} | ||
const preferScheme = this.config.useHttpsDomain ? 'https' : 'http'; | ||
const isValidCallback = typeof callbackFunc === 'function'; | ||
if (!putExtra.fname) { | ||
putExtra.fname = key || '?'; | ||
} | ||
putExtra = getDefaultPutExtra( | ||
putExtra, | ||
{ | ||
key | ||
} | ||
); | ||
if (!putExtra.version) { | ||
putExtra.version = 'v1'; | ||
} | ||
rsStream.on('error', function (err) { | ||
// callbackFunc | ||
callbackFunc(err, null, null); | ||
// callbackFunc | ||
isValidCallback && callbackFunc(err, null, null); | ||
destroy(rsStream); | ||
}); | ||
const accessKey = util.getAKFromUptoken(uploadToken); | ||
const bucket = util.getBucketFromUptoken(uploadToken); | ||
return prepareRegionsProvider({ | ||
config: this.config, | ||
bucketName: util.getBucketFromUptoken(uploadToken), | ||
accessKey: util.getAKFromUptoken(uploadToken) | ||
}) | ||
.then(regionsProvider => { | ||
const resumeInfo = getResumeRecordInfo(putExtra.resumeRecordFile); | ||
let preferredEndpointsProvider; | ||
if (resumeInfo && Array.isArray(resumeInfo.upDomains)) { | ||
preferredEndpointsProvider = new StaticEndpointsProvider( | ||
resumeInfo.upDomains.map(d => new Endpoint(d, { defaultScheme: preferScheme })) | ||
); | ||
} | ||
return doWorkWithRetry({ | ||
workFn: sendPutReq, | ||
util.prepareZone(this, accessKey, bucket, function (err, ctx) { | ||
if (err) { | ||
callbackFunc(err, null, null); | ||
destroy(rsStream); | ||
return; | ||
} | ||
putReq(ctx.config, uploadToken, key, rsStream, rsStreamLen, putExtra, callbackFunc); | ||
}); | ||
}; | ||
function putReq (config, uploadToken, key, rsStream, rsStreamLen, putExtra, callbackFunc) { | ||
// set up hosts order | ||
const upHosts = []; | ||
if (config.useCdnDomain) { | ||
if (config.zone.cdnUpHosts) { | ||
config.zone.cdnUpHosts.forEach(function (host) { | ||
upHosts.push(host); | ||
callbackFunc, | ||
regionsProvider, | ||
// use resume upDomain firstly | ||
preferredEndpointsProvider: preferredEndpointsProvider, | ||
// stream not support retry | ||
retryPolicies: [] | ||
}); | ||
} | ||
config.zone.srcUpHosts.forEach(function (host) { | ||
upHosts.push(host); | ||
}); | ||
} else { | ||
config.zone.srcUpHosts.forEach(function (host) { | ||
upHosts.push(host); | ||
function sendPutReq (endpoint) { | ||
endpoint = Object.create(endpoint); | ||
endpoint.defaultScheme = preferScheme; | ||
return new Promise(resolve => { | ||
putReq( | ||
endpoint, | ||
uploadToken, | ||
key, | ||
rsStream, | ||
rsStreamLen, | ||
putExtra, | ||
(err, ret, info) => resolve({ err, ret, info })); | ||
}); | ||
config.zone.cdnUpHosts.forEach(function (host) { | ||
upHosts.push(host); | ||
}); | ||
} | ||
const scheme = config.useHttpsDomain ? 'https://' : 'http://'; | ||
const upDomain = scheme + upHosts[0]; | ||
}; | ||
/** | ||
* @param {string} resumeRecordFilePath | ||
* @returns {undefined | Object.<string, any>} | ||
*/ | ||
function getResumeRecordInfo (resumeRecordFilePath) { | ||
// get resume record info | ||
let result; | ||
// read resumeRecordFile | ||
if (resumeRecordFilePath) { | ||
try { | ||
const resumeRecords = fs.readFileSync(resumeRecordFilePath).toString(); | ||
result = JSON.parse(resumeRecords); | ||
} catch (e) { | ||
e.code !== 'ENOENT' && console.error(e); | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
* @param {Endpoint} upEndpoint | ||
* @param {string} uploadToken | ||
* @param {string | null} key | ||
* @param {ReadableStream} rsStream | ||
* @param {number} rsStreamLen | ||
* @param {PutExtra} putExtra | ||
* @param {reqCallback} callbackFunc | ||
*/ | ||
function putReq ( | ||
upEndpoint, | ||
uploadToken, | ||
key, | ||
rsStream, | ||
rsStreamLen, | ||
putExtra, | ||
callbackFunc | ||
) { | ||
// make block stream | ||
@@ -139,71 +219,71 @@ const blkStream = rsStream.pipe(new BlockStream({ | ||
// get resume record info | ||
let blkputRets = null; | ||
const blkputRets = getResumeRecordInfo(putExtra.resumeRecordFile); | ||
const totalBlockNum = Math.ceil(rsStreamLen / putExtra.partSize); | ||
// read resumeRecordFile | ||
if (putExtra.resumeRecordFile) { | ||
try { | ||
const resumeRecords = fs.readFileSync(putExtra.resumeRecordFile).toString(); | ||
blkputRets = JSON.parse(resumeRecords); | ||
} catch (e) { | ||
console.error(e); | ||
} | ||
} | ||
// upload parts | ||
// select upload version | ||
/** | ||
* @type {function(SourceOptions, UploadOptions, reqCallback)} | ||
*/ | ||
let doPutReq; | ||
if (putExtra.version === 'v1') { | ||
putReqV1( | ||
{ | ||
blkputRets, | ||
rsStream, | ||
rsStreamLen, | ||
blkStream, | ||
totalBlockNum | ||
}, | ||
{ | ||
key, | ||
upDomain, | ||
uploadToken, | ||
putExtra | ||
}, | ||
callbackFunc | ||
); | ||
doPutReq = putReqV1; | ||
} else if (putExtra.version === 'v2') { | ||
putReqV2( | ||
{ | ||
blkputRets, | ||
blkStream, | ||
totalBlockNum, | ||
rsStreamLen, | ||
rsStream | ||
}, | ||
{ | ||
upDomain, | ||
uploadToken, | ||
key, | ||
putExtra | ||
}, | ||
callbackFunc | ||
); | ||
doPutReq = putReqV2; | ||
} else { | ||
throw new Error('part upload version number error'); | ||
} | ||
// upload parts | ||
doPutReq( | ||
{ | ||
blkputRets, | ||
rsStream, | ||
rsStreamLen, | ||
blkStream, | ||
totalBlockNum | ||
}, | ||
{ | ||
upEndpoint, | ||
uploadToken, | ||
key, | ||
putExtra | ||
}, | ||
function (err, ret, info) { | ||
if (info.statusCode === 200 && putExtra.resumeRecordFile) { | ||
try { | ||
fs.unlinkSync(putExtra.resumeRecordFile); | ||
} catch (_e) { | ||
// ignore | ||
} | ||
} | ||
callbackFunc(err, ret, info); | ||
} | ||
); | ||
} | ||
/** | ||
* @param { Object } sourceOptions | ||
* @param { Object[] | null } sourceOptions.blkputRets | ||
* @param { ReadableStream } sourceOptions.rsStream | ||
* @param { BlockStream } sourceOptions.blkStream | ||
* @param { number } sourceOptions.rsStreamLen | ||
* @param { number } sourceOptions.totalBlockNum | ||
* @param { Object } uploadOptions | ||
* @param { string } uploadOptions.key | ||
* @param { string } uploadOptions.upDomain | ||
* @param { string } uploadOptions.uploadToken | ||
* @param { PutExtra } uploadOptions.putExtra | ||
* @param { reqCallback } callbackFunc | ||
* @typedef SourceOptions | ||
* @property { Object.<string, any> | undefined } blkputRets | ||
* @property { ReadableStream } rsStream | ||
* @property { BlockStream } blkStream | ||
* @property { number } rsStreamLen | ||
* @property { number } totalBlockNum | ||
*/ | ||
/** | ||
* @typedef UploadOptions | ||
* @property { string | null } key | ||
* @property { Endpoint } upEndpoint | ||
* @property { string } uploadToken | ||
* @property { PutExtra } putExtra | ||
*/ | ||
/** | ||
* @param {SourceOptions} sourceOptions | ||
* @param {UploadOptions} uploadOptions | ||
* @param {reqCallback} callbackFunc | ||
* @returns { Promise<UploadResult> } | ||
*/ | ||
function putReqV1 (sourceOptions, uploadOptions, callbackFunc) { | ||
const { | ||
blkputRets, | ||
rsStream, | ||
@@ -214,5 +294,6 @@ blkStream, | ||
} = sourceOptions; | ||
let blkputRets = sourceOptions.blkputRets; | ||
const { | ||
upEndpoint, | ||
key, | ||
upDomain, | ||
uploadToken, | ||
@@ -224,5 +305,20 @@ putExtra | ||
const finishedCtxList = []; | ||
const finishedBlkPutRets = []; | ||
const finishedBlkPutRets = { | ||
upDomains: [], | ||
parts: [] | ||
}; | ||
// backward compatibility with ≤ 7.9.0 | ||
if (Array.isArray(blkputRets)) { | ||
blkputRets = { | ||
upDomains: [], | ||
parts: [] | ||
}; | ||
} | ||
if (blkputRets && Array.isArray(blkputRets.upDomains)) { | ||
finishedBlkPutRets.upDomains = blkputRets.upDomains; | ||
} | ||
finishedBlkPutRets.upDomains.push(upEndpoint.host); | ||
// upload parts | ||
const upDomains = upEndpoint.getValue(); | ||
let readLen = 0; | ||
@@ -235,4 +331,9 @@ let curBlock = 0; | ||
// check uploaded parts | ||
if (blkputRets && blkputRets.length > 0 && blkputRets[curBlock]) { | ||
const blkputRet = blkputRets[curBlock]; | ||
if ( | ||
blkputRets && | ||
blkputRets.parts && | ||
blkputRets.parts.length > 0 && | ||
blkputRets.parts[curBlock] | ||
) { | ||
const blkputRet = blkputRets.parts[curBlock]; | ||
let expiredAt = blkputRet.expired_at; | ||
@@ -244,3 +345,3 @@ // make sure the ctx at least has one day expiration | ||
finishedCtxList.push(blkputRet.ctx); | ||
finishedBlkPutRets.push(blkputRet); | ||
finishedBlkPutRets.parts.push(blkputRet); | ||
} | ||
@@ -253,3 +354,3 @@ } | ||
mkblkReq( | ||
upDomain, | ||
upDomains, | ||
uploadToken, | ||
@@ -269,3 +370,3 @@ chunk, | ||
finishedCtxList.push(blkputRet.ctx); | ||
finishedBlkPutRets.push(blkputRet); | ||
finishedBlkPutRets.parts.push(blkputRet); | ||
if (putExtra.resumeRecordFile) { | ||
@@ -282,3 +383,3 @@ const contents = JSON.stringify(finishedBlkPutRets); | ||
if (finishedCtxList.length === totalBlockNum) { | ||
mkfileReq(upDomain, uploadToken, rsStreamLen, finishedCtxList, key, putExtra, callbackFunc); | ||
mkfileReq(upDomains, uploadToken, rsStreamLen, finishedCtxList, key, putExtra, callbackFunc); | ||
isSent = true; | ||
@@ -293,3 +394,3 @@ } | ||
if (!isSent && rsStreamLen === 0) { | ||
mkfileReq(upDomain, uploadToken, rsStreamLen, finishedCtxList, key, putExtra, callbackFunc); | ||
mkfileReq(upDomains, uploadToken, rsStreamLen, finishedCtxList, key, putExtra, callbackFunc); | ||
} | ||
@@ -301,14 +402,6 @@ destroy(rsStream); | ||
/** | ||
* @param { Object } sourceOptions | ||
* @param { Object | null } sourceOptions.blkputRets | ||
* @param { ReadableStream } sourceOptions.rsStream | ||
* @param { BlockStream } sourceOptions.blkStream | ||
* @param { number } sourceOptions.rsStreamLen | ||
* @param { number } sourceOptions.totalBlockNum | ||
* @param { Object } uploadOptions | ||
* @param { string } uploadOptions.key | ||
* @param { string } uploadOptions.upDomain | ||
* @param { string } uploadOptions.uploadToken | ||
* @param { PutExtra } uploadOptions.putExtra | ||
* @param { reqCallback } callbackFunc | ||
* @param {SourceOptions} sourceOptions | ||
* @param {UploadOptions} uploadOptions | ||
* @param {reqCallback} callbackFunc | ||
* @returns { Promise<UploadResult> } | ||
*/ | ||
@@ -324,5 +417,5 @@ function putReqV2 (sourceOptions, uploadOptions, callbackFunc) { | ||
const { | ||
upEndpoint, | ||
uploadToken, | ||
key, | ||
upDomain, | ||
putExtra | ||
@@ -334,2 +427,3 @@ } = uploadOptions; | ||
const finishedEtags = { | ||
upDomains: [], | ||
etags: [], | ||
@@ -339,7 +433,8 @@ uploadId: '', | ||
}; | ||
if (blkputRets !== null) { | ||
if (blkputRets && Array.isArray(blkputRets.upDomains)) { | ||
// check etag expired or not | ||
const expiredAt = blkputRets.expiredAt; | ||
const timeNow = Date.now() / 1000; | ||
if (expiredAt > timeNow && blkputRets.uploadId !== '') { | ||
if (expiredAt > timeNow && blkputRets.uploadId) { | ||
finishedEtags.upDomains = blkputRets.upDomains; | ||
finishedEtags.etags = blkputRets.etags; | ||
@@ -351,3 +446,5 @@ finishedEtags.uploadId = blkputRets.uploadId; | ||
} | ||
finishedEtags.upDomains.push(upEndpoint.host); | ||
const upDomain = upEndpoint.getValue(); | ||
const bucket = util.getBucketFromUptoken(uploadToken); | ||
@@ -371,2 +468,8 @@ const encodedObjectName = key ? util.urlsafeBase64Encode(key) : '~'; | ||
/** | ||
* @param {string} upDomain | ||
* @param {string} uploadToken | ||
* @param {Buffer | string} blkData | ||
* @param {reqCallback} callbackFunc | ||
*/ | ||
function mkblkReq (upDomain, uploadToken, blkData, callbackFunc) { | ||
@@ -382,2 +485,11 @@ const requestURI = upDomain + '/mkblk/' + blkData.length; | ||
/** | ||
* @param {string} upDomain | ||
* @param {string} uploadToken | ||
* @param {number} fileSize | ||
* @param {string[]} ctxList | ||
* @param {string | null} key | ||
* @param putExtra | ||
* @param callbackFunc | ||
*/ | ||
function mkfileReq ( | ||
@@ -403,3 +515,3 @@ upDomain, | ||
if (putExtra.params) { | ||
// putExtra params | ||
// putExtra params | ||
for (const k in putExtra.params) { | ||
@@ -430,16 +542,26 @@ if (k.startsWith('x:') && putExtra.params[k]) { | ||
const postBody = ctxList.join(','); | ||
rpc.post(requestURI, postBody, headers, function (err, ret, info) { | ||
if (info.statusCode === 200 || info.statusCode === 701) { | ||
if (putExtra.resumeRecordFile) { | ||
try { | ||
fs.unlinkSync(putExtra.resumeRecordFile); | ||
} catch (_e) { | ||
// ignore | ||
} | ||
} | ||
} | ||
callbackFunc(err, ret, info); | ||
}); | ||
rpc.post(requestURI, postBody, headers, callbackFunc); | ||
} | ||
/** | ||
* @typedef FinishedEtags | ||
* @property {{etag: string, partNumber: number}[]}etags | ||
* @property {string} uploadId | ||
* @property {number} expiredAt | ||
*/ | ||
/** | ||
* @param {string} uploadToken | ||
* @param {string} bucket | ||
* @param {string} encodedObjectName | ||
* @param {string} upDomain | ||
* @param {BlockStream} blkStream | ||
* @param {FinishedEtags} finishedEtags | ||
* @param {number} finishedBlock | ||
* @param {number} totalBlockNum | ||
* @param {PutExtra} putExtra | ||
* @param {number} rsStreamLen | ||
* @param {stream.Readable} rsStream | ||
* @param {reqCallback} callbackFunc | ||
*/ | ||
function initReq ( | ||
@@ -467,2 +589,3 @@ uploadToken, | ||
callbackFunc(err, ret, info); | ||
return; | ||
} | ||
@@ -476,2 +599,16 @@ finishedEtags.expiredAt = ret.expireAt; | ||
/** | ||
* @param {string} uploadToken | ||
* @param {string} bucket | ||
* @param {string} encodedObjectName | ||
* @param {string} upDomain | ||
* @param {BlockStream} blkStream | ||
* @param {FinishedEtags} finishedEtags | ||
* @param {number} finishedBlock | ||
* @param {number} totalBlockNum | ||
* @param {PutExtra} putExtra | ||
* @param {number} rsStreamLen | ||
* @param {stream.Readable} rsStream | ||
* @param {reqCallback} callbackFunc | ||
*/ | ||
function resumeUploadV2 ( | ||
@@ -543,2 +680,13 @@ uploadToken, | ||
/** | ||
* @param {string} bucket | ||
* @param {string} upDomain | ||
* @param {string} uploadToken | ||
* @param {string} encodedObjectName | ||
* @param {Buffer | string} chunk | ||
* @param {string} uploadId | ||
* @param {number} partNumber | ||
* @param {PutExtra} putExtra | ||
* @param {reqCallback} callbackFunc | ||
*/ | ||
function uploadPart (bucket, upDomain, uploadToken, encodedObjectName, chunk, uploadId, partNumber, putExtra, callbackFunc) { | ||
@@ -552,18 +700,23 @@ const headers = { | ||
'/' + partNumber.toString(); | ||
rpc.put(requestUrl, chunk, headers, function (err, ret, info) { | ||
if (info.statusCode === 612) { | ||
if (putExtra.resumeRecordFile) { | ||
try { | ||
fs.unlinkSync(putExtra.resumeRecordFile); | ||
} catch (_e) { | ||
// ignore | ||
} | ||
} | ||
} | ||
callbackFunc(err, ret, info); | ||
}); | ||
rpc.put(requestUrl, chunk, headers, callbackFunc); | ||
} | ||
function completeParts (upDomain, bucket, encodedObjectName, uploadToken, finishedEtags, | ||
putExtra, callbackFunc) { | ||
/** | ||
* @param {string} upDomain | ||
* @param {string} bucket | ||
* @param {string} encodedObjectName | ||
* @param {string} uploadToken | ||
* @param {FinishedEtags} finishedEtags | ||
* @param {PutExtra} putExtra | ||
* @param {reqCallback} callbackFunc | ||
*/ | ||
function completeParts ( | ||
upDomain, | ||
bucket, | ||
encodedObjectName, | ||
uploadToken, | ||
finishedEtags, | ||
putExtra, | ||
callbackFunc | ||
) { | ||
const headers = { | ||
@@ -585,16 +738,18 @@ Authorization: 'UpToken ' + uploadToken, | ||
const requestBody = JSON.stringify(body); | ||
rpc.post(requestUrl, requestBody, headers, function (err, ret, info) { | ||
if (info.statusCode === 200 || info.statusCode === 612) { | ||
if (putExtra.resumeRecordFile) { | ||
try { | ||
fs.unlinkSync(putExtra.resumeRecordFile); | ||
} catch (_e) { | ||
// ignore | ||
} | ||
} | ||
} | ||
callbackFunc(err, ret, info); | ||
}); | ||
rpc.post( | ||
requestUrl, | ||
requestBody, | ||
headers, | ||
callbackFunc | ||
); | ||
} | ||
/** | ||
* @param {string} uploadToken | ||
* @param {string | null} key | ||
* @param {string} localFile | ||
* @param {PutExtra} putExtra | ||
* @param {reqCallback} callbackFunc | ||
* @returns {Promise<UploadResult>} | ||
*/ | ||
ResumeUploader.prototype.putFile = function ( | ||
@@ -607,10 +762,6 @@ uploadToken, | ||
) { | ||
const that = this; | ||
const preferScheme = this.config.useHttpsDomain ? 'https' : 'http'; | ||
// PutExtra | ||
putExtra = putExtra || new PutExtra(); | ||
const rsStream = fs.createReadStream(localFile, { | ||
highWaterMark: conf.BLOCK_SIZE | ||
}); | ||
const rsStreamLen = fs.statSync(localFile).size; | ||
const isResumeUpload = putExtra.resumeRecordFile && | ||
fs.existsSync(putExtra.resumeRecordFile); | ||
if (!putExtra.mimeType) { | ||
@@ -624,36 +775,99 @@ putExtra.mimeType = mime.getType(localFile); | ||
return this.putStream(uploadToken, key, rsStream, rsStreamLen, putExtra, | ||
callbackWithRetryFunc); | ||
putExtra = getDefaultPutExtra( | ||
putExtra, | ||
{ | ||
key | ||
} | ||
); | ||
function callbackWithRetryFunc (err, ret, info) { | ||
let needRetry = false; | ||
if (putExtra.version === 'v1' && | ||
info.statusCode === 701 && | ||
isResumeUpload | ||
) { | ||
needRetry = true; | ||
} | ||
if (putExtra.version === 'v2' && | ||
info.statusCode === 612 && | ||
isResumeUpload | ||
) { | ||
needRetry = true; | ||
} | ||
if (needRetry) { | ||
that.putFile( | ||
// regions | ||
return prepareRegionsProvider({ | ||
config: this.config, | ||
bucketName: util.getBucketFromUptoken(uploadToken), | ||
accessKey: util.getAKFromUptoken(uploadToken) | ||
}) | ||
.then(regionsProvider => { | ||
const resumeInfo = getResumeRecordInfo(putExtra.resumeRecordFile); | ||
let preferredEndpointsProvider; | ||
if (resumeInfo && Array.isArray(resumeInfo.upDomains)) { | ||
preferredEndpointsProvider = new StaticEndpointsProvider( | ||
resumeInfo.upDomains.map(d => new Endpoint(d, { defaultScheme: preferScheme })) | ||
); | ||
} | ||
return doWorkWithRetry({ | ||
workFn: sendPutReq, | ||
callbackFunc, | ||
regionsProvider, | ||
uploadApiVersion: putExtra.version, | ||
// use resume upDomain firstly | ||
preferredEndpointsProvider: preferredEndpointsProvider, | ||
resumeRecordFilePath: putExtra.resumeRecordFile, | ||
retryPolicies: this.retryPolicies | ||
}); | ||
}); | ||
function sendPutReq (endpoint) { | ||
endpoint = Object.create(endpoint); | ||
endpoint.defaultScheme = preferScheme; | ||
const rsStream = fs.createReadStream(localFile, { | ||
highWaterMark: conf.BLOCK_SIZE | ||
}); | ||
const rsStreamLen = fs.statSync(localFile).size; | ||
return new Promise((resolve) => { | ||
putReq( | ||
endpoint, | ||
uploadToken, | ||
key, | ||
localFile, | ||
rsStream, | ||
rsStreamLen, | ||
putExtra, | ||
callbackFunc | ||
(err, ret, info) => { | ||
destroy(rsStream); | ||
resolve({ err, ret, info }); | ||
} | ||
); | ||
return; | ||
} | ||
callbackFunc(err, ret, info); | ||
}); | ||
} | ||
}; | ||
ResumeUploader.prototype.putFileWithoutKey = function (uploadToken, localFile, | ||
putExtra, callbackFunc) { | ||
/** | ||
* @param {string} uploadToken | ||
* @param {string} localFile | ||
* @param {PutExtra} putExtra | ||
* @param {reqCallback} callbackFunc | ||
* @returns {Promise<UploadResult>} | ||
*/ | ||
ResumeUploader.prototype.putFileWithoutKey = function ( | ||
uploadToken, | ||
localFile, | ||
putExtra, | ||
callbackFunc | ||
) { | ||
return this.putFile(uploadToken, null, localFile, putExtra, callbackFunc); | ||
}; | ||
/** | ||
* @param {PutExtra} putExtra | ||
* @param {Object} options | ||
* @param {string | null} [options.key] | ||
* @return {PutExtra} | ||
*/ | ||
function getDefaultPutExtra (putExtra, options) { | ||
options = options || {}; | ||
putExtra = putExtra || new PutExtra(); | ||
if (!putExtra.mimeType) { | ||
putExtra.mimeType = 'application/octet-stream'; | ||
} | ||
if (!putExtra.fname) { | ||
putExtra.fname = options.key || '?'; | ||
} | ||
if (!putExtra.version) { | ||
putExtra.version = 'v1'; | ||
} | ||
return putExtra; | ||
} |
@@ -5,3 +5,3 @@ # Qiniu Cloud SDK for Node.js | ||
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE.md) | ||
[![Build Status](https://api.travis-ci.org/qiniu/nodejs-sdk.svg?branch=master)](https://travis-ci.org/qiniu/nodejs-sdk) | ||
[![NodeJS CI](https://github.com/qiniu/nodejs-sdk/actions/workflows/ci-test.yml/badge.svg?branch=master)](https://github.com/qiniu/nodejs-sdk/actions/workflows/ci-test.yml) | ||
[![GitHub release](https://img.shields.io/github/v/tag/qiniu/nodejs-sdk.svg?label=release)](https://github.com/qiniu/nodejs-sdk/releases) | ||
@@ -34,10 +34,10 @@ [![Code Climate](https://codeclimate.com/github/qiniu/nodejs-sdk.svg)](https://codeclimate.com/github/qiniu/nodejs-sdk) | ||
参考文档:[七牛云存储 Node.js SDK 使用指南](http://developer.qiniu.com/kodo/sdk/nodejs) | ||
参考文档:[七牛云存储 Node.js SDK 使用指南](http://developer.qiniu.com/kodo/sdk/nodejs) | ||
## 测试 | ||
``` | ||
$ cd ./test/ | ||
$ source test-env.sh | ||
``` | ||
$ cd ./test/ | ||
$ source test-env.sh | ||
$ mocha --grep 'bucketinfo' | ||
``` | ||
``` | ||
@@ -44,0 +44,0 @@ ## 贡献代码 |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
243036
33
6857
Updatedurllib@^2.41.0