【问题标题】:getting Java Bad File Descriptor Close Bug while reading multipart/form-data http body在读取 multipart/form-data http body 时获取 Java Bad File Descriptor Close Bug
【发布时间】:2011-11-17 07:52:18
【问题描述】:

我的网络服务托管在 Play 上!框架。我从非播放上传的图像文件很少!基于框架的客户端使用标准 HTTP 客户端请求,内容类型为 multipart/form-data。

在网络服务方面,我尝试使用 Play! ApacheMultipartParser 解析 Http.request.body,但因 Java IO Bad File Descriptor 异常而失败。

问题似乎来自 Java MultipartStream,通过查看以下调用堆栈

    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数据如下。在 Web 服务方面,我可以使用 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--

【问题讨论】:

    标签: parsing playframework multipartform-data


    【解决方案1】:

    我遇到了完全相同的问题。问题在于Play的方式!处理分段上传。通常您可以将 FileUpload 添加到您的上传方法并在那里获取您的文件。这很有帮助,因为您可以直接从 Play 获取文件名和大小以及所有这些内容:

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

    但是,使用此逻辑会阻止您使用 HTTPRequest。因此,如果您使用非播放方式上传文件(例如使用 XMLHTTPRequest),而自动映射到 fileUpload 将不起作用,则会发生以下情况:

    • Play 尝试将请求绑定到您的参数
    • Play 遇到您的 File 参数并解析请求。
    • Play 找不到任何用处(因为它不理解 XMLHttpRequest)并将您的 File 参数映射为 null。

    现在请求输入流已经被 Play 消费了,并且您收到了“错误的文件描述符”消息。

    解决方案是不使用任何 Play!魔术,如果您想使用 same 方法通过 Form 和 XMLHttpRequest (XHR) 上传。除了我自己的基于表单的上传方法之外,我还想使用 Valum 的文件上传脚本 (http://github.com/valums/file-uploader)。一种使用 XHR,另一种使用普通的多部分表单上传。我在控制器中创建了以下方法,该方法从“qqfile”参数中获取上传的文件,并与基于表单和 XHR-Uploads 一起使用:

    @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;
        }
    }
    

    希望这会有所帮助,我花了大约一天的时间才完成这项工作。

    【讨论】:

    • 太棒了!非常感谢详细的解释!我会选择解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-28
    • 2011-11-19
    • 2015-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多