Cisco Support Community
cancel
Showing results for 
Search instead for 
Did you mean: 

UCCX: Send a Recording (or any Document) over HTTP safely using Base64 encoding

This document was inspired by this thread.

Problem: UCCX needs to send a recording using the Hypertext Transfer Protocol (HTTP), which is not always suitable for binary data (like a recording).

Analysis: The Base64 encoding algorithm is an excellent method of transforming binary objects into ASCII characters that may be safely transmitted over a text-based protocol like HTTP.

Disclaimer: * * * The usual blah-blah. I am not responsible for anything. UCCX Premium or IP IVR license only (custom Java used). Tested with UCCX Premium 7.0(1)_Build168. * * *

Solution:

1. Download the Apache Commons Codec library. N.B.: there are separate versions of Commons Codec for different Java versions, so this might be the right time to look up the Java version of your UCCX server. Use the compatibility matrix - or, alternatively, use Recipe #2 from this document. In this example, I use UCCX 7.0, this means Java 5 is I need to work with, so I downloaded Commons Codec 1.6).

2. Unzip/Untar-gz the package, and upload the commons-codec-1.x.jar file to UCCX's classpath, and select it on the "Custom File Configuration" page.

3. Reload the server. Or reboot it. Yes, it's really necessary. No, it won't work without this.

4. Create a new script, using the UCCX Script editor.

5. Insert the following variables:

base64encodedRecording - type String - inital value: ""

recording - type Document - initial value: DOC[]

serverResponse - type Document - initial value: DOC[]

url - type String - the URL of the target application, we will send the Base64 encoded recording to here.

6. Insert all the necessary steps to the script (for instance, Accept, Play Prompt, etc).

7. Insert a Recording step. The result document would be the recording variable.

8. Add a Set node to the Successful branch of the Recording step. The result would be the variable named base64encodedRecording. Insert the following code block:

{

java.io.InputStream is = new org.apache.commons.codec.binary.Base64InputStream(recording.getInputStream(),true);

byte[] b = new byte[1024]; // change it to a higher value if it's too slow

StringBuffer buf = new StringBuffer();

int bytesRead = 0;

while ((bytesRead = is.read(b,0,b.length)) != -1) {buf.append(new String(b,0,bytesRead)); }

return buf.toString();

}

As we can see, I got the InputStream object of the recording document, and wrapped it into another InputStream, more precisely, with a Base64InputStream (by specifying true - see the second parameter) I asked it to encode everything I read from the source InputStream, using the Base64 algorithm. Then I just read chunks of bytes out of it - 1024 bytes long chunks - and appending them into a StringBuffer, which then I hand over at the last line, transformed into a String.

9. The next two steps are easy: with a Create URL Document step, with the URL pointing to the HTTP application which, the HTTP method would be POST, and the parameter, in this example named "base64encodedRecording" holds the value of the variable named base64encodedRecording (the last setting, the Document is not used in this example, I used a dummy variable named ServerResponse). Then, using the Cache Document step, the script actually issues the HTTP request.

Testing:

I created a simple Grails application "Base64Recording", with one controller "TakeAndSaveController" with one method, "index" (this is why, in my script the value of the url variable is "http://192.168.11.37:8080/Base64Recording/takeAndSave/index":

package base64encodecprompt

class TakeAndSaveController {

    def index() {

        def encoded = params.base64encodedRecording.replaceAll("\r|\n", "").trim()

        def decoded = encoded.decodeBase64()

        new File("/tmp/recording.wav").withOutputStream {

            it.write(decoded)

        }

    }

}

Here's a screenshot:

screenshot1.png

What happens here: I take the parameter named "base64encodedRecording" (out of the params object), while replacing all the newline characters (\n, \r) and trimming the leading and trailing whitespaces (just to be on the safe side) - this yields the encoded variable.

Then, I decode the binary object, by calling the decodeBase64() method on encoded - resulting decoded.

Finally, I create a file named /tmp/recording.wav, and with its OutputStream I just write the binary object into it.

I created an application, then a trigger, called in, after the prompt I created a recording, and in a couple of moments, a file named /tmp/recording.wav appeared on my computer, created by the Grails application.

There are several things to be aware of:

- While encoding the binary stream into Base64 text, line separators (by default, CRLF) are added, after the 76th character. There's nothing wrong with it, but the receiving application may need to remove these extra characters (this is what I did by calling the replaceAll method in my Grails controller).

- Base64 will introduce overhead, making the encoded string longer by 33% of the original string. This may be taken into account when sending large recordings.

Screenshot of the whole UCCX script, for inspiration:

screenshot2.png

Enjoy.

G.

Version history
Revision #:
1 of 1
Last update:
‎05-15-2013 07:47 AM
Updated by:
 
Labels (1)
Comments
New Member

I am trying to implement this encoding.

I am using uccx 9.0.2 and downloaded Commons Codec 1.9.  I installed it like is documented here and created a test script and am unable to create an application with this new script.  The error I get is

%MIVR-APP_MGR-1-SCRIPT_LOAD_ERROR:Failed to load script: Script=SCRIPT[SOAP_Connector-5.aef],Exception=com.cisco.script.ScriptIOException: Failed to load script: SOAP_Connector-5.aef; nested exception is:
 com.cisco.io.IOException: (class: org/apache/commons/codec/binary/Base64InputStream, method: <init> signature: (Ljava/io/InputStream;ZI[B)V) Incompatible argument to function; nested exception is:
 java.lang.VerifyError: (class: org/apache/commons/codec/binary/Base64InputStream, method: <init> signature: (Ljava/io/InputStream;ZI[B)V) Incompatible argument to function

 

Here is the text of the set statement

{
java.io.InputStream is = new org.apache.commons.codec.binary.Base64InputStream(dRecorded_Msg.getInputStream(),true);
byte[] b = new byte[2048]; // change it to a higher value if it's too slow
StringBuffer buf = new StringBuffer();
int bytesRead = 0;
while ((bytesRead = is.read(b,0,b.length)) != -1) {buf.append(new String(b,0,bytesRead)); }
return buf.toString();
}

I looked at the Base64InputStream documentation and it looks right.

 

Does anyone have a suggestion how to fix the incompatible argument to function error?

 

Hi,

apologies for not responding earlier, I've been a bit busy lately.

I tested Commons Codec 1.9 with UCCX 8.0 (I don't have a 9.x version in my lab). Used a similar script, successfully. The only thing that comes to my mind is that the d_Recorded_Msg variable must be incompatible at runtime.

Can you please reveal more about your script? Would you mind attaching the script or at least a screenshot of the relevant part of it?

Thanks.

G.

New Member

Sure here is a screenshot of what I have so far.

Er... I am afraid it was lost in transit. Can you post it again?

New Member

I changed the name of the recording to recorded.

New Member

New Member

Trying different browsers to see if the screenshot will post

Hi, this might sound weird but can you double check your variables? The Recording step assigns the recording to the variable named "Recorded" but in your Set step with the Java code you sort of reference a variable named "recorded". Variable names are case sensitive.

G.

New Member

Yes I caught the Recorded after I did the snapshot.  I also stripped everything out.  I still get the same error.

OK, can you add a Set step right before the existing one, like this:

recorded = (Document) N[1]

And then just try stepping over the script. Does it still throw the same error?

New Member

I cannot create an application with the script.  When I go into application management and attempt to use the new test script I get the error so I cannot debug anything.

Did you reload the UCCX engine after uploading that jar file you downloaded?

Also, if you try validating your script in UCCX Script Editor, does it pass?

G.
 

New Member

I simplified the java to narrow down where the script loading error is.  When I use this script I am able to go to application > soaptest > script > SOAP_Connector-7.aef. and update the application with no errors.

New Member

When I add one line to the script envoke the org.apache.commons.codec.binary.Base64InputStream function I then get a MADM error when trying to associate the changed script to the existing application.  I have tried commons-codec-1.9.jar, commons-codec-1.8.jar and commons-codec-1.7.jar one at a time but get the same result.  Not sure what else to try.

9665: Sep 07 16:31:21.701 EDT %MADM-SCRIPT_MGR-3-UNABLE_LOAD_SCRIPT:Unable to load script: Script=/SOAP_Connector-6.aef,Exception=com.cisco.io.IOException: (class: org/apache/commons/codec/binary/Base64InputStream, method: <init> signature: (Ljava/io/InputStream;ZI[B)V) Incompatible argument to function; nested exception is:
    java.lang.VerifyError: (class: org/apache/commons/codec/binary/Base64InputStream, method: <init> signature: (Ljava/io/InputStream;ZI[B)V) Incompatible argument to function

New Member

After loading the commons-codec-1.X.jar file I reboot the UCCX 9.0.2 server.  Both scripts validate in the script editor with no issues.

OK, I am installing a 9.0 UCCX to test this. I hope I can replicate this problem there. G.