Skip to Content

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

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-integration-cpi/

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

Add a comment
10|10000 characters needed characters exceeded

Related questions

7 Answers

  • Best Answer
    Posted on Jan 28, 2020 at 04:23 AM

    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

    Add a comment
    10|10000 characters needed characters exceeded

    • 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

  • Posted on Jan 28, 2020 at 06:22 AM

    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

    Add a comment
    10|10000 characters needed characters exceeded

  • Posted on Jan 28, 2020 at 02:44 PM

    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

    Add a comment
    10|10000 characters needed characters exceeded

    • 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..

  • Posted on Feb 27, 2020 at 10:50 PM

    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


    Add a comment
    10|10000 characters needed characters exceeded

    • 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

  • Posted on Feb 27, 2020 at 11:09 PM

    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

    Add a comment
    10|10000 characters needed characters exceeded

  • Posted on Feb 28, 2020 at 12:14 AM

    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

    Add a comment
    10|10000 characters needed characters exceeded

  • Posted on Feb 28, 2020 at 07:14 AM

    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

    Add a comment
    10|10000 characters needed characters exceeded

Before answering

You should only submit an answer when you are proposing a solution to the poster's problem. If you want the poster to clarify the question or provide more information, please leave a comment instead, requesting additional details. When answering, please include specifics, such as step-by-step instructions, context for the solution, and links to useful resources. Also, please make sure that you answer complies with our Rules of Engagement.
You must be Logged in to submit an answer.

Up to 10 attachments (including images) can be used with a maximum of 1.0 MB each and 10.5 MB total.