security: fix critical/high auth, idor, races and persistence
This commit is contained in:
@@ -3,117 +3,120 @@
|
||||
package gosqs
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/utils"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/persistence"
|
||||
"shared-sqs/app/utils"
|
||||
)
|
||||
|
||||
func DeleteMessageBatchV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
|
||||
requestBody := models.NewDeleteMessageBatchRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - DeleteMessageBatchV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
requestBody := models.NewDeleteMessageBatchRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - DeleteMessageBatchV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
|
||||
queueUrl := requestBody.QueueUrl
|
||||
queueName := ""
|
||||
if queueUrl == "" {
|
||||
vars := mux.Vars(req)
|
||||
queueName = vars["queueName"]
|
||||
} else {
|
||||
uriSegments := strings.Split(queueUrl, "/")
|
||||
queueName = uriSegments[len(uriSegments)-1]
|
||||
}
|
||||
queueUrl := requestBody.QueueUrl
|
||||
queueName := ""
|
||||
if queueUrl == "" {
|
||||
vars := mux.Vars(req)
|
||||
queueName = vars["queueName"]
|
||||
} else {
|
||||
uriSegments := strings.Split(queueUrl, "/")
|
||||
queueName = uriSegments[len(uriSegments)-1]
|
||||
}
|
||||
|
||||
key := tenantQueueKey(t.AccessKey, queueName)
|
||||
key := tenantQueueKey(t.AccessKey, queueName)
|
||||
|
||||
if _, ok := models.SyncQueues.Queues[key]; !ok {
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
if _, ok := models.SyncQueues.Queues[key]; !ok {
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
|
||||
if len(requestBody.Entries) == 0 {
|
||||
return utils.CreateErrorResponseV1("EmptyBatchRequest", true)
|
||||
}
|
||||
if len(requestBody.Entries) == 0 {
|
||||
return utils.CreateErrorResponseV1("EmptyBatchRequest", true)
|
||||
}
|
||||
|
||||
if len(requestBody.Entries) > 10 {
|
||||
return utils.CreateErrorResponseV1("TooManyEntriesInBatchRequest", true)
|
||||
}
|
||||
if len(requestBody.Entries) > 10 {
|
||||
return utils.CreateErrorResponseV1("TooManyEntriesInBatchRequest", true)
|
||||
}
|
||||
|
||||
ids := map[string]bool{}
|
||||
for _, v := range requestBody.Entries {
|
||||
if _, found := ids[v.Id]; found {
|
||||
return utils.CreateErrorResponseV1("BatchEntryIdsNotDistinct", true)
|
||||
}
|
||||
ids[v.Id] = true
|
||||
}
|
||||
ids := map[string]bool{}
|
||||
for _, v := range requestBody.Entries {
|
||||
if _, found := ids[v.Id]; found {
|
||||
return utils.CreateErrorResponseV1("BatchEntryIdsNotDistinct", true)
|
||||
}
|
||||
ids[v.Id] = true
|
||||
}
|
||||
|
||||
models.SyncQueues.Lock()
|
||||
defer models.SyncQueues.Unlock()
|
||||
models.SyncQueues.Lock()
|
||||
defer models.SyncQueues.Unlock()
|
||||
|
||||
deleteMessageMap := make(map[string]*deleteEntry)
|
||||
for _, entry := range requestBody.Entries {
|
||||
deleteMessageMap[entry.ReceiptHandle] = &deleteEntry{
|
||||
Id: entry.Id,
|
||||
ReceiptHandle: entry.ReceiptHandle,
|
||||
Deleted: false,
|
||||
}
|
||||
}
|
||||
deleteMessageMap := make(map[string]*deleteEntry)
|
||||
for _, entry := range requestBody.Entries {
|
||||
deleteMessageMap[entry.ReceiptHandle] = &deleteEntry{
|
||||
Id: entry.Id,
|
||||
ReceiptHandle: entry.ReceiptHandle,
|
||||
Deleted: false,
|
||||
}
|
||||
}
|
||||
|
||||
deletedEntries := make([]models.DeleteMessageBatchResultEntry, 0)
|
||||
remainingMessages := make([]models.SqsMessage, 0, len(models.SyncQueues.Queues[key].Messages))
|
||||
deletedEntries := make([]models.DeleteMessageBatchResultEntry, 0)
|
||||
remainingMessages := make([]models.SqsMessage, 0, len(models.SyncQueues.Queues[key].Messages))
|
||||
|
||||
for _, message := range models.SyncQueues.Queues[key].Messages {
|
||||
if de, found := deleteMessageMap[message.ReceiptHandle]; found {
|
||||
log.Debugf("FIFO Queue %s unlocking group %s:", queueName, message.GroupID)
|
||||
models.SyncQueues.Queues[key].UnlockGroup(message.GroupID)
|
||||
delete(models.SyncQueues.Queues[key].Duplicates, message.DeduplicationID)
|
||||
de.Deleted = true
|
||||
deletedEntries = append(deletedEntries, models.DeleteMessageBatchResultEntry{Id: de.Id})
|
||||
} else {
|
||||
remainingMessages = append(remainingMessages, message)
|
||||
}
|
||||
}
|
||||
for _, message := range models.SyncQueues.Queues[key].Messages {
|
||||
if de, found := deleteMessageMap[message.ReceiptHandle]; found {
|
||||
log.Debugf("FIFO Queue %s unlocking group %s:", queueName, message.GroupID)
|
||||
models.SyncQueues.Queues[key].UnlockGroup(message.GroupID)
|
||||
delete(models.SyncQueues.Queues[key].Duplicates, message.DeduplicationID)
|
||||
de.Deleted = true
|
||||
deletedEntries = append(deletedEntries, models.DeleteMessageBatchResultEntry{Id: de.Id})
|
||||
} else {
|
||||
remainingMessages = append(remainingMessages, message)
|
||||
}
|
||||
}
|
||||
|
||||
models.SyncQueues.Queues[key].Messages = remainingMessages
|
||||
models.SyncQueues.Queues[key].Messages = remainingMessages
|
||||
// Персистим обновлённое состояние очереди, чтобы не терять batch-delete после рестарта.
|
||||
persistence.SaveQueue(key, models.SyncQueues.Queues[key])
|
||||
|
||||
notFoundEntries := make([]models.BatchResultErrorEntry, 0)
|
||||
for _, de := range deleteMessageMap {
|
||||
if !de.Deleted {
|
||||
notFoundEntries = append(notFoundEntries, models.BatchResultErrorEntry{
|
||||
Code: "1",
|
||||
Id: de.Id,
|
||||
Message: "Message not found",
|
||||
SenderFault: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
notFoundEntries := make([]models.BatchResultErrorEntry, 0)
|
||||
for _, de := range deleteMessageMap {
|
||||
if !de.Deleted {
|
||||
notFoundEntries = append(notFoundEntries, models.BatchResultErrorEntry{
|
||||
Code: "1",
|
||||
Id: de.Id,
|
||||
Message: "Message not found",
|
||||
SenderFault: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
respStruct := models.DeleteMessageBatchResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Result: models.DeleteMessageBatchResult{
|
||||
Successful: deletedEntries,
|
||||
Failed: notFoundEntries,
|
||||
},
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
}
|
||||
respStruct := models.DeleteMessageBatchResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Result: models.DeleteMessageBatchResult{
|
||||
Successful: deletedEntries,
|
||||
Failed: notFoundEntries,
|
||||
},
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
}
|
||||
|
||||
return http.StatusOK, respStruct
|
||||
return http.StatusOK, respStruct
|
||||
}
|
||||
|
||||
type deleteEntry struct {
|
||||
Id string
|
||||
ReceiptHandle string
|
||||
Error string
|
||||
Deleted bool
|
||||
Id string
|
||||
ReceiptHandle string
|
||||
Error string
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
+33
-32
@@ -3,44 +3,45 @@
|
||||
package gosqs
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http"
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/utils"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func GetQueueUrlV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
|
||||
requestBody := models.NewGetQueueUrlRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - GetQueueUrlV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
requestBody := models.NewGetQueueUrlRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - GetQueueUrlV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
|
||||
queueName := requestBody.QueueName
|
||||
key := tenantQueueKey(t.AccessKey, queueName)
|
||||
queueName := requestBody.QueueName
|
||||
key := tenantQueueKey(t.AccessKey, queueName)
|
||||
|
||||
// Fix #10: RLock перед чтением SyncQueues — иначе data race
|
||||
models.SyncQueues.RLock()
|
||||
queue, ok := models.SyncQueues.Queues[key]
|
||||
models.SyncQueues.RUnlock()
|
||||
if !ok {
|
||||
log.Errorf("Get Queue URL: %s, queue does not exist for tenant %s", queueName, t.ID)
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
log.Debug("Get Queue URL:", queue.Name)
|
||||
// Fix #10: RLock перед чтением SyncQueues — иначе data race
|
||||
models.SyncQueues.RLock()
|
||||
queue, ok := models.SyncQueues.Queues[key]
|
||||
models.SyncQueues.RUnlock()
|
||||
if !ok {
|
||||
log.Errorf("Get Queue URL: %s, queue does not exist for tenant %s", queueName, t.ID)
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
log.Debug("Get Queue URL:", queue.Name)
|
||||
|
||||
respStruct := models.GetQueueUrlResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Result: models.GetQueueUrlResult{QueueUrl: queue.URL},
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
}
|
||||
return http.StatusOK, respStruct
|
||||
respStruct := models.GetQueueUrlResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Result: models.GetQueueUrlResult{QueueUrl: queue.URL},
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
}
|
||||
return http.StatusOK, respStruct
|
||||
}
|
||||
|
||||
+35
-35
@@ -4,48 +4,48 @@
|
||||
package gosqs
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/utils"
|
||||
)
|
||||
|
||||
func ListQueuesV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
|
||||
requestBody := models.NewListQueuesRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, true)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - ListQueuesV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
requestBody := models.NewListQueuesRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, true)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - ListQueuesV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
|
||||
log.Infof("Listing Queues for tenant: %s", t.ID)
|
||||
queueUrls := make([]string, 0)
|
||||
prefix := t.AccessKey + ":"
|
||||
log.Infof("Listing Queues for tenant: %s", t.ID)
|
||||
queueUrls := make([]string, 0)
|
||||
prefix := t.AccessKey + ":"
|
||||
|
||||
models.SyncQueues.Lock()
|
||||
for key, queue := range models.SyncQueues.Queues {
|
||||
// Показываем только очереди этого тенанта
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
if strings.HasPrefix(queue.Name, requestBody.QueueNamePrefix) {
|
||||
queueUrls = append(queueUrls, queue.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
models.SyncQueues.Unlock()
|
||||
models.SyncQueues.RLock()
|
||||
for key, queue := range models.SyncQueues.Queues {
|
||||
// Показываем только очереди этого тенанта
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
if strings.HasPrefix(queue.Name, requestBody.QueueNamePrefix) {
|
||||
queueUrls = append(queueUrls, queue.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
models.SyncQueues.RUnlock()
|
||||
|
||||
respStruct := models.ListQueuesResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
Result: models.ListQueuesResult{QueueUrls: queueUrls},
|
||||
}
|
||||
respStruct := models.ListQueuesResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
Result: models.ListQueuesResult{QueueUrls: queueUrls},
|
||||
}
|
||||
|
||||
return http.StatusOK, respStruct
|
||||
return http.StatusOK, respStruct
|
||||
}
|
||||
|
||||
+140
-139
@@ -4,169 +4,170 @@
|
||||
package gosqs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/utils"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/utils"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func ReceiveMessageV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
|
||||
requestBody := models.NewReceiveMessageRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - ReceiveMessageV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
requestBody := models.NewReceiveMessageRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - ReceiveMessageV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
|
||||
maxNumberOfMessages := requestBody.MaxNumberOfMessages
|
||||
if maxNumberOfMessages == 0 {
|
||||
maxNumberOfMessages = 1
|
||||
}
|
||||
// Fix #8: clamp MaxNumberOfMessages к AWS лимиту 1–10
|
||||
maxNumberOfMessages = ClampInt(maxNumberOfMessages, MinNumberOfMessagesLimit, MaxNumberOfMessagesLimit)
|
||||
maxNumberOfMessages := requestBody.MaxNumberOfMessages
|
||||
if maxNumberOfMessages == 0 {
|
||||
maxNumberOfMessages = 1
|
||||
}
|
||||
// Fix #8: clamp MaxNumberOfMessages к AWS лимиту 1–10
|
||||
maxNumberOfMessages = ClampInt(maxNumberOfMessages, MinNumberOfMessagesLimit, MaxNumberOfMessagesLimit)
|
||||
|
||||
queueName := ""
|
||||
if requestBody.QueueUrl == "" {
|
||||
vars := mux.Vars(req)
|
||||
queueName = vars["queueName"]
|
||||
} else {
|
||||
uriSegments := strings.Split(requestBody.QueueUrl, "/")
|
||||
queueName = uriSegments[len(uriSegments)-1]
|
||||
}
|
||||
queueName := ""
|
||||
if requestBody.QueueUrl == "" {
|
||||
vars := mux.Vars(req)
|
||||
queueName = vars["queueName"]
|
||||
} else {
|
||||
uriSegments := strings.Split(requestBody.QueueUrl, "/")
|
||||
queueName = uriSegments[len(uriSegments)-1]
|
||||
}
|
||||
|
||||
key := tenantQueueKey(t.AccessKey, queueName)
|
||||
key := tenantQueueKey(t.AccessKey, queueName)
|
||||
|
||||
if _, ok := models.SyncQueues.Queues[key]; !ok {
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
if _, ok := models.SyncQueues.Queues[key]; !ok {
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
|
||||
var messages []*models.ResultMessage
|
||||
respStruct := models.ReceiveMessageResponse{}
|
||||
var messages []*models.ResultMessage
|
||||
respStruct := models.ReceiveMessageResponse{}
|
||||
|
||||
waitTimeSeconds := requestBody.WaitTimeSeconds
|
||||
if waitTimeSeconds == 0 {
|
||||
models.SyncQueues.RLock()
|
||||
waitTimeSeconds = models.SyncQueues.Queues[key].ReceiveMessageWaitTimeSeconds
|
||||
models.SyncQueues.RUnlock()
|
||||
}
|
||||
// Fix #4: clamp WaitTimeSeconds к AWS лимиту 0–20
|
||||
waitTimeSeconds = ClampInt(waitTimeSeconds, 0, MaxReceiveMessageWaitTimeSeconds)
|
||||
waitTimeSeconds := requestBody.WaitTimeSeconds
|
||||
if waitTimeSeconds == 0 {
|
||||
models.SyncQueues.RLock()
|
||||
waitTimeSeconds = models.SyncQueues.Queues[key].ReceiveMessageWaitTimeSeconds
|
||||
models.SyncQueues.RUnlock()
|
||||
}
|
||||
// Fix #4: clamp WaitTimeSeconds к AWS лимиту 0–20
|
||||
waitTimeSeconds = ClampInt(waitTimeSeconds, 0, MaxReceiveMessageWaitTimeSeconds)
|
||||
|
||||
// Long polling: ждём появления сообщения до waitTimeSeconds*10 итераций по 100ms
|
||||
loops := waitTimeSeconds * 10
|
||||
for loops > 0 {
|
||||
models.SyncQueues.RLock()
|
||||
_, queueFound := models.SyncQueues.Queues[key]
|
||||
if !queueFound {
|
||||
models.SyncQueues.RUnlock()
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
messageFound := len(models.SyncQueues.Queues[key].Messages)-numberOfHiddenMessagesInQueue(*models.SyncQueues.Queues[key]) != 0
|
||||
models.SyncQueues.RUnlock()
|
||||
if !messageFound {
|
||||
continueTimer := time.NewTimer(100 * time.Millisecond)
|
||||
select {
|
||||
case <-req.Context().Done():
|
||||
continueTimer.Stop()
|
||||
return http.StatusOK, models.ReceiveMessageResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Result: models.ReceiveMessageResult{},
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
}
|
||||
case <-continueTimer.C:
|
||||
continueTimer.Stop()
|
||||
}
|
||||
loops--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Debugf("Getting Message from Queue:%s (tenant: %s)", queueName, t.ID)
|
||||
// Long polling: ждём появления сообщения до waitTimeSeconds*10 итераций по 100ms
|
||||
loops := waitTimeSeconds * 10
|
||||
for loops > 0 {
|
||||
models.SyncQueues.RLock()
|
||||
_, queueFound := models.SyncQueues.Queues[key]
|
||||
if !queueFound {
|
||||
models.SyncQueues.RUnlock()
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
messageFound := len(models.SyncQueues.Queues[key].Messages)-numberOfHiddenMessagesInQueue(*models.SyncQueues.Queues[key]) != 0
|
||||
models.SyncQueues.RUnlock()
|
||||
if !messageFound {
|
||||
continueTimer := time.NewTimer(100 * time.Millisecond)
|
||||
select {
|
||||
case <-req.Context().Done():
|
||||
continueTimer.Stop()
|
||||
return http.StatusOK, models.ReceiveMessageResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Result: models.ReceiveMessageResult{},
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
}
|
||||
case <-continueTimer.C:
|
||||
continueTimer.Stop()
|
||||
}
|
||||
loops--
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
log.Debugf("Getting Message from Queue:%s (tenant: %s)", queueName, t.ID)
|
||||
|
||||
models.SyncQueues.Lock()
|
||||
defer models.SyncQueues.Unlock()
|
||||
models.SyncQueues.Lock()
|
||||
defer models.SyncQueues.Unlock()
|
||||
|
||||
if len(models.SyncQueues.Queues[key].Messages) > 0 {
|
||||
numMsg := 0
|
||||
messages = make([]*models.ResultMessage, 0)
|
||||
for i := range models.SyncQueues.Queues[key].Messages {
|
||||
if numMsg >= maxNumberOfMessages {
|
||||
break
|
||||
}
|
||||
if len(models.SyncQueues.Queues[key].Messages) > 0 {
|
||||
numMsg := 0
|
||||
messages = make([]*models.ResultMessage, 0)
|
||||
for i := range models.SyncQueues.Queues[key].Messages {
|
||||
if numMsg >= maxNumberOfMessages {
|
||||
break
|
||||
}
|
||||
|
||||
if models.SyncQueues.Queues[key].Messages[i].ReceiptHandle != "" {
|
||||
continue
|
||||
}
|
||||
if models.SyncQueues.Queues[key].Messages[i].ReceiptHandle != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
msg := &models.SyncQueues.Queues[key].Messages[i]
|
||||
if !msg.IsReadyForReceipt() {
|
||||
continue
|
||||
}
|
||||
msg := &models.SyncQueues.Queues[key].Messages[i]
|
||||
if !msg.IsReadyForReceipt() {
|
||||
continue
|
||||
}
|
||||
|
||||
if models.SyncQueues.Queues[key].IsFIFO {
|
||||
if models.SyncQueues.Queues[key].IsLocked(msg.GroupID) {
|
||||
continue
|
||||
}
|
||||
models.SyncQueues.Queues[key].LockGroup(msg.GroupID)
|
||||
}
|
||||
if models.SyncQueues.Queues[key].IsFIFO {
|
||||
if models.SyncQueues.Queues[key].IsLocked(msg.GroupID) {
|
||||
continue
|
||||
}
|
||||
models.SyncQueues.Queues[key].LockGroup(msg.GroupID)
|
||||
}
|
||||
|
||||
randomId := uuid.NewString()
|
||||
msg.ReceiptHandle = msg.Uuid + "#" + randomId
|
||||
msg.ReceiptTime = time.Now().UTC()
|
||||
randomId := uuid.NewString()
|
||||
msg.ReceiptHandle = msg.Uuid + "#" + randomId
|
||||
msg.ReceiptTime = time.Now().UTC()
|
||||
|
||||
if requestBody.VisibilityTimeout != 0 {
|
||||
msg.VisibilityTimeout = time.Now().Add(time.Duration(requestBody.VisibilityTimeout) * time.Second)
|
||||
} else {
|
||||
msg.VisibilityTimeout = time.Now().Add(time.Duration(models.SyncQueues.Queues[key].VisibilityTimeout) * time.Second)
|
||||
}
|
||||
if requestBody.VisibilityTimeout != 0 {
|
||||
msg.VisibilityTimeout = time.Now().Add(time.Duration(requestBody.VisibilityTimeout) * time.Second)
|
||||
} else {
|
||||
msg.VisibilityTimeout = time.Now().Add(time.Duration(models.SyncQueues.Queues[key].VisibilityTimeout) * time.Second)
|
||||
}
|
||||
|
||||
messages = append(messages, buildResultMessage(msg))
|
||||
numMsg++
|
||||
}
|
||||
messages = append(messages, buildResultMessage(msg))
|
||||
numMsg++
|
||||
}
|
||||
|
||||
respStruct = models.ReceiveMessageResponse{
|
||||
"http://queue.amazonaws.com/doc/2012-11-05/",
|
||||
models.ReceiveMessageResult{Messages: messages},
|
||||
models.ResponseMetadata{RequestId: "00000000-0000-0000-0000-000000000000"},
|
||||
}
|
||||
} else {
|
||||
log.Warning("No messages in Queue:", queueName)
|
||||
respStruct = models.ReceiveMessageResponse{
|
||||
Xmlns: "http://queue.amazonaws.com/doc/2012-11-05/",
|
||||
Result: models.ReceiveMessageResult{},
|
||||
Metadata: models.ResponseMetadata{RequestId: "00000000-0000-0000-0000-000000000000"},
|
||||
}
|
||||
}
|
||||
respStruct = models.ReceiveMessageResponse{
|
||||
"http://queue.amazonaws.com/doc/2012-11-05/",
|
||||
models.ReceiveMessageResult{Messages: messages},
|
||||
models.ResponseMetadata{RequestId: "00000000-0000-0000-0000-000000000000"},
|
||||
}
|
||||
} else {
|
||||
log.Warning("No messages in Queue:", queueName)
|
||||
respStruct = models.ReceiveMessageResponse{
|
||||
Xmlns: "http://queue.amazonaws.com/doc/2012-11-05/",
|
||||
Result: models.ReceiveMessageResult{},
|
||||
Metadata: models.ResponseMetadata{RequestId: "00000000-0000-0000-0000-000000000000"},
|
||||
}
|
||||
}
|
||||
|
||||
return http.StatusOK, respStruct
|
||||
return http.StatusOK, respStruct
|
||||
}
|
||||
|
||||
func buildResultMessage(m *models.SqsMessage) *models.ResultMessage {
|
||||
return &models.ResultMessage{
|
||||
MessageId: m.Uuid,
|
||||
Body: m.MessageBody,
|
||||
ReceiptHandle: m.ReceiptHandle,
|
||||
MD5OfBody: utils.GetMD5Hash(m.MessageBody),
|
||||
MD5OfMessageAttributes: m.MD5OfMessageAttributes,
|
||||
MessageAttributes: m.MessageAttributes,
|
||||
Attributes: map[string]string{
|
||||
"ApproximateFirstReceiveTimestamp": fmt.Sprintf("%d", m.ReceiptTime.UnixNano()/int64(time.Millisecond)),
|
||||
"SenderId": models.CurrentEnvironment.AccountID,
|
||||
"ApproximateReceiveCount": fmt.Sprintf("%d", m.NumberOfReceives+1),
|
||||
"SentTimestamp": fmt.Sprintf("%d", time.Now().UTC().UnixNano()/int64(time.Millisecond)),
|
||||
},
|
||||
}
|
||||
return &models.ResultMessage{
|
||||
MessageId: m.Uuid,
|
||||
Body: m.MessageBody,
|
||||
ReceiptHandle: m.ReceiptHandle,
|
||||
MD5OfBody: utils.GetMD5Hash(m.MessageBody),
|
||||
MD5OfMessageAttributes: m.MD5OfMessageAttributes,
|
||||
MessageAttributes: m.MessageAttributes,
|
||||
Attributes: map[string]string{
|
||||
"ApproximateFirstReceiveTimestamp": fmt.Sprintf("%d", m.ReceiptTime.UnixNano()/int64(time.Millisecond)),
|
||||
"SenderId": models.CurrentEnvironment.AccountID,
|
||||
"ApproximateReceiveCount": fmt.Sprintf("%d", m.NumberOfReceives+1),
|
||||
"SentTimestamp": fmt.Sprintf("%d", time.Now().UTC().UnixNano()/int64(time.Millisecond)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,25 +63,27 @@ func SendMessageV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
|
||||
|
||||
key := tenantQueueKey(t.AccessKey, queueName)
|
||||
|
||||
if _, ok := models.SyncQueues.Queues[key]; !ok {
|
||||
models.SyncQueues.RLock()
|
||||
queue, exists := models.SyncQueues.Queues[key]
|
||||
if !exists {
|
||||
models.SyncQueues.RUnlock()
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
maxMessageSize := queue.MaximumMessageSize
|
||||
currentMsgCount := len(queue.Messages)
|
||||
queueIsFIFO := queue.IsFIFO
|
||||
delaySecs := queue.DelaySeconds
|
||||
models.SyncQueues.RUnlock()
|
||||
|
||||
if models.SyncQueues.Queues[key].MaximumMessageSize > 0 &&
|
||||
len(messageBody) > models.SyncQueues.Queues[key].MaximumMessageSize {
|
||||
if maxMessageSize > 0 && len(messageBody) > maxMessageSize {
|
||||
return utils.CreateErrorResponseV1("MessageTooBig", true)
|
||||
}
|
||||
|
||||
// Fix #11: лимит сообщений в очереди — защита от OOM
|
||||
models.SyncQueues.RLock()
|
||||
currentMsgCount := len(models.SyncQueues.Queues[key].Messages)
|
||||
queueIsFIFO := models.SyncQueues.Queues[key].IsFIFO
|
||||
models.SyncQueues.RUnlock()
|
||||
if currentMsgCount >= MaxMessagesForQueue(queueIsFIFO) {
|
||||
return utils.CreateErrorResponseV1("OverLimit", true)
|
||||
}
|
||||
|
||||
delaySecs := models.SyncQueues.Queues[key].DelaySeconds
|
||||
if requestBody.DelaySeconds != 0 {
|
||||
delaySecs = requestBody.DelaySeconds
|
||||
}
|
||||
|
||||
+126
-119
@@ -3,143 +3,150 @@
|
||||
package gosqs
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/utils"
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"shared-sqs/app/interfaces"
|
||||
"shared-sqs/app/models"
|
||||
"shared-sqs/app/persistence"
|
||||
"shared-sqs/app/utils"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func SendMessageBatchV1(req *http.Request) (int, interfaces.AbstractResponseBody) {
|
||||
requestBody := models.NewSendMessageBatchRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - SendMessageBatchV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
requestBody := models.NewSendMessageBatchRequest()
|
||||
ok := utils.REQUEST_TRANSFORMER(requestBody, req, false)
|
||||
if !ok {
|
||||
log.Error("Invalid Request - SendMessageBatchV1")
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
t := getTenantFromContext(req)
|
||||
if t == nil {
|
||||
return utils.CreateErrorResponseV1("InvalidClientTokenId", true)
|
||||
}
|
||||
|
||||
queueUrl := requestBody.QueueUrl
|
||||
queueName := ""
|
||||
if queueUrl == "" {
|
||||
vars := mux.Vars(req)
|
||||
queueName = vars["queueName"]
|
||||
} else {
|
||||
uriSegments := strings.Split(queueUrl, "/")
|
||||
queueName = uriSegments[len(uriSegments)-1]
|
||||
}
|
||||
queueUrl := requestBody.QueueUrl
|
||||
queueName := ""
|
||||
if queueUrl == "" {
|
||||
vars := mux.Vars(req)
|
||||
queueName = vars["queueName"]
|
||||
} else {
|
||||
uriSegments := strings.Split(queueUrl, "/")
|
||||
queueName = uriSegments[len(uriSegments)-1]
|
||||
}
|
||||
|
||||
key := tenantQueueKey(t.AccessKey, queueName)
|
||||
key := tenantQueueKey(t.AccessKey, queueName)
|
||||
|
||||
if _, ok := models.SyncQueues.Queues[key]; !ok {
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
models.SyncQueues.RLock()
|
||||
queue, exists := models.SyncQueues.Queues[key]
|
||||
if !exists {
|
||||
models.SyncQueues.RUnlock()
|
||||
return utils.CreateErrorResponseV1("QueueNotFound", true)
|
||||
}
|
||||
maxMsgSize := queue.MaximumMessageSize
|
||||
currentMsgCount := len(queue.Messages)
|
||||
queueIsFIFO := queue.IsFIFO
|
||||
models.SyncQueues.RUnlock()
|
||||
|
||||
sendEntries := requestBody.Entries
|
||||
sendEntries := requestBody.Entries
|
||||
|
||||
if len(sendEntries) == 0 {
|
||||
return utils.CreateErrorResponseV1("EmptyBatchRequest", true)
|
||||
}
|
||||
if len(sendEntries) == 0 {
|
||||
return utils.CreateErrorResponseV1("EmptyBatchRequest", true)
|
||||
}
|
||||
|
||||
if len(sendEntries) > 10 {
|
||||
return utils.CreateErrorResponseV1("TooManyEntriesInBatchRequest", true)
|
||||
}
|
||||
ids := map[string]struct{}{}
|
||||
for _, v := range sendEntries {
|
||||
if _, ok := ids[v.Id]; ok {
|
||||
return utils.CreateErrorResponseV1("BatchEntryIdsNotDistinct", true)
|
||||
}
|
||||
// Валидация длины BatchEntryId (макс 80 chars)
|
||||
if err := ValidateBatchEntryID(v.Id); err != nil {
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
// Валидация DeduplicationID и GroupID (макс 128 chars)
|
||||
if err := ValidateDeduplicationID(v.MessageDeduplicationId); err != nil {
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
if err := ValidateGroupID(v.MessageGroupId); err != nil {
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
ids[v.Id] = struct{}{}
|
||||
}
|
||||
if len(sendEntries) > 10 {
|
||||
return utils.CreateErrorResponseV1("TooManyEntriesInBatchRequest", true)
|
||||
}
|
||||
ids := map[string]struct{}{}
|
||||
for _, v := range sendEntries {
|
||||
if _, ok := ids[v.Id]; ok {
|
||||
return utils.CreateErrorResponseV1("BatchEntryIdsNotDistinct", true)
|
||||
}
|
||||
// Валидация длины BatchEntryId (макс 80 chars)
|
||||
if err := ValidateBatchEntryID(v.Id); err != nil {
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
// Валидация DeduplicationID и GroupID (макс 128 chars)
|
||||
if err := ValidateDeduplicationID(v.MessageDeduplicationId); err != nil {
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
if err := ValidateGroupID(v.MessageGroupId); err != nil {
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
ids[v.Id] = struct{}{}
|
||||
}
|
||||
|
||||
sentEntries := make([]models.SendMessageBatchResultEntry, 0)
|
||||
log.Debugf("Batch sending to Queue: %s (tenant: %s)", queueName, t.ID)
|
||||
sentEntries := make([]models.SendMessageBatchResultEntry, 0)
|
||||
log.Debugf("Batch sending to Queue: %s (tenant: %s)", queueName, t.ID)
|
||||
|
||||
// Fix #2: проверяем размер каждого сообщения в batch (Critical — batch size bypass)
|
||||
maxMsgSize := models.SyncQueues.Queues[key].MaximumMessageSize
|
||||
if maxMsgSize <= 0 {
|
||||
maxMsgSize = MaxMessageSizeDefault
|
||||
}
|
||||
for _, entry := range sendEntries {
|
||||
if len(entry.MessageBody) > maxMsgSize {
|
||||
return utils.CreateErrorResponseV1("MessageTooBig", true)
|
||||
}
|
||||
// Валидация количества message attributes (макс 10 по AWS)
|
||||
if len(entry.MessageAttributes) > MaxMessageAttributes {
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
}
|
||||
// Fix #2: проверяем размер каждого сообщения в batch (Critical — batch size bypass)
|
||||
if maxMsgSize <= 0 {
|
||||
maxMsgSize = MaxMessageSizeDefault
|
||||
}
|
||||
for _, entry := range sendEntries {
|
||||
if len(entry.MessageBody) > maxMsgSize {
|
||||
return utils.CreateErrorResponseV1("MessageTooBig", true)
|
||||
}
|
||||
// Валидация количества message attributes (макс 10 по AWS)
|
||||
if len(entry.MessageAttributes) > MaxMessageAttributes {
|
||||
return utils.CreateErrorResponseV1("InvalidParameterValue", true)
|
||||
}
|
||||
}
|
||||
|
||||
// Fix #11: лимит сообщений в очереди — защита от OOM
|
||||
models.SyncQueues.RLock()
|
||||
currentMsgCount := len(models.SyncQueues.Queues[key].Messages)
|
||||
queueIsFIFO := models.SyncQueues.Queues[key].IsFIFO
|
||||
models.SyncQueues.RUnlock()
|
||||
if currentMsgCount+len(sendEntries) > MaxMessagesForQueue(queueIsFIFO) {
|
||||
return utils.CreateErrorResponseV1("OverLimit", true)
|
||||
}
|
||||
// Fix #11: лимит сообщений в очереди — защита от OOM
|
||||
if currentMsgCount+len(sendEntries) > MaxMessagesForQueue(queueIsFIFO) {
|
||||
return utils.CreateErrorResponseV1("OverLimit", true)
|
||||
}
|
||||
|
||||
for _, sendEntry := range sendEntries {
|
||||
msg := models.SqsMessage{MessageBody: sendEntry.MessageBody}
|
||||
if len(sendEntry.MessageAttributes) > 0 {
|
||||
msg.MessageAttributes = sendEntry.MessageAttributes
|
||||
msg.MD5OfMessageAttributes = utils.HashAttributes(sendEntry.MessageAttributes)
|
||||
}
|
||||
msg.MD5OfMessageBody = utils.GetMD5Hash(sendEntry.MessageBody)
|
||||
msg.GroupID = sendEntry.MessageGroupId
|
||||
msg.DeduplicationID = sendEntry.MessageDeduplicationId
|
||||
msg.Uuid = uuid.NewString()
|
||||
msg.SentTime = time.Now()
|
||||
models.SyncQueues.Lock()
|
||||
queue = models.SyncQueues.Queues[key]
|
||||
for _, sendEntry := range sendEntries {
|
||||
msg := models.SqsMessage{MessageBody: sendEntry.MessageBody}
|
||||
if len(sendEntry.MessageAttributes) > 0 {
|
||||
msg.MessageAttributes = sendEntry.MessageAttributes
|
||||
msg.MD5OfMessageAttributes = utils.HashAttributes(sendEntry.MessageAttributes)
|
||||
}
|
||||
msg.MD5OfMessageBody = utils.GetMD5Hash(sendEntry.MessageBody)
|
||||
msg.GroupID = sendEntry.MessageGroupId
|
||||
msg.DeduplicationID = sendEntry.MessageDeduplicationId
|
||||
msg.Uuid = uuid.NewString()
|
||||
msg.SentTime = time.Now()
|
||||
|
||||
models.SyncQueues.Lock()
|
||||
fifoSeqNumber := ""
|
||||
if models.SyncQueues.Queues[key].IsFIFO {
|
||||
fifoSeqNumber = models.SyncQueues.Queues[key].NextSequenceNumber(sendEntry.MessageGroupId)
|
||||
}
|
||||
if !models.SyncQueues.Queues[key].IsDuplicate(sendEntry.MessageDeduplicationId) {
|
||||
models.SyncQueues.Queues[key].Messages = append(models.SyncQueues.Queues[key].Messages, msg)
|
||||
} else {
|
||||
log.Debugf("Duplicate deduplicationId [%s] in queue [%s]", sendEntry.MessageDeduplicationId, queueName)
|
||||
}
|
||||
models.SyncQueues.Queues[key].InitDuplicatation(sendEntry.MessageDeduplicationId)
|
||||
models.SyncQueues.Unlock()
|
||||
fifoSeqNumber := ""
|
||||
if queue.IsFIFO {
|
||||
fifoSeqNumber = queue.NextSequenceNumber(sendEntry.MessageGroupId)
|
||||
}
|
||||
if !queue.IsDuplicate(sendEntry.MessageDeduplicationId) {
|
||||
queue.Messages = append(queue.Messages, msg)
|
||||
} else {
|
||||
log.Debugf("Duplicate deduplicationId [%s] in queue [%s]", sendEntry.MessageDeduplicationId, queueName)
|
||||
}
|
||||
queue.InitDuplicatation(sendEntry.MessageDeduplicationId)
|
||||
|
||||
sentEntries = append(sentEntries, models.SendMessageBatchResultEntry{
|
||||
Id: sendEntry.Id,
|
||||
MessageId: msg.Uuid,
|
||||
MD5OfMessageBody: msg.MD5OfMessageBody,
|
||||
MD5OfMessageAttributes: msg.MD5OfMessageAttributes,
|
||||
SequenceNumber: fifoSeqNumber,
|
||||
})
|
||||
log.Infof("%s: Queue: %s, Message: %s", time.Now().Format("2006-01-02 15:04:05"), queueName, msg.MessageBody)
|
||||
}
|
||||
sentEntries = append(sentEntries, models.SendMessageBatchResultEntry{
|
||||
Id: sendEntry.Id,
|
||||
MessageId: msg.Uuid,
|
||||
MD5OfMessageBody: msg.MD5OfMessageBody,
|
||||
MD5OfMessageAttributes: msg.MD5OfMessageAttributes,
|
||||
SequenceNumber: fifoSeqNumber,
|
||||
})
|
||||
log.Infof("%s: Queue: %s, Message: %s", time.Now().Format("2006-01-02 15:04:05"), queueName, msg.MessageBody)
|
||||
}
|
||||
// Персистим батч-изменение одним снапшотом под lock.
|
||||
persistence.SaveQueue(key, queue)
|
||||
models.SyncQueues.Unlock()
|
||||
|
||||
respStruct := models.SendMessageBatchResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Result: models.SendMessageBatchResult{Entry: sentEntries},
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
}
|
||||
respStruct := models.SendMessageBatchResponse{
|
||||
Xmlns: models.BaseXmlns,
|
||||
Result: models.SendMessageBatchResult{Entry: sentEntries},
|
||||
Metadata: models.BaseResponseMetadata,
|
||||
}
|
||||
|
||||
return http.StatusOK, respStruct
|
||||
return http.StatusOK, respStruct
|
||||
}
|
||||
|
||||
@@ -12,16 +12,16 @@ import (
|
||||
// ── AWS SQS лимиты ────────────────────────────────────────────────────────────
|
||||
const (
|
||||
// Очередь
|
||||
MaxQueueNameLength = 80
|
||||
MaxQueueNameLength = 80
|
||||
MaxMessageSizeDefault = 262144 // 256 KB
|
||||
MaxMessageSizeLimit = 262144
|
||||
MinMessageSizeLimit = 1024 // 1 KB
|
||||
MinMessageSizeLimit = 1024 // 1 KB
|
||||
|
||||
// Атрибуты очереди
|
||||
MaxDelaySeconds = 900 // 15 min
|
||||
MaxVisibilityTimeout = 43200 // 12 hours
|
||||
MaxReceiveMessageWaitTimeSeconds = 20 // long polling cap
|
||||
MinMessageRetentionPeriod = 60 // 1 min
|
||||
MaxDelaySeconds = 900 // 15 min
|
||||
MaxVisibilityTimeout = 43200 // 12 hours
|
||||
MaxReceiveMessageWaitTimeSeconds = 20 // long polling cap
|
||||
MinMessageRetentionPeriod = 60 // 1 min
|
||||
MaxMessageRetentionPeriod = 1209600 // 14 days
|
||||
|
||||
// Receive
|
||||
@@ -29,7 +29,7 @@ const (
|
||||
MinNumberOfMessagesLimit = 1
|
||||
|
||||
// Message attributes
|
||||
MaxMessageAttributes = 10
|
||||
MaxMessageAttributes = 10
|
||||
MaxMessageAttributeSize = 262144 // 256 KB суммарно (тело + атрибуты)
|
||||
|
||||
// Deduplication / GroupID
|
||||
|
||||
Reference in New Issue
Block a user