首页

关于tomcat源码文件附件上传FileUploadBase、DiskFileUpload及FileUpload源码说明

标签:tomcat源码,文件附件上传,FileUploadBase,DiskFileUpload,FileUpload,MultipartStream,文件流,multipart/form-data     发布时间:2018-10-24   

一、前言

关于tomcat源码中实现文件附件表单multipart/form-data数据流后端解析FileUploadBase通用抽象类,并针对不同的场景定义具体实现类DiskFileUpload、FileUpload,详情参见源码说明部分。

二、源码说明

1. FileUploadBase通用抽象实现类

package org.apache.tomcat.util.http.fileupload; @b@@b@import java.io.IOException;@b@import java.io.InputStream;@b@import java.io.OutputStream;@b@import java.util.ArrayList;@b@import java.util.HashMap;@b@import java.util.List;@b@import java.util.Map;@b@import javax.servlet.http.HttpServletRequest; @b@ @b@public abstract class FileUploadBase@b@{@b@@b@    // ---------------------------------------------------------- Class methods@b@@b@@b@    /**@b@     * Utility method that determines whether the request contains multipart@b@     * content.@b@     *@b@     * @param req The servlet request to be evaluated. Must be non-null.@b@     *@b@     * @return <code>true</code> if the request is multipart;@b@     *         <code>false</code> otherwise.@b@     */@b@    public static final boolean isMultipartContent(HttpServletRequest req)@b@    {@b@        String contentType = req.getHeader(CONTENT_TYPE);@b@        if (contentType == null)@b@        {@b@            return false;@b@        }@b@        if (contentType.startsWith(MULTIPART))@b@        {@b@            return true;@b@        }@b@        return false;@b@    }@b@@b@@b@    // ----------------------------------------------------- Manifest constants@b@@b@@b@    /**@b@     * HTTP content type header name.@b@     */@b@    public static final String CONTENT_TYPE = "Content-type";@b@@b@@b@    /**@b@     * HTTP content disposition header name.@b@     */@b@    public static final String CONTENT_DISPOSITION = "Content-disposition";@b@@b@@b@    /**@b@     * Content-disposition value for form data.@b@     */@b@    public static final String FORM_DATA = "form-data";@b@@b@@b@    /**@b@     * Content-disposition value for file attachment.@b@     */@b@    public static final String ATTACHMENT = "attachment";@b@@b@@b@    /**@b@     * Part of HTTP content type header.@b@     */@b@    public static final String MULTIPART = "multipart/";@b@@b@@b@    /**@b@     * HTTP content type header for multipart forms.@b@     */@b@    public static final String MULTIPART_FORM_DATA = "multipart/form-data";@b@@b@@b@    /**@b@     * HTTP content type header for multiple uploads.@b@     */@b@    public static final String MULTIPART_MIXED = "multipart/mixed";@b@@b@@b@    /**@b@     * The maximum length of a single header line that will be parsed@b@     * (1024 bytes).@b@     */@b@    public static final int MAX_HEADER_SIZE = 1024;@b@@b@@b@    // ----------------------------------------------------------- Data members@b@@b@@b@    /**@b@     * The maximum size permitted for an uploaded file. A value of -1 indicates@b@     * no maximum.@b@     */@b@    private long sizeMax = -1;@b@@b@@b@    /**@b@     * The content encoding to use when reading part headers.@b@     */@b@    private String headerEncoding;@b@@b@@b@    // ----------------------------------------------------- Property accessors@b@@b@@b@    /**@b@     * Returns the factory class used when creating file items.@b@     *@b@     * @return The factory class for new file items.@b@     */@b@    public abstract FileItemFactory getFileItemFactory();@b@@b@@b@    /**@b@     * Sets the factory class to use when creating file items.@b@     *@b@     * @param factory The factory class for new file items.@b@     */@b@    public abstract void setFileItemFactory(FileItemFactory factory);@b@@b@@b@    /**@b@     * Returns the maximum allowed upload size.@b@     *@b@     * @return The maximum allowed size, in bytes.@b@     *@b@     * @see #setSizeMax(long)@b@     *@b@     */@b@    public long getSizeMax()@b@    {@b@        return sizeMax;@b@    }@b@@b@@b@    /**@b@     * Sets the maximum allowed upload size. If negative, there is no maximum.@b@     *@b@     * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.@b@     *@b@     * @see #getSizeMax()@b@     *@b@     */@b@    public void setSizeMax(long sizeMax)@b@    {@b@        this.sizeMax = sizeMax;@b@    }@b@@b@@b@    /**@b@     * Retrieves the character encoding used when reading the headers of an@b@     * individual part. When not specified, or <code>null</code>, the platform@b@     * default encoding is used.@b@     *@b@     * @return The encoding used to read part headers.@b@     */@b@    public String getHeaderEncoding()@b@    {@b@        return headerEncoding;@b@    }@b@@b@@b@    /**@b@     * Specifies the character encoding to be used when reading the headers of@b@     * individual parts. When not specified, or <code>null</code>, the platform@b@     * default encoding is used.@b@     *@b@     * @param encoding The encoding used to read part headers.@b@     */@b@    public void setHeaderEncoding(String encoding)@b@    {@b@        headerEncoding = encoding;@b@    }@b@@b@@b@    // --------------------------------------------------------- Public methods@b@@b@@b@    /**@b@     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>@b@     * compliant <code>multipart/form-data</code> stream. If files are stored@b@     * on disk, the path is given by <code>getRepository()</code>.@b@     *@b@     * @param req The servlet request to be parsed.@b@     *@b@     * @return A list of <code>FileItem</code> instances parsed from the@b@     *         request, in the order that they were transmitted.@b@     *@b@     * @exception FileUploadException if there are problems reading/parsing@b@     *                                the request or storing files.@b@     */@b@    public List /* FileItem */ parseRequest(HttpServletRequest req)@b@        throws FileUploadException@b@    {@b@        if (null == req)@b@        {@b@            throw new NullPointerException("req parameter");@b@        }@b@@b@        ArrayList items = new ArrayList();@b@        String contentType = req.getHeader(CONTENT_TYPE);@b@@b@        if ((null == contentType) || (!contentType.startsWith(MULTIPART)))@b@        {@b@            throw new InvalidContentTypeException(@b@                "the request doesn't contain a "@b@                + MULTIPART_FORM_DATA@b@                + " or "@b@                + MULTIPART_MIXED@b@                + " stream, content type header is "@b@                + contentType);@b@        }@b@        int requestSize = req.getContentLength();@b@@b@        if (requestSize == -1)@b@        {@b@            throw new UnknownSizeException(@b@                "the request was rejected because it's size is unknown");@b@        }@b@@b@        if (sizeMax >= 0 && requestSize > sizeMax)@b@        {@b@            throw new SizeLimitExceededException(@b@                "the request was rejected because "@b@                + "it's size exceeds allowed range");@b@        }@b@@b@        try@b@        {@b@            int boundaryIndex = contentType.indexOf("boundary=");@b@            if (boundaryIndex < 0)@b@            {@b@                throw new FileUploadException(@b@                        "the request was rejected because "@b@                        + "no multipart boundary was found");@b@            }@b@            byte[] boundary = contentType.substring(@b@                    boundaryIndex + 9).getBytes();@b@@b@            InputStream input = req.getInputStream();@b@@b@            MultipartStream multi = new MultipartStream(input, boundary);@b@            multi.setHeaderEncoding(headerEncoding);@b@@b@            boolean nextPart = multi.skipPreamble();@b@            while (nextPart)@b@            {@b@                Map headers = parseHeaders(multi.readHeaders());@b@                String fieldName = getFieldName(headers);@b@                if (fieldName != null)@b@                {@b@                    String subContentType = getHeader(headers, CONTENT_TYPE);@b@                    if (subContentType != null && subContentType@b@                                                .startsWith(MULTIPART_MIXED))@b@                    {@b@                        // Multiple files.@b@                        byte[] subBoundary =@b@                            subContentType.substring(@b@                                subContentType@b@                                .indexOf("boundary=") + 9).getBytes();@b@                        multi.setBoundary(subBoundary);@b@                        boolean nextSubPart = multi.skipPreamble();@b@                        while (nextSubPart)@b@                        {@b@                            headers = parseHeaders(multi.readHeaders());@b@                            if (getFileName(headers) != null)@b@                            {@b@                                FileItem item =@b@                                        createItem(headers, false);@b@                                OutputStream os = item.getOutputStream();@b@                                try@b@                                {@b@                                    multi.readBodyData(os);@b@                                }@b@                                finally@b@                                {@b@                                    os.close();@b@                                }@b@                                items.add(item);@b@                            }@b@                            else@b@                            {@b@                                // Ignore anything but files inside@b@                                // multipart/mixed.@b@                                multi.discardBodyData();@b@                            }@b@                            nextSubPart = multi.readBoundary();@b@                        }@b@                        multi.setBoundary(boundary);@b@                    }@b@                    else@b@                    {@b@                        if (getFileName(headers) != null)@b@                        {@b@                            // A single file.@b@                            FileItem item = createItem(headers, false);@b@                            OutputStream os = item.getOutputStream();@b@                            try@b@                            {@b@                                multi.readBodyData(os);@b@                            }@b@                            finally@b@                            {@b@                                os.close();@b@                            }@b@                            items.add(item);@b@                        }@b@                        else@b@                        {@b@                            // A form field.@b@                            FileItem item = createItem(headers, true);@b@                            OutputStream os = item.getOutputStream();@b@                            try@b@                            {@b@                                multi.readBodyData(os);@b@                            }@b@                            finally@b@                            {@b@                                os.close();@b@                            }@b@                            items.add(item);@b@                        }@b@                    }@b@                }@b@                else@b@                {@b@                    // Skip this part.@b@                    multi.discardBodyData();@b@                }@b@                nextPart = multi.readBoundary();@b@            }@b@        }@b@        catch (IOException e)@b@        {@b@            throw new FileUploadException(@b@                "Processing of " + MULTIPART_FORM_DATA@b@                    + " request failed. " + e.getMessage());@b@        }@b@@b@        return items;@b@    }@b@@b@@b@    // ------------------------------------------------------ Protected methods@b@@b@@b@    /**@b@     * Retrieves the file name from the <code>Content-disposition</code>@b@     * header.@b@     *@b@     * @param headers A <code>Map</code> containing the HTTP request headers.@b@     *@b@     * @return The file name for the current <code>encapsulation</code>.@b@     */@b@    protected String getFileName(Map /* String, String */ headers)@b@    {@b@        String fileName = null;@b@        String cd = getHeader(headers, CONTENT_DISPOSITION);@b@        if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT))@b@        {@b@            int start = cd.indexOf("filename=\"");@b@            int end = cd.indexOf('"', start + 10);@b@            if (start != -1 && end != -1)@b@            {@b@                fileName = cd.substring(start + 10, end).trim();@b@            }@b@        }@b@        return fileName;@b@    }@b@@b@@b@    /**@b@     * Retrieves the field name from the <code>Content-disposition</code>@b@     * header.@b@     *@b@     * @param headers A <code>Map</code> containing the HTTP request headers.@b@     *@b@     * @return The field name for the current <code>encapsulation</code>.@b@     */@b@    protected String getFieldName(Map /* String, String */ headers)@b@    {@b@        String fieldName = null;@b@        String cd = getHeader(headers, CONTENT_DISPOSITION);@b@        if (cd != null && cd.startsWith(FORM_DATA))@b@        {@b@            int start = cd.indexOf("name=\"");@b@            int end = cd.indexOf('"', start + 6);@b@            if (start != -1 && end != -1)@b@            {@b@                fieldName = cd.substring(start + 6, end);@b@            }@b@        }@b@        return fieldName;@b@    }@b@@b@@b@    /**@b@     * Creates a new {@link FileItem} instance.@b@     *@b@     * @param headers       A <code>Map</code> containing the HTTP request@b@     *                      headers.@b@     * @param isFormField   Whether or not this item is a form field, as@b@     *                      opposed to a file.@b@     *@b@     * @return A newly created <code>FileItem</code> instance.@b@     *@b@     * @exception FileUploadException if an error occurs.@b@     */@b@    protected FileItem createItem(Map /* String, String */ headers,@b@                                  boolean isFormField)@b@        throws FileUploadException@b@    {@b@        return getFileItemFactory().createItem(getFieldName(headers),@b@                getHeader(headers, CONTENT_TYPE),@b@                isFormField,@b@                getFileName(headers));@b@    }@b@@b@@b@    /**@b@     * <p> Parses the <code>header-part</code> and returns as key/value@b@     * pairs.@b@     *@b@     * <p> If there are multiple headers of the same names, the name@b@     * will map to a comma-separated list containing the values.@b@     *@b@     * @param headerPart The <code>header-part</code> of the current@b@     *                   <code>encapsulation</code>.@b@     *@b@     * @return A <code>Map</code> containing the parsed HTTP request headers.@b@     */@b@    protected Map /* String, String */ parseHeaders(String headerPart)@b@    {@b@        Map headers = new HashMap();@b@        char buffer[] = new char[MAX_HEADER_SIZE];@b@        boolean done = false;@b@        int j = 0;@b@        int i;@b@        String header, headerName, headerValue;@b@        try@b@        {@b@            while (!done)@b@            {@b@                i = 0;@b@                // Copy a single line of characters into the buffer,@b@                // omitting trailing CRLF.@b@                while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n')@b@                {@b@                    buffer[i++] = headerPart.charAt(j++);@b@                }@b@                header = new String(buffer, 0, i - 2);@b@                if (header.equals(""))@b@                {@b@                    done = true;@b@                }@b@                else@b@                {@b@                    if (header.indexOf(':') == -1)@b@                    {@b@                        // This header line is malformed, skip it.@b@                        continue;@b@                    }@b@                    headerName = header.substring(0, header.indexOf(':'))@b@                        .trim().toLowerCase();@b@                    headerValue =@b@                        header.substring(header.indexOf(':') + 1).trim();@b@                    if (getHeader(headers, headerName) != null)@b@                    {@b@                        // More that one heder of that name exists,@b@                        // append to the list.@b@                        headers.put(headerName,@b@                                    getHeader(headers, headerName) + ','@b@                                        + headerValue);@b@                    }@b@                    else@b@                    {@b@                        headers.put(headerName, headerValue);@b@                    }@b@                }@b@            }@b@        }@b@        catch (IndexOutOfBoundsException e)@b@        {@b@            // Headers were malformed. continue with all that was@b@            // parsed.@b@        }@b@        return headers;@b@    }@b@@b@@b@    /**@b@     * Returns the header with the specified name from the supplied map. The@b@     * header lookup is case-insensitive.@b@     *@b@     * @param headers A <code>Map</code> containing the HTTP request headers.@b@     * @param name    The name of the header to return.@b@     *@b@     * @return The value of specified header, or a comma-separated list if@b@     *         there were multiple headers of that name.@b@     */@b@    protected final String getHeader(Map /* String, String */ headers,@b@                                     String name)@b@    {@b@        return (String) headers.get(name.toLowerCase());@b@    }@b@@b@@b@    /**@b@     * Thrown to indicate that the request is not a multipart request.@b@     */@b@    public static class InvalidContentTypeException@b@        extends FileUploadException@b@    {@b@        /**@b@         * Constructs a <code>InvalidContentTypeException</code> with no@b@         * detail message.@b@         */@b@        public InvalidContentTypeException()@b@        {@b@            super();@b@        }@b@@b@        /**@b@         * Constructs an <code>InvalidContentTypeException</code> with@b@         * the specified detail message.@b@         *@b@         * @param message The detail message.@b@         */@b@        public InvalidContentTypeException(String message)@b@        {@b@            super(message);@b@        }@b@    }@b@@b@@b@    /**@b@     * Thrown to indicate that the request size is not specified.@b@     */@b@    public static class UnknownSizeException@b@        extends FileUploadException@b@    {@b@        /**@b@         * Constructs a <code>UnknownSizeException</code> with no@b@         * detail message.@b@         */@b@        public UnknownSizeException()@b@        {@b@            super();@b@        }@b@@b@        /**@b@         * Constructs an <code>UnknownSizeException</code> with@b@         * the specified detail message.@b@         *@b@         * @param message The detail message.@b@         */@b@        public UnknownSizeException(String message)@b@        {@b@            super(message);@b@        }@b@    }@b@@b@@b@    /**@b@     * Thrown to indicate that the request size exceeds the configured maximum.@b@     */@b@    public static class SizeLimitExceededException@b@        extends FileUploadException@b@    {@b@        /**@b@         * Constructs a <code>SizeExceededException</code> with no@b@         * detail message.@b@         */@b@        public SizeLimitExceededException()@b@        {@b@            super();@b@        }@b@@b@        /**@b@         * Constructs an <code>SizeExceededException</code> with@b@         * the specified detail message.@b@         *@b@         * @param message The detail message.@b@         */@b@        public SizeLimitExceededException(String message)@b@        {@b@            super(message);@b@        }@b@    }@b@@b@}

2.  应用常见具体DiskFileUpload、FileUpload实现类

package org.apache.tomcat.util.http.fileupload; @b@@b@import java.io.File;@b@import java.util.List;@b@import javax.servlet.http.HttpServletRequest; @b@ @b@public class DiskFileUpload@b@    extends FileUploadBase@b@ { @b@@b@    /**@b@     * The factory to use to create new form items.@b@     */@b@    private DefaultFileItemFactory fileItemFactory;@b@@b@@b@    // ----------------------------------------------------------- Constructors@b@@b@@b@    /**@b@     * Constructs an instance of this class which uses the default factory to@b@     * create <code>FileItem</code> instances.@b@     *@b@     * @see #DiskFileUpload(DefaultFileItemFactory fileItemFactory)@b@     */@b@    public DiskFileUpload()@b@    {@b@        super();@b@        this.fileItemFactory = new DefaultFileItemFactory();@b@    }@b@@b@@b@    /**@b@     * Constructs an instance of this class which uses the supplied factory to@b@     * create <code>FileItem</code> instances.@b@     *@b@     * @see #DiskFileUpload()@b@     */@b@    public DiskFileUpload(DefaultFileItemFactory fileItemFactory)@b@    {@b@        super();@b@        this.fileItemFactory = fileItemFactory;@b@    }@b@@b@@b@    // ----------------------------------------------------- Property accessors@b@@b@@b@    /**@b@     * Returns the factory class used when creating file items.@b@     *@b@     * @return The factory class for new file items.@b@     */@b@    public FileItemFactory getFileItemFactory()@b@    {@b@        return fileItemFactory;@b@    }@b@@b@@b@    /**@b@     * Sets the factory class to use when creating file items. The factory must@b@     * be an instance of <code>DefaultFileItemFactory</code> or a subclass@b@     * thereof, or else a <code>ClassCastException</code> will be thrown.@b@     *@b@     * @param factory The factory class for new file items.@b@     */@b@    public void setFileItemFactory(FileItemFactory factory)@b@    {@b@        this.fileItemFactory = (DefaultFileItemFactory) factory;@b@    }@b@@b@@b@    /**@b@     * Returns the size threshold beyond which files are written directly to@b@     * disk.@b@     *@b@     * @return The size threshold, in bytes.@b@     *@b@     * @see #setSizeThreshold(int)@b@     */@b@    public int getSizeThreshold()@b@    {@b@        return fileItemFactory.getSizeThreshold();@b@    }@b@@b@@b@    /**@b@     * Sets the size threshold beyond which files are written directly to disk.@b@     *@b@     * @param sizeThreshold The size threshold, in bytes.@b@     *@b@     * @see #getSizeThreshold()@b@     */@b@    public void setSizeThreshold(int sizeThreshold)@b@    {@b@        fileItemFactory.setSizeThreshold(sizeThreshold);@b@    }@b@@b@@b@    /**@b@     * Returns the location used to temporarily store files that are larger@b@     * than the configured size threshold.@b@     *@b@     * @return The path to the temporary file location.@b@     *@b@     * @see #setRepositoryPath(String)@b@     */@b@    public String getRepositoryPath()@b@    {@b@        return fileItemFactory.getRepository().getPath();@b@    }@b@@b@@b@    /**@b@     * Sets the location used to temporarily store files that are larger@b@     * than the configured size threshold.@b@     *@b@     * @param repositoryPath The path to the temporary file location.@b@     *@b@     * @see #getRepositoryPath()@b@     */@b@    public void setRepositoryPath(String repositoryPath)@b@    {@b@        fileItemFactory.setRepository(new File(repositoryPath));@b@    }@b@@b@@b@    // --------------------------------------------------------- Public methods@b@@b@@b@    /**@b@     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>@b@     * compliant <code>multipart/form-data</code> stream. If files are stored@b@     * on disk, the path is given by <code>getRepository()</code>.@b@     *@b@     * @param req           The servlet request to be parsed. Must be non-null.@b@     * @param sizeThreshold The max size in bytes to be stored in memory.@b@     * @param sizeMax       The maximum allowed upload size, in bytes.@b@     * @param path          The location where the files should be stored.@b@     *@b@     * @return A list of <code>FileItem</code> instances parsed from the@b@     *         request, in the order that they were transmitted.@b@     *@b@     * @exception FileUploadException if there are problems reading/parsing@b@     *                                the request or storing files.@b@     */@b@    public List /* FileItem */ parseRequest(HttpServletRequest req,@b@                                            int sizeThreshold,@b@                                            long sizeMax, String path)@b@        throws FileUploadException@b@    {@b@        setSizeThreshold(sizeThreshold);@b@        setSizeMax(sizeMax);@b@        setRepositoryPath(path);@b@        return parseRequest(req);@b@    }@b@@b@}
package org.apache.tomcat.util.http.fileupload;@b@ @b@public class FileUpload@b@    extends FileUploadBase@b@ {@b@@b@    // ----------------------------------------------------------- Data members@b@@b@@b@    /**@b@     * The factory to use to create new form items.@b@     */@b@    private FileItemFactory fileItemFactory;@b@@b@@b@    // ----------------------------------------------------------- Constructors@b@@b@@b@    /**@b@     * Constructs an instance of this class which uses the default factory to@b@     * create <code>FileItem</code> instances.@b@     *@b@     * @see #FileUpload(FileItemFactory)@b@     */@b@    public FileUpload()@b@    {@b@        super();@b@    }@b@@b@@b@    /**@b@     * Constructs an instance of this class which uses the supplied factory to@b@     * create <code>FileItem</code> instances.@b@     *@b@     * @see #FileUpload()@b@     */@b@    public FileUpload(FileItemFactory fileItemFactory)@b@    {@b@        super();@b@        this.fileItemFactory = fileItemFactory;@b@    }@b@@b@@b@    // ----------------------------------------------------- Property accessors@b@@b@@b@    /**@b@     * Returns the factory class used when creating file items.@b@     *@b@     * @return The factory class for new file items.@b@     */@b@    public FileItemFactory getFileItemFactory()@b@    {@b@        return fileItemFactory;@b@    }@b@@b@@b@    /**@b@     * Sets the factory class to use when creating file items.@b@     *@b@     * @param factory The factory class for new file items.@b@     */@b@    public void setFileItemFactory(FileItemFactory factory)@b@    {@b@        this.fileItemFactory = factory;@b@    }@b@@b@@b@}
<<热门下载>>