Archive for August, 2014

Streaming a pdf from a streamReader to a browser with ColdFusion 10

August 26, 2014

Following up on my article on ‘retrieving a MIME-encoded binary attachment through MTOM with ColdFusion¬†10’, it was only logical that I had to send the content i retrieved to the browser.
Remember, we received the content from a webservice through a streamReader.

Now there’s two ways to go at it sending it to the browser. One could appended all the bytes from the stream to variable, and wrap it in a cfcontent tag to send it to the browser. This would of course consume a substantial amount of memory from the JVM running the CF engine, because the whole content has to be recreated in memory.

In my particular case, the content would be pdf files, which can be streamed to pdf reader plugins in the browser. The advantage of streaming a pdf file is that an intelligent pdf reader can already render the first pages while the next pages are still being received, hence for large pdf files the user doesn’t have to wait until the complete document is downloaded before he can start reading it.

Additionally, if we could forward the stream we receive from the webservice directly to the browser, the only memory we would consume would theoretically be the size of our byteArray buffer.

I found a few articles on streaming binary content to a browser, all of which dated before 2011. Unfortunately none of those worked on my ColdFusion 10 server, maybe because of the different java application server running under the hood, prior to version 10. It took a little digging to find out a working equivalent in CF10.

In the code below, I wrapped the call to the WebService that offers the pdf files in a DAO. The nice thing about CFC’s is that apparetly they can also return java objects of type streamReader if you declare the returntype to ‘Any’.

In my view template, I will call the GetStream method on the DAO, and I will stream the content I receive directly to the ColdFusion Output buffer and flush after each 1024 bytes (size could perhaps be chosen better).

The DAO method:


public Any function GetStream(string docType='', numeric docNumber='', numeric docYear='', string docQualifier='', DataType.LanguageIdentifier language) output = true {
var _variables = StructNew();
var _fds=CreateObject("Java", "javax.activation.FileDataSource").init('F:\ColdFusion10\cfusion\tmpCache\temp.pdf');
var _dhl=CreateObject("Java", "javax.activation.DataHandler").init(_fds);

_variables.ws=createObject(“webservice”,”http://contentServer/DocContentServiceWS/DocContentServiceWSPort?WSDL”,{wsversion=”2″,refreshwsdl=”false”,timeout=25});
_variables.result = StructNew();
_variables.result.filenameOut = ”;
_variables.result.errorMessage = ”;
_variables.result.errorCode = ”;
_variables.response=_variables.ws.getmanifestationcontent(‘USERNAME’,arguments.docType,JavaCast(“bigdecimal”,arguments.docNumber),JavaCast(“bigdecimal”,arguments.docYear),arguments.docQualifier,UCase(arguments.language.identifier),’INTRA’,’PDF’,JavaCast(“bigdecimal”,1),_variables.result.filenameOut,_dhl,_variables.result.errorMessage,_variables.result.errorCode);
_variables.inputStream=_variables.response.getFileContent_out().getInputStream();
/*
_variables.outStream=createObject(“Java”,”java.io.ByteArrayOutputStream”).init();
_variables.byteClass=createObject(“Java”,”java.lang.Byte”).TYPE;
_variables.byteArray=createObject(“Java”,”java.lang.reflect.Array”).newInstance(_variables.byteClass, javacast(“int”, 1024));
_variables.length=_variables.inputStream.read(_variables.byteArray);
_variables.offset=0;
while (_variables.length GT 0) {
_variables.outStream.write(_variables.byteArray,_variables.offset,_variables.length);
_variables.length=_variables.inputStream.read(_variables.byteArray);
}
_variables.outStream.close();
_variables.inputStream.close();
*/
return _variables.inputStream;
}

The view template:

FileInputStream = documentsDAO.GetStream(URL.docType,URL.docNumber,URL.docYear,URL.docQualifier,language);
// Response = GetPageContext().GetResponse().GetResponse(); -- this doesn't work in CF10
Response = getPageContext().getFusionContext().getResponse();
Response.setHeader( 'Content-Type', 'application/pdf' );
Response.ResetBuffer();

outStream = Response.GetOutputStream();
byteClass=createObject(“Java”,”java.lang.Byte”).TYPE;
byteArray=createObject(“Java”,”java.lang.reflect.Array”).newInstance(byteClass, javacast(“int”, 1024));
length=FileInputStream.read(byteArray);
offset=0;
while (length GT 0) {
outStream.write(byteArray, offset, length);
outStream.Flush();
length = FileInputStream.read(byteArray);
}
Response.Reset();
FileInputStream.Close();
outStream.Close();

Advertisements