try {
AWSSQS.sendMessage(someQueue, someStringMessage)
} catch(AWS.ServiceException ex) {
// Perform appropriate handling here
}
Last active
February 3, 2022 09:47
-
-
Save brianmfear/db444a87f35a5a6348784a683b4cbd18 to your computer and use it in GitHub Desktop.
AWS SQS Methods, in Apex Code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
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
Trying to use the apex classes however, AWSSQS is not working as Variable not visible at Line 15. Can you help to make this work please? I'm trying to send SQS message from salesforce.