Saturday, July 21, 2007

FileServlet

NOTICE - NEWER VERSION AVAILABLE!

There's a newer article out for more effective file serving supporting caching, resume and range requests (which is required by most media/video players). You may find it useful: FileServlet supporting resume and caching and GZIP.

Serve your files

If you have stored files in a path outside of the web container or in a database, then the client cannot access the files directly by a relative URI. A good practice is to create a Servlet which loads the file from a path outside of the web container or from a database and then streams the file to the HttpServletResponse. The client should get a 'Save as' popup dialogue, thanks to the Content-disposition header being set to attachment. You can pass the file name or the file ID as a part of the request URI. You can also consider to pass it as a request parameter, but that would cause problems with getting the filename right during saving in certain web browsers (Internet Explorer and so on).

Back to top

FileServlet serving from absolute path

Here is a basic example of a FileServlet which serves a file from a path outside of the web container.

package mypackage;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * The File servlet for serving from absolute path.
 * @author BalusC
 * @link https://balusc.omnifaces.org/2007/07/fileservlet.html
 */
public class FileServlet extends HttpServlet {

    // Constants ----------------------------------------------------------------------------------

    private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB.

    // Properties ---------------------------------------------------------------------------------

    private String filePath;

    // Actions ------------------------------------------------------------------------------------

    public void init() throws ServletException {

        // Define base path somehow. You can define it as init-param of the servlet.
        this.filePath = "/files";

        // In a Windows environment with the Applicationserver running on the
        // c: volume, the above path is exactly the same as "c:\files".
        // In UNIX, it is just straightforward "/files".
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        // Get requested file by path info.
        String requestedFile = request.getPathInfo();

        // Check if file is actually supplied to the request URI.
        if (requestedFile == null) {
            // Do your thing if the file is not supplied to the request URI.
            // Throw an exception, or send 404, or show default/warning page, or just ignore it.
            response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404.
            return;
        }

        // Decode the file name (might contain spaces and on) and prepare file object.
        File file = new File(filePath, URLDecoder.decode(requestedFile, "UTF-8"));

        // Check if file actually exists in filesystem.
        if (!file.exists()) {
            // Do your thing if the file appears to be non-existing.
            // Throw an exception, or send 404, or show default/warning page, or just ignore it.
            response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404.
            return;
        }

        // Get content type by filename.
        String contentType = getServletContext().getMimeType(file.getName());

        // If content type is unknown, then set the default value.
        // For all content types, see: http://www.w3schools.com/media/media_mimeref.asp
        // To add new content types, add new mime-mapping entry in web.xml.
        if (contentType == null) {
            contentType = "application/octet-stream";
        }

        // Init servlet response.
        response.reset();
        response.setBufferSize(DEFAULT_BUFFER_SIZE);
        response.setContentType(contentType);
        response.setHeader("Content-Length", String.valueOf(file.length()));
        response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");

        // Prepare streams.
        BufferedInputStream input = null;
        BufferedOutputStream output = null;

        try {
            // Open streams.
            input = new BufferedInputStream(new FileInputStream(file), DEFAULT_BUFFER_SIZE);
            output = new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE);

            // Write file contents to response.
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            int length;
            while ((length = input.read(buffer)) > 0) {
                output.write(buffer, 0, length);
            }
        } finally {
            // Gently close streams.
            close(output);
            close(input);
        }
    }

    // Helpers (can be refactored to public utility class) ----------------------------------------

    private static void close(Closeable resource) {
        if (resource != null) {
            try {
                resource.close();
            } catch (IOException e) {
                // Do your thing with the exception. Print it, log it or mail it.
                e.printStackTrace();
            }
        }
    }

}

In order to get the FileServlet to work, add the following entries to the Web Deployment Descriptor web.xml:

<servlet>
    <servlet-name>fileServlet</servlet-name>
    <servlet-class>mypackage.FileServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>fileServlet</servlet-name>
    <url-pattern>/file/*</url-pattern>
</servlet-mapping>

Of course you can change the url-pattern of the servlet-mapping as you like it.

Here are some basic use examples:

<!-- XHTML or JSP -->
<a href="file/foo.exe">download foo.exe</a>
<a href="file/bar.zip">download bar.zip</a>

<!-- JSF -->
<h:outputLink value="file/foo.exe">download foo.exe</h:outputLink>
<h:outputLink value="file/bar.zip">download bar.zip</h:outputLink>
<h:outputLink value="file/#{myBean.fileName}">
    <h:outputText value="download #{myBean.fileName}" />
</h:outputLink>

Important note: this servlet example does not take the requested file as request parameter, but just as part of the absolute URL, because some browsers such as Internet Explorer would take the last part of the servlet URL path as filename during the 'Save As' dialogue instead of the in the headers supplied filename. Some browsers would also not be able to detect the correct content type and the associated application (yes, it ignores the Content-Type header as well!!). Using the filename as part of the absolute URL (and thus not as request parameter) will fix this stupid browser behaviour.

Back to top

FileServlet serving from database

First prepare a DTO (Data Transfer Object) for File which can be used to hold information about the file (this is not the same as java.io.File! you may choose another name if this is too confusing). You can map this DTO to the database and use a DAO class to obtain it. You can get the file as InputStream from the database using ResultSet#getBinaryStream().

The data layer and the DAO pattern is explained in this tutorial: DAO tutorial - the data layer.

package mymodel;

import java.io.InputStream;

public class File {

    // Init ---------------------------------------------------------------------------------------

    private String id;
    private String name;
    private String contentType;
    private InputStream content;
    private Integer length;

    // Implement default getters and setters here.

}

Here is a basic example of a FileServlet which serves a file from a database.

package mypackage;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import mydao.DAOFactory;
import mydao.FileDAO;
import mymodel.File;

/**
 * The File servlet for serving from database.
 * @author BalusC
 * @link https://balusc.omnifaces.org/2007/07/fileservlet.html
 */
public class FileServlet extends HttpServlet {

    // Constants ----------------------------------------------------------------------------------

    private static final int DEFAULT_BUFFER_SIZE = 10240; // 10KB.

    // Statics ------------------------------------------------------------------------------------

    private static FileDAO fileDAO = DAOFactory.getFileDAO();

    // Actions ------------------------------------------------------------------------------------

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException
    {
        // Get ID from request.
        String fileId = request.getParameter("id");

        // Check if ID is supplied to the request.
        if (fileId == null) {
            // Do your thing if the ID is not supplied to the request.
            // Throw an exception, or send 404, or show default/warning page, or just ignore it.
            response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404.
            return;
        }

        // Lookup File by FileId in database.
        // Do your "SELECT * FROM File WHERE FileID" thing.
        File file = fileDAO.find(fileId);

        // Check if file is actually retrieved from database.
        if (file == null) {
            // Do your thing if the file does not exist in database.
            // Throw an exception, or send 404, or show default/warning page, or just ignore it.
            response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404.
            return;
        }

        // Init servlet response.
        response.reset();
        response.setBufferSize(DEFAULT_BUFFER_SIZE);
        response.setContentType(file.getContentType());
        response.setHeader("Content-Length", String.valueOf(file.getLength()));
        response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");

        // Prepare streams.
        BufferedInputStream input = null;
        BufferedOutputStream output = null;

        try {
            // Open streams.
            input = new BufferedInputStream(file.getContent(), DEFAULT_BUFFER_SIZE);
            output = new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE);

            // Write file contents to response.
            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
            int length;
            while ((length = input.read(buffer)) > 0) {
                output.write(buffer, 0, length);
            }
        } finally {
            // Gently close streams.
            close(output);
            close(input);
        }
    }

    // Helpers (can be refactored to public utility class) ----------------------------------------

    private static void close(Closeable resource) {
        if (resource != null) {
            try {
                resource.close();
            } catch (IOException e) {
                // Do your thing with the exception. Print it, log it or mail it.
                e.printStackTrace();
            }
        }
    }

}

In order to get the FileServlet to work, add the following entries to the Web Deployment Descriptor web.xml:

<servlet>
    <servlet-name>fileServlet</servlet-name>
    <servlet-class>mypackage.FileServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>fileServlet</servlet-name>
    <url-pattern>/file/*</url-pattern>
</servlet-mapping>

Of course you can change the url-pattern of the servlet-mapping as you like it.

Here are some basic use examples:

<!-- XHTML or JSP -->
<a href="file?id=250d7f5086d02a46f9aeec9c51d43c63">download file1</a>
<a href="file?id=0412c29576c708cf0155e8de242169b1">download file2</a>

<!-- JSF -->
<h:outputLink value="file?id=250d7f5086d02a46f9aeec9c51d43c63">download file1</h:outputLink>
<h:outputLink value="file?id=0412c29576c708cf0155e8de242169b1">download file2</h:outputLink>
<h:outputLink value="file?id=#{myBean.fileId}">download file1</h:outputLink>
Back to top

Security considerations

In the last example of an FileServlet serving from database, the ID is encrypted by MD5. It's your choice how you want to implement the use of ID, but keep in mind that plain numeric ID's like 1, 2, 3 and so on makes the hacker easy to guess for another files in the database, which they probably may not view at all. Then rather use a MD5 hash based on a combination of the numeric ID, the filename and the filesize for example. And last but not least, use PreparedStatement instead of a basic Statement to request the file by ID from database, otherwise you will risk an SQL injection when a hacker calls for example "file?id=';TRUNCATE TABLE File--".

Back to top

Copyright - There is no copyright on the code. You can copy, change and distribute it freely. Just mentioning this site should be fair.

(C) July 2007, BalusC

86 comments:

Ina said...

Hello BalusC,
I need the same functionality in jsf portlet. maybe you could suggest how to do that.
Thanks in advance.
Ina

Hakan Erdogan said...

Thanks a lot BalusC, great work...

Unknown said...

Hello BalusC,
Thanks for pointing me to your "FileServlet serving from Database" example. Can you send me or point me to the code listing for the FileDao class? That's the one missing piece from the FileServlet code listing.

thanks,

Grant
grantepalmer@msn.com

BalusC said...

Just do your DAO thing to get the file details :)

Unknown said...

No, really, is the FileDao class just a collection of File objects with a field named id?

BalusC said...

DAO = Data Access Object. It should be a class which interacts between the database and Java objects. The method FileDAO#load(fileId) should just do a "SELECT id, fileName, contentType, content FROM files WHERE id = fileId" and return a File DTO.

Read on about DAO patterns and get through a JDBC tutorial if you don't know how to let Java interact with databases.

telman said...

Hi BalusC,

Trying to test FileServlet I was success with .txt and .bmp content types. but the .jpg files are corrupted after download and .pdf requires password to be opened

Could you please clarify me?

Best Regards,
Telman

BalusC said...

@telman: sorry, I cannot reproduce this behaviour with the posted code.

Necromancer said...

what about 2Gb or more size files?
if you look functionality and simple file server look at
http://myfreecode.blogspot.com/2008/05/simple-java-file-server-servlet-eng.html

telman said...

Hi Balusc,
The last mentioned problem is still actual.
Actually FileServlet is fine and no problem when I use simple JSP or HTML for servlet call. but in case of faces it fails for .jpg.(jpg is corrupted after download)
1. success case :
from Simple JSP or HTML
2. fail case :
f:view
h:outputLink value='FileServlet?name=image.jpg'
h:outputText value='from Faces' /
/h:outputLink
/f:view
I use Java Platform Standard Edition 6
version 6 update 6 as JDK
and Sun Java System Application Server Platform Edition 9.0 Update 1 as J2EE

Any advice will be appreciated.
Thanks and Regards.
Telman

Danish said...

Hello BalusC,
i want a to create a code/file repository in my jsf application,so that a user can upload/dwnld his code.if he wants others to access his code he has to provide url to him.I m storing the path of file in database.how can i use your code?what modification i need to do? can u help me out?

telman said...

Hi BalusC,

my problem has been fixed and I want to explain my mistake.

in case of calling FileServlet from faces as

h:outputLink value="FileServlet/#{row}"

it gets the related servlet as

http://localhost:8080/myproject-war/faces/FileServlet/image.jpg

it happens when there was previously redirection with h:commandButton.

in this case the .txt and .bmp files are normal. but the jpg,mp3,jpg are corrupted.


having been modified to

h:outputLink value="../FileServlet/#{row}"

it gets servlet as
http://localhost:8080/myproject-war/FileServlet/image.jpg

which solves the mentioned issue.

I will be very pleasant if you say a few words for this code behavior.

Thanks&Regards,
Telman

Unknown said...

Hello BalusC,
I'm using PDF forms and I need to display a PDF form (in an IFrame) in which the user fills several fields. When done, the user clicks a "Submit" button which is part of the form (it is linked to a URL of a servlet). This servlet needs to certfy the form and send it back to the user for final confirmation.
Can you please think on a way to turn on a confirmation button on the page (outside of the IFrame)?
I can't find a way to link the IFrame content to the page outside of the IFrame.

BalusC said...

@Telman: the FileServlet should indeed not be passed through the FacesServlet. It should be called as an independent servlet.

@h&s: I don't fully understand your question, but I at least have the idea that this doesn't have anything to do with the FileServlet.

Ganesh Parthasarathi said...

Hi Balusc,

Thanks a lot for your code . It saved me a lot of time. The code is real pro. Thanks a lot.

Ganesh

Unknown said...

Hello BalusC,
I've seen the code for reading a pdf from a servlet but i need to read it from a jsp and display it in that,actually i've created a pdf using jasper report which is in dir,i need to display that pdf in report.jsp!!please help

Unknown said...

Hello BalusC, i have a question, can i know the relative path where user save the file that he downloaded in local directory? i want to write data in the file that he downloaded without asking the path.

nssa said...

Hello BalusC,
I'm a beginner concern with file downloading. Your post is really helpful to me. And one more thing that I want to ask u is that if the files are existed in different server rather than same server with that application hosted then how can we configure to download from the browser and I also want let the user to delete that files in the server from the browser. Suppose the user has valid right to delete the files in the server.
Best regards,
Phyo

Anonymous said...

Thank you very much. It helped me to resolve my problem

Sri Bodapati said...

thanks for the reply.

In my case, I am making a request from my Portal server and my PDFs are on my app server which is on a different machine.

My portal and app servers are not on the same machine.

So when I click a link from a jsp on my Portal server on one machine how can I open the PDF from the app server on a different machine.

Sorry to ask again.

Thanks
Sri.

BalusC said...

Run the servlet at that appserver and link to it.

Mike said...

Thanks very much for posting this - it was extremely helpful to me.

Nitin said...

Thanks BalusC for putting up the code. its so common to have a requirement of such a code. Just need to know one thing more, how can I direct the client to a thank you page after the download of file?

BalusC said...

One request can only produce one response. You want to send two responses, so you need the client to have fired two requests. You could use Javascript for this, but it won't work if the client has disabled Javascript in his browser.

<a href="thankyou.jsp" onclick="window.open('files/foo.ext');">click</a>

Sso said...

I JUST WANNA THINK YOU BalusC VERY MUCH

That Code Was Very helpful for Me

XtremeCode said...

Hello BalusC

Thanks for your example it helped me too much, I would like to know if there is a way to open the PDF inside the browser (instead of opening a save as dialog of the browser)

Thanks in advance,
Joaquin

Sso said...

@XtremeCode

you need just to replace this code line :
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");


by this

response.setHeader("Content-Disposition", "inline; filename=\"" + file.getName() + "\"");

Unknown said...

Hello BalusC,
Many thanks for your article. It is very useful.
And as I`m a beginner concerning file downloading I want to ask you about the following.
I`ve got FacesServlet and added FileServlet into web.xml. How can I call out independent FileServlet, i.e. how should I set value for outputLink in order to call out FileServlet?

BalusC said...

Check the examples in the article.

Unknown said...

Hello BalusC,

I tried your example to download PDF(blob) from database and display it using JSF. I'm getting the below error:

java.lang.IllegalStateException: Servlet response already use stream, Writer not possible

Any solution please..

BalusC said...

All you should have in JSF is just a link pointing to the fileservlet. No managed bean action or so. Also see the JSF code examples below the fileservlet code.

If you need to generate PDF based on some managed bean properties, rather check the "PDF Handling" article in this blog.

Unknown said...

hey, i got it worked.. I didnt have context.responseComplete(); and when I included it, it worked :) Thanks for your help.

Yogesh said...

Hello, code is very good, but doesnot work for files greater than 400 MB. Is there any solution for this.?

BalusC said...

Your problem lies somewhere else.

Unknown said...

Hi BalusC. First off all I must thank you for your great articles. They helped me a lot to finish my first JSF project. Especially this one and the one with datatables. But I have a small problem with opening my files. I save them as byte[] in my database. I call them from different web pages. Those 2 pages use exactly same conroller and the same methods. One page runs great. But when I click the link on the other page, I see no 'save as' dialog opened and I see a junk like '%PDF-1.5 %???? 1 0 obj<> endobj 2 0 obj<> endobj 5 0 obj<>stream xڍXK?^?-?_q??E??~l ?!!??,B?z?Q@?@!?>???1fT???ͻpx?7????????? ?y?ڎ?????????;)?? ???ˇ????_???z???d_?p?Xp<ҽK???ޏϿoo~ ʑJw???CȰbq?/N?7exq%?!???؂?jkq????!h?;?.vp?k??n??Cu?xG?X??'?? ^?Q?D'??:_?f??/?????7?;?ӪR??z???rr .xPM_?;???L#??? ??Fv??-?f?? AΕ i?93n>?????d L4?.?&'6?3á' in my browser (IE6). I checked my method several times. All the values (content type etc.) are same when I call from both pages. my method is below:

Dokuman dok = (Dokuman) this.dokumanList.getRowData();
DokumanIcerik icerik = this.balikciTekneleriService.findById(DokumanIcerik.class, dok.getId()); //I get content here which is byte[]

FacesContext context = FacesContext.getCurrentInstance();
ExternalContext externalContext = context.getExternalContext(); HttpServletResponse response = (HttpServletResponse)externalContext.getResponse();
response.reset();

try {
OutputStream os = response.getOutputStream();
os.write(icerik.getContent());
response.setContentType(dok.getContentType() + "; charset=UTF-8");
response.setHeader("Content-Length", String.valueOf(dok.getDosyaBoyutu()));
os.flush();
os.close();


} catch (IOException e) {
e.printStackTrace();
}
context.responseComplete();

can you give me a clue where I look for my mistake? thank you very mucn..

Unknown said...

sorry.. I forgot to add response.setHeader("Content-disposition", " attachment; filename=\"" + ... line... But now it is working.. I found out from one of the pages I call my methot in controller with h:commandlink.. from the other page I call th method with a4j:commandlink .. I still can not understand what makes the difference but both pages are ok now... thanks anyway..

Narayana said...

BalusC, It is useful example. I have a questions here which is not on your servlet but on somthing similar to that. Hope you will help me.

I have a JSP having link to download PDF.
When user clicks on the link, I am using RequestDispatcher.forward to a servlet which is similar to your's and that will have a code to download the pdf from the server.

Client wants to have a pop up window while processing. I mean in between user click on Download PDF and getting the response back from Servelt.

Can you please advice the design or reply if my question is not clear.

Thanks
Narayana

BalusC said...

With other words, you want 2 responses so you need to fire 2 requests. You cannot mix 2 responses (the popup and the pdf) into response.

You can use javascript in the onclick event to open a popup.

spand said...

Hello BalusC,

How do I change this to download multiple files?

Thanks,

Sonia

BalusC said...

Add multiple h:inputFileUpload fields.

Or if you mean to select multiple files at once, then you need to know that you can't do that with HTML input type="file" and thus also not with JSF. Best option would be an applet, webstart or flash thing. In case of applet/webstart, the Swing's JFileChooser can select multiple files at once. If I am correct RichFaces and/or PrimeFaces have a component for this.

kokhandiar said...

1. I have a commandLink in a JSF page in a portal environment. On clicking it, I redirect to a servlet (by calling facesContext.getExternalcontext().sendRedirect(//Servlet URL//)).

2. the servlet is created for downloading an excel file from the server.
3. A Save As File dialog opens up with the JSF Portal page in the background.

This works fine, but when I have downloaded the file, I find that when I click on any action component on the portal JSF page, it just refreshes the page instead of performing the action. I have to click that action component again for it to work.

Wehn I used a PhaseListener to track the JSF lifecycle, I find that after the redirect, Step 6, i.e.renderResponse phase is missing. Could this be the issue? How can I resolve this?

Pierre said...

Hi BaluC
I have to download files from a Domino server to a PC. Applet communicates with Servlet that uses your code. Unfortunately, when I run the servlet, I get the following message: HTTP JVM: java.lang.NoSuchMethodError: javax.servlet.http.HttpServletResponse: method reset()V not found:
Any idea?

Thanks in advance
Pierre

BalusC said...

Maybe you're using an old or proprietary Servlet API version. You can safely remove it, you should only ensure that the repsonse isn't modified by some other filter/servlet beforehand.

adi_f said...

Hello. Firstly, thanks for the great tutorials on web development.
I used yout FileServlet for serving a client with a file. All works perfect, but when I have bigger files( > 25 MB) I get an OutOfMemoryException.
Why is that?
I have an JSF 2.0 Application(Tomahawk) and Glassfish 2.1.

Thanks again

BalusC said...

This isn't possible unless you changed the code to have the entire file in a byte[] instead of InputStream.

Swapna said...

Hi BalusC,
How do i call the FileServlet from my jsf page? I used your code and when i click on the command link, it just displays the file without giving the dialog to open or save it.

BalusC said...

Use outputlink, not commandlink.

Swapna said...
This comment has been removed by the author.
BalusC said...

Wait, it is an image? Then apparently the webbrowser itself is the default associated application. Try other types of files and you'll see that it works.

Swapna said...

i have tried with outputlink also.
I have given value in my outputlink like "../images/folder.gif" as i have my file in images folder
i have configured my web.xml as mentioned by you.

what could be the problem?

Swapna said...

Thank u for the reply.
Actually the problem was with my servlet mapping.

Swapna said...

Hi BalusC,
I have one more problem. I need to open the save dialog on click of a menuitem. I have tried the below code but it opens the image
String url="myfile.download?fileID=12\\abc\\12\\Chery-Altair.png";
context.getExternalContext().dispatch(url);

Is there a way where i can call it from the actionlistener of the menu item.

BalusC said...

Do a redirect to the image.

Swapna said...

Thanks a lot..
It works now

adi_f said...

Hi , I posted a few days ago a comment. I use your FileServlet, but by larger files I get this error:

type Exception report

message

descriptionThe server encountered an internal error () that prevented it from fulfilling this request.

exception

javax.servlet.ServletException: PWC1243: Filter execution threw an exception

root cause

java.lang.OutOfMemoryError: Java heap space

I use Glassfish 1.2

This doesen't happend from the first time, but by the 3. or 4. big file, the heap space gets full. How can I avoid this?
Thanks.

BalusC said...

@adi: I have already answered this: "This isn't possible unless you changed the code to have the entire file in a byte[] instead of InputStream.". Another cause may be that you're retrieving it from the database and that the JDBC driver is "behind the scenes" holding it in a byte[] instead of InputStream.

adi_f said...

I didn't change the code:

File file = new File(SOME_FILE_PATH);
context = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();

ServletContext servletContext = (ServletContext) context.getExternalContext().getContext();

String contentType = servletContext.getMimeType(file.getName());
if (contentType == null) {
contentType = "application/octet-stream";
}
response.reset();

response.setBufferSize(DEFAULT_BUFFER_SIZE);
response.setHeader("Content-Type", contentType);
response.setHeader("Content-Length", String.valueOf(file.length()));
response.setHeader("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"");
// Open streams.
input = new BufferedInputStream(new FileInputStream(file), DEFAULT_BUFFER_SIZE);
output = new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE);

// Write file contents to response.
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);

// Finalize task.
output.flush();
}
[...]
FileUtil.close(output);
FileUtil.close(input);
context.responseComplete();

As you see, I don't hold the files in a DB...
This is really strange

PhongTn said...

Thanks, your article is wonderful, please allow me to keep it as private lessons for myself!

abcabc said...

Hello BalusC,

I have also written similiar code but problem is

Below is my code

HttpServletResponse response = ((BCDActionHttpContext)ctx).getResponse();
response.setContentType( "application/x-downloadx-download" + "; charset=UTF-8" );
response.setHeader( "Content-Disposition", filename );

try
{
PrintWriter output = response.getWriter();
output.write( stack );
}

problem is my application is able to download over HTTP but we deploy it on https protocol so it fails when its on server


any idea why it fails for HTTPS protocol.

Please help!!!

dEV said...

Hello BalusC,
It would be of really great help if you can tell me how to download a file with the filename in some other language than english..like chinese or japanese...
the name of the file gets converted to some wiered characters in the browser save-as message pop up box...
thanx

Unknown said...

Thank you very much. I learned a lot from it.
Meir

Marc said...

Hey BalusC thank you very much, you saved my ass ^.^

Mohsin Khan said...

HI BalusC,

I am able to export the report in pdf format. The problem is it is opening in the same window. Save dialog to save the pdf file is not coming. What may be the problem.

BalusC said...

This can have 2 causes:

1) Content-Disposition header is not set to "attachment" (the code example in this blog does that correctly).

2) The browser is configured to skip the save as dialog and open it immediately. In Firefox for example this is configureable in Tools > Options > Applications.

Mohsin Khan said...

My code is

ServletContext context = (ServletContext) externalContext.getContext();
HttpServletRequest request = (HttpServletRequest) externalContext.getRequest();
HttpServletResponse response = (HttpServletResponse) externalContext.getResponse();


ReportConfigUtil.compileReport(context, getCompileDir(), getCompileFileName());

File reportFile = new File(ReportConfigUtil.getJasperFilePath(context, getCompileDir(), getCompileFileName()+".jasper"));
String fileName = reportFile.getName();
System.out.println(" fileName >> "+fileName);
JasperPrint jasperPrint = ReportConfigUtil.fillReport(reportFile, getReportParameters(), getJRDataSource());

if(getExportOption().equals(ExportOption.HTML)) {
ReportConfigUtil.exportReportAsHtml(jasperPrint, response.getWriter());
}else {
request.getSession().setAttribute(BaseHttpServlet.DEFAULT_JASPER_PRINT_SESSION_ATTRIBUTE, jasperPrint);
response.setHeader("Content-Disposition", "attachment; filename=\"" + reportFile.getName() + "\"");
response.sendRedirect(request.getContextPath()+"/servlets/report/"+getExportOption());
}

Mohsin Khan said...

Hi

See this link., it will be usefull
it solved my problem.

http://community.jboss.org/message/530330

Unknown said...

Hi BaluSC,
great post as always;-)
I have a little problem, cause when I send the file the browser doesn't open it but displays squares, little faces and so on.
I'm currently using a JSF 2 environment with JBOSS richFaces.
Here is my code :
try {
File f = new File(ESReIn.getRptAbsPath());
if (!f.exists()) {
return false;
}
final int DEFAULT_BUFFER_SIZE = 10240;
FacesContext faces = FacesContext.getCurrentInstance();
HttpServletResponse response =
(HttpServletResponse) faces.getExternalContext().getResponse();
String contentType = faces.getExternalContext().getMimeType(f.getName());

response.reset();
response.setBufferSize(DEFAULT_BUFFER_SIZE);
response.setContentType(contentType);
response.setHeader("Content-Length", String.valueOf(ESReIn.getRptSize()));
response.setHeader("Content-Disposition", "attachment; filename=\"" + f.getName() + "\"");

BufferedInputStream input = null;
BufferedOutputStream output = null;

try {
input = new BufferedInputStream(new FileInputStream(f), DEFAULT_BUFFER_SIZE);
output = new BufferedOutputStream(response.getOutputStream(), DEFAULT_BUFFER_SIZE);

byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
int length;
while ((length = input.read(buffer)) > 0) {
output.write(buffer, 0, length);
}
} finally {
// Gently close streams.
output.close();
input.close();
}

faces.responseComplete();
return true;
} catch (Exception e) {
return false;
}

Unknown said...

Hi all,
sorry for the wasted space, i found the solution on the link http://community.jboss.org/message/530330 provided by Mohsin Khan.
My problem was that i used a4j:commandLink tag to submit the request and this is obviously an ajax submit ; changing to h:commandLink tag solved the problem.
Thanks to all.
Flavio

eblogger said...

My app allows the user to upload a PDF file & makes it available for download from another screen.

The production environment is clustered.

Since I am storing the PDFs on the server (not DB), how can I ensure that the files will be available for download independent of which server the user is currently on?

e3gar said...

i have a problem, when I click on the h:outputlink, i get a new white page with this on the bar address http://3dgar-pc:8080/images/dynamic/?file=1/ReportePBecas.pdf

i do not get the message with the opcion save as

David said...

Hello BalusC !

First of all, I added your FileServlet class to a Web Application under Netbeans, Struts 2 and GlassfishServer 3

The AppServer (glassfish) runs in the D: volume, so I put a "files" directory right there:

D:\files\

Now when I execute the requests with the filenames it returns a 404 message:

"The requested resource () is not available."

Is there a difference in running this under Glassfish?

David said...

Hello BalusC !

First of all, I added your FileServlet class to a Web Application under Netbeans, Struts 2 and GlassfishServer 3

The AppServer (glassfish) runs in the D: volume, so I put a "files" directory right there:

D:\files\

Now when I execute the requests with the filenames it returns a 404 message:

"The requested resource () is not available."

Is there a difference in running this under Glassfish?

ponzetti said...

Hey Balus C.
Seem Internet Explorer ignore completely your header code.
The code works fine under firefox en chrome but ie (8 and 9) open the document in a new page without recognize the file with the specifyied content in the response header and opens it like text. I tryied to track down this strange behaviour and I discover, analyzing http headers that in ie there isnt the configured headers, like it dont reach the browser.
Have an idea about this?

Traduce said...

This code is really rocks ,thanks balus.............awesome

telman said...

File file = new File(filePath, URLDecoder.decode(requestedFile, "UTF-8"));

I think that after decode we need to encode too?
for example when we use turkish chars
String downloadFileName = "ığüşiöç";
we get empty chars at ı,ğ,ş without encode.

downloadFileName = URLEncoder.encode(downloadFileName, "UTF-8");

solve the issue

regards,
telman

Unknown said...

You can easily make your long path file copied just by using long path tool. Its really works.

Unknown said...

Hello BalusC,
This code works great when i am in server embedded development mode in eclipse.
But when i deployed then on tomcat7, it doesn't work anymore (404 error)...strange behaviour , what could be the issue ?

Unknown said...

Hey,

In the init() method, would there be a way to access another server (shared folder), say W:\, instead of c:\ environment?


Thanks!
-Oakes

Unknown said...
This comment has been removed by the author.
Unknown said...

why not use github for this servlet ?

Unknown said...

Muchas gracias hice uan modificaciones para consumir de un ftp .. muy agradecido

John Coleman said...

Hi BalusC,
nice one - thanks a lot. It seems the solution is always 'out there' - if you ask the right question.
:-)

qasim said...

Hi balusC, can you tell me if the file path is coming from managed bean how can I use this servlet then ? where do I pass the file path in the servlet

milan said...

First of all, @balusC, God bless you!

@qasim, you can use method redicrect from ExternalContext -
FacesContext.getCurrentInstance().getExternalContext(), and pass the path. Just make sure you have passed "../file/" (in this case), and then your file path.

Unknown said...

Hi balusC, Can you please tell me the filepath i need to upload the images? In windows it should be C:/files (folder) ? And what about ubuntu? Where it should be saved? I'm not able to follow as you've mentioned in web.xml as files/* as URL Pattern, what does this mean? Is it the path C:/files ? or files:///C:/ like this?

Ehab Abu Rezeq Al Nawajha said...

Still helpful in 2023.
Thanks.