Skip to content

Instantly share code, notes, and snippets.

@brianmfear
Last active Feb 3, 2022
Embed
What would you like to do?
AWS SQS Methods, in Apex Code.

Example Usage

try {
    AWSSQS.sendMessage(someQueue, someStringMessage)
} catch(AWS.ServiceException ex) {
  // Perform appropriate handling here
}
<?xml version="1.0" encoding="UTF-8"?>
<CustomObject xmlns="http://soap.sforce.com/2006/04/metadata">
<customSettingsType>Hierarchy</customSettingsType>
<enableFeeds>false</enableFeeds>
<fields>
<fullName>AccessKey__c</fullName>
<externalId>false</externalId>
<label>Access Key</label>
<length>20</length>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Text</type>
<unique>false</unique>
</fields>
<fields>
<fullName>DeleteQueue__c</fullName>
<externalId>false</externalId>
<label>Delete Queue</label>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Url</type>
</fields>
<fields>
<fullName>DispatchQueue__c</fullName>
<externalId>false</externalId>
<label>Dispatch Queue</label>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Url</type>
</fields>
<fields>
<fullName>Endpoint__c</fullName>
<externalId>false</externalId>
<label>Endpoint</label>
<length>32</length>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Text</type>
<unique>false</unique>
</fields>
<fields>
<fullName>Region__c</fullName>
<externalId>false</externalId>
<label>Region</label>
<length>16</length>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Text</type>
<unique>false</unique>
</fields>
<fields>
<fullName>ScaleWidths__c</fullName>
<externalId>false</externalId>
<label>ScaleWidths</label>
<length>255</length>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Text</type>
<unique>false</unique>
</fields>
<fields>
<fullName>SecretKey__c</fullName>
<externalId>false</externalId>
<label>Secret Key</label>
<length>40</length>
<required>false</required>
<trackTrending>false</trackTrending>
<type>Text</type>
<unique>false</unique>
</fields>
<label>AmazonSQS</label>
<visibility>Protected</visibility>
</CustomObject>
public abstract class AWS {
// Post initialization logic (after constructor, before call)
protected abstract void init();
// XML Node utility methods that will help read elements
public static Boolean getChildNodeBoolean(Dom.XmlNode node, String ns, String name) {
try {
return Boolean.valueOf(node.getChildElement(name, ns).getText());
} catch(Exception e) {
return null;
}
}
public static DateTime getChildNodeDateTime(Dom.XmlNode node, String ns, String name) {
try {
return (DateTime)JSON.deserialize(node.getChildElement(name, ns).getText(), DateTime.class);
} catch(Exception e) {
return null;
}
}
public static Integer getChildNodeInteger(Dom.XmlNode node, String ns, String name) {
try {
return Integer.valueOf(node.getChildElement(name, ns).getText());
} catch(Exception e) {
return null;
}
}
public static String getChildNodeText(Dom.XmlNode node, String ns, String name) {
try {
return node.getChildElement(name, ns).getText();
} catch(Exception e) {
return null;
}
}
// Turns an Amazon exception into something we can present to the user/catch
public class ServiceException extends Exception {
public String Code, Message, Resource, RequestId;
public ServiceException(Dom.XmlNode node) {
String ns = node.getNamespace();
Code = getChildNodeText(node, ns, 'Code');
Message = getChildNodeText(node, ns, 'Message');
Resource = getChildNodeText(node, ns, 'Resource');
RequestId = getChildNodeText(node, ns, 'RequestId');
setMessage(Message);
}
public String toString() {
return JSON.serialize(this);
}
}
// Things we need to know about the service. Set these values in init()
protected String host, region, service, resource, accessKey, payloadSha256, endpointUrl;
public Url endpoint { get { return endpointUrl==null? null: new Url(endpointUrl); } set { endpointUrl = value==null? null: value.toExternalForm(); } }
protected HttpMethod method;
protected Blob payload;
protected DateTime requestTime;
// Not used externally, so we hide these values
Blob signingKey;
Map<String, String> queryParams, headerParams;
// Make sure we can't misspell methods
public enum HttpMethod { XGET, XPUT, XHEAD, XOPTIONS, XDELETE, XPOST }
// Add a header
protected void setHeader(String key, String value) {
headerParams.put(key.toLowerCase(), value);
}
// Add a query param
protected void setQueryParam(String key, String value) {
queryParams.put(key, uriEncode(value));
}
// Call this constructor with super() in subclasses
protected AWS() {
requestTime = DateTime.now();
queryParams = new Map<String, String>();
headerParams = new Map<String, String>();
}
// Create a canonical query string (used during signing)
String createCanonicalQueryString() {
String[] results = new String[0], keys = new List<String>(queryParams.keySet());
keys.sort();
for(String key: keys) {
results.add(key+'='+queryParams.get(key));
}
return String.join(results, '&');
}
// Create the canonical headers (used for signing)
String createCanonicalHeaders(String[] keys) {
keys.addAll(headerParams.keySet());
keys.sort();
String[] results = new String[0];
for(String key: keys) {
results.add(key+':'+headerParams.get(key));
}
return String.join(results, '\n')+'\n';
}
// Create the entire canonical request
String createCanonicalRequest(String[] headerKeys) {
return String.join(
new String[] {
method.name().removeStart('X'), // METHOD
new Url(endPoint, resource).getPath(), // RESOURCE
createCanonicalQueryString(), // CANONICAL QUERY STRING
createCanonicalHeaders(headerKeys), // CANONICAL HEADERS
String.join(headerKeys, ';'), // SIGNED HEADERS
payloadSha256 // SHA256 PAYLOAD
},
'\n'
);
}
// We have to replace ~ and " " correctly, or we'll break AWS on those two characters
protected string uriEncode(String value) {
return value==null? null: EncodingUtil.urlEncode(value, 'utf-8').replaceAll('%7E','~').replaceAll('\\+','%20');
}
// Create the entire string to sign
String createStringToSign(String[] signedHeaders) {
String result = createCanonicalRequest(signedHeaders);
return String.join(
new String[] {
'AWS4-HMAC-SHA256',
headerParams.get('date'),
String.join(new String[] { requestTime.formatGMT('yyyyMMdd'), region, service, 'aws4_request' },'/'),
EncodingUtil.convertToHex(Crypto.generateDigest('sha256', Blob.valueof(result)))
},
'\n'
);
}
// Create our signing key
protected void createSigningKey(String secretKey) {
signingKey = Crypto.generateMac('hmacSHA256', Blob.valueOf('aws4_request'),
Crypto.generateMac('hmacSHA256', Blob.valueOf(service),
Crypto.generateMac('hmacSHA256', Blob.valueOf(region),
Crypto.generateMac('hmacSHA256', Blob.valueOf(requestTime.formatGMT('yyyyMMdd')), Blob.valueOf('AWS4'+secretKey))
)
)
);
}
// Create all of the bits and pieces using all utility functions above
HttpRequest createRequest() {
init();
if(payload == null) {
payload = Blob.valueOf('');
}
payloadSha256 = EncodingUtil.convertToHex(Crypto.generateDigest('sha-256', payload));
setHeader('x-amz-content-sha256', payloadSha256);
if(host == null) {
host = endpoint.getHost();
}
setHeader('host', host);
HttpRequest request = new HttpRequest();
request.setMethod(method.name().removeStart('X'));
if(payload.size() > 0) {
setHeader('Content-Length', String.valueOf(payload.size()));
request.setBodyAsBlob(payload);
}
String
finalEndpoint = new Url(endpoint, resource).toExternalForm(),
queryString = createCanonicalQueryString();
if(queryString != '') {
finalEndpoint += '?'+queryString;
}
request.setEndpoint(finalEndpoint);
for(String key: headerParams.keySet()) {
request.setHeader(key, headerParams.get(key));
}
String[] headerKeys = new String[0];
String stringToSign = createStringToSign(headerKeys);
request.setHeader(
'Authorization',
String.format(
'AWS4-HMAC-SHA256 Credential={0},SignedHeaders={1},Signature={2}',
new String[] {
String.join(new String[] { accessKey, requestTime.formatGMT('yyyyMMdd'), region, service, 'aws4_request' },'/'),
String.join(headerKeys,';'), EncodingUtil.convertToHex(Crypto.generateMac('hmacSHA256', Blob.valueOf(stringToSign), signingKey))}
));
return request;
}
// Actually perform the request, and throw exception if response code is not valid
protected HttpResponse sendRequest(Set<Integer> validCodes) {
HttpResponse response = new Http().send(createRequest());
if(!validCodes.contains(response.getStatusCode())) {
if(response.getBody() != null) {
throw new ServiceException(response.getBodyDocument().getRootElement());
}
}
return response;
}
// Same as above, but assume that only 200 is valid
// This method exists because most of the time, 200 is what we expect
protected HttpResponse sendRequest() {
return sendRequest(new Set<Integer> { 200 });
}
}
public class AWSSQS {
abstract class Core extends AWS {
Core() {
super();
method = HttpMethod.XGET;
payload = Blob.valueOf('');
}
public virtual override void init() {
AmazonSQS__c configSettings = AmazonSQS__c.getOrgDefaults();
endpoint = new Url('https://'+configSettings.Endpoint__c);
accessKey = configSettings.AccessKey__c;
region = configSettings.Region__c;
service = 'sqs';
host = String.join(new String[] { service, region, 'amazonaws.com' },'.');
setHeader('Date', requestTime.formatGmt('yyyyMMdd\'T\'HHmmss\'Z\''));
setQueryParam('Version','2012-11-05');
setQueryParam('AWSAccessKeyID', accessKey);
setQueryParam('Timestamp', requestTime.formatGMT('yyyy-MM-dd\'T\'HH:mm:ss\'Z\''));
setQueryParam('SignatureVersion', '4');
setQueryParam('SignatureMethod','HmacSHA256');
setHeader('Accept','application/json');
// Prevent leaking the secret key by only exposing the signing key
createSigningKey(configSettings.SecretKey__c);
}
}
public class ListQueuesRequest extends Core {
ListQueuesRequest() {
super();
resource = '/';
setQueryParam('Action', 'ListQueues');
}
public ListQueuesResult execute() {
String jsonBody = sendRequest().getBody();
return ((ListQueueResponse)JSON.deserialize(jsonBody, ListQueueResponse.class)).ListQueuesResponse.ListQueuesResult;
}
}
public class SendMessageRequest extends Core {
public void execute(String queueName, String data) {
resource = queueName;
setQueryParam('Action','SendMessage');
setQueryParam('MessageBody', data);
setQueryParam('QueueUrl', queueName);
sendRequest();
}
}
public class ListQueueResponseMetadata {
public String RequestId;
}
public class ListQueuesResult {
public String[] queueUrls;
}
public class ListQueuesResponse {
public ListQueuesResult ListQueuesResult;
}
public class ListQueueResponse {
public ListQueuesResponse ListQueuesResponse;
public ListQueueResponseMetadata ResponseMetadata;
}
public static void sendMessage(String queueName, String message) {
SendMessageRequest req = new SendMessageRequest();
req.execute(queueName, message);
}
public static ListQueuesResult listQueues() {
return new ListQueuesRequest().execute();
}
}
@Maxxpower
Copy link

Maxxpower commented Jun 28, 2017

Where can i put my account number? in the queuename? This format maybe? /accountnumber/queuename ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment