-
Notifications
You must be signed in to change notification settings - Fork 0
HandleSQSSend allow for array body #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
ebb0458
5d333e2
86ed16f
867c53f
6f4af4f
2580855
1a175c4
3271606
b71fcfd
0895201
a05a5ac
32fe950
607e387
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ package main | |
import ( | ||
"context" | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
|
@@ -18,6 +19,7 @@ import ( | |
"github.com/aws/aws-sdk-go-v2/aws" | ||
awsconfig "github.com/aws/aws-sdk-go-v2/config" | ||
sqs "github.com/aws/aws-sdk-go-v2/service/sqs" | ||
sqstypes "github.com/aws/aws-sdk-go-v2/service/sqs/types" | ||
) | ||
|
||
var sqsARNPat = regexp.MustCompile(`^arn:aws:sqs:([^:]+):([^:]+):(.+)$`) | ||
|
@@ -168,17 +170,63 @@ func handleSQSSend(w http.ResponseWriter, r *http.Request) { | |
} | ||
sqsCl := sqs.NewFromConfig(c) | ||
|
||
if _, err := sqsCl.SendMessage(context.Background(), &sqs.SendMessageInput{ | ||
MessageBody: aws.String(string(body)), | ||
QueueUrl: aws.String(qURL), | ||
MessageGroupId: groupID, | ||
}); err != nil { | ||
log.Printf("error sending SQS message: %v", err) | ||
http.Error(w, fmt.Sprintf("Error sending SQS message: %v", err), http.StatusInternalServerError) | ||
return | ||
} | ||
// Check if body starts with '[' to detect JSON array (multiple messages) | ||
if len(body) > 0 && body[0] == '[' { | ||
// Multiple messages - parse and use batch send | ||
var messages []json.RawMessage | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is assuming each message itself is also JSON. Why? This will be limiting what you can send to a queue unnecessarily and requires the receiving end to parse the message as JSON. One alternative is to accept JSON array of strings: var messages []string |
||
if err := json.Unmarshal(body, &messages); err != nil { | ||
http.Error(w, "Invalid JSON array", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
if len(messages) == 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless there is a significant performance advantage, I suggest accepting up to the maximum of 10 messages allowed by SQS API and thus making a single call to SQS. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would the best way to determine this be to deploy a simple lambdafied function in the dev account and run requests against it? |
||
http.Error(w, "Empty message array", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
// Build entries for batch send (split into groups of 10) | ||
var allEntries []sqstypes.SendMessageBatchRequestEntry | ||
for i, msg := range messages { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: You can construct the message instances in the next loop where you're actually sending them to SQS 10 at a time, instead of all at once, taking up memory. |
||
allEntries = append(allEntries, sqstypes.SendMessageBatchRequestEntry{ | ||
Id: aws.String(fmt.Sprintf("msg-%d", i)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the extra |
||
MessageBody: aws.String(string(msg)), | ||
MessageGroupId: groupID, | ||
}) | ||
} | ||
|
||
// Send in batches of 10 (SQS limit) | ||
for i := 0; i < len(allEntries); i += 10 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid magic numbers and define these as file level const maxSQSBatchSize = 10 |
||
end := i + 10 | ||
if end > len(allEntries) { | ||
end = len(allEntries) | ||
} | ||
batch := allEntries[i:end] | ||
|
||
if _, err := sqsCl.SendMessageBatch(context.Background(), &sqs.SendMessageBatchInput{ | ||
QueueUrl: aws.String(qURL), | ||
Entries: batch, | ||
}); err != nil { | ||
log.Printf("error sending SQS message batch: %v", err) | ||
http.Error(w, fmt.Sprintf("Error sending SQS message batch: %v", err), http.StatusInternalServerError) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is swallowing individual errors of the batch send. The client would want to know if some of the items managed to get through. |
||
return | ||
} | ||
} | ||
|
||
log.Printf("sent an SQS message to '%s'", qURL) | ||
log.Printf("sent %d SQS messages to '%s'", len(messages), qURL) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. readability: should return early here and unindent the rest of the code instead of using |
||
} else { | ||
// Single message - use regular send | ||
if _, err := sqsCl.SendMessage(context.Background(), &sqs.SendMessageInput{ | ||
MessageBody: aws.String(string(body)), | ||
QueueUrl: aws.String(qURL), | ||
MessageGroupId: groupID, | ||
}); err != nil { | ||
log.Printf("error sending SQS message: %v", err) | ||
http.Error(w, fmt.Sprintf("Error sending SQS message: %v", err), http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
log.Printf("sent an SQS message to '%s'", qURL) | ||
} | ||
|
||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the message starts with
[
. This will break existing clients.A safe and "non-magical" option is to check for a new header
SQS-Batch-Message
and if set (value is not important) AND theContent-Type
header starts withapplication/json
(not==
since you can have things like; charset=utf-8
suffix — best to useParseMediaType
from stdlib to properly parse the MIME type), THEN and only then is the request valid and will be treated as a JSON array of string objects to be sent as a batch of messages to SQS.Why new header
Since we allowed HTTP requests with any content and any content type to be treated as a byte string and to be sent on the queue, we essentially removed any possibility of the content type OR the content to define anything meaningful.
Therefore, we need an extra signal. The
SQS-Batch-Message
is that signal.Why
Content-Type: application/json
You should never assume the content type of an HTTP request based on its content. HTTP content is typed, unlike say a file on disk. Compliant clients/servers will treat the same bytes in the body differently based on what the content type is. It's therefore best to build conforming servers/clients that don't fall over when interacting with unforeseen peers in the future.