Skip to content

Commit 53cd5d6

Browse files
authored
feat: Add mtime as optional ETag algorithm in file backend alongside default md5 (#613)
* fix: Optimize etag generation to improve file backend performance * feat: Add configurable etag algorithm for file backend * md5 as default
1 parent 360a04b commit 53cd5d6

File tree

3 files changed

+23
-10
lines changed

3 files changed

+23
-10
lines changed

.env.sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ AWS_SECRET_ACCESS_KEY=secret1234
100100
# File Backend
101101
#######################################
102102
STORAGE_FILE_BACKEND_PATH=./data
103-
103+
STORAGE_FILE_ETAG_ALGORITHM=md5
104104

105105
#######################################
106106
# Image Transformation

src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type StorageConfigType = {
2020
uploadFileSizeLimit: number
2121
uploadFileSizeLimitStandard?: number
2222
storageFilePath?: string
23+
storageFileEtagAlgorithm: 'mtime' | 'md5'
2324
storageS3MaxSockets: number
2425
storageS3Bucket: string
2526
storageS3Endpoint?: string
@@ -273,6 +274,7 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType {
273274
'STORAGE_FILE_BACKEND_PATH',
274275
'FILE_STORAGE_BACKEND_PATH'
275276
),
277+
storageFileEtagAlgorithm: getOptionalConfigFromEnv('STORAGE_FILE_ETAG_ALGORITHM') || 'md5',
276278

277279
// Storage - S3
278280
storageS3MaxSockets: parseInt(

src/storage/backend/file.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,25 @@ const METADATA_ATTR_KEYS = {
4545
export class FileBackend implements StorageBackendAdapter {
4646
client = null
4747
filePath: string
48+
etagAlgorithm: 'mtime' | 'md5'
4849

4950
constructor() {
50-
const { storageFilePath } = getConfig()
51+
const { storageFilePath, storageFileEtagAlgorithm } = getConfig()
5152
if (!storageFilePath) {
5253
throw new Error('FILE_STORAGE_BACKEND_PATH env variable not set')
5354
}
5455
this.filePath = storageFilePath
56+
this.etagAlgorithm = storageFileEtagAlgorithm
57+
}
58+
59+
private async etag(file: string, stats: fs.Stats): Promise<string> {
60+
if (this.etagAlgorithm === 'md5') {
61+
const checksum = await fileChecksum(file)
62+
return `"${checksum}"`
63+
} else if (this.etagAlgorithm === 'mtime') {
64+
return `"${stats.mtimeMs.toString(16)}-${stats.size.toString(16)}"`
65+
}
66+
throw new Error('FILE_STORAGE_ETAG_ALGORITHM env variable must be either "mtime" or "md5"')
5567
}
5668

5769
/**
@@ -70,7 +82,7 @@ export class FileBackend implements StorageBackendAdapter {
7082
// 'Range: bytes=#######-######
7183
const file = path.resolve(this.filePath, withOptionalVersion(`${bucketName}/${key}`, version))
7284
const data = await fs.stat(file)
73-
const checksum = await fileChecksum(file)
85+
const eTag = await this.etag(file, data)
7486
const fileSize = data.size
7587
const { cacheControl, contentType } = await this.getFileMetadata(file)
7688
const lastModified = new Date(0)
@@ -92,7 +104,7 @@ export class FileBackend implements StorageBackendAdapter {
92104
contentRange: `bytes ${startRange}-${endRange}/${fileSize}`,
93105
httpStatusCode: 206,
94106
size: size,
95-
eTag: checksum,
107+
eTag,
96108
contentLength: chunkSize,
97109
},
98110
httpStatusCode: 206,
@@ -107,7 +119,7 @@ export class FileBackend implements StorageBackendAdapter {
107119
lastModified: lastModified,
108120
httpStatusCode: 200,
109121
size: data.size,
110-
eTag: checksum,
122+
eTag,
111123
contentLength: fileSize,
112124
},
113125
body,
@@ -205,12 +217,12 @@ export class FileBackend implements StorageBackendAdapter {
205217
await this.setFileMetadata(destFile, Object.assign({}, originalMetadata, metadata))
206218

207219
const fileStat = await fs.lstat(destFile)
208-
const checksum = await fileChecksum(destFile)
220+
const eTag = await this.etag(destFile, fileStat)
209221

210222
return {
211223
httpStatusCode: 200,
212224
lastModified: fileStat.mtime,
213-
eTag: checksum,
225+
eTag,
214226
}
215227
}
216228

@@ -252,15 +264,14 @@ export class FileBackend implements StorageBackendAdapter {
252264
const { cacheControl, contentType } = await this.getFileMetadata(file)
253265
const lastModified = new Date(0)
254266
lastModified.setUTCMilliseconds(data.mtimeMs)
255-
256-
const checksum = await fileChecksum(file)
267+
const eTag = await this.etag(file, data)
257268

258269
return {
259270
httpStatusCode: 200,
260271
size: data.size,
261272
cacheControl: cacheControl || 'no-cache',
262273
mimetype: contentType || 'application/octet-stream',
263-
eTag: `"${checksum}"`,
274+
eTag,
264275
lastModified: data.birthtime,
265276
contentLength: data.size,
266277
}

0 commit comments

Comments
 (0)