DataSourceException: ASPOSE Service Exception

I see. We will try to establish some new logs in our environment where we can track the request more precisely

@yaroslaw.ekimov

do you have update on this? Client is chasing us for update

I’m sorry, but we have nothing new to share. We are unable to find any issues on our side, and for now, we do not have many ideas on how to determine what may be causing the problem on your side. If I have any new information, I will contact you.

@yaroslaw.ekimov while looking into code we found that the Content-type is getting set like below when we are uploading a file. Do you think this is causing the intermittent error? And if yes what is the default content-type Aspose API is using in case of an unsupported or unrecognized type?

request.setHeader(‘Content-Type’, ‘$MultiPart/Form-Data’);

@yaroslaw.ekimov

We are getting the “USER_DEBUG|[147]|DEBUG|ASPOSE Access Token Exception : System.CalloutException: Read timed out” exception in the below getJWTToken() method while trying to get the Access token:

Code:
public static string GetJWT(){
try {
Aspose.Credentials credentials = AsposeService.getCredentials();
String grant_type = ‘client_credentials’;

        String client_id= credentials.appSID;
        String client_secret= credentials.appKey;  
        HttpRequest request = new HttpRequest();
       
        request.setEndpoint(credentials.accessToken);
        request.setMethod('GET');                
        request.setHeader('Content-Type', 'application/x-www-form-urlencoded');              
        request.setHeader('Accept', 'application/json');
       
        String body = 'grant_type=' + grant_type + '&client_id=' + client_id + '&client_secret='+ client_secret ;
        request.setBody(body);
       
        Http http = new Http();
        HttpResponse res = http.send(request);
        String jsonInput = res.getBody();
        Map<String, Object> a =(Map<String, Object>)JSON.deserializeUntyped(jsonInput);

        return a.get('access_token').ToString();
    }catch(Exception ex){
        system.debug('ASPOSE Access Token Exception : '+ex);
        return null;
    }
}

Q1. In the above method we are setting the setMethod as ‘GET.’ So as shared by the reference example( Aspose.Words Cloud - API References), do we need to set it as ‘POST’ method?

Q2. We are not setting any setTimout value to the HTTP request, and since we are getting “Read time out” error, do we need to set any specific timeout value?

Q1. In the reference page you sent - the example is

POST request to:
https://api.aspose.cloud/connect/token
Headers:
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Body:
grant_type: client_credentials
client_id: CLIENT_ID
client_secret: CLIENT_SECRET

/connect/token URL has never been a GET only POST

Q2. If you do not set any timeout there is probably a default timeout of you HttpClient class, which is probably you should look at the documentation.

@yaroslaw.ekimov

we have an older conversation where we were asked to use GET, can you check below link? was there any recent change to use POST instead of GET from aspose cloud?

I’m not sure why you were asked to use GET. Perhaps an old version of the authentication service automatically changed GET to POST because the request included a body parameter. And as authorization is out of the scope of our service, I can’t be sure why it worked before, but it shouldn’t

@yaroslaw.ekimov So POST is the method we should be using going forward?

Yes, /connect/token url we expect to receive POST requests

@yaroslaw.ekimov

Can you respond on this? do we need to use $

request.setHeader(‘Content-Type’, ‘$MultiPart/Form-Data’);

“Content-Type: multipart/form-data” this is how you should set content type for upload file method

@yaroslaw.ekimov

We did update Content type and POST Method for Aspose request (added document) but we are getting error if we use “Content-Type: multipart/form-data” and no error if we use “Content-Type: $multipart/form-data”

can you check the documents? I have also attached JSON data and merge document used.

AsposeIssueInV4 1.docx (538.2 KB)

JSONData.docx (29.4 KB)

Generic Interaction Merge Template-1.docx (36.4 KB)

Well, it seems it is an APEX feature that $multipart/form-data is working, but multipart doesn’t, so you can use an APEX version

@yaroslaw.ekimov

Can we encode the sign-in part in V4 as per version 1.1? is it required to avoid any errors also

We had a discussion with Salesforce Support, We are receiving responses for all the requests we are sending. The content type being passed is treated as a plain string.

“Content-Type: multipart/form-data” or “Content-Type: $multipart/form-data”

so for “Content-Type: multipart/form-data” we are Getting the response from Aspose CALLOUT_RESPONSE is:
Status = Not Found, StatusCode = 404, which occurs when the dollar sign ($) is not included in the request.

We also performed a steps to reproduce using Beeceptor(#aspose - API Mock Server) and observed that the request headers are being forwarded to the third-party system exactly as they are.

Request:
HttpRequest request = new HttpRequest();
request.setEndpoint(‘https://aspose.free.beeceptor.com/’);
request.setHeader(‘Content-Type’, ‘multipart/form-data’);
request.setMethod(‘GET’);
Http http = new Http();
HttpResponse res = http.send(request);
String jsonInput = res.getBody();

Respose:

{ “user-agent”: “SFDC-Callout/64.0”, “accept”: “text/html, image/gif, image/jpeg, /; q=0.2”, “cache-control”: “no-cache”, “content-type”: “multipart/form-data”, “pragma”: “no-cache”, “sfdc_stack_depth”: “1”, “x-forwarded-for”: “155.226.144.0”, “x-forwarded-host”: “aspose.free.beeceptor.com”, “x-forwarded-proto”: “https”, “accept-encoding”: “gzip” }

Request:
HttpRequest request = new HttpRequest();
request.setEndpoint(‘https://aspose.free.beeceptor.com/’);
request.setHeader(‘Content-Type’, ‘$multipart/form-data’);
request.setMethod(‘GET’);
Http http = new Http();
HttpResponse res = http.send(request);
String jsonInput = res.getBody();

Respose:

{ “user-agent”: “SFDC-Callout/64.0”, “accept”: “text/html, image/gif, image/jpeg, /; q=0.2”, “cache-control”: “no-cache”, “content-type”: “$multipart/form-data”, “pragma”: “no-cache”, “sfdc_stack_depth”: “1”, “x-forwarded-for”: “155.226.144.1”, “x-forwarded-host”: “aspose.free.beeceptor.com”, “x-forwarded-proto”: “https”, “accept-encoding”: “gzip” }

can you confirm about encoding and which content type we need to use?

You don’t need to sign requests for the v4.0 version; you obtain a token for authorization. If you still sign the request, please remove that code for any requests that you send to v4.0 endpoints.
Also, I still didn’t get why you set multipart/form-data and then use request.setMethod(‘GET’); it makes no sense.
Could you please guide me through your workflow and the code you use during the mail merge or conversion process?
So I put the list steps that I think you do so you can share the code for each of these steps:

  1. Get a token
  2. Upload File
  3. Execute MailMerge
  4. Download file/Convert file

This is the code we are using in Aspose v4.0 for each step:

    public String outFormat {get; set;}
    private final string WORDS = '/words/';
    public static Aspose.Credentials getCredentials() {
		Aspose_Credentials__c credential = Aspose_Credentials__c.getOrgDefaults();//Aspose_Credentials__c:{Aspose_App_Key__c=876rt8hg87654322345h9bd3bdf12348, Aspose_App_Sid__c=ABC8GT3H-N0HG-BH5R-HY61-8XV18H1B1987, Custom_Font_Folder__c=customFonts, Endpoint_URL__c=https://api.aspose.cloud/v4.0, AccessToken_URL__c=https://api.aspose.cloud/connect/token}
		Aspose.Credentials credentials = new Aspose.Credentials(credential.Aspose_App_Sid__c,credential.Aspose_App_Key__c,credential.AccessToken_URL__c);
        return credentials;
	}

    public AsposeService() {
		Aspose_Credentials__c credential = Aspose_Credentials__c.getOrgDefaults();//Aspose_Credentials__c:{Aspose_App_Key__c=876rt8hg87654322345h9bd3bdf12348, Aspose_App_Sid__c=ABC8GT3H-N0HG-BH5R-HY61-8XV18H1B1987, Custom_Font_Folder__c=customFonts, Endpoint_URL__c=https://api.aspose.cloud/v4.0, AccessToken_URL__c=https://api.aspose.cloud/connect/token}
    	Aspose.BaseProductUri = credential.Endpoint_URL__c;
    	outFormat = String.valueOf(Aspose.SaveFormat.Pdf);
        customFontFolder = credential.Custom_Font_Folder__c; 	
    }
  1. Get a token
        try {
            Aspose.Credentials credentials = AsposeService.getCredentials();//Credentials:[accessToken=https://api.aspose.cloud/connect/token, appKey=876rt8hg87654322345h9bd3bdf12348, appSID=ABC8GT3H-N0HG-BH5R-HY61-8XV18H1B1987]
            String grant_type = 'client_credentials';

            String client_id= credentials.appSID;
            String client_secret= credentials.appKey;	
            HttpRequest request = new HttpRequest();
            
            request.setEndpoint(credentials.accessToken);
            request.setMethod('POST');                
            request.setHeader('Content-Type', 'application/x-www-form-urlencoded');              
            request.setHeader('Accept', 'application/json');
            
            String body = 'grant_type=' + grant_type + '&client_id=' + client_id + '&client_secret='+ client_secret ;
            request.setBody(body);
            request.setTimeout(20000);
            Http http = new Http();
            HttpResponse res = http.send(request);
            String jsonInput = res.getBody();
            Map<String, Object> a =(Map<String, Object>)JSON.deserializeUntyped(jsonInput);

            return a.get('access_token').ToString();
        }catch(Exception ex){
            system.debug('ASPOSE Access Token Exception : '+ex);
            return null;
        }
    }
  1. Upload File
        String strJSON;
        try {
        	Aspose.Credentials credentials = AsposeService.getCredentials();//Credentials:[accessToken=https://api.aspose.cloud/connect/token, appKey=876rt8hg87654322345h9bd3bdf12348, appSID=ABC8GT3H-N0HG-BH5R-HY61-8XV18H1B1987]
            fileName = fileName.replace(' ', '%20');
            signedURI = Aspose.BaseProductUri + '/words/storage/file/' + fileName;
            accessToken = GetJWT();
            strJSON = uploadFileBinary(signedURI, 'PUT', fileBlob, accessToken);
            return strJSON;
        }
        catch (Exception ex) {
            system.debug('ASPOSE File Upload Service Exception : '+ex);
            throw new DataSourceException('ASPOSE File Upload Service Exception : '+ex);
            return null;
        }
    }
    public static String uploadFileBinary(String strURI, String strHttpCommand, Blob strContent, String accessToken) {
        try {
            HttpRequest request = new HttpRequest();
            Integer len = 0;
            if (strContent != null) {
                request.setBodyAsBlob(strContent);
                len = strContent.size();
            }
            
            request.setEndpoint(strURI);
            request.setMethod(strHttpCommand);
            request.setHeader('Content-Type', '$multipart/form-data');
            request.setHeader('Content-Length', String.valueOf(len));
            request.setHeader('Accept', 'application/json');
            if(accessToken != null)
                request.setHeader('Authorization', 'Bearer '+accessToken);
			request.setTimeout(99999);
			
            if(TEST.isRunningTest() ){
            	return 'Success';
            } else{
            	Http http = new Http();
	            HttpResponse res = http.send(request);
	            return res.getBody();
            }
        } catch (Exception ex) {

            throw new DataSourceException('ASPOSE Service Exception : '+ex);
            return null;
        }
    }
  1. Execute MailMerge
        String DocName, httpRequestMethod;
        try {
            FileName = FileName.replace(' ', '%20');
            signedURI = Aspose.BaseProductUri + WORDS + FileName + '/MailMerge?withRegions=True&cleanup=ContainingFields,EmptyParagraphs,UnusedFields,UnusedRegions,RemoveTitleRow,RemoveTitleRowInInnerTables&useWholeParagraphAsRegion=False&destFileName=merge_'+FileName;
            accessToken = GetJWT();
            httpRequestMethod = 'PUT';
            String strJSON = ProcessCommand(signedURI, httpRequestMethod, dataFile, 'json', accessToken);
            Map<String, Object> params = (Map<String, Object>) JSON.deserializeUntyped(strJSON);
            if(params.containsKey('Document')){
                Map<String, Object> doc = (Map<String, Object>) params.get('Document');
                DocName = (String) doc.get('FileName');
            }

            return DocName;
        }
        catch (Exception ex) {
            system.debug('ASPOSE Merge Service Exception : '+ex);
            throw new DataSourceException('ASPOSE Merge Service Exception : '+ex);
        }
    }

    public static String ProcessCommand(String strURI, String strHttpCommand, String strContent, String ContentType, String accessToken) {
        try {
            HttpRequest request = new HttpRequest();
            Integer len = 0;
            if (strContent != null && strContent != '') {
                request.setBody(strContent);
                len = strContent.length();
            }
            
            commonApiExecution(strURI,strHttpCommand,contentType,request,accessToken,len);
        
            Http http = new Http();
            HttpResponse res = http.send(request);
            return res.getBody();
        
        } catch (Exception ex) {

            throw new DataSourceException('ASPOSE Service Exception : '+ex);
            return null;
        }
    }

    public static String commonApiExecution(String strURI, String strHttpCommand,String contentType, HttpRequest request, string        accessToken,integer len){
        request.setEndpoint(strURI);
        request.setMethod(strHttpCommand);
        request.setHeader('Content-Type', 'multipart/form-data');
        request.setHeader('Content-Length', String.valueOf(len));
        request.setHeader('Accept', 'application/json');
        if(accessToken != null)
            request.setHeader('Authorization', 'Bearer '+accessToken);
        request.setTimeout(99999);
        return null;
    }```
4. Download file/Convert file

```    public Blob downloadFile(String fileName) {
        try {
        	Aspose.Credentials credentials = AsposeService.getCredentials();
            if (String.isBlank(customFontFolder)){
                strURI =  Aspose.BaseProductUri + WORDS + fileName + '?format='+outFormat;
            }else{
                strURI =  Aspose.BaseProductUri + WORDS + fileName + '?fontsLocation=' + customFontFolder + '&format='+outFormat;
            }
            Blob strJSON = AsposeUtils.ProcessCommand(strURI, 'GET', 'json', accessToken);
            return strJSON;
        }
        catch (Exception ex) {
            system.debug('ASPOSE File Download Service Exception : '+ex);
            throw new DataSourceException('ASPOSE File Download Service Exception : '+ex);
        }
    }

    public static Blob ProcessCommand(String strURI, String strHttpCommand,  String ContentType, String accessToken) {
        try {
            HttpRequest request = new HttpRequest();
            Integer len = 0;
            
            commonApiExecution(strURI,strHttpCommand,contentType,request,accessToken,len);
            Http http = new Http();
            HttpResponse res = http.send(request);
            return res.getBodyAsBlob();
        } catch (Exception ex) {
            throw new DataSourceException('ASPOSE Service Exception : '+ex);
            return null;
        }
    } ```

Thank you for sharing, I will look at this.

In continuation of our last message. Below is the code used for deleting in the end:

public String deleteFile(String filename) {
    Aspose.Credentials credentials = AsposeService.getCredentials();
    String signedURI = '';
    fileName = fileName.replace(' ', '%20');
    signedURI = Aspose.BaseProductUri + '/words/storage/file/'+fileName;
    accessToken = GetJWT();

    HttpRequest request = new HttpRequest();
    Integer len = 0;
    
    commonApiExecution(signedURI,'DELETE','json',request,accessToken,len);
    
    Http http = new Http();
    HttpResponse res = http.send(request);
    return res.getBody();
    
}

Yes, I understood the idea, I will try to debug it.