ビールとプリンとプログラミング。

頭の悪いプログラマのぼやき。

JAX-RSでmultipart/form-dataを受け取るために

久しぶりに時間ができたので、ビールを飲みながらメモします。


どうも、私です。




仕事でJavaEE7を使用しているんですが、
1年目から、業務をこなすのにいっぱいいっぱい。
実はあまり理解してなかったりしてます。


そこで、勉強がてら、家で触ってみたりしてるんですけども、なかなか理解が追い付かず・・・。



JavaEE7関連で、最近、読んでて面白い記事を見つけたのでご紹介。


enterprisegeeks.hatenablog.com



中途半端な知識で、何となく使ってた機能とか、仕組みとか、
ストンとお腹の中に落ちてきていい感じです。




しかし、勉強不足だなあ。





さて、

今日は、はまったところを、メモしておこうかと思います。



JAX-RSを利用したmultipart/form-dataの取り扱いについてです。




前述のとおり、JavaEE7を利用して、色々と遊んでます。

最近、画像のアップロード機能を実装したいなあ、なんて思いまして、しかし、色々と苦戦しました。なんとか「とりあえずでけた」程度のモノを実装することができましたので、記録しておきます。



私のために。


環境

私が開発を楽しんでる環境は

です。
…あれ、環境って何書けばいいんだ。

JAX-RSの実装とMaven

今回はJAX-RSを利用したWebアプリケーションを想定していますが、
JAX-RSはそのものは、仕様を指しているようです。

JAX-RS - Wikipedia

で、GlassFishで基本的なものは実装されている…程度の認識で、
JSONタイプ(application/json)等々のやり取りは、特に気にせず実装できると思うんですが、
今回やりたい「画像のアップデート」については、「multipart/form-data」を使用する必要があり、
特に気にせず実装することができませんでした。

ということで、
今回はJAX-RSのリファレンス実装であるJerseyを利用したいと思います。



Mavenを使ってビルドします。
pom.xmlのdependenciesに以下を追加しちゃいます。

<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-multipart</artifactId>
  <version>2.13</version>
</dependency>
<dependency>
  <groupId>org.glassfish.jersey.core</groupId>
  <artifactId>jersey-server</artifactId>
  <version>2.22.1</version>
</dependency>

versionについては、今の最新を利用しています。特に意味はありません。


今回の主役はjersey-media-multipartさんでしょうか。
jersey-media-multipartのライブラリを組み込むことで、
multipartなデータをオブジェクトとして扱えるようにします。

そしてjersey-serverさん。
どうやら、multipartなデータを扱うには「JerseyのApplicationクラス」を利用する必要があるみたいです。
「JerseyのApplicationクラス」を利用するにはjersey-serverのライブラリを組み込む必要があります。

JerseyのApplicationクラス

通常、NetbeansさんでJavaEE7なRESTを構成しようとすると、以下のようなクラスが自動生成されると思います。

@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends Application {

@Override
public Set<Class<?>> getClasses() {
  Set<Class<?>> resources = new java.util.HashSet<>();
  addRestResourceClasses(resources);
  return resources;
}

/**
 * Do not modify addRestResourceClasses() method.
 * It is automatically populated with
 * all resources defined in the project.
 * If required, comment out calling this method in getClasses().
 */
private void addRestResourceClasses(Set<Class<?>> resources) {
  resources.add(jp.rued.webapptest.app.resource.FileResource.class);
}


この場合「FileResource」クラスに「GET」「POST」等のRESTなメソッドを作ることになりますが、
このRESTを構成するクラスは、Netbeansを使用している場合、
javax.ws.rs.Pathアノテーションを付けると、ApplicationクラスであるApplicationConfigクラスに自動で追加されます。

ところが、これでは「multipart/form-data」のデータを受け取ることができません。

「multipart/form-data」のデータを受け取るためには「JerseyのApplicationクラス」を使用する必要があるようです。
「JerseyのApplicationクラス」とは、org.glassfish.jersey.server.ResourceConfigのことです。
さらに、Applicationクラスにorg.glassfish.jersey.media.multipart.MultiPartFeatureを登録する必要があるようです。

Netbeansで自動追加される機能を生かしつつ、「multipart/form-data」のデータを受け取るために、以下のように改変してみました。

@javax.ws.rs.ApplicationPath("webresources")
public class ApplicationConfig extends extends ResourceConfig {

public ApplicationConfig() {
  super(MultiPartFeature.class);
  Set<Class<?>> resources = new java.util.HashSet<>();
  addRestResourceClasses(resources);
  super.registerClasses(resources);
}

/**
 * Do not modify addRestResourceClasses() method.
 * It is automatically populated with
 * all resources defined in the project.
 * If required, comment out calling this method in getClasses().
 */
private void addRestResourceClasses(Set<Class<?>> resources) {
  resources.add(jp.rued.webapptest.app.resource.FileResource.class);
}

Resourceクラスの実装

上記のライブラリの読み込みと、Applicationクラスの修正を行えば、あとは難しくはないです。
クライアント側から送られてくるmultipartなデータをmultipartなオブジェクトとして受け取れるようになります。

サーバーサイド

@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Path("/upload")
public void upload(@FormDataParam("file") InputStream file,
                   @FormDataParam("file") FormDataContentDisposition dispoosition) {
                   
  // InputStreamでファイルの中身を参照できる。
  
  // FormDataContentDispositionでファイルの名前等を参照できる。
}


クライアントサイド

<form id="form" method="post" action="webresources/file/upload" enctype="multipart/form-data">
  <input type="file" name="file" />
  <p><input type="submit" value="Send it!"></p>
</form>


漠然としたイメージですが、こんな感じで使用できます。
具体的な使い方は割愛。というか、まだあまり把握しきれてないのが現実。。。











と、こんな感じで画像のアップロード等を実現することができるわけですね。
終わってみれば大したことないんですが、なかなかはまってしまいました。


参考にさせていただいた方の記事も載せておきます。


qiita.com

yumix.hatenablog.jp





JerseyのApplicationクラスを利用しないで実装しているコードも見かけたような気がするので、別の方法があるのかもしれないのですが、そちらのやり方がいまいちわからない。

純粋にjavax.ws.rs.core.Applicationだけで実装できるなら、それに越したことはないと思うんですよね。




いろんなところをつまみ食いしながら実装していったので、
わりとめちゃくちゃなことをやっているかもしれませんが、
もし何か良いアドバイスがあれば、指摘していただけると嬉しいです。







いずれにせよ、Facebookに画像を投稿することができたので満足!

JAX-RS Clientの使い方がさっぱりなので、誰か教えてください。



以上、yusuke_s24でした。