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

Accepted Solutions (1)

Accepted Solutions (1)

NickSYYang
Active Participant
0 Kudos

Hi João,

Could you change the code in processData method from

message.setBody(outputStream)

to

message.setBody(outputStream.toByteArray())

and let me know how it goes.

Kind regards,

Nick

joao_miguel_17
Explorer
0 Kudos

Hello Nick,

Thank you for your answer.

I've managed to resolve the issue changing the following bit of code:

 def zipContent = message.getBody()

to this:

 def zipContent = message.getBody(byte[])

I think it follows your line of processing, in this case, getting a byte array from the body content.

Thank you and best regards,

João Gomes

Answers (8)

Answers (8)

NickSYYang
Active Participant

Hi João,

So, this is the issue in HTTP adapter "status code 411: Content-Length is required" that I face before. You can raise a support ticket to CPI team to fix this issue in HTTP adapter.

I haven't done client certificate login using Groovy script before but can look into this later.

Kind regards,

Nick

NickSYYang
Active Participant

Hey João,

Before dive into the capability/possibility for CPI to call external service using client certificate login.

We need to check whether Ariba integration toolkit security supports client certificate login or not?

Can you check setting by following below steps?

Cheers.

Kind regards,

Nick

joao_miguel_17
Explorer
0 Kudos

Hello Nick,

Thank you for your fast response.

I can confirm that Ariba Integration Toolkit Security Supports client certificate login. I've managed to make it work removing the script to call Ariba directly and use HTTP Adapter instead. But then the error "status code 411: Content-Length is required" returned.. I cannot escape from the usage of the script is my conclusion.

Best regards,

João Gomes

NickSYYang
Active Participant

Hi João,

Maybe you have done your test with other tools like postman recommended by Sriprasad. In my case, before I started developing CPI integration program, I used below HTML web page to observe what is a working HTTP POST payload and then later tried to produce exactly same output from CPI.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
    <meta http-equiv="content-type" content="text/html;charset=ISO-8859-1">
    <title>Direct access - File upload</title>
</head>
<body>
    <h1>Direct access - File upload</h1>
    <form method="post" enctype="multipart/form-data" action="https://s1-integration.ariba.com/Buyer/fileupload?realm=YourSiteID">
        Event: <input type="text" name="event" value="Import Batch Data">
        <br>
        <input type="radio" name="fullload" value="true">Full Load<br>
        <input type="radio" name="fullload" value="false" checked>Incremental<br>
        Password: <input type="password" name="sharedsecret" value="">
        <br>
        Client Type: <input type="text" name="clienttype" value="Firefox">
        <br>
        Client Info: <input type="text" name="clientinfo" value="Master Data Upload Form">
        <br>
        Client Version: <input type="text" name="clientversion" value="1.0">
        <br>
        File: <input type="file" name="content">
        <br>
        <input type=submit value="Submit Post">
    </form>
</body>
</html>

Kind regards,

Nick

joao_miguel_17
Explorer
0 Kudos

Hello Nick,

Thank you for your inputs. I use Postman on a regular basis but I never ran into http error code 411 before. HTTP Adapter bug is bothersome.

Actually, now I ran into another issue. The customer wants Certificate Based Authentication from CPI to Ariba. So I would need to change postZipContentToAriba function to use CPI certificate in the keystore to make the connection, but I'm stuck in it..

Sriprasadsbhat
Active Contributor

Hello Joao,

Better try same scenario with exact parameters in Postman which gives the response and what is missing in your request payload( instead of just shooting HTTP 400 without more details ).

Regards,

Sriprasad Shivaram Bhat

mpothuganti
Explorer
0 Kudos

Hi Sri,

I am facing the similar issue for the same scenario.

I am able to successfully post the data from Postman. But issue with CPI only.

Just an observation:
Am able to post using CPI aswell with less number or records. But if if the data is reaching a certain limit then Its throwing error "Throwable caught: Content length required || No stack available."
Can you please help me with the resolution ?

saidanturti34
Discoverer
0 Kudos

@joao_miguel_17 Please refer to the approach detailed in below blog. This has less custom coding and works like a charm for uploading zipped content to Ariba.

https://community.sap.com/t5/technology-blogs-by-members/sap-ariba-integration-using-sap-cloud-integ...

shishira123
Discoverer
0 Kudos

Hi,

Not working for me above code. I am getting handshake error.

NickSYYang
Active Participant
0 Kudos

Hi João,

Great to see you figure out the workable script.

Interesting thing here is that I didn't see you specify alias and put credential when preparing sslcontext. If the code is working then it must be ok.

Cheers.

Kind regards,

Nick

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.