cancel
Showing results for 
Search instead for 
Did you mean: 

Send zip file and form data through CPI and HTTP Adapter to Ariba

joao_miguel_17
Explorer
0 Kudos

Hello everyone,

I'm running into an issue regarding HTTP Adapter in CPI. The scenario is to pick up a file in a SFTP Server, create form data with the zip file attached and send it through HTTP Adapter to Ariba.

The problem is I keep running into error status code 411: Content-Length is required.

I've tried to define this header manually, but with no success. Sending the message to another host confirms that this header is created automatically when the message is sent by CPI, so it must be another thing unknown to me.

Exploring the blogs I've found another post talking about this exact same issue. The link is down below:

https://blogs.sap.com/2019/11/14/what-is-form-data-and-how-to-send-it-from-sap-cloud-platform-integr...

Nick Yang proposed a solution that revolves around opening a conection to the destination directly into the script, discarding the necessity of using HTTP Adapter. But I tried to replicate it, but keep getting the error:

java.lang.NoSuchMethodException: No signature of method: sun.net.www.http.PosterOutputStream.write() is applicable for argument types: (org.apache.camel.converter.stream.InputStreamCache) values: [org.apache.camel.converter.stream.InputStreamCache@144c18fd] Possible solutions: write([B), write(int), write(int), write(int), wait(), wait(long)

I assume this is because I'm creating the form data before in another script, but I don't convert the body of the message to byte array afterwards.

Can someone help me on this?

This is my current script:

def Message postZipContentToAriba(Message message){
    def messageLog = messageLogFactory.getMessageLog(message)
    def propertiesMap = message.getProperties()
    def headersMap = message.getHeaders()
    // POST
    def post = new URL("Ariba-Network-URL").openConnection();
    def zipContent = message.getBody()
    
    post.setRequestMethod("POST")
    post.setDoOutput(true)
    post.setRequestProperty("Content-Type", headersMap.get("Content-Type") as String)
    post.getOutputStream().write(zipContent);
    
    def postRC = post.getResponseCode();
    if(postRC.equals(200)) {
        messageLog.addAttachmentAsString("Post result:", post.getInputStream().getText() as String, "text/plain")
    }else{
        def exceptionMsg = "HTTP" + postRC.toString() + ", " + post.getInputStream().getText()
        throw new Exception(exceptionMsg as String)
    }
    
    return message
}


Message processData(Message message) {


    return postZipContentToAriba(message)
}

The script used before this one is to create multi-part form-data:

Message processData(Message message) {
    
    // Set multipart into body
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
    
    try{
    
        byte[] bytes = message.getBody(byte[])
        //  Construct Multipart
        MimeBodyPart bodyPartHead1 = new MimeBodyPart()
        bodyPartHead1.setText('true')
        bodyPartHead1.setDisposition('form-data; name="fullload"')
        
        MimeBodyPart bodyPartHead2 = new MimeBodyPart()
        bodyPartHead2.setText('Import External System Master Data')
        bodyPartHead2.setDisposition('form-data; name="event"')
        
        MimeBodyPart bodyPartHead3 = new MimeBodyPart()
        bodyPartHead3.setText(secret)
        bodyPartHead3.setDisposition('form-data; name="sharedsecret"')
        
        MimeBodyPart bodyPartHead4 = new MimeBodyPart()
        bodyPartHead4.setText(systemId)
        bodyPartHead4.setDisposition('form-data; name="systemId"')
        
        MimeBodyPart bodyPartHead5 = new MimeBodyPart()
        bodyPartHead5.setText('Load And Delete')
        bodyPartHead5.setDisposition('form-data; name="Operation"')
        
        MimeBodyPart bodyPartHead6 = new MimeBodyPart()
        bodyPartHead6.setText('Cloud')
        bodyPartHead6.setDisposition('form-data; name="clienttype"')
        
        MimeBodyPart bodyPartHead7 = new MimeBodyPart()
        bodyPartHead7.setText('CPI')
        bodyPartHead7.setDisposition('form-data; name="clientinfo"')
        
        MimeBodyPart bodyPartHead8 = new MimeBodyPart()
        bodyPartHead8.setText('1.0.0')
        bodyPartHead8.setDisposition('form-data; name="clientversion"')
        
        
        MimeBodyPart bodyPart = new MimeBodyPart()
        ByteArrayDataSource dataSource = new ByteArrayDataSource(bytes, 'application/zip')
        DataHandler byteDataHandler = new DataHandler(dataSource)
        bodyPart.setDataHandler(byteDataHandler)
        bodyPart.setFileName('content.zip')
        bodyPart.setDisposition('form-data; name="content"')
    
        MimeMultipart multipart = new MimeMultipart()
        multipart.addBodyPart(bodyPartHead1)
        multipart.addBodyPart(bodyPartHead2)
        multipart.addBodyPart(bodyPartHead3)
        multipart.addBodyPart(bodyPartHead4)
        multipart.addBodyPart(bodyPartHead5)
        multipart.addBodyPart(bodyPartHead6)
        multipart.addBodyPart(bodyPartHead7)
        multipart.addBodyPart(bodyPartHead8)
        multipart.addBodyPart(bodyPart)
        
        multipart.updateHeaders()
        
        multipart.writeTo(outputStream)
        message.setBody(outputStream)
    
        // Set Content type with boundary
        String boundary = (new ContentType(multipart.contentType)).getParameter('boundary');
        message.setHeader('Content-Type', "multipart/form-data; boundary=\"${boundary}\"")
        
        // Calculate message size
        //message.setHeader('content-length', message.getBodySize())
    
    }catch(Exception e){
        
    }finally{
        outputStream.close()
    }


    return message
}

Thank you and kind regards,

João Gomes

0 Kudos

Hi joao_miguel_17,

I see you are trying to post master data to Ariba using ITK based approach ( uploading zip file containing csv file). I am also trying this to do from SAP CPI. For me data gets to CPI in XML. Is it possible for you to provide the code where you are building the csv files and zipping them.

former_member71230
Discoverer
0 Kudos

Hi joao_miguel_17,

We have a similar requirement where we need to send data to Ariba. First, we need to pick up a CSV file from SFTP, ZIP it and send it in form of multipart form data. We have followed your blog and we are getting "Throwable caught: Illegal filename: ID-vsa10692304-1668261292152-120-2 || No stack available."

Can you please help us to troubleshoot this error?

Thanks,

Jeevitha

View Entire Topic
joao_miguel_17
Explorer
0 Kudos

Hello everyone,

With some search and fine tunning, I was able to use client certificate authentication using a mix between java and groovy, changing the already used script for shared secret authentication.

Here it follows:

import com.sap.gateway.ip.core.customdev.util.Message
import javax.activation.DataHandler
import javax.mail.internet.ContentType
import javax.mail.internet.MimeBodyPart
import javax.mail.internet.MimeMultipart
import javax.mail.util.ByteArrayDataSource
import java.nio.charset.StandardCharsets
import java.util.Base64;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.HttpsURLConnection;
import java.net.URL;
import java.net.URLConnection;

def Message postZipContentToAriba(Message message){
    
    // We build a SSLContext with both our trust/key managers
    SSLContext sslContext = SSLContext.getInstance("TLS");
    com.sap.it.api.keystore.KeystoreService ks = com.sap.it.api.ITApiFactory.getApi(com.sap.it.api.keystore.KeystoreService.class, null)   
    if (ks != null){
        // Get relevant objects for SSLContext
        KeyManager[] km = ks.getKeyManagers();
        TrustManager[] tm = ks.getTrustManagers();
        // Initialize Context
        sslContext.init(km, tm, null);
    }
    
    SSLSocketFactory sslSf = sslContext.getSocketFactory();
    
    // Prepare URL to connect
    def messageLog = messageLogFactory.getMessageLog(message)
    def propertiesMap = message.getProperties()
    def headersMap = message.getHeaders()
    
    String aribaAddress = propertiesMap.get("aribaAddress")
    String queryParameters = propertiesMap.get("queryParameters")
    
    // We prepare a URLConnection 
    URL url = new URL(aribaAddress + "?" + queryParameters);
    URLConnection urlConnection = url.openConnection();
    // Before actually opening the sockets, we affect the SSLSocketFactory
    HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
    httpsUrlConnection.setSSLSocketFactory(sslSf);


    // POST
    def zipContent = message.getBody(byte[])
    httpsUrlConnection.setRequestMethod("POST")
    httpsUrlConnection.setDoOutput(true)
    httpsUrlConnection.setRequestProperty("Content-Type", headersMap.get("Content-Type") as String)
    httpsUrlConnection.getOutputStream().write(zipContent);
    
    def postRC = httpsUrlConnection.getResponseCode();
    if(postRC.equals(200)) {
        messageLog.addAttachmentAsString("Post result:", httpsUrlConnection.getInputStream().getText() as String, "text/plain")
    }else{
        def exceptionMsg = "HTTP" + postRC.toString() + ", " + httpsUrlConnection.getInputStream().getText()
        throw new Exception(exceptionMsg as String)
    }
    
    return message
}


Message processData(Message message) {


    return postZipContentToAriba(message)
}

I hope it helps anyone trying this approach!
Best regards,

João Gomes

masjo
Explorer
0 Kudos

I have tried to implement the client cert authentication based on your example, Joao. I keep getting 401 unauthorised.

I have made the config change on the Ariba side to be certificate authentication and have pasted the CPI cert.