46

How should I implement simple file download servlet?

The idea is that with the GET request index.jsp?filename=file.txt, the user can download for example. file.txt from the file servlet and the file servlet would upload that file to user.

I am able to get the file, but how can I implement file download?

alvinarul
  • 301
  • 4
  • 15
newbie
  • 24,286
  • 80
  • 201
  • 301
  • 4
    You should give serious consideration to the security risks involved. See for example https://www.owasp.org/index.php/Path_Traversal – Mark W May 20 '16 at 15:43

5 Answers5

66

Assuming you have access to servlet as below

http://localhost:8080/myapp/download?id=7

I need to create a servlet and register it to web.xml

web.xml

<servlet>
     <servlet-name>DownloadServlet</servlet-name>
     <servlet-class>com.myapp.servlet.DownloadServlet</servlet-class>
</servlet>
<servlet-mapping>
     <servlet-name>DownloadServlet</servlet-name>
     <url-pattern>/download</url-pattern>
</servlet-mapping>

DownloadServlet.java

public class DownloadServlet extends HttpServlet {


    protected void doGet( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

         String id = request.getParameter("id");

         String fileName = "";
         String fileType = "";
         // Find this file id in database to get file name, and file type

         // You must tell the browser the file type you are going to send
         // for example application/pdf, text/plain, text/html, image/jpg
         response.setContentType(fileType);

         // Make sure to show the download dialog
         response.setHeader("Content-disposition","attachment; filename=yourcustomfilename.pdf");

         // Assume file name is retrieved from database
         // For example D:\\file\\test.pdf

         File my_file = new File(fileName);

         // This should send the file to browser
         OutputStream out = response.getOutputStream();
         FileInputStream in = new FileInputStream(my_file);
         byte[] buffer = new byte[4096];
         int length;
         while ((length = in.read(buffer)) > 0){
            out.write(buffer, 0, length);
         }
         in.close();
         out.flush();
    }
}
Ali Irawan
  • 2,094
  • 2
  • 18
  • 23
  • 2
    Note that the "read" while condition should use -1 and not 0: `while ((length = in.read(buffer)) > -1)` – reformy Jul 10 '14 at 13:12
  • 1
    Use annotation @WebServlet for simplify the code @WebServlet(description = "Download Servlet", urlPatterns = {"/download"}) – Mise Jan 29 '16 at 20:05
  • 1
    If the ID parameter is not a hash, an attacker could try to guess other IDs and get files from your database. Also, it is good to validate that the received ID does not contain any SQL Injection. – Cristian Arteaga Aug 03 '18 at 23:27
57

That depends. If said file is publicly available via your HTTP server or servlet container you can simply redirect to via response.sendRedirect().

If it's not, you'll need to manually copy it to response output stream:

OutputStream out = response.getOutputStream();
FileInputStream in = new FileInputStream(my_file);
byte[] buffer = new byte[4096];
int length;
while ((length = in.read(buffer)) > 0){
    out.write(buffer, 0, length);
}
in.close();
out.flush();

You'll need to handle the appropriate exceptions, of course.

ChssPly76
  • 99,456
  • 24
  • 206
  • 195
  • 12
    what's about content-disposition and content-type? – Udo Aug 12 '13 at 15:37
  • 4
    Note that the "read" while condition should use -1 and not 0: `while ((length = in.read(buffer)) > -1)` – reformy Jul 10 '14 at 13:13
  • @reformy 0 works just as well. If the number of bytes read was 0, there's nothing to write to output. – ChssPly76 Jul 10 '14 at 16:30
  • 1
    @ChssPly76 The doc says _-1 if there is no more data because the end of the file has been reached_. Why take the risk? maybe the disk is unavailable for a second so you get 0 but suppose to continue reading? – reformy Jul 10 '14 at 19:34
  • 2
    @reformy Perhaps you should read the documentation fully before you quote it here. Pay special attention to phrases like "This method blocks until input data is available" and "at least one byte is read and stored". – ChssPly76 Jul 11 '14 at 20:21
  • Does this have performance or scalability implications, since a Thread in the application is kept open to allow this download ? Is there a more robust approach, while allowing the link to be only available to this user ? – abdelrahman-sinno Jan 12 '16 at 14:22
  • I am new to java - what is the meaning of `new byte[4096]` is that `4MB` memory allocation – Veshraj Joshi Apr 24 '18 at 13:59
  • @VeshrajJoshi, 4096 BYTES ~ 4KB – Turkhan Badalov Nov 29 '18 at 16:04
11

Try with Resource

File file = new File("Foo.txt");
try (PrintStream ps = new PrintStream(file)) {
   ps.println("Bar");
}
response.setContentType("application/octet-stream");
response.setContentLength((int) file.length());
response.setHeader( "Content-Disposition",
         String.format("attachment; filename=\"%s\"", file.getName()));

OutputStream out = response.getOutputStream();
try (FileInputStream in = new FileInputStream(file)) {
    byte[] buffer = new byte[4096];
    int length;
    while ((length = in.read(buffer)) > 0) {
        out.write(buffer, 0, length);
    }
}
out.flush();
Sorter
  • 9,704
  • 6
  • 64
  • 74
  • 2
    "out" must be inside try() block too. And so out.flush(). – Simon Logic Sep 18 '17 at 19:22
  • 1
    this approach is really useful when you need to download a big file (more than 5MB for example). If you don't specify the content length the client could close the TCP connection with your server, and then you get a SocketException: Connection reset by peer: socket write error – FelipeCaparelli Mar 25 '19 at 09:41
2

The easiest way to implement the download is that you direct users to the file location, browsers will do that for you automatically.

You can easily achieve it through:

HttpServletResponse.sendRedirect()
Winston Chen
  • 6,799
  • 12
  • 52
  • 81
0

And to send a largFile

byte[] pdfData = getPDFData();

String fileType = "";

res.setContentType("application/pdf");

httpRes.setContentType("application/.pdf");
httpRes.addHeader("Content-Disposition", "attachment; filename=IDCards.pdf");
httpRes.setStatus(HttpServletResponse.SC_OK);
OutputStream out = res.getOutputStream();
System.out.println(pdfData.length);
         
out.write(pdfData);
System.out.println("sendDone");
out.flush();
Hamid Ali
  • 875
  • 1
  • 8
  • 22