Repository: localstack/localstack

Issue:
bug: SNS PublishBatch fails if a message has no Subject
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

Using the `latest` localstack container, when publishing a batch of messages using the new PublishBatch API, if one of the messages has no Subject, then an empty response is returned: `{}`.

### Expected Behavior

`PublishBatchRequestEntry.Subject` is a non-required field, therefore a batch with messages that have no Subject should be successfully published and a valid response should be returned.

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

Run the following commands:
```
awslocal sns create-topic --name local-test-topic
awslocal sns publish-batch --topic-arn "arn:aws:sns:eu-central-1:000000000000:local-test-topic" --publish-batch-request-entries Id=1,Message="has subject",Subject=subject Id=2,Message="has no subject"
```
The output of the last command is:
```
Unable to parse response (not well-formed (invalid token): line 1, column 0), invalid XML received. Further retries may succeed:
b'{}'
```

The localstack container logs show the following error:
```
2022-01-19T13:58:39.806:INFO:localstack.utils.run: Thread run method <function AdaptiveThreadPool.submit.<locals>._run at 0x7f586ade9160>(None) failed: 'Subject' Traceback (most recent call last):
  File "/opt/code/localstack/localstack/utils/run.py", line 153, in run
    result = self.func(self.params, **kwargs)
  File "/opt/code/localstack/localstack/utils/async_utils.py", line 31, in _run
    return fn(*args, **kwargs)
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 915, in handler
    response = modify_and_forward(
  File "/opt/code/localstack/localstack/utils/aws/request_context.py", line 205, in modify_and_forward
    result = modify_and_forward_orig(
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 555, in modify_and_forward
    listener_result = listener.forward_request(
  File "/opt/code/localstack/localstack/services/edge.py", line 173, in forward_request
    result = do_forward_request(api, method, path, data, headers, port=port)
  File "/opt/code/localstack/localstack/services/edge.py", line 228, in do_forward_request
    result = do_forward_request_inmem(api, method, path, data, headers, port=port)
  File "/opt/code/localstack/localstack/services/edge.py", line 252, in do_forward_request_inmem
    response = modify_and_forward(
  File "/opt/code/localstack/localstack/services/generic_proxy.py", line 555, in modify_and_forward
    listener_result = listener.forward_request(
  File "/opt/code/localstack/localstack/services/sns/sns_listener.py", line 238, in forward_request
    response = publish_batch(topic_arn, req_data, headers)
  File "/opt/code/localstack/localstack/services/sns/sns_listener.py", line 699, in publish_batch
    data["Subject"] = [message["Subject"]]
KeyError: 'Subject'
```

### Environment

```markdown
- OS: MacOS 11.6
- LocalStack: Latest
```


### Anything else?

_No response_


Discussion:

Resolving patch:
diff --git a/localstack/services/sns/sns_listener.py b/localstack/services/sns/sns_listener.py
--- a/localstack/services/sns/sns_listener.py
+++ b/localstack/services/sns/sns_listener.py
@@ -725,7 +725,7 @@ def publish_batch(topic_arn, messages, headers):
         data = {}
         data["TopicArn"] = [topic_arn]
         data["Message"] = [message["Message"]]
-        data["Subject"] = [message["Subject"]]
+        data["Subject"] = [message.get("Subject")]
         if ".fifo" in topic_arn:
             data["MessageGroupId"] = message.get("MessageGroupId")
         # TODO: add MessageDeduplication checks once ASF-SQS implementation becomes default


Test patch:
diff --git a/tests/integration/test_sns.py b/tests/integration/test_sns.py
--- a/tests/integration/test_sns.py
+++ b/tests/integration/test_sns.py
@@ -1022,6 +1022,10 @@ def test_publish_batch_messages_from_sns_to_sqs(self):
                     "Message": "Test Message without attribute",
                     "Subject": "Subject",
                 },
+                {
+                    "Id": "4",
+                    "Message": "Test Message without subject",
+                },
             ],
         )
 
@@ -1036,7 +1040,7 @@ def get_messages(queue_url):
             response = self.sqs_client.receive_message(
                 QueueUrl=queue_url, MessageAttributeNames=["All"], MaxNumberOfMessages=10
             )
-            assert len(response["Messages"]) == 3
+            assert len(response["Messages"]) == 4
             for message in response["Messages"]:
                 assert "Body" in message