web-dev-qa-db-fra.com

Comment retourner une image PNG de Jersey REST service au navigateur

J'ai un serveur Web fonctionnant avec Jersey REST ressources et je me demande comment obtenir une référence image/png pour la balise img des navigateurs; après avoir soumis un formulaire ou obtenu une réponse Ajax. Le traitement de l'image le code pour l’ajout de graphiques fonctionne, il suffit de le retourner d’une manière ou d’une autre.

Code:

@POST
@Path("{fullsize}")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces("image/png")
// Would need to replace void
public void getFullImage(@FormDataParam("photo") InputStream imageIS,
                         @FormDataParam("submit") String extra) {

      BufferedImage image = ImageIO.read(imageIS);

      // .... image processing
      //.... image processing

      return ImageIO.  ..  ?

}

À votre santé

46
gorn

Je ne suis pas convaincu que ce soit une bonne idée de renvoyer les données d'image dans un service REST. Cela bloque la mémoire de votre serveur d'applications et IO la bande passante. déléguez cette tâche à un serveur Web approprié optimisé pour ce type de transfert (vous pouvez y parvenir en envoyant une redirection vers la ressource image (sous la forme d'une réponse HTTP 302 avec l'URI de l'image). Cela suppose bien entendu que vos images sont organisés en tant que contenu Web.

Cela dit, si vous décidez que vous devez réellement transférer des données d'image à partir d'un service Web, vous pouvez le faire avec le code (pseudo) suivant:

@Path("/whatever")
@Produces("image/png")
public Response getFullImage(...) {

    BufferedImage image = ...;

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ImageIO.write(image, "png", baos);
    byte[] imageData = baos.toByteArray();

    // uncomment line below to send non-streamed
    // return Response.ok(imageData).build();

    // uncomment line below to send streamed
    // return Response.ok(new ByteArrayInputStream(imageData)).build();
}

Ajouter dans la gestion des exceptions, etc. etc.

91
Perception

J'ai construit une méthode générale pour cela avec les fonctionnalités suivantes:

  • renvoyer "non modifié" si le fichier n'a pas été modifié localement, un Status.NOT_MODIFIED est envoyé à l'appelant. Usages Apache Commons Lang
  • en utilisant un objet flux de fichier au lieu de lire le fichier lui-même

Voici le code:

import org.Apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Utils.class);

@GET
@Path("16x16")
@Produces("image/png")
public Response get16x16PNG(@HeaderParam("If-Modified-Since") String modified) {
    File repositoryFile = new File("c:/temp/myfile.png");
    return returnFile(repositoryFile, modified);
}

/**
 * 
 * Sends the file if modified and "not modified" if not modified
 * future work may put each file with a unique id in a separate folder in Tomcat
 *   * use that static URL for each file
 *   * if file is modified, URL of file changes
 *   * -> client always fetches correct file 
 * 
 *     method header for calling method public Response getXY(@HeaderParam("If-Modified-Since") String modified) {
 * 
 * @param file to send
 * @param modified - HeaderField "If-Modified-Since" - may be "null"
 * @return Response to be sent to the client
 */
public static Response returnFile(File file, String modified) {
    if (!file.exists()) {
        return Response.status(Status.NOT_FOUND).build();
    }

    // do we really need to send the file or can send "not modified"?
    if (modified != null) {
        Date modifiedDate = null;

        // we have to switch the locale to ENGLISH as parseDate parses in the default locale
        Locale old = Locale.getDefault();
        Locale.setDefault(Locale.ENGLISH);
        try {
            modifiedDate = DateUtils.parseDate(modified, org.Apache.http.impl.cookie.DateUtils.DEFAULT_PATTERNS);
        } catch (ParseException e) {
            logger.error(e.getMessage(), e);
        }
        Locale.setDefault(old);

        if (modifiedDate != null) {
            // modifiedDate does not carry milliseconds, but fileDate does
            // therefore we have to do a range-based comparison
            // 1000 milliseconds = 1 second
            if (file.lastModified()-modifiedDate.getTime() < DateUtils.MILLIS_PER_SECOND) {
                return Response.status(Status.NOT_MODIFIED).build();
            }
        }
    }        
    // we really need to send the file

    try {
        Date fileDate = new Date(file.lastModified());
        return Response.ok(new FileInputStream(file)).lastModified(fileDate).build();
    } catch (FileNotFoundException e) {
        return Response.status(Status.NOT_FOUND).build();
    }
}

/*** copied from org.Apache.http.impl.cookie.DateUtils, Apache 2.0 License ***/

/**
 * Date format pattern used to parse HTTP date headers in RFC 1123 format.
 */
public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";

/**
 * Date format pattern used to parse HTTP date headers in RFC 1036 format.
 */
public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss zzz";

/**
 * Date format pattern used to parse HTTP date headers in ANSI C
 * <code>asctime()</code> format.
 */
public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";

public static final String[] DEFAULT_PATTERNS = new String[] {
    PATTERN_RFC1036,
    PATTERN_RFC1123,
    PATTERN_ASCTIME
};

Notez que le changement de paramètres régionaux ne semble pas être thread-safe. Je pense qu'il est préférable de changer les paramètres régionaux globalement. Je ne suis toutefois pas sûr des effets secondaires ...

12
koppor

en ce qui concerne la réponse de @Perception, il est vrai que vous devez utiliser beaucoup de mémoire lorsque vous travaillez avec des tableaux d'octets, mais vous pouvez aussi simplement écrire dans le flux de sortie

@Path("/picture")
public class ProfilePicture {
  @GET
  @Path("/thumbnail")
  @Produces("image/png")
  public StreamingOutput getThumbNail() {
    return new StreamingOutput() {
      @Override
      public void write(OutputStream os) throws IOException, WebApplicationException {
        //... read your stream and write into os
      }
    };
  }
}
7
comeGetSome

Si vous avez plusieurs méthodes de ressources image, il est utile de créer un MessageBodyWriter pour générer BufferedImage:

@Produces({ "image/png", "image/jpg" })
@Provider
public class BufferedImageBodyWriter implements MessageBodyWriter<BufferedImage>  {
  @Override
  public boolean isWriteable(Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
    return type == BufferedImage.class;
  }

  @Override
  public long getSize(BufferedImage t, Class<?> type, Type type1, Annotation[] antns, MediaType mt) {
    return -1; // not used in JAX-RS 2
  }

  @Override
  public void writeTo(BufferedImage image, Class<?> type, Type type1, Annotation[] antns, MediaType mt, MultivaluedMap<String, Object> mm, OutputStream out) throws IOException, WebApplicationException {
    ImageIO.write(image, mt.getSubtype(), out);
  } 
}

Ce MessageBodyWriter sera utilisé automatiquement si la détection automatique est activée pour Jersey. Sinon, il doit être renvoyé par une sous-classe Application personnalisée. Voir Fournisseurs d'entités JAX-RS pour plus d'informations.

Une fois que cela est configuré, renvoyez simplement une BufferedImage à partir d'une méthode de ressource et elle sera sortie en tant que données de fichier image:

@Path("/whatever")
@Produces({"image/png", "image/jpg"})
public Response getFullImage(...) {
  BufferedImage image = ...;
  return Response.ok(image).build();
}

Quelques avantages de cette approche:

  • Il écrit dans la réponse OutputSteam plutôt qu’un intermédiaire BufferedOutputStream
  • Il prend en charge les sorties png et jpg (en fonction des types de média autorisés par la méthode de ressource)
6
Justin Emery