web-dev-qa-db-ja.com

JerseyからPNG画像を返す方法RESTサービスメソッドをブラウザに

Jersey REST resources up)で実行しているWebサーバーがあり、フォームを送信した後、またはAjax応答を取得した後、ブラウザーのimgタグのimage/png参照を取得する方法を疑問に思います。グラフィックを追加するためのコードは機能しています。どうにかして返す必要があります。

コード:

@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.  ..  ?

}

乾杯

46
gorn

REST=サービス。アプリケーションサーバーのメモリとIO帯域幅。この種の転送に最適化された適切なWebサーバーにそのタスクを委任します(画像のURIを持つHTTP 302応答として)画像リソースにリダイレクトを送信することでこれを達成できます。 Webコンテンツとして配置されます。

そうは言っても、Webサービスから画像データを本当に転送する必要があると判断した場合は、次の(擬似)コードを使用して転送できます。

@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();
}

例外処理などを追加します。

91
Perception

次の機能を使用して、そのための一般的な方法を構築しました。

  • ファイルがローカルで変更されていない場合、「not modified」を返すと、Status.NOT_MODIFIEDが呼び出し元に送信されます。使用 Apache Commons Lang
  • ファイル自体を読み取る代わりにファイルストリームオブジェクトを使用する

ここにコード:

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
};

ロケールの切り替えはスレッドセーフではないようです。ロケールをグローバルに切り替える方が良いと思います。副作用についてはわかりませんが...

12
koppor

@Perceptionからの回答に関しては、バイト配列を操作するときに非常にメモリを消費するのは本当ですが、出力ストリームに単純に書き戻すこともできます

@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

多数の画像リソースメソッドがある場合は、MessageBodyWriterを作成して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);
  } 
}

このMessageBodyWriterは、自動検出がJerseyで有効になっている場合に自動的に使用されます。そうでない場合は、カスタムApplicationサブクラスによって返される必要があります。詳細については、 JAX-RS Entity Providers を参照してください。

これが設定されたら、リソースメソッドからBufferedImageを返すだけで、画像ファイルデータとして出力されます。

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

このアプローチにはいくつかの利点があります。

  • 中間のBufferedOutputStreamではなく、応答OutputSteamに書き込みます。
  • Pngとjpgの両方の出力をサポートします(リソースメソッドで許可されているメディアタイプによって異なります)
6
Justin Emery