Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
"r": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
"ecmaVersion": 2020
}
}
44 changes: 31 additions & 13 deletions lib/remote-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,23 +149,33 @@ module.exports = class RemoteStorage {
}
const content = await fs.readFile(file)
const mimeType = mime.lookup(path.extname(file))
// first we will grab it from the global config: htmlCacheDuration, etc.
const cacheControlString = this._getCacheControlConfig(mimeType, appConfig.app)
const uploadParams = {
Bucket: this.bucket,
Key: urlJoin(prefix, path.basename(file)),
Body: content,
CacheControl: cacheControlString
Body: content
}
// if we found it in the global config, we will use it ( for now )
if (cacheControlString) {
uploadParams.CacheControl = cacheControlString
}
// add response headers if specified in manifest
const responseHeaders = this.getResponseHeadersForFile(file, distRoot, appConfig)
if (responseHeaders) {
uploadParams.Metadata = responseHeaders
// here we allow overriding the cache control if specified in response headers
// this is considered more specific than the general cache control config
// ideally we deprecate cache control config in favor of response headers directly
if (responseHeaders?.['adp-cache-control']) {
uploadParams.CacheControl = responseHeaders['adp-cache-control']
delete responseHeaders['adp-cache-control']
}
uploadParams.Metadata = responseHeaders ?? {}

uploadParams.Metadata['adp-AuditUserId'] = appConfig.auditUserId
// s3 misses some mime types like for css files
if (mimeType) {
uploadParams.ContentType = mimeType
}

// Note: putObject is recommended for files < 100MB and has a limit of 5GB, which is ok for our use case of storing static web assets
// if we intend to store larger files, we should use multipart upload and https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_lib_storage.html
return this.s3.putObject(uploadParams)
Expand All @@ -190,6 +200,13 @@ module.exports = class RemoteStorage {
return responseHeaders
}

/**
* Checks if a header can be added to a file based on the rule
* @param {string} file - file path
* @param {string} distRoot - distribution root
* @param {string} rule - rule to check
* @returns {boolean} true if header can be added, false otherwise
*/
canAddHeader (file, distRoot, rule) {
const filePath = path.parse(file)
const normalisedRule = rule.replace(/\//g, path.sep)
Expand All @@ -198,7 +215,6 @@ module.exports = class RemoteStorage {
if (folderPathToMatch.endsWith(path.sep)) {
folderPathToMatch = folderPathToMatch.substring(0, folderPathToMatch.length - 1) // remove any trailing path separator
}

if (rule === '/*') { // all content
return true
} else if (rule.endsWith('/*')) { // all content in a folder ex. /test/*
Expand Down Expand Up @@ -293,17 +309,19 @@ module.exports = class RemoteStorage {
* @param {Object} appConfig - application config
*/
_getCacheControlConfig (mimeType, appConfig) {
const cacheControlStr = 's-maxage=0'
const cacheControlStr = 's-maxage=60'
if (!mimeType) {
return cacheControlStr
return null
} else if (mimeType === mime.lookup('html')) {
return cacheControlStr + ', max-age=' + appConfig.htmlCacheDuration
return `${cacheControlStr}, max-age=${appConfig.htmlCacheDuration}`
} else if (mimeType === mime.lookup('js')) {
return cacheControlStr + ', max-age=' + appConfig.jsCacheDuration
return `${cacheControlStr}, max-age=${appConfig.jsCacheDuration}`
} else if (mimeType === mime.lookup('css')) {
return cacheControlStr + ', max-age=' + appConfig.cssCacheDuration
return `${cacheControlStr}, max-age=${appConfig.cssCacheDuration}`
} else if (mimeType.startsWith('image')) {
return cacheControlStr + ', max-age=' + appConfig.imageCacheDuration
} else { return cacheControlStr }
return `${cacheControlStr}, max-age=${appConfig.imageCacheDuration}`
} else {
return null
}
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"klaw": "^4",
"lodash.clonedeep": "^4.5.0",
"mime-types": "^2.1.24",
"parcel": "^2.7.0",
"parcel": "^2.15.4",
"proxy-agent": "^6.3.0",
"regenerator-runtime": "^0.13.7"
},
Expand Down
43 changes: 38 additions & 5 deletions test/lib/remote-storage.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,31 +300,31 @@ describe('RemoteStorage', () => {
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')
expect(response).toBe('s-maxage=60, 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')
expect(response).toBe('s-maxage=60, 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')
expect(response).toBe('s-maxage=60, 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')
expect(response).toBe('s-maxage=60, 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')
expect(response).toBe(null)
})

// response header tests
Expand Down Expand Up @@ -573,4 +573,37 @@ describe('RemoteStorage', () => {
}
expect(mockS3.putObject).toHaveBeenCalledWith(expect.objectContaining(expected))
})

test('Cache control override from response headers', async () => {
global.addFakeFiles(vol, 'fakeDir', { 'index.html': 'fake content' })
const rs = new RemoteStorage(global.fakeTVMResponse)
const files = await rs.walkDir('fakeDir')
const fakeDistRoot = path.parse(files[0]).dir
const filePath = files[0] // Use absolute path from walkDir
const newConfig = global.configWithModifiedWeb(global.fakeConfig, {
'response-headers': {
'/*.html': {
'cache-control': 'max-age=3600, s-maxage=7200',
testHeader: 'generic-header'
}
}
})
await rs.uploadFile(filePath, 'fakeprefix', newConfig, fakeDistRoot)
const body = Buffer.from('fake content', 'utf8')
const expected = {
Bucket: 'fake-bucket',
Key: 'fakeprefix/index.html',
Body: body,
ContentType: 'text/html',
CacheControl: 'max-age=3600, s-maxage=7200',
Metadata: {
'adp-testHeader': 'generic-header',
'adp-AuditUserId': undefined
}
}
expect(mockS3.putObject).toHaveBeenCalledWith(expect.objectContaining(expected))
// Verify that adp-cache-control was removed from metadata
const putObjectCall = mockS3.putObject.mock.calls[0][0]
expect(putObjectCall.Metadata).not.toHaveProperty('adp-cache-control')
})
})
Loading