Nick Johnson写了一些关于这个的博客文章。他使用了一个常用且被广泛认可的JavaScript上传组件,称为Plupload,并将文件上传到用Python编写的AppEngine应用程序中。 Plupload支持不同的后端(运行时)以支持多文件选择(HTML5,flash,Silverlight等),并处理上传进度和其他上传相关的客户端事件。
他的解决方案存在两个问题:(1)它是用Python编写的,(2)它是用JavaScript编写的。这就是gwt-plupload出现的地方。这是由Samuli Järvelä编写的Plupload的JSNI包装器,它使得在GWT环境中可以使用Plupload。但是,该项目已过时(自2010年以来没有提交),但我们可以将其用作启示。
因此,下面是构建多文件上传组件的逐步说明。这将全部放在一个项目中,但是(特别是JSNI包装器)可以提取到自己的.jar文件或库中以在其他项目中重用。源代码在这里的Bitbucket上可用。
该应用程序可在AppEngine上使用(不计费,因此不能保证它可用或有效):http://gwt-gaemultiupload-example.appspot.com/。
Blobstore的工作方式如下:
为了支持这一点,我们需要两个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无法将文件POST到另一个域。这仅会在本地开发服务器上发生问题,发布的应用程序将只有一个URL。通过在Eclipse中编辑运行配置,将-bindAddress <computername>
添加到参数中来解决此问题。这将导致本地开发服务器将Web应用程序托管在http://<computername>:8888/
上。在更改后,您可能需要允许GWT浏览器插件加载应用程序中的<computername>
。
到目前为止,我们已经拥有了所需的servlet。
下载Plupload(我使用了最新版本1.5.4),解压缩并将js
文件夹复制到我们的GWT应用程序中的war
目录下。对于本示例,我们不会使用jquery.plupload.queue
或jquery.ui.plupload
,因为我们将创建自己的GUI。我们还需要jQuery,我从Google API下载了它。
接下来,我们需要在应用程序中包含JavaScripts,因此编辑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
,可以实现以接收客户端事件。
现在我们需要实际在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
)添加自己,并创建并初始化Plupload。要显示准备上传的文件,我们只需在来自UploadListener
的事件中向tblFilesDataProvider
列表数据提供程序添加数据即可。
@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源代码所做的更改(除了小型错误修复); 我添加了一个名为fetchNewUploadUrl
的函数到Plupload中。它会执行一个Ajax GET请求来获取我们之前定义的servlet的上传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之前必须是同步的原因是:否则,上传将在我们收到上传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开发人员。在经过数小时的挫败和找不到我想要的功能之后,这就是我想出来的东西。在我让它工作之后,我认为我应该写一个完整的示例,因为我使用/遵循的每个组件/博客文章等都留下了一些部分。我不认为这是最佳实践代码。评论、改进和建议都受欢迎!