Access part of response content as binary in Javascript policy

Not applicable

I've got a SOAP webservice response of the type 'multipart/related; type="application/xop+xml"'. This means I have an XML part, but also a binary part that contains a PDF. I would like to include this PDF, base64 encoded, as part of the XML and have that converted to JSON.

I don't have any issue parsing the response to pick out these 2 parts and reassemble them and I also figured out how the do the base64 encoding. The problem I've got is that if I take this part from the returned JSON and base64 decode it and put it in a file I don't get my PDF back.

After looking at the problem it looks to me like the content I get access to in the Javascript policy is already incorrect and so no matter how I treat it (or no matter the encoding I try) I can't get it right.

Some Googling and looking around on the forum seems to indicate this, but I can't find a clear answer. The only alternatives I'm currently seeing are doing a Java callout or maybe nodejs... and a distant 3rd option is to split the call into 2 so that you first get the XML part and then do a 2nd call, with info from the XML, to get the PDF, avoiding the multipart response. I'd like to stay with a simple JS solution, but can't find a way to do it in Apigee.

Solved Solved
0 4 1,771
1 ACCEPTED SOLUTION

The JS Callout in Apigee is not well-suited to handling octet streams, or byte arrays.

There is no Buffer object, and the various native type arrays that you may be familiar with in Javascript, are also not available in that context. It means that handling byte arrays is a hassle.

It will work in a Java callout, or in nodejs. Either is fine. But either will require extra coding!

Or, breaking it into 2 calls.

Sorry!

View solution in original post

4 REPLIES 4

The JS Callout in Apigee is not well-suited to handling octet streams, or byte arrays.

There is no Buffer object, and the various native type arrays that you may be familiar with in Javascript, are also not available in that context. It means that handling byte arrays is a hassle.

It will work in a Java callout, or in nodejs. Either is fine. But either will require extra coding!

Or, breaking it into 2 calls.

Sorry!

In the mean time I've been able to create a separate node proxy that works as I expect when called separately. It processes the input (my SOAP response saved from SoapUI), finds the PDF part and encodes that as base64 and returns it.

The strange thing now is that if I use this node proxy as a service call out in my original REST -> SOAP -> REST proxy I don't get the result I expect. The base64 encoded PDF I get in my result is still wrong.

The problem seems to be with the Assign Message step that I'm using to set the SOAP response as the request content for the node call out. When I check the size of the request it is a lot bigger than the one from the call that I do with Postman to the node proxy directly. So it still seems that something is going wrong, encoding wise???, when trying to do something with the SOAP response?

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage async="false" continueOnError="false" enabled="true" name="SOAP request to node input">
    <DisplayName>Process multipart response</DisplayName>
    <AssignTo createNew="true" type="request">multipartRequest</AssignTo>
    <Set>
        <Payload>{response.content}</Payload>
    </Set>
    <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</AssignMessage><br>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout async="false" continueOnError="false" enabled="true" name="Process-multipart">
    <DisplayName>Process multipart</DisplayName>
    <Properties/>
    <Request clearPayload="false" variable="multipartRequest">
        <IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
    </Request>
    <Response>multipartResponse</Response>
    <LocalTargetConnection>
        <APIProxy>hello-node</APIProxy>
        <ProxyEndpoint>default</ProxyEndpoint>
    </LocalTargetConnection>
</ServiceCallout><br>
var http = require('http');

console.log('node.js application starting...');

if (!Buffer.prototype.indexOf) {
    Buffer.prototype.indexOf = function (value, offset) {
        offset = offset || 0;

        // Always wrap the input as a Buffer so that this method will support any
        // data type such as array octet, string or buffer.
        if (typeof value === "string" || value instanceof String) {
            value = new Buffer(value);
        } else if (typeof value === "number" || value instanceof Number) {
            value = new Buffer([ value ]);
        }

        var len = value.length;

        for (var i = offset; i <= this.length - len; i++) {
            var mismatch = false;
            for (var j = 0; j < len; j++) {
                if (this[i + j] != value[j]) {
                    mismatch = true;
                    break;
                }
            }

            if (!mismatch) {
                return i;
            }
        }

        return -1;
    };
}

var svr = http.createServer(function(request, response) {
    var body = [];
    
    request.on('data', function (chunk) {
        body.push(chunk);
    });
    
    request.on('end', function () {
        var soapResponse = Buffer.concat(body);
        
        var startPdf = soapResponse.indexOf("%PDF");
        var endPdf = soapResponse.indexOf("%%EOF");
        var pdf = soapResponse.slice(startPdf, endPdf + 5);
        
        response.end(pdf.toString('base64'));
    });
});

svr.listen(9000, function() {
    console.log('Node HTTP server is listening');
});

Is this issue resolved?

The followup issue is probably moot now, as Apigee can no longer run nodejs proxies. 

I suppose you, BharathNP, are having a particular problem that is unique to your situation.  I suggest that you ask a specific question related to your case.