Многопоточные проблемы с getSignedUrl в AWS JavaScript SDK S3
С большим удивлением обнаружил существование проблемы, которой уже больше года, в библиотеке @aws-sdk/client-s3 при работе с несколькими параллельными запросами одновременно.
Суть бага в том, что выполнении параллельного кода ломаются запросы к AWS S3 если один из запросов, который использует инстанс клиента — это запрос getSignedUrl. В коде это выглядит примерно таким образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
import { S3Client, HeadObjectCommandInput, HeadObjectCommand } from '@aws-sdk/client-s3' import { getSignedUrl } from '@aws-sdk/s3-request-presigner' export const s3 = new S3Client({ region: 'us-east-1' }) const headObjectParams: HeadObjectCommandInput = { Bucket: 'my-fancy-bucket', Key: 'bucket/key/file.png', } const command = new HeadObjectCommand(headObjectParams) const responsePromise = s3.send(command) const signedUrlPromise = getSignedUrl(s3, command, { expiresIn: 1000, }) Promise.all([ responsePromise, signedUrlPromise, ]) |
В подобном коде с большой долей вероятности не пройдёт HeadObjectCommand
.
После долгого и вдумчивого чтения документации и исходников я выяснил, что при выполнении метода send
запрос с данными нашей команды проходит через цепочку middleware внутри клиента S3.
Под капотом getSignedUrl так же лежит вызова метода send
.
Теперь немного отвлечёмся для прояснения деталей. Что же делает getSignedUrl? Он возвращает так называемый pre-signed url. То есть разрешение на действие для любого, кто имеет эту предварительно подписанную ссылку. Эти действия одни и те же, что и просто команды API взаимодействия c S3.
Получается, что используется один и тот же набор данных как для формирования запроса с командой, так и для формирования pre-signed url.
И что же сделали программисты амазона? Они просто воткнули middleware в цепочку, который после авторизации прерывает выполнение запроса и формирует URL. На том же самом инстансе клиента, который мы используем для остальных запросов. Пруф: https://github.com/aws/aws-sdk-js-v3/blob/63d7f8c539e0fd782fa5bf997bd6ac2730e4bfda/packages/s3-request-presigner/src/getSignedUrl.ts#L55
То есть в нашем случае модифицированная цепочка middleware не позволяет выполняться запросу на API пока не закончится запрос формирования getSignedUrl.
После определения причины я уже с лёгкостью нашёл открытый баг возрастом примерно с год: https://github.com/aws/aws-sdk-js-v3/issues/2438
Для обхода проблемы пока единственным решением я вижу создание двух инстансов клиента S3 и вызов getSignedUrl на одном, и всех остальных методов на другом инстансе.