Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
mattisebastian
Participant

Disclaimer


This blog is meant for documentation/educational purposes only.

It is the result of our findings and no guarantee that the same solution will work in all cases. I was informed that multipart sending/form-data is not officially supported by SAP Cloud Integration.

Introduction


Our team solved an interesting challenge involving SAP Cloud Platform Integration and an API expecting to receive form-data today.

We faced an API which accepted a POST request using the Postman tool but would always throw an error when 'exactly the same content' (we will learn more about that later) was sent from the CPI.

Because the POST request needed to send form-data, this strange behaviour made us dig into the formal definition of form-data by W3C. We also found some very nice blogs like this one by Pieterjan who explains form-data from a little different angle.

In this blog, we will explain the concept of form-data, how to send it from Postman and how to send it from the Cloud Platform to another system.

What is form-data anyway?


Form-data originates from HTML forms which take user input and send it through the browser to a web server. The same technology can be used to send data between applications other than browsers.

It comes in two flavours: application/x-www-form-urlencoded and multipart/form-data. Both are used to send key-value mappings. In HTML forms, a field is represented as a mapping of the name of the field to the content. 

Urlencoded form-data is the standard way but not sufficient for sending large quantities of binary data also known as files. For more information see the W3C definition.

Two ways of sending form-data


The rest of this blog will talk about the content type multipart/form-data. Don't be scared by the multipart, it also works if your request contains only one part.

First, we will explore how to send the form-data through Postman. This is easily done with the below settings. The body is set to form-data and key-value pairs can be added. Note that it is possible to add simple strings, formatted data like JSON and also files as values.


Postman takes care of the rest behind the scenes. To recreate this request in the SAP Cloud Platform Integration we first recreated this request in Postman using the raw content type. This is needed as the CPI does not support form-data directly but has only the basic capabilities to alter header and body.

The information we needed to enter was gathered from the W3C definition in combination with Postman's handy feature to generate code from a request by clicking on Code (in the top right corner of the screenshot above). The details will follow in the next section.

Header and body


Two things are important in the generated code to create the form-data: the header Content-Type and the body formatting.
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

The header labels the content as form-data and introduces the boundary, which is a user-chosen string. Postman generates this one for us. Don't worry about the multipart, this is used for all form data, even if it contains only one part like in our examples.
Content-Disposition: form-data; name="formElement"

ExampleValue
------WebKitFormBoundary7MA4YWxkTrZu0gW--

The body declares form-data again but also gives the data a name, this is the key in the Postman example in the previous section. We also see our "ExampleValue" which is the value in the said example. Please note that the boundary in the body has two more dashes "--" in the beginning than the boundary declared in the header. This is the rule per the definition.

More key-value pairs could be added in the body here but let's keep it simple for now. The very last boundary needs to also have to dashes in the end (compared again to the boundary in the header).

 

To the Cloud Platform Integration


In the CPI you would now go ahead and create the header:



With the value (note the much simpler but still working boundary):
Content-Type: multipart/form-data; boundary=cpi

and the body:



 
Content-Disposition: form-data; name="formElement"

ExampleValue
--cpi--

Normally our story would end here. The CPI sends out the well-formatted form-data and we are happy.

The anomaly


In our case, the API we were trying to talk to would accept the form-data from Postman with either way, raw or as form-data. If we copied the exact same header and body which worked in Postman to the CPI we'd get an HTTP 400 error "Unable to parse multipart body". This happens for example when you leave out the newline between Content-Disposition and the ExampleValue in the example above.

After observing different payloads over and over again we tried not to create the form-data in the CPI but send it there from Postman and route it through to the API. Normally for our use-case, we need to send a SOAP message but as nothing was working this seemed like something we could give a try.

And it was successful. With the message from Postman routed through the CPI, the API accepted the request. So we checked what the difference was between the request we created "by hand" in the CPI (see chapter To Cloud Platform Integration) and the one coming from Postman.

Even comparing them side by side in the CPI tracing view showed no difference. We also created an Iflow that stripped away all headers but the Content-Type but with no luck.

Finally, we compared the two payloads in Notepad++ which can show hidden characters like line ending characters (View -> Show Symbol -> Show All Characters). It seems Postman on Windows creates the "correct" line ending characters and the CPI creates UNIX line endings which our API could not handle. Turns out it wasn't 'exactly the same data'.

The solution


To solve our problem we created a script which would automatically convert the linefeeds. You can find it below should you ever run into this kind of trouble.
import com.sap.gateway.ip.core.customdev.util.Message;
import java.util.HashMap;
def Message processData(Message message) {
//Body
def body = message.getBody(String);
body = body.replaceAll("\n", "\r\n");
body = """--cpi\r\nContent-Disposition: form-data; name="envelope"\r\n\r\n\r\n""" + body + """\r\n\r\n--cpi--"""

message.setBody(body);
return message;
}

Conclusion


In this blog we explained what form-data is, showed the different forms and how to send it through Postman. Then we moved to the Cloud Platform Integration and sent form-data from there. In the end we showcased an error with an API that threw an error on UNIX line endings and how to fix it.

Thank you for reading! Please let me know if you faced similar problems or if you have further questions regarding form-data requests or other CPI topics.
16 Comments