public 폴더 밖에 저장된 파일의 다운로드 구현

파일 업로드하는 부분은 너무 일반적인 구현이고, 딱히 팁이라 할 수 있는 부분이 없어 생략했습니다. 다운로드 부분은 Content-Type을 지정하는 부분이 팁이라 할 수 있을 것 같네요.

Step #1 라우팅

// app/Http/routes.php

Route::get('files/{id}/download', [
    'as'   => 'files.download',
    'uses' => 'FilesController@download'
]);

Step #2 컨트롤러

// app/Http/Controllers/FilesController

class ReleasesController extends Controller 
{
    public function __construct()
    {
        $this->middleware('auth'); // 로그인된 사용자만 download 메소드에 접근할 수 있게 한다.
    }

    // files 폴더에 파일이 저장되어 있다고 가정한다.
    // Files 모델은 name 속성을 가지고 있고, 파일명을 담고 있다고 가정한다.
    public function download($id) 
    {
        $file = \App\Files::findOrFail($id);
        $path = base_path('files' . DIRECTORY_SEPARATOR . $file->name);

        return response()->download($path, $file->name, $this->getContentTypeHeader($file->name));
    }

    // 특히 모바일의 경우, Response Header의 Content-Type에 따라
    // 미리 지정된 App을 뛰우는 경우가 많아, Content-Type 지정은 꼭 필요하다.
    // Laravel에 그런 기능이 없어서, 아래 메소드와 Step#3의 Config로 별도 구현한 것이다.
    // 다른 컨트롤러에서도 사용할 수 있도록 아래 메소드는 app/Http/Controller.php 로 옮기는 것이 좋다.
    protected function getContentTypeHeader($path) 
    {
        $extension = pathinfo($path, PATHINFO_EXTENSION);
        $contentType = config("mime.{$extension}") ?: 'application/octet-stream';

        return ['Content-Type' => $contentType];
    }
}

Step #3 mime.php Config 파일 생성

// config/mime.php

<?php

return [

    'avi'  => 'video/avi',
    'apk'  => 'application/vnd.android.package-archive',
    'csv'  => 'text/csv',
    'doc'  => 'application/msword',
    'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    'flv'  => 'video/x-flv',
    'gz'   => 'application/gzip',
    'gzip' => 'application/gzip',
    'html' => 'text/html',
    'java' => 'text/plain',
    'jpg'  => 'image/jpeg',
    'jpeg' => 'image/jpeg',
    'json' => 'application/json',
    'm3u'  => 'audio/x-mpequrl',
    'mkv'  => 'video/x-matroska',
    'mp4'  => 'video/mp4',
    'ogg'  => 'video/ogg',
    'pdf'  => 'application/pdf',
    'rtf'  => 'application/rtf',
    'png'  => 'image/png',
    'ppt'  => 'application/vnd.ms-powerpoint',
    'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
    'txt'  => 'text/plain',
    'webm' => 'video/webm',
    'wmv'  => 'video/x-ms-wmv',
    'xls'  => 'application/vnd.ms-excel',
    'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    'xml'  => 'application/xml',
    'zip'  => 'application/zip'

];

참고 보통 모델명은 단수(Singular)로 씁니다. 과거 L4 시절, File이라고 모델명을 썼다가, File 파사드와 중복되어 디버깅하는데 엄청 고생한 기억이 있습니다. L5 넘어오고, Psr4로 바뀌면서 이런 문제점은 없어졌지만, 여전히 주의가 요구됩니다.

comments powered by Disqus