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

Posted

技术标签:

【中文标题】如何检测空的多部分数据传输【英文标题】:How to detect an empty multipart data transmission 【发布时间】:2019-06-09 17:16:15 【问题描述】:

所以我编写了一个小 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 字节大。

这让我意识到我不知道如何检查“没有选择文件”。

在这件该死的事情上花了足够的时间,我不妨把它备份到 *** 上以供以后参考。谁知道呢,也许其他人也觉得它有用。或者有人有有用的评论。

【问题讨论】:

【参考方案1】:

我不想检查文件大小,因为文件可能恰好是 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 表达式引用的局部变量必须是 final 或有效 final

这与synchronized 块无关,但更重要的是整个事物都封装在一个

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

我想用AtomicBoolean 替换boolean 可能会起作用,但我不太喜欢这种解决方案,因为我讨厌添加我知道没有用的同步。

所以...下一个想法:

与其选择forEach,不如选择map。这样,我们将 Parts 列表变成了一个语句列表,无论上传与该 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 不支持折叠之外,还有一个与功能有些相似的东西需要第三个参数,即“组合器”。因此,我们必须在网络上搜索以找出该组合器实际发挥作用的原因和方式。

找到https://***.com/a/24316429/3322533,sn-p就变成了

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);

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

【讨论】:

以上是关于如何检测空的多部分数据传输的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 与 uuid 的多对多关系返回总是空的

如何从文件系统发送包含 json 字符串和大文件的多部分数据的 HTTP 请求?

vue添加第一行为空的多选框

asp.net c# 想实现条件能为空的多条件查询

将下一个空的多单元格范围分配给函数返回的变量

使用 Undertow 的多部分表单数据示例