Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 18 additions & 28 deletions lib/remote-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ const { ProxyAgent } = require('proxy-agent')
const { codes, logAndThrow } = require('./StorageError')

const fileExtensionPattern = /\*\.[0-9a-zA-Z]+$/
const cacheControlHeader = 'cache-control'
const awsCacheControlMetadata = 'cacheControl'

// /**
// * Joins url path parts
Expand Down Expand Up @@ -149,18 +151,21 @@ module.exports = class RemoteStorage {
}
const content = await fs.readFile(file)
const mimeType = mime.lookup(path.extname(file))
const cacheControlString = this._getCacheControlConfig(mimeType, appConfig.app)
const uploadParams = {
Bucket: this.bucket,
Key: urlJoin(prefix, path.basename(file)),
Body: content,
CacheControl: cacheControlString
Body: content
}
// add response headers if specified in manifest
const responseHeaders = this.getResponseHeadersForFile(file, distRoot, appConfig)
const { responseHeaders, cacheControl } = this.getResponseHeadersForFile(file, distRoot, appConfig)
if (responseHeaders) {
uploadParams.Metadata = responseHeaders
uploadParams.Metadata = { ...responseHeaders }
}

if (cacheControl) {
uploadParams[awsCacheControlMetadata] = cacheControl
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we lock this to specific values?

}

// s3 misses some mime types like for css files
if (mimeType) {
uploadParams.ContentType = mimeType
Expand All @@ -173,21 +178,26 @@ module.exports = class RemoteStorage {

getResponseHeadersForFile (file, distRoot, appConfig) {
let responseHeaders
let cacheControl
if (appConfig.web && appConfig.web['response-headers']) {
responseHeaders = {}
const cdnConfig = appConfig.web['response-headers']
const headerPrefix = 'adp-'

Object.keys(cdnConfig).forEach(rule => {
if (this.canAddHeader(file, distRoot, rule)) {
Object.keys(cdnConfig[rule]).forEach(header => {
this.validateHTTPHeader(header, cdnConfig[rule][header])
responseHeaders[headerPrefix + header] = cdnConfig[rule][header]
if (header.toLowerCase() === cacheControlHeader) {
// treat cache control header as a special case and store it in the cacheControl metadata
cacheControl = cdnConfig[rule][header]
} else {
responseHeaders[headerPrefix + header] = cdnConfig[rule][header]
}
})
}
})
}
return responseHeaders
return { responseHeaders, cacheControl }
}

canAddHeader (file, distRoot, rule) {
Expand Down Expand Up @@ -286,24 +296,4 @@ module.exports = class RemoteStorage {
}
return allResults
}

/**
* Get cache control string based on mime type and config
* @param {string|boolean} mimeType - string if valid mimeType or false for unknown files
* @param {Object} appConfig - application config
*/
_getCacheControlConfig (mimeType, appConfig) {
const cacheControlStr = 's-maxage=0'
if (!mimeType) {
return cacheControlStr
} else if (mimeType === mime.lookup('html')) {
return cacheControlStr + ', max-age=' + appConfig.htmlCacheDuration
} else if (mimeType === mime.lookup('js')) {
return cacheControlStr + ', max-age=' + appConfig.jsCacheDuration
} else if (mimeType === mime.lookup('css')) {
return cacheControlStr + ', max-age=' + appConfig.cssCacheDuration
} else if (mimeType.startsWith('image')) {
return cacheControlStr + ', max-age=' + appConfig.imageCacheDuration
} else { return cacheControlStr }
}
}
117 changes: 65 additions & 52 deletions test/lib/remote-storage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,36 +297,6 @@ describe('RemoteStorage', () => {
expect(cbMock).toHaveBeenCalledTimes(4)
})

test('cachecontrol string for html', async () => {
const rs = new RemoteStorage(global.fakeTVMResponse)
const response = rs._getCacheControlConfig('text/html', global.fakeConfig.app)
expect(response).toBe('s-maxage=0, max-age=60')
})

test('cachecontrol string for JS', async () => {
const rs = new RemoteStorage(global.fakeTVMResponse)
const response = rs._getCacheControlConfig('application/javascript', global.fakeConfig.app)
expect(response).toBe('s-maxage=0, max-age=604800')
})

test('cachecontrol string for CSS', async () => {
const rs = new RemoteStorage(global.fakeTVMResponse)
const response = rs._getCacheControlConfig('text/css', global.fakeConfig.app)
expect(response).toBe('s-maxage=0, max-age=604800')
})

test('cachecontrol string for Image', async () => {
const rs = new RemoteStorage(global.fakeTVMResponse)
const response = rs._getCacheControlConfig('image/jpeg', global.fakeConfig.app)
expect(response).toBe('s-maxage=0, max-age=604800')
})

test('cachecontrol string for default', async () => {
const rs = new RemoteStorage(global.fakeTVMResponse)
const response = rs._getCacheControlConfig('application/pdf', global.fakeConfig.app)
expect(response).toBe('s-maxage=0')
})

// response header tests
test('get response header from config with multiple rules', async () => {
const rs = new RemoteStorage(global.fakeTVMResponse)
Expand Down Expand Up @@ -354,11 +324,11 @@ describe('RemoteStorage', () => {
const fakeDistRoot = path.parse(files[0]).dir

const expectedValMap = {
'index.html': { 'adp-testHeader': 'generic-header' },
'test.js': { 'adp-testHeader': 'specific-file-header' }
'index.html': { responseHeaders: { 'adp-testHeader': 'generic-header' }, cacheControl: undefined },
'test.js': { responseHeaders: { 'adp-testHeader': 'specific-file-header' }, cacheControl: undefined }
}
expectedValMap[folderPath1] = { 'adp-testHeader': 'folder-header' }
expectedValMap[folderPath2] = { 'adp-testHeader': 'all-js-file-in-folder-header' }
expectedValMap[folderPath1] = { responseHeaders: { 'adp-testHeader': 'folder-header' }, cacheControl: undefined }
expectedValMap[folderPath2] = { responseHeaders: { 'adp-testHeader': 'all-js-file-in-folder-header' }, cacheControl: undefined }

files.forEach(f => {
const fileName = f.replace(path.join(fakeDistRoot, path.sep), '')
Expand Down Expand Up @@ -399,13 +369,13 @@ describe('RemoteStorage', () => {

// set the expectation
const expectedValMap = {
'index.html': { 'adp-testHeader': 'generic-header' },
'test.js': { 'adp-testHeader': 'generic-header' }
'index.html': { responseHeaders: { 'adp-testHeader': 'generic-header' }, cacheControl: undefined },
'test.js': { responseHeaders: { 'adp-testHeader': 'generic-header' }, cacheControl: undefined }
}
expectedValMap[folderPath1] = { 'adp-testHeader': 'all-files-in-css-folder-header' }
expectedValMap[folderPath2] = { 'adp-testHeader': 'all-files-in-js-folder-header' }
expectedValMap[folderPath3] = { 'adp-testHeader': 'all-files-in-images-folder-header' }
expectedValMap[folderPath4] = { 'adp-testHeader': 'all-files-in-images-folder-header' }
expectedValMap[folderPath1] = { responseHeaders: { 'adp-testHeader': 'all-files-in-css-folder-header' }, cacheControl: undefined }
expectedValMap[folderPath2] = { responseHeaders: { 'adp-testHeader': 'all-files-in-js-folder-header' }, cacheControl: undefined }
expectedValMap[folderPath3] = { responseHeaders: { 'adp-testHeader': 'all-files-in-images-folder-header' }, cacheControl: undefined }
expectedValMap[folderPath4] = { responseHeaders: { 'adp-testHeader': 'all-files-in-images-folder-header' }, cacheControl: undefined }

// check header application per file
files.forEach(f => {
Expand Down Expand Up @@ -455,12 +425,12 @@ describe('RemoteStorage', () => {

// set the expectation
const expectedValMap = {
'index.html': { 'adp-testHeader': 'generic-header' },
'test.js': { 'adp-testHeader': 'generic-header' }
'index.html': { responseHeaders: { 'adp-testHeader': 'generic-header' }, cacheControl: undefined },
'test.js': { responseHeaders: { 'adp-testHeader': 'generic-header' }, cacheControl: undefined }
}
expectedValMap[folderPath1] = { 'adp-testHeader': 'specific-css-file-header' }
expectedValMap[folderPath2] = { 'adp-testHeader': 'specific-js-file-header' }
expectedValMap[folderPath3] = { 'adp-testHeader': 'specific-image-file-header' }
expectedValMap[folderPath1] = { responseHeaders: { 'adp-testHeader': 'specific-css-file-header' }, cacheControl: undefined }
expectedValMap[folderPath2] = { responseHeaders: { 'adp-testHeader': 'specific-js-file-header' }, cacheControl: undefined }
expectedValMap[folderPath3] = { responseHeaders: { 'adp-testHeader': 'specific-image-file-header' }, cacheControl: undefined }

// check header application per file
files.forEach(f => {
Expand Down Expand Up @@ -501,12 +471,12 @@ describe('RemoteStorage', () => {

// set the expectation
const expectedValMap = {
'index.html': { 'adp-testHeader': 'generic-header' },
'test.js': { 'adp-testHeader': 'all-js-files-header' }
'index.html': { responseHeaders: { 'adp-testHeader': 'generic-header' }, cacheControl: undefined },
'test.js': { responseHeaders: { 'adp-testHeader': 'all-js-files-header' }, cacheControl: undefined }
}
expectedValMap[folderPath1] = { 'adp-testHeader': 'all-css-files-header' }
expectedValMap[folderPath2] = { 'adp-testHeader': 'all-js-files-header' }
expectedValMap[folderPath3] = { 'adp-testHeader': 'all-png-files-header' }
expectedValMap[folderPath1] = { responseHeaders: { 'adp-testHeader': 'all-css-files-header' }, cacheControl: undefined }
expectedValMap[folderPath2] = { responseHeaders: { 'adp-testHeader': 'all-js-files-header' }, cacheControl: undefined }
expectedValMap[folderPath3] = { responseHeaders: { 'adp-testHeader': 'all-png-files-header' }, cacheControl: undefined }

// check header application per file
files.forEach(f => {
Expand Down Expand Up @@ -555,7 +525,8 @@ describe('RemoteStorage', () => {
const newConfig = global.configWithModifiedWeb(global.fakeConfig, {
'response-headers': {
'/*': {
testHeader: 'generic-header'
testHeader: 'generic-header',
'cache-control': 's-maxage=3600, max-age=60'
}
}
})
Expand All @@ -569,8 +540,50 @@ describe('RemoteStorage', () => {
ContentType: 'application/javascript',
Metadata: {
'adp-testHeader': 'generic-header'
}
},
cacheControl: 's-maxage=3600, max-age=60'
}
expect(mockS3.putObject).toHaveBeenCalledWith(expect.objectContaining(expected))
})

test('set cache control response headers for config with multiple rules', async () => {
// custom cache control are now set via response headers
const rs = new RemoteStorage(global.fakeTVMResponse)
const newConfig = global.configWithModifiedWeb(global.fakeConfig, {
'response-headers': {
'/*': {
'cache-control': 's-maxage=0, max-age=60'
},
'/testFolder/*': {
'Cache-Control': 's-maxage=500, max-age=0'
},
'/testFolder/*.js': {
'Cache-CoNtrol': 's-maxage=0, max-age=604800'
},
'/test.js': {
'Cache-COntrol': 's-maxage=604800, max-age=604800'
}
}
})

const folderPath1 = 'testFolder' + path.sep + 'index.html'
const folderPath2 = 'testFolder' + path.sep + 'test.js'
await global.addFakeFiles(vol, 'fakeDir', ['index.html', 'test.js', folderPath1, folderPath2])
const files = await rs.walkDir('fakeDir')
const fakeDistRoot = path.parse(files[0]).dir

const expectedValMap = {
'index.html': { responseHeaders: {}, cacheControl: 's-maxage=0, max-age=60' },
'test.js': { responseHeaders: {}, cacheControl: 's-maxage=604800, max-age=604800' }
}
expectedValMap[folderPath1] = { responseHeaders: {}, cacheControl: 's-maxage=500, max-age=0' }
expectedValMap[folderPath2] = { responseHeaders: {}, cacheControl: 's-maxage=0, max-age=604800' }

files.forEach(f => {
const fileName = f.replace(path.join(fakeDistRoot, path.sep), '')
const response = rs.getResponseHeadersForFile(f, fakeDistRoot, newConfig)
const expected = expectedValMap[fileName]
expect(response).toStrictEqual(expected)
})
})
})