ジャージーでマルチパート形式でアップロードされたファイルを読み取るサービスの開発に成功しました。これが私がやってきたことの非常に単純化されたバージョンです:
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("file") InputStream uploadedInputStream,
@FormDataParam("file") FormDataContentDisposition fileDetail) throws IOException {
//handle the file
}
これは問題なく機能しますが、新しい要件が与えられました。アップロードするファイルに加えて、任意の数のリソースを処理する必要があります。これらが画像ファイルであると仮定しましょう。
ファイルへの1つの入力、最初の画像への1つの入力、およびフォームに入力を追加できるボタンをクライアントに提供するだけだと思いました(AJAXまたは単にプレーンJavaScript)。
<form action="blahblahblah" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="file" name="image" />
<input type="button" value="add another image" />
<input type="submit" />
</form>
したがって、ユーザーは次のように、画像の入力をフォームに追加できます。
<form action="blahblahblah" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="file" name="image" />
<input type="file" name="image" />
<input type="file" name="image" />
<input type="button" value="add another image" />
<input type="submit" />
</form>
コレクションと同じ名前のフィールドを読み取るのが簡単だと思いました。私はMVC.NETのテキスト入力でそれを成功させましたが、ジャージーではそれほど難しくないと思いました。私が間違っていたことがわかりました。
このテーマに関するチュートリアルが見つからなかったので、私は実験を始めました。
それを行う方法を確認するために、私は問題を単純なテキスト入力に落とし込みました。
<form action="blahblabhblah" method="post" enctype="multipart/form-data">
<fieldset>
<legend>Multiple inputs with the same name</legend>
<input type="text" name="test" />
<input type="text" name="test" />
<input type="text" name="test" />
<input type="text" name="test" />
<input type="submit" value="Upload It" />
</fieldset>
</form>
明らかに、メソッドのパラメーターとして何らかのコレクションが必要でした。これが私が試したもので、コレクションの種類ごとにグループ化されています。
最初に、Jerseyが単純な配列を処理するのに十分賢いかどうかを確認しました。
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@FormDataParam("test") String[] inputs) {
//handle the request
}
しかし、アレイは期待どおりに注入されませんでした。
惨めに失敗したので、MultiValuedMap
オブジェクトを箱から出して処理できることを思い出しました。
@POST
@Path("FileCollection")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(MultiValuedMap<String, String> formData) {
//handle the request
}
しかし、それも機能しません。今回は例外がありました
SEVERE: A message body reader for Java class javax.ws.rs.core.MultivaluedMap,
and Java type javax.ws.rs.core.MultivaluedMap<Java.lang.String, Java.lang.String>,
and MIME media type multipart/form-data;
boundary=----WebKitFormBoundaryxgxeXiWk62fcLALU was not found.
この例外はmimepull
ライブラリを含めることで取り除くことができると言われたので、pomに次の依存関係を追加しました。
<dependency>
<groupId>org.jvnet</groupId>
<artifactId>mimepull</artifactId>
<version>1.3</version>
</dependency>
残念ながら、問題は解決しません。おそらく、適切なボディリーダーを選択し、ジェネリックにさまざまなパラメーターを使用することです。これを行う方法がわかりません。ファイル入力とテキスト入力の両方、およびその他のいくつか(主にLong
値とカスタムパラメータークラス)を使用したいと思います。
さらに調査した結果、 FormDataMultiPart クラスが見つかりました。フォームから文字列値を抽出するために使用しました
@POST
@Path("upload2")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadMultipart(FormDataMultiPart multiPart){
List<FormDataBodyPart> fields = multiPart.getFields("test");
System.out.println("Name\tValue");
for(FormDataBodyPart field : fields){
System.out.println(field.getName() + "\t" + field.getValue());
//handle the values
}
//prepare the response
}
問題は、これが私の問題の単純化されたバージョンの解決策であるということです。ジャージーによって注入されたすべてのパラメーターは、ある時点で文字列を解析することによって作成されることを知っていますが(当然、HTTPです)、独自のパラメータークラスを作成した経験はありますが、これらのフィールドをに変換する方法は実際にはありません。さらに処理するためのInputStream
またはFile
インスタンス。
したがって、これらのオブジェクトがどのように作成されるかを確認するためにジャージーのソースコードに飛び込む前に、(サイズが不明な)ファイルのセットを読み取る簡単な方法があるかどうかをここで尋ねることにしました。この難問を解決する方法を知っていますか?
FormDataMultipart
の例に従って、解決策を見つけました。私は答えに非常に近かったことがわかりました。
FormDataBodyPart
クラスは、ユーザーが値をInputStream
(または理論的には、メッセージ本文リーダーが存在する他のクラス)として読み取ることができるメソッドを提供します。
最終的な解決策は次のとおりです。
フォームは変更されません。同じ名前のフィールドがいくつかあり、そこにファイルを配置できます。 multiple
フォーム入力(ディレクトリから多くのファイルをアップロードするときにこれらが必要)と名前を共有する多数の入力(異なる場所から不特定の数のファイルをアップロードする柔軟な方法)の両方を使用できます。 JavaScriptを使用して、フォームに入力を追加することもできます。
<form action="/files" method="post" enctype="multipart/form-data">
<fieldset>
<legend>Multiple inputs with the same name</legend>
<input type="file" name="test" multiple="multiple"/>
<input type="file" name="test" />
<input type="file" name="test" />
</fieldset>
<input type="submit" value="Upload It" />
</form>
FormDataMultipart
これは、マルチパートフォームからファイルのコレクションを読み取る簡略化された方法です。同じ入力はすべてList
に割り当てられ、それらの値は InputStream
メソッド getValueAs
を使用してFormDataBodyPart
に変換されます。これらのファイルをInputStream
インスタンスとして取得すると、それらを使用してほとんどすべてのことを簡単に実行できます。
@POST
@Path("files")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadMultipart(FormDataMultiPart multiPart) throws IOException{
List<FormDataBodyPart> fields = multiPart.getFields("test");
for(FormDataBodyPart field : fields){
handleInputStream(field.getValueAs(InputStream.class));
}
//prepare the response
}
private void handleInputStream(InputStream is){
//read the stream any way you want
}
@Path("/upload/multiples")
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.APPLICATION_JSON)
public Response uploadImage(@FormDataParam("image") List<FormDataBodyPart> imageDatas){
for( FormDataBodyPart imageData : imageDatas ){
// Your actual code.
imageData.getValueAs(InputStream.class);
}
}
誰かが私と同じname
属性で一般的な_type=text
_入力ボックスを実行しようとしている場合は、それらを_type=hidden
_入力に切り替えて、それを@FormParam("inputName") List<String> nameList
。
明らかに、非表示の入力に切り替える場合、唯一のポイントは、UI要素を作成せずにサーバーにデータを送信することです。そのため、別の表示UIに切り替える必要があります(たとえば、簡単にクリックできるようにボタン要素を使用しました) -機能を削除する)。