5

GWT と AppEngine Blobstore で最新の Gmail のような複数ファイルのアップロードを作成するにはどうすればよいですか?

最も一般的に提案されているソリューションは、Manolo Carrasco によって作成された優れた GWT コンポーネントであるgwtuploadです。ただし、最新バージョン 0.6.6 は blobstore で動作せず (少なくとも動作させることはできません)、複数のファイル選択をサポートしていません。最新の 0.6.7 スナップショットには複数のファイルを選択するためのパッチがありますが、(HTML5 の「複数」属性を使用して) 複数のファイルを選択できますが、1 つの巨大な POST 要求でそれらを送信します (進行状況は大量のファイル)。

SO に関する他の質問もあります (たとえば、ここまたはここ) が、回答は通常、HTML5 の「複数」属性を使用し、1 つの大きな POST 要求として送信します。それは機能しますが、私が求めているものではありません。

4

1 に答える 1

14

Nick Johnson は、これについていくつかの素晴らしいブログ記事を書きました。彼は、 Pluploadと呼ばれる一般的で広く受け入れられている JavaScript アップロード コンポーネントを使用し、Python で記述された AppEngine アプリにファイルをアップロードします。Plupload は、複数のファイル選択 (HTML5、フラッシュ、Silverlight など) をサポートするためのさまざまなバックエンド (ランタイム) をサポートし、アップロードの進行状況やその他のアップロード関連のクライアント側イベントを処理します。

彼のソリューションの問題点は、(1) Python にあり、(2) JavaScript にあることです。ここでgwt-pluploadが登場します。これは、GWT 環境内で Plupload を使用できるようにする、Samuli Järvelä によって作成されたPlupload用の JSNI ラッパーです。ただし、プロジェクトは古くなっています (2010 年以降はコミットされていません) が、インスピレーションとして使用できます。

したがって、複数ファイルのアップロード コンポーネントを構築するための段階的な手順は次のとおりです。これはすべて 1 つのプロジェクトになりますが、(特に JSNI ラッパー) を独自の .jar ファイルまたはライブラリに抽出して、他のプロジェクトで再利用することができます。ソース コードは、こちらの Bitbucket で入手できます。

このアプリケーションは、http://gwt-gaemultiupload-example.appspot.com/ の AppEngine で利用できます (課金対象外なので、利用可能または動作しているとは考えないでください) 。

スクリーンショット

サンプル アプリケーションのスクリーンショット サンプル アプリケーションのスクリーンショット サンプル アプリケーションのスクリーンショット

ステップ 1 - サーブレット

ブロブストアは次のように機能します。

  1. クライアントは、ファイルのアップロードに使用できる URL を blobstore に要求します。
  2. クライアントは、受信した URL にファイルを POST します。
  3. POST 全体が受信されると、blobstore はクライアントを成功 URL (アップロード URL の作成時に指定) にリダイレクトします。

これをサポートするには、2 つのサーブレットが必要です。1 つはファイルのアップロード用の URL を生成するため (各ファイルのアップロードには一意の URL が必要であることに注意してください)、もう 1 つは完了したアップロードを受け取るためのものです。どちらも非常に単純です。以下は、URL ジェネレーター サーブレットです。これは、URL をプレーン テキストで HTTP 応答に書き込むだけです。

public class BlobstoreUrlGeneratorServlet extends HttpServlet {     
    private static BlobstoreService blobstore = BlobstoreServiceFactory.getBlobstoreService();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("Content-Type", "text/plain");
        resp.getWriter().write(blobstore.createUploadUrl("/uploadfinished"));
    }
}

次に、成功したアップロードを受信するためのサーブレットで、ブロブキーをSystem.out次の場所に出力します。

public class BlobstoreUploadFinishedServlet extends HttpServlet {
    private static BlobstoreService blobstore = BlobstoreServiceFactory.getBlobstoreService();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Map<String, List<BlobKey>> blobs = blobstore.getUploads(req);
        List<BlobKey> blobKeyList = blobs.get("file");

        if (blobKeyList.size() == 0)
            return;

        BlobKey blobKey = blobKeyList.get(0);

        System.out.println("File with blobkey " + blobKey.getKeyString() + " was saved in blobstore.");
    }
}

これらも に登録する必要がありweb.xmlます。

<servlet>
    <servlet-name>urlGeneratorServlet</servlet-name>
    <servlet-class>gaemultiupload.server.BlobstoreUrlGeneratorServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>urlGeneratorServlet</servlet-name>
    <url-pattern>/generateblobstoreurl</url-pattern>
</servlet-mapping>

<servlet>
    <servlet-name>uploadFinishedServlet</servlet-name>
    <servlet-class>gaemultiupload.server.BlobstoreUploadFinishedServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>uploadFinishedServlet</servlet-name>
    <url-pattern>/uploadfinished</url-pattern>
</servlet-mapping>

ここでアプリを実行して にアクセスするhttp://127.0.0.1:8888/generateblobstoreurlと、次のようなものが表示されます

http://<computername>:8888/_ah/upload/ahpnd3QtZ2FlbXVsdGl1cGxvYWQtZXhhbXBsZXIbCxIVX19CbG9iVXBsb2FkU2Vzc2lvbl9fGAEM 

その URL にファイルを投稿すると、ブロブストアに保存されます。ただし、ローカル開発 Web サーバーのデフォルト URL は であるのhttp://127.0.0.1:8888/に対し、blobstore によって生成される URLは であることに注意してくださいhttp://<computername>:8888/。これは後で問題を引き起こします。セキュリティ上の理由から、Plupload はファイルを別のドメインに POST できないからです。これはローカル開発サーバーでのみ発生し、公開されたアプリには 1 つの URL しかありません。Eclipse で実行構成を編集して修正-bindAddress <computername>し、引数に追加します。これにより、ローカル開発サーバーがhttp://<computername>:8888/代わりに Web アプリをホストします。<computername>この変更後にアプリをロードするには、GWT ブラウザー プラグインで許可する必要がある場合があります。

これまでのところ、必要なサーブレットは揃っています。

ステップ 2 - Plupload

Plupload をダウンロードし (私は最新バージョンの 1.5.4 を使用しました)、解凍し、jsフォルダーをwarGWT アプリケーションのディレクトリーにコピーします。この例では、独自の GUI を作成するため、jquery.plupload.queueorは使用しません。Google APIjquery.ui.pluploadからダウンロードした jQuery も必要です。

次に、アプリケーションに JavaScript を含める必要があるため、以下を編集してタグindex.htmlに追加します。<head>

<script type="text/javascript" language="javascript" src="js/jquery.min.js"></script>
<script type="text/javascript" language="javascript" src="js/plupload.full.js"></script>

これで、Plupload がアプリケーションに組み込まれました。次に、GWT で使用できるようにラップする必要があります。ここで gwt-plupload が使用されます。プロジェクトの jar ファイルは使用しませんでしたが、代わりにソース ファイルをコピーして変更できるようにしました。ラッパーの主なオブジェクトは、Pluploadによって構築されるクラスPluploadBuilderです。PluploadListenerクライアント側のイベントを受け取るために実装できるinterface もあります。

ステップ 3 - 組み立てる

それでは、実際に GWT アプリケーションで Plupload を使用する必要があります。Index.ui.xmlUIBinderに以下を追加しました。

<g:Button text="Browse" ui:field="btnBrowse" />
<g:Button text="Start Upload" ui:field="btnStart" /><br />
<br />
<h:CellTable width="600px" ui:field="tblFiles" />

ファイルを参照するためのボタン、アップロードを開始するためのボタン、およびアップロード ステータスを表示するために使用する CellTable があります。ではIndex.java、次のように Plupload を初期化します。

btnBrowse.getElement().setId("btn-browse");
PluploadBuilder builder = new PluploadBuilder();
builder.runtime("html5");
builder.useQueryString(false);
builder.multipart(true);
builder.browseButton(btnBrowse.getElement().getId());
builder.listener(this);
plupload = builder.create();
plupload.init();

このruntime属性は、どのバックエンドを使用するかを Plupload に指示します (私は HTML5 のみをテストしましたが、他のバックエンドも同様に機能するはずです)。Blobstoremultipartを有効にする必要があります。また、ブラウズ ボタンの ID を設定し、Plupload にその ID を使用するように指示する必要があります。このボタンをクリックすると、Plupload のファイル選択ダイアログが表示されます。最後に、自分自身をリスナー (実装PluploadListener)create()およびinit()Plupload として追加します。

アップロードの準備が整ったファイルを表示するにはtblFilesDataProvider、 からのイベントでリスト データ プロバイダーにデータを追加するだけですUploadListener

@Override
public void onFilesAdded(Plupload p, List<File> files) {
    tblFilesDataProvider.getList().addAll(files);
}

進行状況を表示するには、進行状況の変更が通知されるたびにリストを更新するだけです。

@Override
public void onFileUploadProgress(Plupload p, File file) {
    tblFilesDataProvider.refresh();
}

btnStartまた、Plupload にアップロードを開始するように指示する のクリック ハンドラーも実装します。

@UiHandler("btnStart")
void btnStart_Click(ClickEvent event) {
    plupload.start();
}

ファイルを選択できるようになりました。ファイルは保留中のアップロード リストに追加され、アップロードを開始できます。あとは、先ほど実装したサーブレットを実際に使用するだけです。現在、Plupload は POST アップロード先の URL を認識していないため、それを伝える必要があります。ここで、gwt-plupload ソース コードに変更を加えました (マイナーなバグ修正は別として)。Plupload に という関数を追加しましたfetchNewUploadUrl。これが行うことは、先ほど定義したサーブレットで Ajax GET リクエストを実行して、アップロード URL をフェッチすることです。これは同期的に行われます (理由は後で明らかになります)。リクエストが返されると、この URL を Plupload の POST URL として設定します。

private native void fetchNewUploadUrl(Plupload pl) /*-{
    $wnd.$.ajax({
        url: '/generateblobstoreurl',
        async: false,
        success: function(data) {
          pl.settings.url = data;
        },
    });
}-*/;

public void fetchNewUploadUrl() {
    fetchNewUploadUrl(this);
}

Plupload は、各ファイルを独自の POST リクエストで投稿します。これは、各アップロードが開始される前に、新しい URL を提供する必要があることを意味します。幸いなことに、PluploadListener実装できるイベントがあります。リクエストが同期でなければならない理由は次のとおりです。そうしないと、以下のイベント ハンドラーでアップロード URL を受け取る前にアップロードが開始されます (pl.fetchNewUploadUrl()すぐに返されます)。

@Override
public void onBeforeUpload(Plupload pl, File cast) {
    pl.fetchNewUploadUrl();
}

以上です!AppEngine Blobstore にファイルを配置する GWT HTML5 複数ファイル アップロード機能が追加されました。

パラメータを渡す

追加のパラメーター (アップロードされたファイルが属するエンティティの ID など) が必要な場合は、追加する方法の例を追加しました。次のように実装したPluploadというメソッドがあります。setExtraValue()

public native void setExtraValue(String value) /*-{
    this.settings.multipart_params = {extravalue: value}
}-*/;

追加の値は として渡すことができますmultipart_params。これはマップであるため、機能を拡張して任意のキーと値のペアを多数許可できます。onBeforeUpload()値はイベント ハンドラで設定できます

@Override
public void onBeforeUpload(Plupload pl, File cast) {
    pl.setExtraValue(System.currentTimeMillis() + " is unique.");
    pl.fetchNewUploadUrl();
}

完了したアップロードを受信するサーブレットで次のように取得されます

String value = req.getParameter("extravalue");

サンプル プロジェクトには、このためのサンプル コードが含まれています。

最後の言葉

私は GWT 開発の専門家ではありません。これは、求めていた機能が見つからないことに何時間もフラストレーションを感じた後に思いついたものです。動作するようになった後、使用した/フォローしたすべてのコンポーネント/ブログ投稿/その他で一部が省略されていたため、完全な例を作成する必要があると考えました。これがベスト プラクティス コードであることを意味するものではありません。コメント、改善、提案は大歓迎です!

于 2013-01-03T14:07:59.107 に答える