This blog comes to us via Enreeco, who in the past few months has been tearing through the ranks, and is even our January Member of the Month!
Without further ado, check out Enreeco's killer blog post below, (inspired by a CloudSpokes challenge of course!) Also make sure to head over to Enreeco's blog to see even more great posts.
[Salesforce / Apex] POST Mutipart/form-data with HttpRequest
Grandma says you cannot post a Mutipart/form-data using an HttpRequest in APEX?
Well, if she says this now you can tell her this is no more true!
All comes from a CloudSpokes challenge (here is the link)...at the time of starting the challenge I was absolutely sure I would have ended up the challenge in less than a day: http gets/posts are not a big problem in APEX...well so it seemed.
To complete the challenge you had to make 4 REST calls (login, book a new upload, upload the file, set permissions): during testing the last step always failed.
This was the first time I jumped in front of this issue.
If you don't want to know what I did, go directly here.
The first thing I noted was that you cannot send a base64 encoded file to a server expecting a binary file...It wans't that obvious to me, because I've never struggled with file encoding.
The first code was something like this:
Then I was all "Eureka! An encoded string cannot be understood if the server needs a binary", so the only thing to do is to make a concatenation of header + file.Body.toString() + footer! This worksonly if the Blob comes from a text file (i.e. TXT, XML or CSV files): in these cases you don't have any problem...but with binary data all you have is the error:
Blob is not a valid UTF-8 string
I had to find another way.
Searching the web for "uploading binary data using apex" I found those bad links:
That leaded to the block of comments you can see in the challenge's dashboard.
I didn't give up anyway. I had all data needed to send the request so I knew the solution was out there.
First thing was to understand if there was a way to merge Blobs types: it is not possible in APEX if you don't have the original data (in that case you use String concatenation or List of Integers concatenation, if you have bynary data in form of intergers list).
So I came up with the idea to merge header, body and footer using base64 encoded version, something like this:
I found that sometimes it worked (after a bit I understood that that times I was extremely lucky!!).
Debugging and searching the web (see this post for example) I came to know that a base64 encoded String could have padding characters because the base64 encoding is done using chunks of 3 bytes (see Google for details), and if data is not multiple of 3 bytes this padding in needed.
So I decided to remove the trailing "=" from each encoded chunck of the body request and paste them together. But it's not the proper way to play with encoded base64 strings, as removing trailing padding needs a reencoding of the original data.
The idea was to remove in some way, without messing with the encoded strings, all trailing padding "=".
For the header string it was simple, because it was simple text and I could have added some blank spaces to get an encoded string without "=". That's:
So in practice I add extra spaces before the "\n\n" ending characters till I have an encoded string without padding.
The Blob file is the main problem. I need the unencoded data to get the needed trailing, so I need a String value of the body: even if with that String how can I change the file to avoid the "=" ? As this data can be anything (form txt files to encoded zips), it is not so simple to add some padding character to avoid the "=" padding (not clear I know)...
If the encoded body doesn't contain any trailing "=", now the problem is over, the sum of the encoded header, body and footer works.
The problem is the last 4 bytes of the encoded body. That is from the 0th byte to the N-4th byte of the file I have no problem, becase it is an encoded version without "=" trailing.
How do I encode those last 4 bytes merging them with the footer?
I discovered that the HttpRequest class has a strange behavior: the setBodyAsBlob() and getBody() are complementary for the use I need. That is the following code doens't throw a "Blob is not a valid UTF-8 string" exception:
The result is a messing sequence of characters. Are they properly encoded? Yes they are, this is a kind of test:
Using different kind of random encoded data (other that "AA==") the results of encoding, blobbing, httpRequesting (??!!), is always the same.
This is what i needed:
- decode the last 4 bytes in blob
- append it into an HttpRequest using the "setBodyAsBlob()"
- get the body as string with "getBody()"
- merge this string with the footer
- base64 encode the resulting string
- merge the base64 encoding of header, file body (from 0 to N-4th byte), previous merged string
- base64 unencoding the resulting string
- here you are the Blob you needed!
I tested it with different kind of files, dimensions and it always worked. I'd like to know your thoughts.