Загрузка файла через форму. Данные multipart/formdata. jQuery + ajax.

Недавно на работе была задача добавить в одну из форм возможность загрузки файлов. Я рассказал заказчику, что это займёт много времени и вообще сложно, надеясь выиграть время и не кидаться делать задачу сразу же. А потом оказалось, что это довольно просто, и я сделал задачу так быстро, что ещё даже не успел об этом оповестить заказчика.

В этой статье предлагаю своё решение с загрузкой файла. Фронт в моём проекте составлен из jsp-страниц и написан с участием jquery и ajax. Бэк на джаве и состоит из сервлетов.

HTML. Форма с файлом

Для контекста сначала покажу форму.

<form id="form" method="post" enctype="multipart/form-data">
    <input type="file" name="file">
    <div class="data-edit-line-row">
        <p>Описание</p>
        <input name="description" type="text">
        <input name="uploadFile" type="submit" value="Загрузить">
    </div>
    <div class="data-input">
        <p>Комментарий</p>
        <input name="comment" type="text" >
    </div>
</form>

В форме есть поле для выбора файла, текстовые поля для введения комментария и описания и кнопка Загрузить с именем uploadFile.

Скрипт ajax

Теперь покажу, что надо писать в событии по клику на эту кнопку.


$(document).ready(function() {

    $('[name="uploadFile"]').on('click', function () {
            event.preventDefault();
            var form = $('#form')[0];
            var data = new FormData(form);

            $.ajax({
                url: "/sortingUploadFiles",
                type: "POST",
                enctype: 'multipart/form-data',
                data: data,
                processData: false,
                contentType: false,
                cache: false,
                success: function () {
                    location.reload(true);
                },
                error: function (error) {
                    console.log("File upload failed: ", error);
                }
            });

    });

})

Разберу строчки по порядку.

Сначала я прошу preventDefault, чтобы по нажатию на кнопку типа submit форма не начала обрабатываться. Мы сделаем это сами, автоматизация нам тут ни к чему.

Далее создаём объект из формы. Для этого находим все объекты по идентификатору #form. В моём примере такой объект всего один, поэтому можно смело брать нулевой объект с таким идентификатором и в следующей строчке превращаем форму в объект FormData и храним его под именем data.

Далее я иду по адресу /sortingUploadFiles (в вашем приложении он, конечно, будет другим). Там находится сервлет, который обработает запрос типа POST.

enctype задаём как multipart/form-data. А вот contentType должен быть false. Иначе добавится заголовок с типом данных, что нам тоже не нужно. processData тоже устанавливаем на false, чтобы jQuery не пытался превратить наши данные в строку (это ему не удалось бы).

В случае success я перезагружаю страницу (мне так надо), а в случае ошибки ничего путного не делаю, просто для вида вывожу строку в консоль. Вы же можете добавить туда какую только душе угодно логику.

Обработка на бэке. Java. Servlet

Напоследок поделюсь своим бэком. Почему-то его в подобных статьях о проблемах ajax и jQuery часто игнорируют. Видимо, подразумевают, что бэк может быть на любом языке… Но любого языка у меня для вас нет, только java.

public class sortingUploadFiles extends HttpServlet {

    private Random random = new Random();
    private String pathFiles = "/files/";

    SortingFiles sortingFiles = new SortingFiles();

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        boolean isMultipart = ServletFileUpload.isMultipartContent(request);
        if (!isMultipart) {
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
            return;
        }

        DiskFileItemFactory factory = new DiskFileItemFactory();

        factory.setSizeThreshold(1024*1024);

        File tempDir = (File)getServletContext().getAttribute("javax.servlet.context.tempdir");
        factory.setRepository(tempDir);

        ServletFileUpload upload = new ServletFileUpload(factory);

        String pathDocum = "";
        String descrDocum = "";
        String comment = "";
        int iRandom = random.nextInt();

        try {
            List<FileItem> items = upload.parseRequest(request);
            Iterator<FileItem> iter = items.iterator();

            while (iter.hasNext()) {
                FileItem item = iter.next();

                if (!item.isFormField()) {
                    processUploadedFile(item, iRandom);
                    pathDocum = item.getName();
                } else if (item.getFieldName().equals("description")) {
                    descrDocum = item.getString("utf-8");
                } else if (item.getFieldName().equals("comment")) {
                    comment = item.getString("utf-8");
                }
            }

            sortingFiles.setName(descrDocum);
            sortingFiles.setPath(iRandom + "_" + pathDocum);
            sortingFiles.setComment(comment);

            SortingDocumDAOImpl.save(sortingFiles);
        } catch (Exception e) {
            e.printStackTrace();
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            return;
        }

        response.sendRedirect("/sorting");
        response.flushBuffer();
    }

    private void processUploadedFile(FileItem item, int iRandom) throws Exception {
        File uploadedFile = null;

        String path = getServletContext().getRealPath(pathFiles + iRandom + "_" + item.getName());
        uploadedFile = new File(path);

        uploadedFile.createNewFile();
        item.write(uploadedFile);
    }

Здесь довольно много происходит. Но если вкратце, то я записываю файл на диск в нужную папку, а также по очереди достаю из запроса все данные (описание файла и комментарий к нему) и заполняю объект sortingFiles, который потом сохраняю в базе с помощью класса SortingDocumDAOImpl.

Случайное число добавляю к адресу файла, чтобы была большая гарантия, что каждое имя уникально.

Классы DiskFileItemFactory, ServletFileUpload и FileItem находятся в пакете org.apache.commons.fileupload. Если вас интересует лишь обработка формы, то они вам помогут больше всего. Но я специально оставил кусочки остальной логики, чтобы всунуть обработку формы в привычный контекст сохранения данных, в том числе в базу.


Оставьте комментарий