Sunday, April 8, 2007

ImageServlet

Serve your images

Update: there's a newer article out for more effective file serving. You may find it useful: FileServlet supporting resume and caching and GZIP.

If you have stored images in a path outside of the web container or in a database, then the client cannot access the images directly by a relative URI. A good practice is to create a Servlet which loads the image from a path outside of the web container or from a database and then streams the image to the HttpServletResponse. 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 when the user want to save the image in certain web browsers by rightclicking and saving it (Internet Explorer and so on).

Back to top

ImageServlet serving from absolute path

Here is a basic example of a ImageServlet which serves a image from a path outside of the web container.

package com.example.controller;

import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.file.Files;

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

/**
 * The Image servlet for serving from absolute path.
 * @author BalusC
 * @link http://balusc.blogspot.com/2007/04/imageservlet.html
 */
@WebServlet("/image/*")
public class ImageServlet extends HttpServlet {

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

    private String imagePath;

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

    public void init() throws ServletException {

        // Define base path somehow. You can define it as init-param of the servlet.
        this.imagePath = "/var/webapp/images";

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

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

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

        // Get requested image by path info.
        String requestedImage = request.getPathInfo();

        // Check if file name is actually supplied to the request URI.
        if (requestedImage == null) {
            // Do your thing if the image is not supplied to the request URI.
            // Throw an exception, or send 404, or show default/warning image, 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 image = new File(imagePath, URLDecoder.decode(requestedImage, "UTF-8"));

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

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

        // Check if file is actually an image (avoid download of other files by hackers!).
        // For all content types, see: http://www.w3schools.com/media/media_mimeref.asp
        if (contentType == null || !contentType.startsWith("image")) {
            // Do your thing if the file appears not being a real image.
            // Throw an exception, or send 404, or show default/warning image, or just ignore it.
            response.sendError(HttpServletResponse.SC_NOT_FOUND); // 404.
            return;
        }

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

        // Write image content to response.
        Files.copy(image.toPath(), response.getOutputStream());
    }

}

Here are some basic use examples:

<!-- JSP -->
<img src="${pageContext.request.contextPath}/image/test.jpg" />
<img src="${pageContext.request.contextPath}/image/test.gif" />

<!-- Facelets -->
<img src="#{request.contextPath}/image/test.jpg" />
<img src="#{request.contextPath}/image/#{myBean.imageFileName}" />

<!-- JSF -->
<h:graphicImage value="image/test.jpg" />
<h:graphicImage value="image/test.gif" />
<h:graphicImage value="image/#{myBean.imageFileName}" />
Back to top

ImageServlet serving from database

In this simple example we assume "plain JDBC" (instead of EJB/JPA). First prepare a DTO (Data Transfer Object) for Image which can be used to hold information about the image. You can map this DTO to the database and use a DAO class to obtain it. You can get the image as byte[] from the database using ResultSet#getBytes().

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

package com.example.model;

public class Image {

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

    private String id;
    private String name;
    private String contentType;
    private byte[] content;

    // Implement default getters and setters here.

}

Here is a basic example of a ImageServlet which serves a image from a database.

package com.example.controller;

import java.io.IOException;

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

import com.example.dao.DAOFactory;
import com.example.dao.ImageDAO;
import com.example.model.Image;

/**
 * The Image servlet for serving from database.
 * @author BalusC
 * @link http://balusc.blogspot.com/2007/04/imageservlet.html
 */
@WebServlet("/image/*")
public class ImageServlet extends HttpServlet {

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

    private ImageDAO imageDAO;

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

    public void init() throws ServletException {
        this.imageDAO = DAOFactory.getImageDAO();
    }

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

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

        // Get ID from request.
        String imageId = request.getParameter("id");

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

        // Lookup Image by ImageId in database.
        // Do your "SELECT * FROM Image WHERE ImageID" thing.
        Image image = imageDAO.find(imageId);

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

        // Init servlet response.
        response.reset();
        response.setContentType(image.getContentType());
        response.setContentLength(image.getContent().length);
        response.setHeader("Content-Disposition", "inline; filename=\"" + image.getName() + "\"");

        // Write image content to response.
        response.getOutputStream().write(image.getContent());
    }

}

Here are some basic use examples:

<!-- JSP -->
<img src="${pageContext.request.contextPath}/image?id=250d7f5086d02a46f9aeec9c51d43c63" />

<!-- Facelets -->
<img src="#{request.contextPath}/image?id=250d7f5086d02a46f9aeec9c51d43c63" />
<img src="#{request.contextPath}/image?id=#{myBean.imageId}" />

<!-- JSF -->
<h:graphicImage value="image?id=250d7f5086d02a46f9aeec9c51d43c63" />
<h:graphicImage value="image?id=#{myBean.imageId}" />
Back to top

Security considerations

In the last example of an ImageServlet 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 images 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 image by ID from database, otherwise you will risk an SQL injection when a hacker calls for example "image?id=';TRUNCATE TABLE Image--".

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) April 2007, BalusC