I see. We will try to establish some new logs in our environment where we can track the request more precisely
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’);
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.
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
Yes, /connect/token url we expect to receive POST requests
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
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)
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
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:
- Get a token
- Upload File
- Execute MailMerge
- 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;
}
- 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;
}
}
- 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;
}
}
- 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.