From f20612241c6bf5dee1774bc3a272aee71ccbc1a2 Mon Sep 17 00:00:00 2001 From: jingdan Date: Thu, 3 May 2018 18:09:33 +0800 Subject: [PATCH] feat: support html5Runtime put request --- package.json | 5 +- src/core.js | 2 +- src/filerequest.js | 716 +++++++++++++++++++++-------------------- src/html5/transport.js | 170 ++++++---- 4 files changed, 479 insertions(+), 414 deletions(-) diff --git a/package.json b/package.json index 3a0c9fa..ca4d740 100644 --- a/package.json +++ b/package.json @@ -44,5 +44,8 @@ "author": "onbing", "contributors": [], "license": "MIT", - "homepage": "http://uxcore.github.io/uploadcore" + "homepage": "http://uxcore.github.io/uploadcore", + "dependencies": { + "blob-to-buffer": "^1.2.7" + } } diff --git a/src/core.js b/src/core.js index 1ac6352..58d723a 100644 --- a/src/core.js +++ b/src/core.js @@ -8,7 +8,7 @@ const DndCollector = require('./collector/dnd'); const PasteCollector = require('./collector/paste'); const PickerCollector = require('./collector/picker'); -const REQUEST_OPTIONS = ['name', 'url', 'params', 'action', 'data', 'headers', 'withCredentials', 'timeout', 'chunkEnable', 'chunkSize', 'chunkRetries', 'chunkProcessThreads']; +const REQUEST_OPTIONS = ['name', 'url', 'params', 'method', 'sendAsBinary', 'action', 'data', 'headers', 'withCredentials', 'timeout', 'chunkEnable', 'chunkSize', 'chunkRetries', 'chunkProcessThreads']; class Core extends Emitter { diff --git a/src/filerequest.js b/src/filerequest.js index f9c53e6..23d2632 100644 --- a/src/filerequest.js +++ b/src/filerequest.js @@ -1,376 +1,404 @@ const {parseSize} = require('./util'); class ChunkResponse { - /** - * @param {string|null} rawResponse - * @param {ChunkRequest} chunkRequest - */ - constructor(rawResponse, chunkRequest) { - this.rawResponse = rawResponse; - this.chunkRequest = chunkRequest; - } - - getChunkRequest() { - return this.chunkRequest; - } - - getRawResponse() { - return this.rawResponse; - } - - getResponse() { - return this.response || this.rawResponse; - } - - getJson() { - const response = this.getResponse(); - if (response == null) { - return null; - } - if (typeof response.getJson === 'function') { - return response.getJson(); - } - if (typeof response === 'string') { - return response === '' ? null : JSON.parse(response); - } - return response; - } - - setResponse(response) { - this.response = response; - return this; - } + /** + * @param {string|null} rawResponse + * @param {ChunkRequest} chunkRequest + */ + constructor(rawResponse, chunkRequest) { + this.rawResponse = rawResponse; + this.chunkRequest = chunkRequest; + } + + getChunkRequest() { + return this.chunkRequest; + } + + getRawResponse() { + return this.rawResponse; + } + + getResponse() { + return this.response || this.rawResponse; + } + + getJson() { + const response = this.getResponse(); + if (response == null) { + return null; + } + if (typeof response.getJson === 'function') { + return response.getJson(); + } + if (typeof response === 'string') { + return response === '' ? null : JSON.parse(response); + } + return response; + } + + setResponse(response) { + this.response = response; + return this; + } } class ChunkRequest { - /** - * - * @param {int} index - * @param {Blob} blob - * @param {FileRequest} fileRequest - */ - constructor(index, blob, fileRequest) { - this.index = index || 0; - this.fileRequest = fileRequest; - this.blob = blob || fileRequest.getFile().source; - } - - getName() { - return this.fileRequest.getName(); - } - - getFile() { - return this.fileRequest.getFile(); - } - - getBlob() { - return this.blob; - } - - getBlobName() { - return this.isMultiChunk() ? (this.blob.name || 'blob') : this.getFile().name; - } - - getIndex() { - return this.index; - } - - isMultiChunk() { - return this.getFile().source !== this.blob; - } - - getParams() { - if (!this.params) { - this.params = this.fileRequest.getParams().clone(); - } - return this.params; - } - - getParam(name) { - return this.getParams().getParam(name); - } - - setParam(name, value) { - this.getParams().setParam(name, value); - - return this; - } - - getFileRequest() { - return this.fileRequest; - } - - getUrl() { - return this.url || this.fileRequest.getUrl(); - } - - setUrl(url) { - this.url = url; - return this; - } - - getHeaders() { - if (!this.headers) { - this.headers = this.fileRequest.getHeaders().slice(0); - } - return this.headers; - } - - setHeader(name, value) { - var headers = this.getHeaders(); - headers.push({name: name, value: value}); - return this; - } - - isWithCredentials() { - return this.fileRequest.isWithCredentials(); - } - - getTimeout() { - return this.fileRequest.getTimeout(); - } - - createChunkResponse(response) { - return new ChunkResponse(response, this); - } + /** + * + * @param {int} index + * @param {Blob} blob + * @param {FileRequest} fileRequest + */ + constructor(index, blob, fileRequest) { + this.index = index || 0; + this.fileRequest = fileRequest; + this.blob = blob || fileRequest.getFile().source; + } + + getName() { + return this.fileRequest.getName(); + } + + getFile() { + return this.fileRequest.getFile(); + } + + getBlob() { + return this.blob; + } + + getBlobName() { + return this.isMultiChunk() ? (this.blob.name || 'blob') : this.getFile().name; + } + + getIndex() { + return this.index; + } + + isMultiChunk() { + return this.getFile().source !== this.blob; + } + + getParams() { + if (!this.params) { + this.params = this.fileRequest.getParams().clone(); + } + return this.params; + } + + getMethod() { + return this.fileRequest.getMethod(); + } + + getSendAsBinary() { + return this.fileRequest.getSendAsBinary(); + } + + getParam(name) { + return this.getParams().getParam(name); + } + + setParam(name, value) { + this.getParams().setParam(name, value); + + return this; + } + + getFileRequest() { + return this.fileRequest; + } + + getUrl() { + return this.url || this.fileRequest.getUrl(); + } + + setUrl(url) { + this.url = url; + return this; + } + + getHeaders() { + if (!this.headers) { + this.headers = this.fileRequest.getHeaders().slice(0); + } + return this.headers; + } + + setHeader(name, value) { + var headers = this.getHeaders(); + headers.push({name: name, value: value}); + return this; + } + + isWithCredentials() { + return this.fileRequest.isWithCredentials(); + } + + getTimeout() { + return this.fileRequest.getTimeout(); + } + + createChunkResponse(response) { + return new ChunkResponse(response, this); + } } class FileResponse { - /** - * @param {ChunkResponse|Object|string} rawResponse - * @param {FileRequest} fileRequest - */ - constructor(rawResponse, fileRequest) { - this.rawResponse = rawResponse; - this.fileRequest = fileRequest; - } - - isFromMultiChunkResponse() { - if (this.rawResponse instanceof ChunkResponse) { - return this.rawResponse.getChunkRequest().isMultiChunk(); - } - return false; - } - - getFileRequest() { - return this.fileRequest; - } - - getRawResponse() { - return this.rawResponse; - } - - getResponse() { - if (this.response != null) { - return this.response; - } - if (this.rawResponse instanceof ChunkResponse) { - return this.rawResponse.getResponse(); - } else { - return this.rawResponse; - } - } - - getJson() { - const response = this.getResponse(); - if (response == null) { - return null; - } - if (typeof response.getJson === 'function') { - return response.getJson(); - } - if (typeof response === 'string') { - return response === '' ? null : JSON.parse(response); - } - return response; - } - - setResponse(response) { - this.response = response; - return this; - } + /** + * @param {ChunkResponse|Object|string} rawResponse + * @param {FileRequest} fileRequest + */ + constructor(rawResponse, fileRequest) { + this.rawResponse = rawResponse; + this.fileRequest = fileRequest; + } + + isFromMultiChunkResponse() { + if (this.rawResponse instanceof ChunkResponse) { + return this.rawResponse.getChunkRequest().isMultiChunk(); + } + return false; + } + + getFileRequest() { + return this.fileRequest; + } + + getRawResponse() { + return this.rawResponse; + } + + getResponse() { + if (this.response != null) { + return this.response; + } + if (this.rawResponse instanceof ChunkResponse) { + return this.rawResponse.getResponse(); + } else { + return this.rawResponse; + } + } + + getJson() { + const response = this.getResponse(); + if (response == null) { + return null; + } + if (typeof response.getJson === 'function') { + return response.getJson(); + } + if (typeof response === 'string') { + return response === '' ? null : JSON.parse(response); + } + return response; + } + + setResponse(response) { + this.response = response; + return this; + } } class Params { - constructor(params) { - if (Array.isArray(params)) { - this.params = params.slice(0); - } else if (typeof params === 'object') { - this.params = []; - for (let name in params) { - if (params.hasOwnProperty(name)) { - this.params.push({name, value: params[name]}); - } - } - } else { - this.params = []; + constructor(params) { + if (Array.isArray(params)) { + this.params = params.slice(0); + } else if (typeof params === 'object') { + this.params = []; + for (let name in params) { + if (params.hasOwnProperty(name)) { + this.params.push({name, value: params[name]}); } + } + } else { + this.params = []; } + } - setParam(name, value) { - this.removeParam(name); - this.addParam(name, value); - } - - addParam(name, value) { - this.params.push({name, value}); - } + setParam(name, value) { + this.removeParam(name); + this.addParam(name, value); + } - removeParam(name) { - this.params = this.params.filter((param) => param.name !== name); - } + addParam(name, value) { + this.params.push({name, value}); + } - getParam(name, single) { - let ret = this.params.filter((param) => param.name === name) - .map((param) => param.value); + removeParam(name) { + this.params = this.params.filter((param) => param.name !== name); + } - if (single) { - return ret.shift(); - } + getParam(name, single) { + let ret = this.params.filter((param) => param.name === name) + .map((param) => param.value); - return ret; + if (single) { + return ret.shift(); } - clone() { - return new Params(this.params); - } + return ret; + } - toArray() { - return this.params; - } + clone() { + return new Params(this.params); + } - toString() { - const params = this.params.map((param) => { - return encodeURIComponent(param.name) + '=' + (param.value == null ? '' : encodeURIComponent(param.value)); - }); + toArray() { + return this.params; + } - return params.join('&'); //.replace( /%20/g, '+'); - } + toString() { + const params = this.params.map((param) => { + return encodeURIComponent(param.name) + '=' + (param.value == null ? '' : encodeURIComponent(param.value)); + }); + + return params.join('&'); //.replace( /%20/g, '+'); + } } const MIN_CHUNK_SIZE = 256 * 1024; // 256K class FileRequest { - constructor(file, options = {}) { - this.file = file; - this.name = options.name || 'file'; - this.url = options.url || options.action; - this.params = new Params(options.params || options.data); - this.headers = options.headers || []; - this.withCredentials = options.withCredentials; - this.timeout = options.timeout || 0; - this.chunkSize = options.chunkSize || 0; - this.chunkRetries = options.chunkRetries || 0; - this.chunkEnable = options.chunkEnable || false; - this.chunkProcessThreads = options.chunkProcessThreads || 2; - } - - getFile() { - return this.file; - } - - getUrl() { - return this.url || ''; - } - - getName() { - return this.name; - } - - setName(name) { - this.name = name; - return this; - } - - setUrl(url) { - this.url = url; - return this; - } - - getParams() { - return this.params; - } - - getParam(name) { - return this.getParams().getParam(name); - } - - setParam(name, value) { - this.params.setParam(name, value); - return this; - } - - getHeaders() { - return this.headers; - } - - setHeader(name, value) { - this.headers.push({name: name, value: value}); - } - - isWithCredentials() { - return this.withCredentials; - } - - setWithCredentials(flag) { - this.withCredentials = flag; - return this; - } - - getTimeout() { - return this.timeout; - } - - setTimeout(timeout) { - this.timeout = timeout; - return this; - } - - getChunkSize() { - return parseSize(this.chunkSize); - } - - setChunkSize(chunkSize) { - this.chunkSize = chunkSize; - return this; - } - - getChunkRetries() { - return this.chunkRetries; - } - - setChunkRetries(retries) { - this.chunkRetries = retries; - return 0; - } - - isChunkEnable() { - const chunkSize = this.getChunkSize(); - return this.chunkEnable && chunkSize > MIN_CHUNK_SIZE - && this.file.getRuntime().canSlice() && this.file.size > chunkSize; - } - - setChunkEnable(flag) { - this.chunkEnable = flag; - return this; - } - - getChunkProcessThreads() { - return this.chunkProcessThreads; - } - - setChunkProcessThreads(threads) { - this.chunkProcessThreads = threads; - return this; - } - - createChunkRequest(index, blob) { - return new ChunkRequest(index, blob, this); - } - - createFileResponse(response) { - return new FileResponse(response, this); - } + constructor(file, options = {}) { + this.file = file; + this.name = options.name || 'file'; + this.url = options.url || options.action; + this.method = options.method || 'POST'; + this.sendAsBinary = options.sendAsBinary || false; + this.params = new Params(options.params || options.data); + this.headers = options.headers || []; + this.withCredentials = options.withCredentials; + this.timeout = options.timeout || 0; + this.chunkSize = options.chunkSize || 0; + this.chunkRetries = options.chunkRetries || 0; + this.chunkEnable = options.chunkEnable || false; + this.chunkProcessThreads = options.chunkProcessThreads || 2; + } + + getFile() { + return this.file; + } + + getUrl() { + return this.url || ''; + } + + getName() { + return this.name; + } + + setName(name) { + this.name = name; + return this; + } + + setUrl(url) { + this.url = url; + return this; + } + + getParams() { + return this.params; + } + + getParam(name) { + return this.getParams().getParam(name); + } + + setParam(name, value) { + this.params.setParam(name, value); + return this; + } + + getMethod() { + return this.method; + } + + setMethod(method) { + this.method = method; + return this; + } + + getSendAsBinary() { + return this.sendAsBinary; + } + + setSendAsBinary(sendAsBinary) { + this.sendAsBinary = sendAsBinary; + return this; + } + + getHeaders() { + return this.headers; + } + + setHeader(name, value) { + this.headers.push({name: name, value: value}); + } + + isWithCredentials() { + return this.withCredentials; + } + + setWithCredentials(flag) { + this.withCredentials = flag; + return this; + } + + getTimeout() { + return this.timeout; + } + + setTimeout(timeout) { + this.timeout = timeout; + return this; + } + + getChunkSize() { + return parseSize(this.chunkSize); + } + + setChunkSize(chunkSize) { + this.chunkSize = chunkSize; + return this; + } + + getChunkRetries() { + return this.chunkRetries; + } + + setChunkRetries(retries) { + this.chunkRetries = retries; + return 0; + } + + isChunkEnable() { + const chunkSize = this.getChunkSize(); + return this.chunkEnable && chunkSize > MIN_CHUNK_SIZE + && this.file.getRuntime().canSlice() && this.file.size > chunkSize; + } + + setChunkEnable(flag) { + this.chunkEnable = flag; + return this; + } + + getChunkProcessThreads() { + return this.chunkProcessThreads; + } + + setChunkProcessThreads(threads) { + this.chunkProcessThreads = threads; + return this; + } + + createChunkRequest(index, blob) { + return new ChunkRequest(index, blob, this); + } + + createFileResponse(response) { + return new FileResponse(response, this); + } } module.exports = FileRequest; \ No newline at end of file diff --git a/src/html5/transport.js b/src/html5/transport.js index 20cfefc..aa569b4 100644 --- a/src/html5/transport.js +++ b/src/html5/transport.js @@ -1,85 +1,119 @@ +const toBuffer = require('blob-to-buffer'); const {Deferred} = require('../util'); const {TimeoutError, AbortError, NetworkError} = require('../errors'); class Html5Transport { - /** - * @param {ChunkRequest} request - * @returns {*} - */ - generate(request) { - const i = Deferred(); - const xhr = new XMLHttpRequest; - - let timeoutTimer; - - const clean = () => { - xhr.onload = xhr.onerror = null; - if (xhr.upload) { - xhr.upload.onprogress = null; - } - if (timeoutTimer) { - clearTimeout(timeoutTimer); - } - }; - - const abort = () => { - clean(); - try { - xhr.abort(); - } catch (e) {} + /** + * @param {ChunkRequest} request + * @returns {*} + */ + generate(request) { + const i = Deferred(); + const xhr = new XMLHttpRequest; + + let timeoutTimer; + + const clean = () => { + xhr.onload = xhr.onerror = null; + if (xhr.upload) { + xhr.upload.onprogress = null; + } + if (timeoutTimer) { + clearTimeout(timeoutTimer); + } + }; + + const abort = () => { + clean(); + try { + xhr.abort(); + } catch (e) { + } + }; + + const complete = (e) => { + clean(); + if (!xhr.status && e.type === 'error') { + return i.reject(new AbortError(e.message)); + } + if (xhr.status === 0 || xhr.status === 304 || xhr.status >= 200 && xhr.status < 300) { + const headers = xhr.getAllResponseHeaders().split(/\r?\n/); + const res = { + responseText: xhr.responseText, + headers: {}, }; - - const complete = (e) => { - clean(); - if (!xhr.status && e.type === 'error') { - return i.reject(new AbortError(e.message)); - } - if (xhr.status === 0 || xhr.status === 304 || xhr.status >= 200 && xhr.status < 300) { - return i.resolve(xhr.responseText); - } - return i.reject(new NetworkError(xhr.status, xhr.statusText)); - }; - - if (xhr.upload) { - xhr.upload.onprogress = (e) => i.notify(e); - } - xhr.onerror = complete; - xhr.onload = complete; - - // Timeout - const timeout = request.getTimeout(); - if (timeout > 0) { - timeoutTimer = setTimeout(() => { - abort(); - i.reject(new TimeoutError('timeout:'+timeout)); - }, timeout); - } - - try { - xhr.open('POST', request.getUrl(), true); - if (request.isWithCredentials()) { - xhr.withCredentials = true; + headers.forEach(function (header) { + const matches = header.match(/^([^:]+):\s*(.*)/); + if (matches) { + const key = matches[1].toLowerCase(); + if (key === 'set-cookie') { + if (res.headers[key] === undefined) { + res.headers[key] = [] + } + res.headers[key].push(matches[2]) + } else if (res.headers[key] !== undefined) { + res.headers[key] += ', ' + matches[2] + } else { + res.headers[key] = matches[2] } + } + }); + return i.resolve(res); + } + return i.reject(new NetworkError(xhr.status, xhr.statusText)); + }; + + if (xhr.upload) { + xhr.upload.onprogress = (e) => i.notify(e); + } + xhr.onerror = complete; + xhr.onload = complete; + + // Timeout + const timeout = request.getTimeout(); + if (timeout > 0) { + timeoutTimer = setTimeout(() => { + abort(); + i.reject(new TimeoutError('timeout:' + timeout)); + }, timeout); + } - request.getHeaders().forEach((header) => xhr.setRequestHeader(header.name, header.value)); + try { + if (request.isWithCredentials() && 'withCredentials' in xhr) { + xhr.open(request.getMethod(), request.getUrl(), true); + xhr.withCredentials = true; + } else { + xhr.open(request.getMethod(), request.getUrl()); + } - const formData = new FormData; + request.getHeaders().forEach((header) => xhr.setRequestHeader(header.name, header.value)); - request.getParams().toArray().forEach((param) => formData.append(param.name, param.value)); + if (request.getSendAsBinary()) {// 传输二进制数据 + // 转换blob 为 arrayBuffer + toBuffer(request.getBlob(), (err, buffer) => { + if (err) throw err; + xhr.send(buffer); + }) + } else { + const formData = new FormData; - formData.append(request.getName(), request.getBlob(), request.getBlobName()); + request.getParams().toArray().forEach((param) => formData.append(param.name, param.value)); - xhr.send(formData); - } catch (e) { - abort(); - i.reject(new AbortError(e.message)); - } + formData.append(request.getName(), request.getBlob(), request.getBlobName()); - const ret = i.promise(); - ret.abort = abort; + xhr.send(formData); + } - return ret; + } catch (e) { + abort(); + i.reject(new AbortError(e.message)); } + + const ret = i.promise(); + ret.abort = abort; + + return ret; + } } module.exports = Html5Transport; \ No newline at end of file