src/Response.php
public function sendFile($filename, $download = null, array $options = null)
    {
        // 强制指定了mime
        if ( ! empty($options['mime_type']))
        {
            $mime = $options['mime_type'];
        }

        // 返回当前的响应内容，作为本次下载
        if (true === $filename)
        {
            if (empty($download))
            {
                throw new BaseException('Download name must be provided for streaming files');
            }

            $options['delete'] = false;

            if ( ! isset($mime))
            {
                // 根据扩展名，获取指定的mime类型
                $mime = Mime::getMimeFromExtension(strtolower(pathinfo($download, PATHINFO_EXTENSION)));
            }

            // 将响应内容保存到临时文件
            $fileData = (string) $this->message->body;
            $size = strlen($fileData);
            $file = tmpfile();
            fwrite($file, $fileData);

            unset($fileData);
        }
        else
        {
            $filename = realpath($filename);

            if (empty($download))
            {
                $download = pathinfo($filename, PATHINFO_BASENAME);
            }

            $size = filesize($filename);

            if ( ! isset($mime))
            {
                $mime = Mime::getMimeFromExtension(pathinfo($download, PATHINFO_EXTENSION));
            }

            $file = fopen($filename, 'rb');
        }

        if ( ! is_resource($file))
        {
            throw new BaseException('Could not read file to send: :file', [
                ':file' => $download,
            ]);
        }

        // inline和attachment的区别，主要在于浏览器遇到inline类型的下载时，会尝试直接在浏览器打开，而attachment则不会
        $disposition = Arr::get($options, 'inline') ? 'inline' : 'attachment';

        $temp = $this->_calculateByteRange($size);
        $start = array_shift($temp);
        $end = array_shift($temp);

        if ( ! empty($options['resumable']))
        {
            if ($start > 0 || $end < ($size - 1))
            {
                // Partial Content
                $this->status = 206;
            }

            $this->headers('content-range', 'bytes ' . $start . '-' . $end . '/' . $size);
            $this->headers('accept-ranges', 'bytes');
        }

        $this->headers('content-disposition', $disposition . '; filename="' . $download . '"');
        $this->headers('content-type', $mime);
        $this->headers('content-length', (string) (($end - $start) + 1));

        $this->sendHeaders();

        // @notice 此方法在cli环境下可能无效

        while (ob_get_level())
        {
            ob_end_flush();
        }

        ignore_user_abort(true);

        $prevTimeLimit = ini_get('max_execution_time');
        @set_time_limit(0);

        // 16K
        $block = 1024 * 16;

        fseek($file, $start);

        while ( ! feof($file) && ($pos = ftell($file)) <= $end)
        {
            if (connection_aborted())
            {
                break;
            }

            if ($pos + $block > $end)
            {
                $block = $end - $pos + 1;
            }
            echo fread($file, $block);
            flush();
        }

        fclose($file);

        if ( ! empty($options['delete']))
        {
            try
            {
                unlink($filename);
            }
            catch (Exception $e)
            {
                Base::getLog()->error($e->getMessage(), [
                    'code' => $e->getCode(),
                ]);
            }
        }

        // 停止执行
        @set_time_limit($prevTimeLimit);
        Base::getHttp()->end();
    }
public function sendFile($filename, $download = null, array $options = null)
    {
        // 强制指定了mime
        if ( ! empty($options['mime_type']))
        {
            $mime = $options['mime_type'];
        }

        // 返回当前的响应内容，作为本次下载
        if (true === $filename)
        {
            if (empty($download))
            {
                throw new BaseException('Download name must be provided for streaming files');
            }

            $options['delete'] = false;

            if ( ! isset($mime))
            {
                // 根据扩展名，获取指定的mime类型
                $mime = Mime::getMimeFromExtension(strtolower(pathinfo($download, PATHINFO_EXTENSION)));
            }

            // 将响应内容保存到临时文件
            $fileData = (string) $this->message->body;
            $size = strlen($fileData);
            $file = tmpfile();
            fwrite($file, $fileData);

            unset($fileData);
        }
        else
        {
            $filename = realpath($filename);

            if (empty($download))
            {
                $download = pathinfo($filename, PATHINFO_BASENAME);
            }

            $size = filesize($filename);

            if ( ! isset($mime))
            {
                $mime = Mime::getMimeFromExtension(pathinfo($download, PATHINFO_EXTENSION));
            }

            $file = fopen($filename, 'rb');
        }

        if ( ! is_resource($file))
        {
            throw new BaseException('Could not read file to send: :file', [
                ':file' => $download,
            ]);
        }

        // inline和attachment的区别，主要在于浏览器遇到inline类型的下载时，会尝试直接在浏览器打开，而attachment则不会
        $disposition = Arr::get($options, 'inline') ? 'inline' : 'attachment';

        $temp = $this->_calculateByteRange($size);
        $start = array_shift($temp);
        $end = array_shift($temp);

        if ( ! empty($options['resumable']))
        {
            if ($start > 0 || $end < ($size - 1))
            {
                // Partial Content
                $this->status = 206;
            }

            $this->headers('content-range', 'bytes ' . $start . '-' . $end . '/' . $size);
            $this->headers('accept-ranges', 'bytes');
        }

        $this->headers('content-disposition', $disposition . '; filename="' . $download . '"');
        $this->headers('content-type', $mime);
        $this->headers('content-length', (string) (($end - $start) + 1));

        $this->sendHeaders();

        // @notice 此方法在cli环境下可能无效

        while (ob_get_level())
        {
            ob_end_flush();
        }

        ignore_user_abort(true);

        $prevTimeLimit = ini_get('max_execution_time');
        @set_time_limit(0);

        // 16K
        $block = 1024 * 16;

        fseek($file, $start);

        while ( ! feof($file) && ($pos = ftell($file)) <= $end)
        {
            if (connection_aborted())
            {
                break;
            }

            if ($pos + $block > $end)
            {
                $block = $end - $pos + 1;
            }
            echo fread($file, $block);
            flush();
        }

        fclose($file);

        if ( ! empty($options['delete']))
        {
            try
            {
                unlink($filename);
            }
            catch (Exception $e)
            {
                Base::getLog()->error($e->getMessage(), [
                    'code' => $e->getCode(),
                ]);
            }
        }

        // 停止执行
        @set_time_limit($prevTimeLimit);
        Base::getHttp()->end();
    }
通过HTTP发送文件，达到下载文件的效果

// 下载一个已经存在的文件
$response->sendFile('media/packages/package.zip');

// 将返回内容当做是下载处理：
$response->body = $content;
$response->sendFile(true, $filename);

@param  string $filename 文件名、文件路径，如果设置为`true`则返回当前的response内容
@param  string $download 下载文件名，默认为当前要处理的文件名
@param  array  $options  其他选项
@throws BaseException