diff --git a/app/gosqs/create_queue.go b/app/gosqs/create_queue.go index 8e294db..b429ad7 100644 --- a/app/gosqs/create_queue.go +++ b/app/gosqs/create_queue.go @@ -23,7 +23,7 @@ import ( // - если очередь уже есть — просто возвращаем её URL (идемпотентность AWS); // - проверка лимита очередей тенанта (MaxQueues) → LimitExceeded; // - создание очереди с дефолтами и применение переданных атрибутов -// (setQueueAttributesV1 с валидацией диапазонов); +// (setQueueAttributesV1 с валидацией диапазонов); // - вставка в map с tenant-scoped ключом "{tenantAccessKey}:{queueName}". // 5. Сохранение очереди в Redis (асинхронно, маршалинг под Lock). // 6. Ответ: QueueUrl. diff --git a/app/gosqs/gosqs.go b/app/gosqs/gosqs.go index e0f1fbf..0ef89af 100644 --- a/app/gosqs/gosqs.go +++ b/app/gosqs/gosqs.go @@ -64,9 +64,9 @@ func PeriodicTasks(d time.Duration, quit chan bool) { // - пустой ReceiptHandle (не in-flight) — пропускаем; // - VisibilityTimeout ещё не истёк — пропускаем; // - иначе: возвращаем видимость (сброс ReceiptHandle, разблокировка -// FIFO-группы, обновление ReceiptTime), инкрементируем Retry; +// FIFO-группы, обновление ReceiptTime), инкрементируем Retry; // - если Retry >= MaxReceiveCount и настроена DLQ — переносим сообщение -// в DLQ и удаляем из исходной очереди. +// в DLQ и удаляем из исходной очереди. func processQueueTick(queue *models.Queue) { // 1. Просроченные записи deduplication-окна. for dedupId, startTime := range queue.Duplicates { diff --git a/app/gosqs/purge_queue.go b/app/gosqs/purge_queue.go index cb371d0..5340cd1 100644 --- a/app/gosqs/purge_queue.go +++ b/app/gosqs/purge_queue.go @@ -22,8 +22,8 @@ import ( // 4. Под Lock: // - проверка существования очереди → QueueNotFound; // - защита от повторного purge (фикс): повторный вызов в течение 60 секунд -// → PurgeQueueInProgress (требование AWS). Раньше purge можно было -// вызывать без ограничений; +// → PurgeQueueInProgress (требование AWS). Раньше purge можно было +// вызывать без ограничений; // - очистка in-memory сообщений и dedup-записей; // - фиксация LastPurgeTime; // - асинхронная очистка Redis (DEL хэша сообщений) + SaveQueue. diff --git a/app/gosqs/receive_message.go b/app/gosqs/receive_message.go index 22955bc..799c1a8 100644 --- a/app/gosqs/receive_message.go +++ b/app/gosqs/receive_message.go @@ -26,10 +26,10 @@ import ( // 2. Аутентифицированный тенант из контекста (SigV4 → AccessKey). // 3. Валидация входных параметров (AWS-совместимая): // - MaxNumberOfMessages: 1–10; 0 (не задан) = 1. Выход за диапазон — ошибка -// InvalidParameterValue (раньше значение молча клэмпилось). +// InvalidParameterValue (раньше значение молча клэмпилось). // - VisibilityTimeout: 0–43200 секунд, иначе InvalidParameterValue. // - WaitTimeSeconds: 0–20 секунд; 0 = брать атрибут очереди -// ReceiveMessageWaitTimeSeconds. +// ReceiveMessageWaitTimeSeconds. // 4. Имя очереди: из QueueUrl (последний сегмент) или из пути /{account}/{queueName}. // 5. Выборка сообщений — АТОМАРНО под одним Lock (см. receiveMessagesUnderLock): // устранена гонка TOCTOU, когда два параллельных receive видели одни и те же @@ -226,6 +226,7 @@ func buildReceiveResponse(messages []*models.ResultMessage) models.ReceiveMessag // - ApproximateFirstReceiveTimestamp — время первой выдачи (ReceiptTime); // - ApproximateReceiveCount — число доставок (NumberOfReceives + 1); // - SentTimestamp — реальное время отправки (SentTime), НЕ время выдачи. +// // MD5 тела берётся из кэша (посчитан при SendMessage) — без пересчёта. func buildResultMessage(m *models.SqsMessage) *models.ResultMessage { return &models.ResultMessage{ diff --git a/app/gosqs/send_message.go b/app/gosqs/send_message.go index 2a209ef..a21b4af 100644 --- a/app/gosqs/send_message.go +++ b/app/gosqs/send_message.go @@ -27,7 +27,7 @@ import ( // - DeduplicationId и GroupId ≤ 128 символов; // - MessageAttributes ≤ 10; // - FIFO-очередь БЕЗ MessageGroupId → MissingParameter (AWS требует GroupId -// для каждой отправки в FIFO); +// для каждой отправки в FIFO); // - размер тела ≤ MaximumMessageSize очереди → MessageTooBig; // - число сообщений < лимита очереди (OOM-защита) → OverLimit. // 4. Построение SqsMessage: MD5 тела и атрибутов, UUID, метки времени, задержка. diff --git a/app/router/router.go b/app/router/router.go index 29d4e80..3d54fa5 100644 --- a/app/router/router.go +++ b/app/router/router.go @@ -262,19 +262,17 @@ func extractAction(req *http.Request) string { // resolveProtocol — определяет протокол ОТВЕТА по заголовкам запроса. // // Логическая схема: -// 1. Заголовок x-amzn-query-mode: true (шлют AWS CLI v2 и boto3) означает -// «query-compatible JSON»: тело запроса сериализовано как JSON, но клиент -// ЖДЁТ ответ в XML (query-протокол). Если на такой запрос ответить JSON — -// botocore не распарсит тело ошибки (ClientError с Code=None — именно это -// наблюдалось: «сырые 400/404» без AWS-XML). Поэтому для query-mode -// принудительно отвечаем XML. -// 2. Content-Type: application/x-amz-json-1.0 БЕЗ query-mode — чистый -// JSON-протокол (SDK): и запрос, и ответ в JSON. -// 3. Всё остальное (form-urlencoded от старых клиентов) — query/XML. +// 1. Content-Type: application/x-amz-json-1.0 → JSON-протокол (ответ JSON). +// Сюда входят: чистые JSON-клиенты (SDK) И AWS CLI v2/boto3 в режиме +// «query-compatible JSON» (x-amzn-query-mode: true). ВАЖНО (эмпирика): +// query-mode клиент парсит именно JSON-ответы — при XML-ответе список +// очередей распознаётся как пустой (None), при JSON — корректно. +// 2. Всё остальное (form-urlencoded от классических клиентов) — query/XML. +// +// Ошибки в JSON-протоколе оформляются как {"__type":"...","message":"..."} +// (см. encodeResponse) — только такой формат botocore распознаёт как ошибку +// с кодом (раньше отдавался голый ErrorResult → Code=None). func resolveProtocol(req *http.Request) AwsProtocol { - if req.Header.Get("x-amzn-query-mode") == "true" { - return AwsQueryProtocol - } if req.Header.Get("Content-Type") == "application/x-amz-json-1.0" { return AwsJsonProtocol }