Google App Engine

Serving Dynamic Images with Google App Engine (Java)

Austin Chau, Jason Cooper
April 2008
, updated March 2010 for Java

Note: This article is also available for Python.

A picture is worth a thousand words. This is definitely true with today's web applications where images play a big part in reaching the audience. App Engine allows you to store and serve images quickly and easily through its datastore. In this short article, I will walk you through how to store and serve images in App Engine's datastore using code snippets for storing and retrieving movie information.

Storing Images in the Datastore

For starters, we would like to store each movie in the application as an object of the datastore. This is accomplished by defining a simple Java class that models a movie. For this project, we will use Java Data Objects (JDO), a standard interface for storing Java data objects in a persistent database. A Java Persistence API (JPA) wrapper is also available, or you can use the low-level API.

The Movie class below defines all the attributes a Movie object would have. It's a standard Java class peppered with JDO annotations to describe how the various members should be persisted. (For additional background on these annotations, see Using JDO.) One of the fields, image, will contain the actual bytes that make up the movie's thumbnail image. Therefore, we need to declare the image as a Blob, which you can think of as a large array of bytes.

Note: Do not confuse this with the Blobstore API which can also be used to store large data objects such as images but does not do so via the datastore. This article uses the datastore exclusively.

package com.google.appengine.demo.domain;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.Key;

import javax.jdo.annotations.Extension;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

/**
 * JDO-annotated model class for storing movie properties; movie's promotional
 * image is stored as a Blob (large byte array) in the image field.
 */
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Movie {

    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Key key;

    @Persistent
    private String title;

    @Persistent
    @Extension(vendorName="datanucleus", key="gae.unindexed", value="true")
    private String imageType;

    @Persistent
    private Blob image;

    //...

    public Long getId() {
        return key.getId();
    }

    public String getTitle() {
        return title;
    }

    public String getImageType() {
        return imageType;
    }

    public byte[] getImage() {
        if (image == null) {
            return null;
        }

        return image.getBytes();
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setImageType(String imageType) {
        this.imageType = imageType;
    }

    public void setImage(byte[] bytes) {
        this.image = new Blob(bytes);
    }

    //...
}

Now that we have the model defined, we can start using it to store movie objects. We can get movie data from a remote source and feed it into the application. To do this, we'll use App Engine's URL Fetch API which can retrieve data from HTTP and HTTPS URLs. Although App Engine for Java includes a familiar java.net wrapper on top of the URL Fetch service, we'll use the low-level API below. Just specify or otherwise retrieve a reference to the image URL, then pass it into a URLFetchService object's fetch method to fetch the binary content of a remote image.

Next, create a new Movie object and pass the fetched bytes into setImage, which creates a new Blob object. We'll also store the MIME type of the image (e.g. image/jpg, image/png, etc.) which should be returned in the 'Content-Type' header. makePersistent is then called to commit the change to the datastore.

package com.google.appengine.demo.web;

import com.google.appengine.api.urlfetch.HTTPHeader;
import com.google.appengine.api.urlfetch.HTTPResponse;
import com.google.appengine.api.urlfetch.URLFetchService;
import com.google.appengine.api.urlfetch.URLFetchServiceFactory;
import com.google.appengine.demo.domain.Movie;
import com.google.appengine.demo.repo.PMF;

import java.io.IOException;
import java.net.URL;

import javax.jdo.PersistenceManager;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * GET requests fetch the image at the URL specified by the url query string
 * parameter, then persist this image along with the title specified by the
 * title query string parameter as a new Movie object in App Engine's
 * datastore.
 */
public class StoreMovieServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        URLFetchService fetchService =
            URLFetchServiceFactory.getURLFetchService();

        // Fetch the image at the location given by the url query string parameter
        HTTPResponse fetchResponse = fetchService.fetch(new URL(
                req.getParameter("url")));
        
        String fetchResponseContentType = null;
        for (HTTPHeader header : fetchResponse.getHeaders()) {
            // For each request header, check whether the name equals
            // "Content-Type"; if so, store the value of this header
            // in a member variable
            if (header.getName().equalsIgnoreCase("content-type")) {
                fetchResponseContentType = header.getValue();
                break;
            }
        }

        if (fetchResponseContentType != null) {
            // Create a new Movie instance
            Movie movie = new Movie();
            movie.setTitle(req.getParameter("title"));
            movie.setImageType(fetchResponseContentType);

            // Set the movie's promotional image by passing in the bytes pulled
            // from the image fetched via the URL Fetch service
            movie.setImage(fetchResponse.getContent());

            //...

            PersistenceManager pm = PMF.get().getPersistenceManager();
            try {
                // Store the image in App Engine's datastore
                pm.makePersistent(movie);
            } finally {
                pm.close();
            }
        }
    }
}

Retrieving & Displaying Images

Now that the objects are populated in our Movie datastore, let's set up a request handler to retrieve them dynamically from the datastore and render the objects back to the browser as images.

First, we create a new servlet, GetImageServlet, to define the GET request handler. The first thing this request handler does is retrieve the value of 'title' from the URL parameter. Then it retrieves the Movie object that matches the title from the datastore by calling getMovie, which is defined a bit later.

Once we have the reference to the Movie object, we can access its image field. In order for the browser to properly render the image, we set our response's HTTP Content-Type header to the value we extracted earlier when originally storing the Movie object.

package com.google.appengine.demo.web;

import com.google.appengine.demo.domain.Movie;
import com.google.appengine.demo.repo.PMF;

import java.io.IOException;
import java.util.List;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * GET requests return the promotional image associated with the movie with the
 * title specified by the title query string parameter.
 */
public class GetImageServlet extends HttpServlet {

    @Override
    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        String title = req.getParameter("title");
        Movie movie = getMovie(title);

        if (movie != null && movie.getImageType() != null &&
                movie.getImage() != null) {
            // Set the appropriate Content-Type header and write the raw bytes
            // to the response's output stream
            resp.setContentType(movie.getImageType());
            resp.getOutputStream().write(movie.getImage());
        } else {
            // If no image is found with the given title, redirect the user to
            // a static image
            resp.sendRedirect("/static/noimage.jpg");
        }
    }

    //...

The getMovie method uses JDO's query API to retrieve a Movie object that matches the title.

/**
 * Queries the datastore for the Movie object with the passed-in title. If
 * found, returns the Movie object; otherwise, returns null.
 *
 * @param title movie title to look up
 */
private Movie getMovie(String title) {
    PersistenceManager pm = PMF.get().getPersistenceManager();

    // Search for any Movie object with the passed-in title; limit the number
    // of results returned to 1 since there should be at most one movie with
    // a given title
    Query query = pm.newQuery(Movie.class, "title == titleParam");
    query.declareParameters("String titleParam");
    query.setRange(0, 1);

    try {
        List<Movie> results = (List<Movie>) query.execute(title);
        if (results.iterator().hasNext()) {
            // If the results list is non-empty, return the first (and only)
            // result
            return results.get(0);
        }
    } finally {
        query.closeAll();
        pm.close();
    }

    return null;
}

To make it easy to access the images of the movies in the application, we can map the GetImageServlet class to the URL path '/image'. In Java, we do this via the standard web.xml deployment descriptor file:

<?xml version="1.0" encoding="utf-8"?>
<web-app
  xmlns="http://java.sun.com/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
  version="2.5">
  <servlet>
    <servlet-name>StoreMovie</servlet-name>
    <servlet-class>com.google.appengine.demo.web.StoreMovieServlet</servlet-class>
  </servlet>
  <servlet>
    <servlet-name>GetImage</servlet-name>
    <servlet-class>com.google.appengine.demo.web.GetImageServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>StoreMovie</servlet-name>
    <url-pattern>/addMovie</url-pattern>
  </servlet-mapping>
  <servlet-mapping>
    <servlet-name>GetImage</servlet-name>
    <url-pattern>/image</url-pattern>
  </servlet-mapping>
</web-app>

Now when a visitor visits the URL - http://mydomain.com/image?title=matrix, an image for the movie "matrix" is returned to the browser.

Resources

We have just shown you how you can easily serve images from your Google App Engine's datastore. Now it's time for you to get your hands dirty with code! Check out the resources below to get started on building your next great app:

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.