Thursday, March 01, 2007

Streaming Files

I recently was working on a project where I wanted to have the desktop open a file that was on the server. However, the desktop user does not have access to the server directly. In order to do this I had to stream the file to the browser.

First I had to create a servlet
public class DocumentViewer extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet {

Then I have to implement the method doGet to handle the http processing.

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//
DocumentFileTO documentFile=fileDao.getDocumentFile(id);
resp.setContentType(javax.activation.MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType(documentFile.getName()));
resp.setHeader("Content-Disposition","attachment;filename="+documentFile.getName());
InputStream fileStream=documentFile.getFileContents();
BufferedOutputStream outBuffer=new BufferedOutputStream(resp.getOutputStream());

int c=0;
while(-1!=(c=fileStream.read())){
outBuffer.write(c);
}
outBuffer.close();
}



There are a few more methods that were very key in this.
documentFile.getFileContents()

public InputStream getFileContents(){
byte[] _fileContents;
try {
File file=new File(this.getPath()); // Path has already been set.
FileInputStream fileStream=new FileInputStream(file);
ByteArrayOutputStream byteOut=new ByteArrayOutputStream();
int c=0;
while(-1!=(c=fileStream.read())){
byteOut.write(c);
}
_fileContents=byteOut.toByteArray();
return new ByteArrayInputStream(_fileContents);
} catch (Throwable e) {
throw new RuntimeException("Unable to open file " + this.getName() + ": " + e.getMessage(),e); // Name has already been set.
}
}


Finally, there were two very important things that I had to learn.
resp.setContentType needs to hold the MIME-Type information for the browser to understand what it is getting. I found that there is a built-in Java library that will provide this information.javax.activation.MimetypesFileTypeMap.getDefaultFileTypeMap().getContentType()
It takes a filename with extension as the parameter. The function just strips the extension and finds known MIME information for that extension. If you are using a custom extension you will need to add your own records to the FileMap, which is possible. I did not test how extensive the known filet ypes are because I only needed it for a total of 5 or 6 very common types, and they worked.

And resp.setHeader had to have attachment; in the call. Without attachment the file was trying to open in the browser, which for me was opening everything in QuickTime. Having attachment in the call set the file to open on the desktop using the users default application for the file type.

I learned a lot on this project. Streaming in Java, using a servlet to handle a request instead of a JSP page (this was new to me), and a little about the header information for streaming files. However, once it's done the code to perform this feature doesn't look nearly as impressive as the amount of time it took me to research the functionality to make it all work.