получение Java Bad File Descriptor Close Bug при чтении http-тела multipart / form-data - PullRequest
1 голос
/ 17 ноября 2011

Мой веб-сервис размещен на Play!фреймворк.У меня есть несколько файлов изображений, загруженных из не-игры!основанный на фреймворке клиент, использующий стандартный HTTP-запрос клиента с типом содержимого multipart / form-data.

Что касается веб-службы, я пытался использовать Play!ApacheMultipartParser для анализа Http.request.body, но не удалось с исключением Java IO Bad File Descriptor.

Проблема, похоже, исходит от Java MultipartStream, глядя на следующий callstack

    at java.io.FileInputStream.readBytes(Native Method)
    at java.io.FileInputStream.read(FileInputStream.java:208)
    at org.apache.commons.fileupload.MultipartStream$ItemInputStream.makeAvailable(MultipartStream.java:976)
    at org.apache.commons.fileupload.MultipartStream$ItemInputStream.read(MultipartStream.java:886)
    at java.io.InputStream.read(InputStream.java:85)

Я также попытался напрямую прочитать http.request.body в большой буфер для эксперимента, получил то же самоеисключение.Что может быть не так?

Данные http, отправленные со стороны клиента, выглядят примерно так.На стороне веб-сервиса я мог использовать IO.write, чтобы сохранить его в файл без каких-либо проблем.

Content-Type: multipart/form-data; boundary=--3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f

--3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f
Content-Disposition: form-data; name="foo1.jpg"; filename="foo1.jpg"
Content-Length: 5578
Content-Type: image/jpeg

<image data 1 omitted>
--3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f
Content-Disposition: form-data; name="foo2.jpg"; filename="foo2.jpg"
Content-Length: 327
Content-Type: image/jpeg

<image data 2 omitted>
--3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f--

1 Ответ

3 голосов
/ 17 ноября 2011

У меня была точно такая же проблема. Проблема заключается в том, как играть! обрабатывает многочастные загрузки. Обычно вы можете добавить FileUpload в ваш метод загрузки и получить ваши файлы там. Это очень помогает, так как вы можете получить имена файлов, размеры и все эти вещи прямо из Play:

public static void uploadFile(File fileUpload) {
  String name = fileUpload.getName()  // etc.
}

Однако использование этой логики не позволяет использовать HTTPRequest. Поэтому, если вы используете способ загрузки файлов без использования Play (например, с помощью XMLHTTPRequest), когда автоматическое сопоставление с fileUpload не будет работать, происходит следующее:

  • Play пытается связать запрос с вашими аргументами
  • Play встречает ваш аргумент File и анализирует запрос.
  • Play не находит ничего полезного (так как он не понимает XMLHttpRequest) и отображает ваш аргумент File в null.

Теперь поток ввода запроса уже используется Play, и вы получаете сообщение "Bad File Descriptor".

Решение этой проблемы - не использовать Play! Волшебство, если вы хотите использовать тот же самый метод для загрузки через форму и XMLHttpRequest (XHR). Я хотел использовать скрипт загрузки файлов Valum (http://github.com/valums/file-uploader) в дополнение к моему собственному методу загрузки на основе форм. Один использует XHR, другой использует простые многочастные загрузки форм. Я создал следующий метод в моем контроллере, который берет загруженный файл из Параметр "qqfile" и работает с формой и XHR-загрузками:

@SuppressWarnings({"UnusedDeclaration"})
public static void uploadFile() {
    FileUpload qqfile = null;
    DataParser parser = DataParser.parsers.get(request.contentType);
    if (parser != null) {
        // normal upload. I have to manually parse this because
        // play kills the body input stream for XHR-requests when I put the file upload as a method
        // argument to {@link #uploadFile)
        parser.parse(request.body);
        @SuppressWarnings({"unchecked"})
        ArrayList<FileUpload> uploads = (ArrayList<FileUpload>) request.args.get("__UPLOADS");
        for (FileUpload upload : uploads) {
            if ("qqfile".equals(upload.getFieldName())) {
                qqfile = upload;
                break;
            }
        }
    } else {
        //  XHR upload
        qqfile = new FileUpload(new XHRFileItem("qqfile"));
    }

    if (qqfile == null) {
        badRequest();
        return;
    }
    // and now do something with your Fileupload object here (e.g. write it to db or something else) 
}

Вы, вероятно, можете пропустить IF-часть if, если вы разделите этот метод на два, чтобы вы могли использовать обычный Play! магия для загрузки по умолчанию и использовать отдельный метод для загрузки XHR.

Мне также пришлось создать класс XHRFileItem, который просто оборачивает элемент файла, который публикуется с помощью XMLHttpRequest. Возможно, вам придется немного изменить его для работы с несколькими файлами и вашим конкретным загрузчиком файлов, но, тем не менее, вот оно:

package application.util;

import org.apache.commons.fileupload.FileItem;
import org.jetbrains.annotations.Nullable;

import java.io.*;

import static play.mvc.Http.Request.current;

/**
 * An implementation of FileItem to deal with XmlHttpRequest file uploads.
 */
public class XHRFileItem implements FileItem {

    private String fieldName;

    public XHRFileItem(String fieldName) {
        this.fieldName = fieldName;
    }

    public InputStream getInputStream() throws IOException {
        return current().body;
    }

    public String getContentType() {
        return current().contentType;
    }

    public String getName() {
        String fileName = current().params.get(fieldName);
        if (fileName == null) {
            fileName = current().headers.get("x-file-name").value();
        }
        return fileName;
    }

    public boolean isInMemory() {
        return false;
    }

    public long getSize() {
        return 0;
    }

    public byte[] get() {
        return new byte[0];
    }

    public String getString(String s) throws UnsupportedEncodingException {
        return s;
    }

    public String getString() {
        return "";
    }

    public void write(File file) throws Exception {
        FileOutputStream fos = new FileOutputStream(file);
        InputStream is = getInputStream();
        byte[] buf = new byte[64000];

        int read;
        while ((read = is.read(buf)) != -1) {
            fos.write(buf, 0, read);
        }
        fos.close();
    }

    public void delete() {

    }

    public String getFieldName() {
        return fieldName;
    }

    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }

    public boolean isFormField() {
        return false;
    }

    public void setFormField(boolean b) {

    }

    @Nullable
    public OutputStream getOutputStream() throws IOException {
        return null;
    }
}

Надеюсь, это поможет, мне понадобилось около дня, чтобы сделать эту работу с моей стороны.

...