0

Code generates an XML document and PUTs in into an S3 Bucket. If I comment out the Header code for Content Length, it does not throw, but S3 requires Content Lengths in the header. So it fails at the S3 side and not the SF side.

Also fails if the content header line is :

req.setHeader('Content-Length', String.valueOf(attachmentBody.length()));

See :

Script thrown exception in Apex callouts (Spring 22 Issue)

Where the solution is to not send content length, which won't work either. S3 Requires it.

Keys work in Postman with content-length '0', that's not the issue.

This code worked before Spring '22. Any ideas?

public class CalloutToStylineBatch implements Database.Batchable<sObject>,Database.AllowsCallouts {
    
    list<RMA__c> ObjRma =new list<RMA__c>();
    
    public CalloutToStylineBatch(list<RMA__c> ObjRma){
        this.ObjRma=ObjRma;
        system.debug('ObjRma==>'+ObjRma);
    }
     //Query the RMA Records based on ID
    public String query = 'SELECT Id,Name, Business_Name__c, Phone_Number__c, EXT__c,Contact__c,Contact__r.Name,'+
        'Street_1__c, Street_2__c, Street_3__c,Street_4__c,ShippingMethod__c, City__c, E_Mail__c, CountryCode__c,State__c,'+ 
        'State_Province__c,ZIP_Postal_Code__c, Replacement_Part_Number__c, Product_Series_Shipped__c,'+
        'Received_Product_Series__c, Received_Product_Type__c, Jan_Code__c, Part_Number__c,RMA_Type__c,'+
        'RMA_Status__c, POP_Received__c, Received_Date__c, Ship_Date__c, Purchase_Date__c,'+
        'RMA_Order_Creation_Date__c,Tracking_Number__c, Warehouse_Notes__c, Advanced_Fee_Waiver__c,'+
        'New_Used__c, Serial_Number__c,Serial_Number__r.Name, Product_Model__c,Account__c,Product_ID__c,'+
        'Shipping_Method__c,ShippingMethod_Formula__c,Auth_Code__c,Extended_Warranty__c,Item__c FROM RMA__c where Id IN : ObjRma';
    
    public Database.QueryLocator start(Database.BatchableContext BC) {
        return Database.getQueryLocator(query);
    }
    
    //HTTP Callout to Syteline
    public void execute(Database.BatchableContext BC, List<RMA__c> records) {
        try{
            if(!records.isEmpty()){
                for(RMA__c rma : records){
                    CalloutToStyline(rma,rma.Name);
                }        
            }
        }catch(Exception ex){
            string strErrorMessage = ex.getMessage() + ' In execute Method Of CalloutToStylineBatch Class' + ' At Line Number ==> ' + ex.getLineNumber();
            BA_ErrorLog.log(strErrorMessage);
        }
    }
    public void finish(Database.BatchableContext BC){ 
        
    }
    //Prepare the Callout for Amazon S3 bucket
    Public static void CalloutToStyline(RMA__c Rma,string rmaName){
       
        system.debug('rmaName==>'+rmaName);
        string strSetBody = PreapareXMLString(Rma);
        Blob xmlContentBlob = Blob.valueOf(strSetBody);
        String attachmentBody = EncodingUtil.base64Encode(xmlContentBlob);
        String formattedDateString = Datetime.now().formatGMT('EEE, dd MMM yyyy HH:mm:ss z');
        String key = 'xxxxx'; 
        String secret = 'xxxxx'; 
        //https://xxx.s3.us-east-2.amazonaws.com/
        String bucketname = 'support-rma';
        String host = 's3.xxxx.amazonaws.com';
        String method = 'PUT';
        String filename = rmaName+'.xml'; //Rma name for storing file in s3 bucket
        String fileType = 'text/xml';
        
        HttpRequest req = new HttpRequest();
        req.setMethod(method);
        req.setEndpoint('callout:AmazonS3' + '/' + filename);
        req.setHeader('Host', bucketname + '.' + host);
        req.setHeader('Accept-Encoding', '');
        req.setHeader('Content-Encoding', 'UTF-8');
        req.setHeader('Content-type', fileType);
        req.setHeader('Connection', 'keep-alive');
        req.setHeader('Date', formattedDateString);
        req.setHeader('ACL', 'public-read-write');
        req.setHeader('Content-Length', '0');  **<<-- IF I COMMENT OUT THIS LINE IT DOES NOT THROW EXCEPTION (but doesn't work)**
        req.setBodyAsBlob(EncodingUtil.base64Decode(attachmentBody));
        
               
        Http http = new Http();   
        HTTPResponse res = http.send(req);  **<<---- Exception Thrown here**
        try{
        System.debug('res==>'+res);
        System.debug('res.getBody()==>'+res.getBody() );
        System.debug('res.getStatusCode()==>'+res.getStatusCode());
        }catch(Exception ex){
            System.debug('res==>'+res);
            system.debug('exception::'+ex.getStackTraceString());
            string strErrorMessage = ex.getMessage() + ' In CalloutToStyline Method Of CalloutToStylineBatch Class' + ' At Line Number ==> ' + ex.getLineNumber() + ', and Cause ==>'+ ex.getCause();
            BA_ErrorLog.log(strErrorMessage); 
        }

    }
    
    //XML Payload for Amazon s3 bucket
    Public static string PreapareXMLString(RMA__c Rma){
        try{
        system.debug('enter XMLPayload method');
        string strCountry = '';
        string XMLPayload = '';
        
        //set the countries as per remote system requires
        if(rma.CountryCode__c =='United States'){
            strCountry = 'US';
        }else if(rma.CountryCode__c =='Canada'){
            strCountry = 'CA';
        }else if(rma.CountryCode__c =='Mexico'){
            strCountry = 'MX';
        }
         //Get the list of RMA__c object fields in index format
        list<string> lstfields = new list<string>
        {
                Rma.Name,
                Rma.RMA_Type__c,
                Rma.RMA_Status__c,
                Rma.Contact__r.Name,
                Rma.Street_1__c != null ? Rma.Street_1__c : '',
                Rma.Street_2__c != null ? Rma.Street_2__c : '',
                Rma.Street_3__c != null ? Rma.Street_3__c : '',
                Rma.Street_4__c != null ? Rma.Street_4__c : '',
                Rma.State__c != null ? Rma.State__c : '', //Rma.Street_4__c != null ? Rma.Street_4__c : '',
                Rma.City__c !=null ? Rma.City__c : '',
                Rma.ZIP_Postal_Code__c,
                Rma.CountryCode__c = strCountry,
                Rma.E_Mail__c,
                Rma.Phone_Number__c,
                Rma.Replacement_Part_Number__c != null ? Rma.Replacement_Part_Number__c : '',
                Rma.Serial_Number__r.Name,
             // Rma.Shipping_Method__c != null ? Rma.Shipping_Method__c : '',
                Rma.ShippingMethod_Formula__c != null ? Rma.ShippingMethod_Formula__c : '',
                Rma.Ship_Date__c != null ? string.valueOf(Rma.Ship_Date__c) : '',
                Rma.Tracking_Number__c != null ? Rma.Tracking_Number__c : ''
        };
                    
        //String concatenation for XML Payload 
        string XMLString = '<RmaRequest xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'+
            '<RmaNumber>{0}</RmaNumber>'+
            '<Name>{3}</Name>'+
            '<Contact>{3}</Contact>'+
            '<Address1>{4}</Address1>'+
            '<Address2>{5}</Address2>'+
            '<Address3>{6}</Address3>'+
            '<Address4>{7}</Address4>'+
            '<Region>{8}</Region>'+
            '<City>{9}</City>'+
            '<PostalCode>{10}</PostalCode>'+
            '<Country>{11}</Country>'+
            '<Email>{12}</Email>'+
            '<Phone>{13}</Phone>'+
            '<Item>{14}</Item>'+
            '<SerialNumber>{15}</SerialNumber>'+
            '<ShippingType>{16}</ShippingType>'+
            '<ShipDate>{17}</ShipDate>'+
            '<TrackingNumber>{18}</TrackingNumber>'+
            '<PartShipped />'+
            '<ReturnTrackingNumber />'+
            '</RmaRequest>';
        
        //Format the list of fields to indexed position
        XMLPayload= String.format(XMLString,lstfields);
        
        return XMLPayload;
        }catch(Exception ex){
            string strErrorMessage = ex.getMessage() + ' In XMLPayload Method Of CalloutToStylineBatch Class' + ' At Line Number ==> ' + ex.getLineNumber();
            BA_ErrorLog.log(strErrorMessage);
            return null;
        }
    }
}
4
  • See: help.salesforce.com/s/articleView?id=000364305&type=1. When you remove the Content-Length, do you get a 200/201 response from AWS? If so, can you get any logs from the AWS side to understand what is happening?
    – Z33dawg
    Commented May 9, 2022 at 18:23
  • If you are sure, it is spring 22 issue, please create issue with sfdc.
    – metasync
    Commented May 9, 2022 at 18:43
  • Z33dawg is likely correct, but it appears that my S3 Bucket wants the length header. It now returns this error in the debug logs. (Error 403) <Error> <Code>SignatureDoesNotMatch</Code> <Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message> The key has not changed at all, and again, works in postman. I assumed that this was because of the content lenght.
    – Eastmarch
    Commented May 9, 2022 at 19:57
  • The Content-Length header should be set by SF platform. If you do not set the header and the call still fails, you need to open a case with SF support.
    – identigral
    Commented May 9, 2022 at 21:29

1 Answer 1

1

OK, solved the issue.

Note the code :

req.setHeader('Host', bucketname + '.' + host);

The error from Amazon said the header info was :

content-type:text/xml host:support-rma.s3.support-rma.us-east-2.amazonaws.com

Which I assume is an issue using that host header setting in conjunction with the callout function. Didn't write the original code. Why it started failing after Spring '22 is likely due to the info that Z33Dawg showed me, perhaps there was additional code that tried to make it easier to use, and when the training wheels came off, the whole thing collapsed. Commented out that line, and success.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .