Как обнаружить пустую многочастную передачу данных - PullRequest
0 голосов
/ 15 января 2019

Итак, я написал небольшой сервлет для проверки загрузки файлов с помощью.

Форма, используемая для запуска загрузки, очень проста:

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

Соответствующая структура функциональности сервлета может быть обобщена как

@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 голосов
/ 15 января 2019

Я не хочу проверять размер файла, потому что файл МОЖЕТ быть размером 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 отказывается компилировать это, потому что

локальные переменные, на которые ссылается лямбда-выражение, должны быть окончательными или эффективно окончательными

, который не имеет ничего общего с блоком synchronized, но больше относится к тому факту, что все это заключено в

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

Полагаю, замена boolean на AtomicBoolean может сработать, но мне это решение не очень нравится, потому что я ненавижу добавлять синхронизацию, которая, как я ЗНАЮ, бесполезна.

Итак ... следующая идея:

Вместо того, чтобы идти на forEach, давайте на map. Таким образом, мы превращаем наш список Part в список операторов независимо от того, действительно ли загрузка файла, соответствующего этому Part, действительно удалась.

1036 * Т.е. *

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 не поддерживает сгибы, и одна вещь, которая приближается к несколько похожей функциональности, требует третьего аргумента, "Combiner". Поэтому нам нужно поискать в Интернете, чтобы выяснить, почему и как этот 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 не складывается, потому что он наносит ущерб порядку операций (что, к счастью, в данном случае не имеет значения), но он выполняет свою работу.

...