如何检测空的多部分数据传输

时间:2019-01-15 16:38:34

标签: java servlets lambda stream reduce

所以我写了一个小servlet来测试文件上传。

用于触发上传的表单非常简单:

<form method="post" action="/webapp/upload" enctype="multipart/form-data">
    Choose the file(s) to upload:<br>
    <input type="file" name="files" multiple/>
    <input type="submit" value="Upload" />
</form>

相关的servlet功能结构可以概括为

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    PrintWriter out = resp.getWriter();
    resp.setContentType("text/html");

    req.getParts().parallelStream().forEach(p -> {
        //store uploaded file(s) to disk and communicate success/failure to client
    });


    req.getRequestDispatcher(uploadForm).include(req,resp);
}

但是,我不小心点击了上传按钮却没有选择任何文件,这就是返回给我的信息。

uploading '': application/octet-stream, 0KB...OK

的确如此,服务器上的上载文件夹现在包含一个名为(1)的文件(因为我的重命名方法已确定空名称已经存在),文件的大小为0个字节。

这使我意识到我不知道如何检查“未选择文件”。

花了足够的时间在这件事上,所以我最好将它备份在stackoverflow上以供以后参考。谁知道,也许其他人也觉得它有用。或有人有帮助的评论。

1 个答案:

答案 0 :(得分:0)

我不想检查文件的大小,因为一个文件的大小可能恰好是0字节(在大多数情况下,虽然没用,但仍然是一个无用的文件)。而且检查空文件名只能让我决定是否可以丢弃特定的Part

如果可以避免的话,我也不想重复遍历各个部分。

所以我想我能做的就是创建一个布尔标志,如果最终我什么都没有上传的话,我将其设置为true ...

boolean uploadedAnything = false;

由于我使用的是parallelStream,因此更新此标志将需要进行一些同步。我已经在out流上进行同步,所以为什么不简单地将它扔到那里呢?

synchronized (out) {
    out.print(String.format("uploading '%s': %s, %d%s...", fileName, p.getContentType(), sizeInKb, "KB"));
    if (success){
        out.println("OK<br>");
        uploadedAnything = true;
    }
    else out.println("FAILED<br>");
}

除了Java拒绝编译,是因为

  从Lambda表达式引用的

局部变量必须是最终的或实际上是最终的

synchronized块无关,而更重要的是整个内容都封装在

boolean uploadedAnything = false;
req.getParts().parallelStream().forEach(p -> {
    //`uploadedAnything` gets changed here
});

我想用boolean代替AtomicBoolean是可行的,但是我不太喜欢那种解决方案,因为我讨厌添加我不知道没有用的同步。

所以...下一个想法:

让我们去forEach,而不是去map。这样,无论上传Part的文件是否成功,我们都将Part的列表转换为语句列表。

boolean uploadedAnything = req.getParts().parallelStream().map(p -> {
    [...]
    return success;
}).matchAny(Predicate.isEqual(true));

除非我们也不能这样做,因为matchAny会短路处理所有文件。哎呀。

所以...

List<Boolean> uploadStatus = req.getParts().parallelStream().map(p -> {
    [...]
    return success;
}).collect(Collectors.toList());
boolean uploadedAnything = uploadStatus.stream().anyMatch(Predicate.isEqual(true));

...好的,这是可行的,但是现在我创建了一个完整的布尔值列表,只是为了获得一个布尔值。

我想我们可以折叠...

boolean uploadedAnything = req.getParts().parallelStream().fold(false,(status,p) -> {
    [...]
    return status || success;
});

...,除了Java不支持fold之外,与某种相似功能相似的一件事需要第三个参数“ Combiner”。因此,我们必须在网上进行搜索,以找出该组合器真正发挥作用的原因和方式。

找到了https://stackoverflow.com/a/24316429/3322533,该摘录变成了

boolean uploadedAnything = req.getParts().parallelStream().reduce(false,(status,p) -> {
    [...]
    return status || success;
},(accA, accB) -> accA || accB);

我们可以重写为

boolean uploadedAnything = req.getParts().parallelStream().reduce(false,(status,p) -> {
    [...]
    return status || success;
},Boolean::logicalOr);

这个STILL并不是折衷的,因为它对操作顺序造成了严重破坏(幸运的是,在这种情况下这无关紧要),但是它可以完成工作。