com.feilong.servlet.http.ResponseDownloadUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.feilong.servlet.http.ResponseDownloadUtil.java

Source

/*
 * Copyright (C) 2008 feilong
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.feilong.servlet.http;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.feilong.core.UncheckedIOException;
import com.feilong.core.net.URIUtil;
import com.feilong.io.FileUtil;
import com.feilong.io.IOWriteUtil;
import com.feilong.io.MimeTypeUtil;
import com.feilong.io.entity.MimeType;

import static com.feilong.core.CharsetType.UTF8;
import static com.feilong.core.Validator.isNotNullOrEmpty;
import static com.feilong.core.date.DateExtensionUtil.formatDuration;

/**
 *  {@link javax.servlet.http.HttpServletResponse}.
 *
 * @author <a href="http://feitianbenyue.iteye.com/">feilong</a>
 * @see javax.servlet.http.HttpServletResponse
 * @since 1.5.1
 */
public final class ResponseDownloadUtil {

    /** The Constant LOGGER. */
    private static final Logger LOGGER = LoggerFactory.getLogger(ResponseDownloadUtil.class);

    /** Don't let anyone instantiate this class. */
    private ResponseDownloadUtil() {
        //AssertionError?. ?????. ???.
        //see Effective Java 2nd
        throw new AssertionError("No " + getClass().getName() + " instances for you!");
    }

    /**
     * .
     *
     * @param pathname
     *            the pathname
     * @param request
     *            ??request? ,?log
     * @param response
     *            the response
     * @see #download(File, HttpServletRequest, HttpServletResponse)
     * @since 1.4.1
     */
    public static void download(String pathname, HttpServletRequest request, HttpServletResponse response) {
        download(new File(pathname), request, response);
    }

    /**
     * .
     *
     * @param file
     *            the file
     * @param request
     *            ??request? ,?log
     * @param response
     *            the response
     * @see com.feilong.io.FilenameUtil#getFileName(String)
     * @see com.feilong.io.FileUtil#getFileInputStream(File)
     * @see #download(String, InputStream, Number, HttpServletRequest, HttpServletResponse)
     * @since 1.4.1
     */
    public static void download(File file, HttpServletRequest request, HttpServletResponse response) {
        String saveFileName = file.getName();
        // ??.
        InputStream inputStream = FileUtil.getFileInputStream(file);
        long contentLength = FileUtil.getFileSize(file);

        download(saveFileName, inputStream, contentLength, request, response);
    }

    /**
     * ( contentType=application/force-download) .
     *
     * @param saveFileName
     *            ???, Content-Disposition header 
     * @param inputStream
     *            ???
     * @param contentLength
     *            ?????,
     *            <p>
     *            HttpURLConnectionhttpconn=(HttpURLConnection)url.openConnection();<br>
     *            httpconn.getContentLength();
     *            </p>
     *            {@link InputStream#available()} ?
     * @param request
     *            ??request? ,?log
     * @param response
     *            response
     * @see IOWriteUtil#write(InputStream, OutputStream)
     * @see "org.springframework.http.MediaType"
     */
    public static void download(String saveFileName, InputStream inputStream, Number contentLength,
            HttpServletRequest request, HttpServletResponse response) {
        //?
        String contentType = null;
        String contentDisposition = null;
        download(saveFileName, inputStream, contentLength, contentType, contentDisposition, request, response);
    }

    /**
     * .
     *
     * @param saveFileName
     *            ???, Content-Disposition header 
     * @param inputStream
     *            ???
     * @param contentLength
     *            ?????
     * @param contentType
     *            the content type
     * @param contentDisposition
     *            the content disposition
     * @param request
     *            ??request? ,?log
     * @param response
     *            response
     * @see IOWriteUtil#write(InputStream, OutputStream)
     * @see "org.springframework.http.MediaType"
     * @see "org.apache.http.HttpHeaders"
     * @see "org.springframework.http.HttpHeaders"
     * @see com.feilong.io.MimeTypeUtil#getContentTypeByFileName(String)
     * @see javax.servlet.ServletContext#getMimeType(String)
     */
    public static void download(String saveFileName, InputStream inputStream, Number contentLength,
            String contentType, String contentDisposition, HttpServletRequest request,
            HttpServletResponse response) {

        setDownloadResponseHeader(saveFileName, contentLength, contentType, contentDisposition, response);

        //**********************************?********************************************************************
        downLoadData(saveFileName, inputStream, contentLength, request, response);
    }

    /**
     * Down load data.
     *
     * @param saveFileName
     *            the save file name
     * @param inputStream
     *            the input stream
     * @param contentLength
     *            the content length
     * @param request
     *            the request
     * @param response
     *            the response
     */
    private static void downLoadData(String saveFileName, InputStream inputStream, Number contentLength,
            HttpServletRequest request, HttpServletResponse response) {
        Date beginDate = new Date();
        String length = FileUtil.formatSize(contentLength.longValue());
        LOGGER.info("begin download~~,saveFileName:[{}],contentLength:[{}]", saveFileName, length);
        try {
            OutputStream outputStream = response.getOutputStream();
            IOWriteUtil.write(inputStream, outputStream);
            if (LOGGER.isInfoEnabled()) {
                String pattern = "end download,saveFileName:[{}],contentLength:[{}],time use:[{}]";
                LOGGER.info(pattern, saveFileName, length, formatDuration(beginDate));
            }
        } catch (IOException e) {
            /*
             * ?,  ClientAbortException , ?,????, ,.
             * ??, ??
             * ?,???...???,
             * ?, KILL?, ,?? ClientAbortException.
             */
            //ClientAbortException:  java.net.SocketException: Connection reset by peer: socket write error
            final String exceptionName = e.getClass().getName();

            if (StringUtils.contains(exceptionName, "ClientAbortException")
                    || StringUtils.contains(e.getMessage(), "ClientAbortException")) {
                String pattern = "[ClientAbortException],maybe user use Thunder soft or abort client soft download,exceptionName:[{}],exception message:[{}] ,request User-Agent:[{}]";
                LOGGER.warn(pattern, exceptionName, e.getMessage(), RequestUtil.getHeaderUserAgent(request));
            } else {
                LOGGER.error("[download exception],exception name: " + exceptionName, e);
                throw new UncheckedIOException(e);
            }
        }
    }

    /**
     *  download response header.
     *
     * @param saveFileName
     *            the save file name
     * @param contentLength
     *            the content length
     * @param contentType
     *            ?,;,? <code>null</code>,,?? {@link #resolverContentType(String, String)}
     * @param contentDisposition
     *            ?,;,? <code>null</code>,,?? {@link #resolverContentDisposition(String, String)}
     * @param response
     *            the response
     */
    private static void setDownloadResponseHeader(String saveFileName, Number contentLength, String contentType,
            String contentDisposition, HttpServletResponse response) {

        //response
        //getResponsegetWriter()??,??,?response.resetresetBuffer.
        //getOutputStream() has already been called for this response
        //jsp??,response.getOutputStream()??:java.lang.IllegalStateException:getOutputStream() has already been called for this response,Exception
        response.reset();

        //*************************************************************************
        response.addHeader(HttpHeaders.CONTENT_DISPOSITION,
                resolverContentDisposition(saveFileName, contentDisposition));

        // ===================== Default MIME Type Mappings =================== -->
        //??,?,????.????,???,
        response.setContentType(resolverContentType(saveFileName, contentType));

        if (isNotNullOrEmpty(contentLength)) {
            response.setContentLength(contentLength.intValue());
        }

        //************************about buffer***********************************************************

        //?:??,?,.
        //??,?:
        //JSP??
        //
        //JSPout.flush()response.flushbuffer()

        //:?,?,???,???.
        //XXX ?? response.setBufferSize(10240); ^_^

        //see org.apache.commons.io.IOUtils.copyLarge(InputStream, OutputStream) javadoc
        //This method buffers the input internally, so there is no need to use a BufferedInputStream
    }

    /**
     * Resolver content disposition.
     * 
     * <p>
     *  ?
     * </p>
     * 
     * <pre class="code">
     * Content-Disposition takes one of two values, `inline' and  `attachment'.  
     * 'Inline' indicates that the entity should be immediately displayed to the user, 
     * whereas `attachment' means that the user should take additional action to view the entity.
     * The `filename' parameter can be used to suggest a filename for storing the bodypart, if the user wishes to store it in an external file.
     * </pre>
     *
     * @param saveFileName
     *            the save file name
     * @param contentDisposition
     *            the content disposition
     * @return the string
     * @since 1.4.0
     */
    private static String resolverContentDisposition(String saveFileName, String contentDisposition) {
        return isNotNullOrEmpty(contentDisposition) ? contentDisposition
                : "attachment; filename=" + URIUtil.encode(saveFileName, UTF8);
    }

    /**
     * Resolver content type.
     *
     * @param saveFileName
     *            the save file name
     * @param inputContentType
     *            the content type
     * @return the string
     * @since 1.4.0
     */
    private static String resolverContentType(String saveFileName, String inputContentType) {
        //See tomcat web.xml
        //When serving static resources, Tomcat will automatically generate a "Content-Type" header based on the resource's filename extension, based on these mappings.  
        //Additional mappings can be added here (to apply to all web applications), or in your own application's web.xml deployment descriptor.                                               -->

        if (isNotNullOrEmpty(inputContentType)) {
            return inputContentType;
        }
        String contentTypeByFileName = MimeTypeUtil.getContentTypeByFileName(saveFileName);

        //contentType = "application/force-download";//,phpapplication/force-download,??HTTP ?,???
        //application/x-download

        //.*( ?,??)   application/octet-stream
        return isNotNullOrEmpty(contentTypeByFileName) ? contentTypeByFileName : MimeType.BIN.getMime();
        //The HTTP specification recommends setting the Content-Type to application/octet-stream. 
        //Unfortunately, this causes problems with Opera 6 on Windows (which will display the raw bytes for any file whose extension it doesn't recognize) and on Internet Explorer 5.1 on the Mac (which will display inline content that would be downloaded if sent with an unrecognized type).
    }
}