レスポンスにサムネイル画像を返す API のサンプル

公開日: : 最終更新日:2014/01/26 Java , ,

今回は直接 ImageMagick は関係ありませんが、また画像ネタです。この辺りのことを仕事で扱ったんで自分への備忘録として残しておきたいってのがありまして同じようなネタが続いてます。

今回のネタと前回のネタはちょっとだけかぶるのでリンク貼らせてください。

icatch-thumbnail

API 作成の背景的なところ

ユーザが撮影した写真をアップロードする機能を持ったスマホアプリを作りまして、そのアプリの機能の1つに自分が投稿した写真や他のユーザが投稿した写真を一覧で表示するってのがあります。

まず動くものを作りたかったので、最初はサムネイルにしないでアップロードされた画像をそのままレスポンスで返していたのですが、ダウンロードに時間がかかりすぎました。一度に返す画像の枚数は8枚だったのですが、ファイルサイズが結構大きかったためムリがあったという結論になりました。

そこで、やはりというか当たり前ですが、一覧画面ではサムネイル画像を返すことになり、それ用の API を作ったという背景です。

画像はパスで指定しない

サムネイル画像に限った話ではありませんが、取得したい画像の指定はディレクトリパスを使っちゃダメと教わったことがあります。

その当時は Web アプリについてまったくのド素人だったので「パスで指定しちゃダメなんて面倒くさいなぁ」と思ったものですが、その後いろいろ分かってくると次のような脆弱性を減らすための対策だったってことが分かりました。

ホント、いろいろ覚えることが多すぎるなぁと。

サムネイル画像作成のタイミングは?

これはアプリケーションの性質によってということになりそうで、上記のアプリは多くのユーザに使ってもらうものでもないし、とにかく早くレスポンスを返さねば!というものでもなかったのでオンデマンドを採用しました。

つまり、ユーザからサムネイル画像が欲しいとリクエストがあったときにサムネイル画像を作成して返してやるというものです。正しくは「サムネイル画像が存在しなかったらサムネイル画像を作成して返す。存在していたらそいつを返す」です。


画像が次から次へとアップロードされたり、レスポンスタイムを短くすることを求められる場合は、画像を保管するサーバを別に用意したり、サムネイルを作成するプログラムやサムネイル画像を返す API を切り出したりと、いろいろ頑張る必要があるんだと思います。

API(プログラム)の説明

サンプルプログラムの説明になりますが、まずは簡単に仕様から。

仕様

  • API はパラメータとして次のものを受け取る
    • 画像ID → パラメータ:id
    • サムネイルサイズ → パラメータ:size
  • パラメータの指定に誤りがある場合は、それぞれ次のように処理する
    • 画像IDが欠けている → NO_IMAGE 画像を返す
    • 画像IDの形式に誤りがある→ NO_IMAGE 画像を返す
    • サムネイル画像のサイズの指定に誤りがある → アップロード画像を返す
  • 指定した画像IDに該当する画像が存在しない場合は、NO_IMAGE 画像を返す

クラス図

3つしかクラスはないのですが、いちおう登場人物を図にすると次のようになります。

  • Thumbnail
  • AbstractImageResponseApi
  • ThumbnailResponseApi

icatch-thumbnail-api-classes

使用ライブラリ

作成したサンプルプログラムは JAX-RS の参照実装である Jersey を使いました。2014/01/05 現在の最新バージョンは 2.5.1 ですが 1.8 で実装しました。なので、最新バージョンと比べると異なる部分があるかもしれません。

ちなみに Maven の pom.xml に追加した Jersey 関連の dependency は次の通りです(このサンプルに必要のないものも含まれています)。

<dependency>
    <groupId>com.sun.jersey</groupId>
    <artifactId>jersey-bundle</artifactId>
    <version>1.8</version>
</dependency>
<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-multipart</artifactId>
    <version>1.8</version>
</dependency>
<dependency>
    <groupId>org.jvnet</groupId>
    <artifactId>mimepull</artifactId>
    <version>1.4</version>
</dependency>
<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-jaxrs</artifactId>
    <version>1.9.13</version>
</dependency>
<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-xc</artifactId>
    <version>1.9.13</version>
</dependency>
<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-core-asl</artifactId>
    <version>1.9.13</version>
</dependency>
<dependency>
    <groupId>org.codehaus.jackson</groupId>
    <artifactId>jackson-mapper-asl</artifactId>
    <version>1.9.13</version>
</dependency>
<dependency>
    <groupId>asm</groupId>
    <artifactId>asm</artifactId>
    <version>3.3.1</version>
</dependency>

ソースコード(Thumbnail)

ソースコードになります。サンプルなんで特にコメントは書いてないです。

まずは Thumbnail クラスから。

public class Thumbnail {

    private String srcPath;
    private String thumbnailPath;
    private String size;

    /**
     * コンストラクタ。
     * 
     * @param path サムネイル画像の元画像の絶対パス
     * @param size サムネイル画像のサイズ("[0-9]{1,}x[0-9]{1,}")
     */
    public Thumbnail(String path, String size) {
        srcPath = path;
        thumbnailPath = path + "_" + size;
        this.size = size;
    }

    /**
     * サムネイル画像の絶対パスを返す
     * 
     * @return
     */
    public String getPath() {
        File thumbnail = new File(thumbnailPath);
        if(!thumbnail.exists()) {
            // サムネイル画像が作成されていなければ、このタイミングで作成する
            ImageMagick.createThumbnail(srcPath, thumbnailPath, size);
        }
        
        return thumbnail.getAbsolutePath();
    }

}

ソースコード(ThumbnailResponseApi)

続いて ThumbnailResponseApi クラス。

ロジックが日本語で書いてある部分がありますが、それらはプロジェクトによって異なる部分になると思いますので適宜差し替えてください。

@Path("thumbnail")
public class ThumbnailResponseApi extends AbstractImageResponseApi {

    /**
     * 指定の写真のレスポンスを返す。
     * 
     * パラメータは次のものを受け取る。
     * 
     * <ul>
     * <li>画像ID → id</li>
     * <li>サムネイル画像サイズ → size</li>
     * </ul>
     * 
     * パラメータの指定に誤りがあった場合、それぞれ次のように処理をする。
     * 
     * <ul>
     * <li>画像IDが欠けている → NO_IMAGE 画像を返す</li>
     * <li>画像IDの形式に誤りがある→ NO_IMAGE 画像を返す</li>
     * <li>サムネイル画像のサイズの指定に誤りがある → アップロード画像を返す</li>
     * </ul>
     * 
     * @param imageId 画像ID
     * @param size サムネイル画像サイズ
     * @return
     */
    @GET
    @Produces("image/jpeg")
    public Response getImage(
            @QueryParam("id") String imageId,
            @QueryParam("size") String size) {
        if(画像IDがブランク ||
                画像IDの形式に誤りがある) {
            return getNoImageResponse(); // パラメータの指定に誤りがある → NO_IMAGE 画像を返す
        }
        if(validateThumbnailSize(size)) {
            size = null; // size の形式が誤っているのでアップロード画像を返す
        }
        
        Image entity = 画像IDから該当するオブジェクトを取得する
        if(entity == null) {
            return getNoImageResponse(); // 指定の画像IDが登録されていなかった → NO_IMAGE 画像を返す
        } 
        
        return getPhotoResponse(entity, size);
    }
    
    /**
     * アップロード写真のレスポンスを返す。
     * 
     * @param entity
     * @param size サムネイル画像のサイズ
     * @return
     */
    private Response getPhotoResponse(Image entity, String size) {
        String filePath = null;
        if(size == null) {
            filePath = entity.getPath();
        } else {
            Thumbnail thumbnail = new Thumbnail(entity.getPath(), size);
            filePath = thumbnail.getPath();
        }
        
        return getImageResponse(filePath);
    }
}

ソースコード(AbstractImageResponseApi)

最後に AbstractImageResponseApi クラス。

こちらにもロジックが日本語で書いてある部分があります。適宜差し替えてください。

public abstract class AbstractImageResponseResource {

    /**
     * NO_IMAGE 画像のレスポンスを返す
     * 
     * @return
     */
    protected Response getNoImageResponse() {
        return getImageResponse(NO_IMAGE 画像の絶対パスを指定する);
    }

    /**
     * 画像のレスポンスを返す。
     * 
     * 画像が見つからない場合は 404 を設定した <code>Response</code> を返す。
     * 
     * @param absolutePath 画像の絶対パス
     * @return
     */
    protected Response getImageResponse(String absolutePath) {
        try {
            return Response.ok(new FileInputStream(absolutePath)).build();
        } catch (FileNotFoundException e) {
            return Response.status(Status.NOT_FOUND).build();
        }
    }

    /**
     * サムネイル画像のサイズ指定に誤りがあるかを問い合わせる。
     * 
     * @param size
     * @return 誤りがある場合 true
     */
    protected boolean validateThumbnailSize(String size) {
        return サイズ指定に誤りがあるか;
    }
}

Googleアドセンス用(PC)

  • このエントリーをはてなブックマークに追加
  • follow us in feedly

関連記事

no image

Stream#map()とStream#collect()を使ってListを生成する

Java8 から導入された Stream API も少しずつ慣れてきて、「そういえばこういうパターン

記事を読む

GradleLogoReg

SDKMAN!(GVM) を使って Mac に Gradle をインストールする

長らく Java のアプリ開発には Maven を使ってきたが、時代に取り残されている感じがするので

記事を読む

icatch-upload_1229138273_mini-thumbnail

InputStream に格納されているストリームデータのサイズを取得する

Jersey を使った Web アプリを作成中なんですが、 enctype="multipart/f

記事を読む

no image

google-gson でシリアライズ・デシリアライズしたサンプル

これまで Java で JSON を扱うときは JSONIC を使っていたが、google-gson

記事を読む

no image

Stream#filter()で絞り込みmap()で処理するコードスニペット

Java8 から導入された Stream API も少しずつ慣れてきて、「そういえばこういうパターン

記事を読む

icatch_2185253176-resized

Java8 の Optional 型を返す Generics を使ったメソッドを定義する

Java8 の Optional とガード節 に続いて Java8 の Optional ネタ。今回

記事を読む

medium_182531101

ClassLoader を使って getResourceAsStream で取得したファイルを UTF-8 で読み込む

よく使う割には覚えられず毎回調べている気がするので備忘録としてメモしておきます。 文字コードを指定し

記事を読む

no image

Java から VBScript を使って Excel を印刷する

背景 Java のデスクトップアプリを作成していたときの要件に「Excel で作成された帳票を印刷

記事を読む

icatch-3361b5e1-resized

Optional型の変数にget()とifPresent()は使わない

Java8 から導入された Optional を勉強するため、過去にプライベートで書いたコードに O

記事を読む

no image

ファイルに関するユーティリティ

ファイルに関するスニペットをいくつか書いた。いつかコピペする日が来ると思うのでメモしておく。 作成

記事を読む

Googleアドセンス用(PC)

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です


七 × = 21

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Googleアドセンス用(PC)

icatch-jersey_multi_pathparams
Jerseyの@PathParamはスラッシュの間に複数指定できる

http://hoge-api/user/{id}.{format}

icatch-vagrant_box_customize
VagrantのBoxファイルをカスタマイズして独自のBoxファイルを作成する

配布されている Vagrant の Box ファイルを使って検証環境を

icatch-2015-006-1
バリデーションチェックにJava8のOptionalを使ってスマートに書く(自分比)

Web アプリのバリデーションチェックにアノテーションを使うことが増え

icatch-2015-005-1
ユニットテストの偏りを防ぐ命名規則の付け方

ユニットテスト名に以下の命名規則を付けるようにして二ヶ月ぐらい経った。

icatch-2015-004-1
Vagrantで起動したCentOS上のOctopressをホストOSから確認する設定

タイトルの通りだが、Vagrant を使って起動した CentOS に

→もっと見る

PAGE TOP ↑