html - 使用GWT和AppEngine Blobstore上传多个文件?

标签 html google-app-engine gwt file-upload blobstore

我将如何在GWT和AppEngine Blobstore中创建类似Gmail的现代多文件上传格式?

最普遍提出的解决方案是gwtupload,这是Manolo Carrasco编写的出色的GWT组件。但是,最新版本0.6.6不适用于blobstore(至少我无法使其正常工作),并且它不支持多个文件选择。在最新的0.6.7快照中有一个用于选择多个文件的补丁程序,但是尽管它允许选择多个文件(使用HTML5中的“ multiple”属性),但仍会在一个巨大的POST请求中发送它们(并且显示了进度)一堆文件)。

SO上还有其他问题(例如herehere),但是答案通常使用HTML5“ multiple”属性并将其作为一个大型POST请求发送。它有效,但不是我追求的。

最佳答案

尼克·约翰逊(Nick Johnson)为此写了一些great blog posts。他使用了公认的通用JavaScript上载组件Plupload,并将文件上载到用Python编写的AppEngine-app中。 Plupload支持不同的后端(运行时),以支持多个文件选择(HTML5,Flash,Silverlight等),并处理上载进度和其他与上载相关的客户端事件。

他的解决方案存在的问题是(1)使用Python,以及(2)使用JavaScript。这是gwt-plupload输入图片的地方。它是SamuliJärvelä编写的用于Plupload的JSNI包装器,可在GWT环境中使用Plupload。但是,该项目已经过时(自2010年以来没有提交),但是我们可以将其用作灵感。

因此,遵循有关构建多个文件上载组件的分步说明。这全部都在一个项目中,但是可以将它(特别是JSNI-wrapper)提取到其自己的.jar文件或库中,以在其他项目中重用。源代码可用on Bitbucket here

该应用程序可在http://gwt-gaemultiupload-example.appspot.com/上的AppEngine上使用(不可计费,因此不要指望它可用或正在运行)。

屏幕截图





第1步-Servlet

Blobstore的工作方式如下:


客户端要求blobstore提供可用于上传文件的URL。
客户端将文件发布到接收的URL。
收到整个POST后,blobstore会将客户端重定向到成功URL(在创建上传URL时指定)。


为了支持这一点,我们将需要两个servlet。一种用于生成文件上传的URL(请注意,每个文件上传将需要唯一的URL),另一种用于接收完成的上传。两者都将非常简单。下面是URL生成器servlet,它将仅以纯文本形式将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"));
    }
}


然后,用于接收成功上传的servlet,它将把Blobkey打印到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,它将被保存在blobstore中。但是请注意,本地开发Web服务器的默认URL为http://127.0.0.1:8888/,而blobstore生成的URL为http://<computername>:8888/。由于安全原因,Plupload无法将文件发布到另一个域,这将在以后引起问题。这仅在本地开发服务器上发生,已发布的应用程序将只有一个URL。通过在Eclipse中编辑“运行配置”进行修复,将-bindAddress <computername>添加到参数中。这将导致本地开发服务器将Web应用托管在http://<computername>:8888/上。您可能需要允许GWT浏览器插件中的<computername>使其在更改后加载应用程序。

到目前为止,我们已经有了所需的servlet。

第2步-加载

下载Plupload(我使用的是最新版本1.5.4),解压缩并将js文件夹复制到我们的GWT应用程序的war目录中。对于此示例,我们将不使用jquery.plupload.queuejquery.ui.plupload,因为我们将创建自己的GUI。我们还需要jQuery,我是从Google APIs下载的。

接下来,我们需要在应用程序中包含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,可以将其实现为接收客户端事件。

步骤3-放在一起

因此,现在我们需要在GWT应用程序中实际使用Plupload。我将以下内容添加到Index.ui.xml UIBinder:

<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,但其他后端也应该工作)。 Blobstore需要启用multipart。我们还需要为浏览按钮设置一个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();
}


现在可以选择文件,这些文件将被添加到待处理的上传列表中,我们可以开始上传了。剩下的唯一内容是实际使用我们之前实现的servlet。目前,Plupload不知道POST上传到哪个URL,因此我们需要告诉它。这是我对gwt-plupload源代码进行更改的地方(除了较小的错误修复);我向Plupload添加了一个名为fetchNewUploadUrl的函数。它的作用是在我们之前定义的servlet上执行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();
}


就是这样!您现在可以使用GWT HTML5多个文件上传功能,将文件放置在AppEngine Blobstore中!

传递参数

如果要添加其他参数(例如,上载文件所属的实体的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();
}


并在servlet中检索,并接收完成的上传

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


示例项目包含此示例代码。

最后的话

我绝对不是GWT的专业开发人员。经过数小时的挫折之后,我才想到了这个问题,但是却找不到我想要的功能。在工作之后,我想我应该写一个完整的示例,因为我使用/遵循的每个组件/博客文章/等等都遗漏了一部分。我不以任何方式暗示这是最佳实践代码。欢迎提出意见,改进和建议!

关于html - 使用GWT和AppEngine Blobstore上传多个文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14140483/

相关文章:

java - ClassNotFoundException : com. allen_sauer.gwt.log.client.WrappedClientThrowable

如果页面位于 bfcache 中,则 JavaScript 重新加载页面

html - 将链接添加到字段集图例是否有效?

javascript - 缩短或简化 jQuery 逻辑

python - Gae/cloudsql错误: Access denied for user 'root' @'cloudsqlproxy

javascript - 谷歌 body 浏览器建立在什么框架之上(如果有的话)?

android - 如何创建在手机上响起电话号码的链接?

java - Objectify 中的 "ofy().save().entities( )"和 "ofy().save().entitiy()"如何工作

java - 在 Google App Engine 中读取 Java Servlet 输入流时出现问题

java - Maven 对其他其他 Maven 项目的依赖