From e80e281cb396f072642188687062671c1888f49e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=91=A8=E6=B4=8B?= Date: Wed, 24 Mar 2021 14:12:24 +0800 Subject: [PATCH] config: Add enable_virtual_host_style, default false (#78) --- src/__snapshots__/build.test.js.snap | 8 ++++++++ src/__snapshots__/sign.test.js.snap | 2 ++ src/build.js | 26 +++++++++++++++++--------- src/build.test.js | 8 ++++++++ src/config/browser.js | 1 + src/config/node.js | 10 ++++++++-- src/request.js | 7 ++++++- src/sign.js | 16 ++++++++++++++-- src/sign.test.js | 13 +++++++++++++ test/build.test.js | 14 +++++++++++++- test/config.test.js | 3 +++ 11 files changed, 93 insertions(+), 15 deletions(-) diff --git a/src/__snapshots__/build.test.js.snap b/src/__snapshots__/build.test.js.snap index bccd17e..665608c 100644 --- a/src/__snapshots__/build.test.js.snap +++ b/src/__snapshots__/build.test.js.snap @@ -47,3 +47,11 @@ Object { "uri": "https://pek3a.qingstor.com/test_bucket/test_object.jpg?acl&upload_id=test_upload_id", } `; + +exports[`parseVirtualHostRequestURI 1`] = ` +Object { + "endpoint": "https://test_bucket.pek3a.qingstor.com", + "path": "/test_object.jpg?acl", + "uri": "https://test_bucket.pek3a.qingstor.com/test_object.jpg?acl&upload_id=test_upload_id", +} +`; diff --git a/src/__snapshots__/sign.test.js.snap b/src/__snapshots__/sign.test.js.snap index 6050503..b76dd75 100644 --- a/src/__snapshots__/sign.test.js.snap +++ b/src/__snapshots__/sign.test.js.snap @@ -4,6 +4,8 @@ exports[`getAuthorization 1`] = `"L6q5iKoyWbQky0T5/MYELz5jAFecGbvLDyjLdz6fClw="` exports[`getCanonicalizedResource 1`] = `"/test_bucket/test_object.jpg?acl&upload_id=test_upload_id"`; +exports[`getCanonicalizedResource by virtual host 1`] = `"/test_bucket/test_object.jpg?acl&upload_id=test_upload_id"`; + exports[`getQuerySignature 1`] = ` Object { "access_key_id": "test_key", diff --git a/src/build.js b/src/build.js index cc3056e..c3eaa3d 100644 --- a/src/build.js +++ b/src/build.js @@ -119,32 +119,40 @@ class Builder { } parseRequestURI(operation) { - let path = operation.uri; let endpoint = ''; + let path = operation.uri; const parsedProperties = this.parseRequestProperties(operation); if (parsedProperties['zone']) { - endpoint = `${this.config.protocol}://${parsedProperties.zone}.${this.config.host}`; + endpoint = `${parsedProperties.zone}.${this.config.host}`; } else { - endpoint = `${this.config.protocol}://${this.config.host}`; + endpoint = this.config.host; + } + + if (this.config.enable_virtual_host_style) { + const bucketKey = 'bucket-name'; + const prefix = `/<${bucketKey}>`; + + if (path.startsWith(prefix) && parsedProperties[bucketKey]) { + path = path.slice(prefix.length); + endpoint = `${parsedProperties[bucketKey]}.${endpoint}`; + } } if (this.config.port) { endpoint += `:${this.config.port}`; } + endpoint = `${this.config.protocol}://${endpoint}`; + for (const key of Object.keys(parsedProperties)) { path = path.replace(`<${key}>`, parsedProperties[key]); } const parsedParams = this.parseRequestParams(operation); - const parsedUri = buildUri(endpoint, path, parsedParams); + const uri = buildUri(endpoint, path, parsedParams); - return { - endpoint, - path, - uri: parsedUri, - }; + return { endpoint, path, uri }; } } diff --git a/src/build.test.js b/src/build.test.js index 9622c11..bf9a9ef 100644 --- a/src/build.test.js +++ b/src/build.test.js @@ -81,3 +81,11 @@ test('parseRequestProperties', () => { test('parseRequestURI', () => { expect(builder.parseRequestURI(operation)).toMatchSnapshot(); }); + +test('parseVirtualHostRequestURI', () => { + process.env.QINGSTOR_ENABLE_VIRTUAL_HOST_STYLE = true; + + const virtualHostBuilder = new Builder(new Config(), operation); + + expect(virtualHostBuilder.parseRequestURI(operation)).toMatchSnapshot(); +}); diff --git a/src/config/browser.js b/src/config/browser.js index b5e6c3f..8702dfa 100644 --- a/src/config/browser.js +++ b/src/config/browser.js @@ -32,6 +32,7 @@ const DEFAULT_CONFIG = { connection_retries: 3, // Valid levels are "debug", "info", "warn", "error", and "fatal" log_level: 'warn', + enable_virtual_host_style: false, }; class Config { diff --git a/src/config/node.js b/src/config/node.js index ca37df4..461080e 100644 --- a/src/config/node.js +++ b/src/config/node.js @@ -30,6 +30,7 @@ const defaultConfigFileContent = [ 'port: 443', 'protocol: "https"', 'connection_retries: 3', + 'enable_virtual_host_style: false', '', '# Additional User-Agent', 'additional_user_agent: ""', @@ -41,6 +42,7 @@ const defaultConfigFile = '~/.qingstor/config.yaml'; const configPathEnvName = 'QINGSTOR_CONFIG_PATH'; const accessKeyIdEnvName = 'QINGSTOR_ACCESS_KEY_ID'; const secretAccessKeyEnvName = 'QINGSTOR_SECRET_ACCESS_KEY'; +const enableVirtualHostEnvName = 'QINGSTOR_ENABLE_VIRTUAL_HOST_STYLE'; class Config { constructor(options) { @@ -111,14 +113,18 @@ class Config { overrideConfigByENV() { const config = {}; - if (process.env[accessKeyIdEnvName]) { + if (accessKeyIdEnvName in process.env) { config.access_key_id = process.env[accessKeyIdEnvName]; } - if (process.env[secretAccessKeyEnvName]) { + if (secretAccessKeyEnvName in process.env) { config.secret_access_key = process.env[secretAccessKeyEnvName]; } + if (enableVirtualHostEnvName in process.env) { + config.enable_virtual_host_style = process.env[enableVirtualHostEnvName] === 'true'; + } + return this.loadConfig(config); } diff --git a/src/request.js b/src/request.js index 8288145..4c47a33 100644 --- a/src/request.js +++ b/src/request.js @@ -45,7 +45,12 @@ class Request { }); } - this.operation = new Signer(this.config.access_key_id, this.config.secret_access_key).sign(this.operation); + this.operation = new Signer( + this.config.access_key_id, + this.config.secret_access_key, + this.config.enable_virtual_host_style, + ).sign(this.operation); + return Promise.resolve(this); } diff --git a/src/sign.js b/src/sign.js index 5b3cdf6..d39a301 100644 --- a/src/sign.js +++ b/src/sign.js @@ -15,14 +15,16 @@ // +------------------------------------------------------------------------- import logger from 'loglevel'; +import urlParse from 'urlparse'; import hmacSHA256 from 'crypto-js/hmac-sha256'; import Base64 from 'crypto-js/enc-base64'; import { buildUri } from './utils'; class Signer { - constructor(access_key_id, secret_access_key) { + constructor(access_key_id, secret_access_key, enable_virtual_host_style) { this.access_key_id = access_key_id; this.secret_access_key = secret_access_key; + this.enable_virtual_host_style = enable_virtual_host_style; } getSignature(operation) { @@ -128,8 +130,18 @@ class Signer { getCanonicalizedResource() { let canonicalizedResource = this.operation.path; - const parsedParams = this.operation.params || {}; + + if (this.enable_virtual_host_style) { + canonicalizedResource = canonicalizedResource || '/'; + + const { host } = urlParse(this.operation.endpoint); + const [bucket,] = host.split('.'); + + canonicalizedResource = `/${bucket}${canonicalizedResource}`; + } + const query = []; + const parsedParams = this.operation.params || {}; if (Object.keys(parsedParams).length !== 0) { for (const i of Object.keys(parsedParams)) { if (this.isSubResource(i)) { diff --git a/src/sign.test.js b/src/sign.test.js index adc9d12..67f5134 100644 --- a/src/sign.test.js +++ b/src/sign.test.js @@ -91,6 +91,19 @@ test('getCanonicalizedResource', () => { expect(signer.getCanonicalizedResource()).toMatchSnapshot(); }); +test('getCanonicalizedResource by virtual host', () => { + const _operation = { + ...operation, + path: '/test_object.jpg?acl', + endpoint: 'https://test_bucket.pek3a.qingstor.com', + uri: 'https://test_bucket.pek3a.qingstor.com/test_object.jpg?acl&upload_id=test_upload_id', + }; + const vHostSigner = new Signer('test_key', 'test_secret', true); + + vHostSigner._setOperation(_operation); + expect(vHostSigner.getCanonicalizedResource()).toMatchSnapshot(); +}); + test('getAuthorization', () => { signer._setOperation(operation); expect(signer.getAuthorization()).toMatchSnapshot(); diff --git a/test/build.test.js b/test/build.test.js index 8e63878..4c0821d 100644 --- a/test/build.test.js +++ b/test/build.test.js @@ -35,7 +35,7 @@ const userAgent = [ ].join(''); describe('Builder test', function () { - const config = new Config('test_access_key', 'test_secret_key'); + const config = new Config(); config.additional_user_agent = 'UserExample'; const operation = { method: 'PUT', @@ -95,6 +95,18 @@ describe('Builder test', function () { }); }); + it('parseVirtualHostRequestUri test', function() { + process.env.QINGSTOR_ENABLE_VIRTUAL_HOST_STYLE = true; + const _config = new Config(); + const _test = new Builder(_config, operation); + + _test.parseRequestURI(operation).should.eql({ + endpoint: 'https://test_bucket.pek3a.qingstor.com', + path: '/test_object.jpg?acl', + uri: 'https://test_bucket.pek3a.qingstor.com/test_object.jpg?acl&upload_id=test_upload_id', + }); + }); + it('parse test', function () { test.parse().should.eql({ method: 'PUT', diff --git a/test/config.test.js b/test/config.test.js index 935b57e..e3c661e 100644 --- a/test/config.test.js +++ b/test/config.test.js @@ -31,6 +31,7 @@ describe('Config test', function () { config.protocol.should.equal('https'); config.connection_retries.should.equal(3); config.log_level.should.equal('warn'); + config.enable_virtual_host_style.should.equal(false); }); it('getUserConfigFilePath test', function() { @@ -66,11 +67,13 @@ describe('Config test', function () { it('overrideConfigByENV test', function() { process.env.QINGSTOR_ACCESS_KEY_ID = 'example_access_key_id'; process.env.QINGSTOR_SECRET_ACCESS_KEY = 'example_secret_access_key'; + process.env.QINGSTOR_ENABLE_VIRTUAL_HOST_STYLE = true; const test = new Config(); test.access_key_id.should.equal('example_access_key_id'); test.secret_access_key.should.equal('example_secret_access_key'); + test.enable_virtual_host_style.should.equal(true); }); it('overrideConfigByOptions with access key test', function () {