diff --git a/ChangeLog.md b/ChangeLog.md index b69c4813..c36b4666 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,12 @@ Note: This is an Azure Storage only package. The all up Azure node sdk still has the old storage bits in there. In a future release, those storage bits will be removed and an npm dependency to this storage node sdk will be taken. This is a GA release and the changes described below indicate the changes from the Azure node SDK 0.9.8 available here - https://github.com/Azure/azure-sdk-for-node. +2018.08 Version 2.10.1 + +ALL +* Added a parameter `enableGlobalHttpAgent` to all services. To enable global HTTP(s) agent, please set `{blob|queue|table|file}Service.enableGlobalHttpAgent` to true. +* Fixed a bug that content type value is incorrect for json. + 2018.06 Version 2.10.0 ALL diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..09597b9f --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,16 @@ +### Which service(blob, file, queue, table) does this issue concern? + + +### Which version of the SDK was used? + + +### What's the Node.js/Browser version? + + +### What problem was encountered? + + +### Steps to reproduce the issue? + + +### Have you found a mitigation/solution? diff --git a/browser/ChangeLog.md b/browser/ChangeLog.md index 35496f05..3d04a41a 100644 --- a/browser/ChangeLog.md +++ b/browser/ChangeLog.md @@ -1,5 +1,12 @@ Note: This is the change log file for Azure Storage JavaScript Client Library. +2018.08 Version 2.10.101 + +ALL +* Generated browser compatible JavaScript files based on Microsoft Azure Storage SDK for Node.js 2.10.1. +* Fixed a bug that content type value is incorrect for json. +* Fixed an issue that user agent is set in browser environment. + 2018.06 Version 2.10.100 ALL diff --git a/lib/common/services/storageserviceclient.js b/lib/common/services/storageserviceclient.js index a4dfdbca..1dc6c1a3 100644 --- a/lib/common/services/storageserviceclient.js +++ b/lib/common/services/storageserviceclient.js @@ -1,18 +1,18 @@ -// +// // Copyright (c) Microsoft and contributors. All rights reserved. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// +// // See the License for the specific language governing permissions and // limitations under the License. -// +// // Module dependencies. var request = require('../request-wrapper'); @@ -254,12 +254,14 @@ StorageServiceClient.prototype._performRequest = function (webResource, body, op if(!options.clientRequestId) { options.clientRequestId = guid.v1(); } - + webResource.withHeader(HeaderConstants.CLIENT_REQUEST_ID, options.clientRequestId); - // Sets the user-agent string - var userAgentComment = util.format('(NODE-VERSION %s; %s %s)', process.version, os.type(), os.release()); - webResource.withHeader(HeaderConstants.USER_AGENT, Constants.USER_AGENT_PRODUCT_NAME + '/' + Constants.USER_AGENT_PRODUCT_VERSION + ' ' + userAgentComment); + // Sets the user-agent string if the process is not started by the browser + if(!process.browser) { + var userAgentComment = util.format('(NODE-VERSION %s; %s %s)', process.version, os.type(), os.release()); + webResource.withHeader(HeaderConstants.USER_AGENT, Constants.USER_AGENT_PRODUCT_NAME + '/' + Constants.USER_AGENT_PRODUCT_VERSION + ' ' + userAgentComment); + } // Initialize the location that the request is going to be sent to. if(azureutil.objectIsNull(options.locationMode)) { @@ -277,18 +279,18 @@ StorageServiceClient.prototype._performRequest = function (webResource, body, op } this._initializeLocation(options); - + // Initialize the operationExpiryTime this._setOperationExpiryTime(options); - // If the output stream already got sent to server and got error back, + // If the output stream already got sent to server and got error back, // we should NOT retry within the SDK as the stream data is not valid anymore if we retry directly. // And it's very hard for SDK to re-wind the stream. // - // If users want to retry on this kind of error, they can implement their own logic to parse the response and + // If users want to retry on this kind of error, they can implement their own logic to parse the response and // determine if they need to re-prepare a stream and call our SDK API to retry. // - // Currently for blobs/files with size greater than 32MB (DEFAULT_SINGLE_BLOB_PUT_THRESHOLD_IN_BYTES), + // Currently for blobs/files with size greater than 32MB (DEFAULT_SINGLE_BLOB_PUT_THRESHOLD_IN_BYTES), // we'll send the steam by chunk buffers which doesn't have this issue. var outputStreamSent = false; @@ -333,7 +335,7 @@ StorageServiceClient.prototype._performRequest = function (webResource, body, op var requestStream; var requestWithDefaults; - + if(self.proxy) { if(requestWithDefaults === undefined) { requestWithDefaults = request.defaults({'proxy':self.proxy}); @@ -356,7 +358,7 @@ StorageServiceClient.prototype._performRequest = function (webResource, body, op if (contentLength !== undefined) { errorMessageBuffer = new Buffer(contentLength); } - + requestStream.on('data', function (data) { if (contentLength !== undefined) { data.copy(errorMessageBuffer, index); @@ -402,7 +404,7 @@ StorageServiceClient.prototype._performRequest = function (webResource, body, op if(azureutil.objectIsNull(options.disableContentMD5Validation) || options.disableContentMD5Validation === false) { response.contentMD5 = internalHash.digest('base64'); } - + response.length = responseLength; endResponse = response; }); @@ -443,7 +445,7 @@ StorageServiceClient.prototype._performRequest = function (webResource, body, op if (!azureutil.isBrowser() && Buffer.isBuffer(body.outputData)) { // Request module will take 200MB additional memory when we pass a 100MB buffer as body // Transfer buffer to stream will highly reduce the memory used by request module - finalRequestOptions.body = new BufferStream(body.outputData); + finalRequestOptions.body = new BufferStream(body.outputData); } else { finalRequestOptions.body = body.outputData; } @@ -578,10 +580,10 @@ StorageServiceClient.prototype._buildRequestOptions = function (webResource, bod } webResource.withHeader(HeaderConstants.ACCEPT_CHARSET, 'UTF-8'); - // Browsers cache GET/HEAD requests by adding conditional headers such as 'IF_MODIFIED_SINCE' after Azure Storage 'Authorization header' calculation, - // which may result in a 403 authorization error. So add timestamp to GET/HEAD request URLs thus avoid the browser cache. + // Browsers cache GET/HEAD requests by adding conditional headers such as 'IF_MODIFIED_SINCE' after Azure Storage 'Authorization header' calculation, + // which may result in a 403 authorization error. So add timestamp to GET/HEAD request URLs thus avoid the browser cache. if (azureutil.isBrowser() && ( - webResource.method === Constants.HttpConstants.HttpVerbs.GET || + webResource.method === Constants.HttpConstants.HttpVerbs.GET || webResource.method === Constants.HttpConstants.HttpVerbs.HEAD)) { webResource.withQueryOption(HeaderConstants.FORCE_NO_CACHE_IN_BROWSER, new Date().getTime()); } @@ -597,7 +599,7 @@ StorageServiceClient.prototype._buildRequestOptions = function (webResource, bod if(!azureutil.objectIsNull(options.timeoutIntervalInMs) && options.timeoutIntervalInMs > 0) { webResource.withQueryOption(QueryStringConstants.TIMEOUT, Math.ceil(options.timeoutIntervalInMs / 1000)); } - + if(options.accessConditions) { webResource.withHeader(HeaderConstants.IF_MATCH, options.accessConditions.EtagMatch); webResource.withHeader(HeaderConstants.IF_MODIFIED_SINCE, options.accessConditions.DateModifedSince); @@ -616,7 +618,7 @@ StorageServiceClient.prototype._buildRequestOptions = function (webResource, bod webResource.withHeader(HeaderConstants.SOURCE_IF_NONE_MATCH, options.sourceAccessConditions.EtagNonMatch); webResource.withHeader(HeaderConstants.SOURCE_IF_UNMODIFIED_SINCE, options.sourceAccessConditions.DateUnModifiedSince); } - + if (!webResource.headers || webResource.headers[HeaderConstants.CONTENT_TYPE] === undefined) { // work around to add an empty content type header to prevent the request module from magically adding a content type. webResource.headers[HeaderConstants.CONTENT_TYPE] = ''; @@ -634,6 +636,8 @@ StorageServiceClient.prototype._buildRequestOptions = function (webResource, bod delete webResource.headers[HeaderConstants.CONTENT_LENGTH]; } + var enableGlobalHttpAgent = this.enableGlobalHttpAgent; + // Sets the request url in the web resource. this._setRequestUrl(webResource, options); @@ -657,14 +661,17 @@ StorageServiceClient.prototype._buildRequestOptions = function (webResource, bod //set encoding of response data. If set to null, the body is returned as a Buffer requestOptions.encoding = options.responseEncoding; } - + if (options && options.clientRequestTimeoutInMs) { requestOptions.timeout = options.clientRequestTimeoutInMs; } else { requestOptions.timeout = Constants.DEFAULT_CLIENT_REQUEST_TIMEOUT_IN_MS; // 2 minutes } - requestOptions.forever = true; + // If global HTTP agent is not enabled, use forever agent. + if (enableGlobalHttpAgent !== true) { + requestOptions.forever = true; + } } callback(error, requestOptions); @@ -946,7 +953,7 @@ StorageServiceClient.prototype._setRequestUrl = function (webResource, options) if(host && host.lastIndexOf('/') !== (host.length - 1)){ host = host + '/'; } - + var fullPath = url.format({pathname: webResource.path, query: webResource.queryString}); webResource.uri = url.resolve(host, fullPath); webResource.path = url.parse(webResource.uri).pathname; @@ -1033,7 +1040,7 @@ StorageServiceClient.prototype.parseMetadataHeaders = function (headers) { metadata[key] = headers[header]; } } - + return metadata; }; @@ -1043,7 +1050,7 @@ StorageServiceClient.prototype.parseMetadataHeaders = function (headers) { * * @this {StorageServiceClient} * @param {object} [options] The request options. -* @param {LocationMode} [options.locationMode] Specifies the location mode used to decide which location the request should be sent to. +* @param {LocationMode} [options.locationMode] Specifies the location mode used to decide which location the request should be sent to. * Please see StorageUtilities.LocationMode for the possible values. * @param {int} [options.timeoutIntervalInMs] The server timeout interval, in milliseconds, to use for the request. * @param {int} [options.clientRequestTimeoutInMs] The timeout of client requests, in milliseconds, to use for the request. @@ -1052,40 +1059,40 @@ StorageServiceClient.prototype.parseMetadataHeaders = function (headers) { * execution time is checked intermittently while performing requests, and before executing retries. * @param {bool} [options.useNagleAlgorithm] Determines whether the Nagle algorithm is used; true to use the Nagle algorithm; otherwise, false. * The default value is false. -* @param {errorOrResult} callback `error` will contain information if an error occurs; otherwise, `result` will contain the properties +* @param {errorOrResult} callback `error` will contain information if an error occurs; otherwise, `result` will contain the properties * and `response` will contain information related to this operation. */ StorageServiceClient.prototype.getAccountServiceProperties = function (optionsOrCallback, callback) { var userOptions; azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; }); - + validate.validateArgs('getServiceProperties', function (v) { v.callback(callback); }); - + var options = extend(true, {}, userOptions); - + var webResource = WebResource.get() .withQueryOption(QueryStringConstants.COMP, 'properties') .withQueryOption(QueryStringConstants.RESTYPE, 'service'); - + options.requestLocationMode = RequestLocationMode.PRIMARY_OR_SECONDARY; - + var processResponseCallback = function (responseObject, next) { responseObject.servicePropertiesResult = null; if (!responseObject.error) { responseObject.servicePropertiesResult = ServicePropertiesResult.parse(responseObject.response.body.StorageServiceProperties); } - + // function to be called after all filters var finalCallback = function (returnObject) { callback(returnObject.error, returnObject.servicePropertiesResult, returnObject.response); }; - + // call the first filter next(responseObject, finalCallback); }; - + this.performRequest(webResource, null, options, processResponseCallback); }; @@ -1096,7 +1103,7 @@ StorageServiceClient.prototype.getAccountServiceProperties = function (optionsOr * @this {StorageServiceClient} * @param {object} serviceProperties The service properties. * @param {object} [options] The request options. -* @param {LocationMode} [options.locationMode] Specifies the location mode used to decide which location the request should be sent to. +* @param {LocationMode} [options.locationMode] Specifies the location mode used to decide which location the request should be sent to. * Please see StorageUtilities.LocationMode for the possible values. * @param {int} [options.timeoutIntervalInMs] The server timeout interval, in milliseconds, to use for the request. * @param {int} [options.clientRequestTimeoutInMs] The timeout of client requests, in milliseconds, to use for the request. @@ -1112,30 +1119,30 @@ StorageServiceClient.prototype.getAccountServiceProperties = function (optionsOr StorageServiceClient.prototype.setAccountServiceProperties = function (serviceProperties, optionsOrCallback, callback) { var userOptions; azureutil.normalizeArgs(optionsOrCallback, callback, function (o, c) { userOptions = o; callback = c; }); - + validate.validateArgs('setServiceProperties', function (v) { v.object(serviceProperties, 'serviceProperties'); v.callback(callback); }); - + var options = extend(true, {}, userOptions); var servicePropertiesXml = ServicePropertiesResult.serialize(serviceProperties); - + var webResource = WebResource.put() .withQueryOption(QueryStringConstants.COMP, 'properties') .withQueryOption(QueryStringConstants.RESTYPE, 'service') .withHeader(HeaderConstants.CONTENT_TYPE, 'application/xml;charset="utf-8"') .withHeader(HeaderConstants.CONTENT_LENGTH, Buffer.byteLength(servicePropertiesXml)) .withBody(servicePropertiesXml); - + var processResponseCallback = function (responseObject, next) { var finalCallback = function (returnObject) { callback(returnObject.error, returnObject.response); }; - + next(responseObject, finalCallback); }; - + this.performRequest(webResource, webResource.body, options, processResponseCallback); }; @@ -1164,14 +1171,14 @@ StorageServiceClient._normalizeError = function (error, response) { // blob/queue errors should have error.Error, table errors should have error['odata.error'] var errorProperties = error.Error || error.error || error['odata.error'] || error['m:error'] || error; normalizedError.code = errorProperties.message; // The message exists when there is error.Error. - + for (var property in errorProperties) { if (errorProperties.hasOwnProperty(property)) { var key = property.toLowerCase(); if(key.indexOf('m:') === 0) { key = key.substring(2); } - + normalizedError[key] = errorProperties[property]; // if this is a table error, message is an object - flatten it to normalize with blob/queue errors diff --git a/lib/common/util/constants.js b/lib/common/util/constants.js index 761d3b55..ddef9183 100644 --- a/lib/common/util/constants.js +++ b/lib/common/util/constants.js @@ -37,7 +37,7 @@ var Constants = { * @const * @type {string} */ - USER_AGENT_PRODUCT_VERSION: '2.10.0', + USER_AGENT_PRODUCT_VERSION: '2.10.1', /** * The number of default concurrent requests for parallel operation. @@ -1448,7 +1448,7 @@ var Constants = { * @const * @type {string} */ - JSON_CONTENT_TYPE_VALUE: 'application/json;', + JSON_CONTENT_TYPE_VALUE: 'application/json', /** * The header that specifies storage SKU, also known as account type. diff --git a/lib/services/blob/blobservice.core.js b/lib/services/blob/blobservice.core.js index a8af688a..37dc0be8 100644 --- a/lib/services/blob/blobservice.core.js +++ b/lib/services/blob/blobservice.core.js @@ -86,6 +86,8 @@ var StorageError = errors.StorageError; * the value specified by the singleBlobPutThresholdInBytes property in size. * useNagleAlgorithm Determines whether the Nagle algorithm is used for requests made via the Blob service; true to use the * Nagle algorithm; otherwise, false. The default value is false. +* enableGlobalHttpAgent Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use +* http(s).Agent({keepAlive:true}). * @constructor * @extends {StorageServiceClient} * diff --git a/lib/services/file/fileservice.core.js b/lib/services/file/fileservice.core.js index 27eef1da..13140927 100644 --- a/lib/services/file/fileservice.core.js +++ b/lib/services/file/fileservice.core.js @@ -77,6 +77,8 @@ var ArgumentError = errors.ArgumentError; * parallelOperationThreadCount The number of parallel operations that may be performed when uploading a file. * useNagleAlgorithm Determines whether the Nagle algorithm is used for requests made via the file service; true to use the * Nagle algorithm; otherwise, false. The default value is false. +* enableGlobalHttpAgent Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use +* http(s).Agent({keepAlive:true}). * @constructor * @extends {StorageServiceClient} * diff --git a/lib/services/queue/queueservice.js b/lib/services/queue/queueservice.js index 6d3a79cf..f183e23e 100644 --- a/lib/services/queue/queueservice.js +++ b/lib/services/queue/queueservice.js @@ -55,6 +55,8 @@ var ServiceStatsParser = azureCommon.ServiceStatsParser; * defaultLocationMode The default location mode for requests made via the Queue service. * useNagleAlgorithm Determines whether the Nagle algorithm is used for requests made via the Queue service; true to use the * Nagle algorithm; otherwise, false. The default value is false. +* enableGlobalHttpAgent Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use +* http(s).Agent({keepAlive:true}). * @constructor * @augments {StorageServiceClient} * diff --git a/lib/services/table/models/batchresult.js b/lib/services/table/models/batchresult.js index 4e39ad95..49497ffd 100644 --- a/lib/services/table/models/batchresult.js +++ b/lib/services/table/models/batchresult.js @@ -16,6 +16,7 @@ // Module dependencies. var azureCommon = require('./../../../common/common.core'); +var azureutil = azureCommon.util; var Md5Wrapper = require('./../../../common/md5-wrapper'); var StorageServiceClient = azureCommon.StorageServiceClient; var WebResource = azureCommon.WebResource; @@ -115,7 +116,11 @@ BatchResult.prototype._serializeOperation = function (operation, count) { webResource.headers[HeaderConstants.CONTENT_ID] = count; } - if (webResource.headers[HeaderConstants.CONTENT_TYPE]) { + var contentType = webResource.headers[HeaderConstants.CONTENT_TYPE]; + if (contentType) { + if (!azureutil.stringEndsWith(contentType, ';')) { + webResource.headers[HeaderConstants.CONTENT_TYPE] += ';'; + } webResource.headers[HeaderConstants.CONTENT_TYPE] += 'type=entry'; } diff --git a/lib/services/table/tableservice.js b/lib/services/table/tableservice.js index 6ddf5505..4b58d481 100644 --- a/lib/services/table/tableservice.js +++ b/lib/services/table/tableservice.js @@ -61,6 +61,8 @@ var TableUtilities = require('./tableutilities'); * defaultPayloadFormat The default payload format for requests made via the Table service. * useNagleAlgorithm Determines whether the Nagle algorithm is used for requests made via the Table service.; true to use the * Nagle algorithm; otherwise, false. The default value is false. +* enableGlobalHttpAgent Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use +* http(s).Agent({keepAlive:true}). * @constructor * @extends {StorageServiceClient} * diff --git a/package.json b/package.json index 4622af4b..089e4415 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "azure-storage", "author": "Microsoft Corporation", - "version": "2.10.0", + "version": "2.10.1", "description": "Microsoft Azure Storage Client Library for Node.js", "typings": "typings/azure-storage/azure-storage.d.ts", "tags": [ diff --git a/typings/azure-storage/azure-storage.d.ts b/typings/azure-storage/azure-storage.d.ts index f220b3ed..6b9a5ca1 100644 --- a/typings/azure-storage/azure-storage.d.ts +++ b/typings/azure-storage/azure-storage.d.ts @@ -86,6 +86,8 @@ declare module azurestorage { * the value specified by the singleBlobPutThresholdInBytes property in size. * useNagleAlgorithm Determines whether the Nagle algorithm is used for requests made via the Blob service; true to use the * Nagle algorithm; otherwise, false. The default value is false. + * enableGlobalHttpAgent Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use + * http(s).Agent({keepAlive:true}). * @constructor * @extends {StorageServiceClient} * @@ -3240,6 +3242,8 @@ declare module azurestorage { * defaultLocationMode The default location mode for requests made via the Queue service. * useNagleAlgorithm Determines whether the Nagle algorithm is used for requests made via the Queue service; true to use the * Nagle algorithm; otherwise, false. The default value is false. + * enableGlobalHttpAgent Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use + * http(s).Agent({keepAlive:true}). * If no connection string or storageaccount and storageaccesskey are provided, * the AZURE_STORAGE_CONNECTION_STRING or AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_ACCESS_KEY environment variables will be used. * @augments {StorageServiceClient} @@ -5513,6 +5517,8 @@ declare module azurestorage { * defaultPayloadFormat The default payload format for requests made via the Table service. * useNagleAlgorithm Determines whether the Nagle algorithm is used for requests made via the Table service.; true to use the * Nagle algorithm; otherwise, false. The default value is false. + * enableGlobalHttpAgent Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use + * http(s).Agent({keepAlive:true}). * @constructor * @extends {StorageServiceClient} * @@ -7654,6 +7660,8 @@ declare module azurestorage { * parallelOperationThreadCount The number of parallel operations that may be performed when uploading a file. * useNagleAlgorithm Determines whether the Nagle algorithm is used for requests made via the file service; true to use the * Nagle algorithm; otherwise, false. The default value is false. + * enableGlobalHttpAgent Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use + * http(s).Agent({keepAlive:true}). * @constructor * @extends {StorageServiceClient} * @@ -9217,15 +9225,21 @@ declare module azurestorage { defaultClientRequestTimeoutInMs: number; /** * Determines whether the Nagle algorithm is used for requests made via the Queue service; true to use the - * Nagle algorithm; otherwise, false. The default value is false. + * Nagle algorithm; otherwise, false. The default value is false. * @member {bool} StorageServiceClient#useNagleAlgorithm */ useNagleAlgorithm: boolean; + /** + * Determines whether global HTTP(s) agent is enabled; true to use Global HTTP(s) agent; otherwise, false to use + * http(s).Agent({keepAlive:true}). + * @member {bool} StorageServiceClient#enableGlobalHttpAgent + */ + enableGlobalHttpAgent: boolean; /** The proxy object specified by caller. * @member {Proxy} StorageServiceClient#proxy */ proxy: Proxy; - /** The logging settings object. + /** The logging settings object. * @member {diagnostics.logger.Logger} StorageServiceClient#logger */ logger: diagnostics.logger.Logger;