ASP.NET Core에서 정적 파일 서비스하기

등록일시: 2017-06-02 08:00,  수정일시: 2017-11-30 12:00
조회수: 8,252
이 문서는 ASP.NET Core 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 ASP.NET Core 응용 프로그램에서 정적 파일을 서비스하는 방법을 살펴봅니다.

HTML, CSS, 이미지 및 JavaScript 같은 정적 파일은 ASP.NET Core 응용 프로그램이 클라이언트에 직접 서비스할 수 있는 자산입니다.

예제 코드 살펴보기 및 다운로드

정적 파일 서비스하기

일반적으로 정적 파일은 웹 루트 폴더에 위치합니다 (<content-root>/wwwroot). 이에 대한 보다 자세한 정보는 콘텐츠 루트웹 루트를 참고하시기 바랍니다. 보통 현재 디렉터리를 콘텐츠 루트로 설정해서, 개발 중 프로젝트의 웹 루트가 노출되도록 만드는 경우가 대부분입니다.

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .Build();

    host.Run();
}

정적 파일은 웹 루트 하위에 존재하는 모든 폴더에 저장될 수 있으며, 웹 루트 기준의 상대 경로를 통해서 접근할 수 있습니다. 예를 들어서, Visual Studio를 이용해서 기본 웹 응용 프로그램 프로젝트를 생성하면, wwwroot 폴더 하위에 css, imagesjs 같은 몇 가지 폴더가 만들어집니다. 이 경우, images 하위 폴더에 위치한 이미지에 접근하기 위한 URI는 다음과 같습니다:

  • http://<app>/images/<imageFileName>

  • http://localhost:9189/images/banner3.svg

정적 파일을 서비스하기 위해서는 정적 파일에 대한 미들웨어를 구성해서 파이프라인에 추가해야 합니다. 프로젝트에 Microsoft.AspNetCore.StaticFiles 패키지에 대한 의존성을 추가한 다음, Startup.Configure 메서드에서 UseStaticFiles 확장 메서드를 호출해서 정적 파일 미들웨어를 구성할 수 있습니다:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles();
}

app.UseStaticFiles(); 확장 메서드 호출은 웹 루트 (기본적으로 wwwroot) 하위의 파일들을 서비스 가능하게 만들어줍니다. 본문의 뒷부분에서는 UseStaticFiles 확장 메서드를 이용해서 다른 디렉터리에 존재하는 콘텐츠를 서비스 가능하게 만드는 방법도 살펴볼 것입니다.

정적 파일을 서비스하기 위해서는 반드시 "Microsoft.AspNetCore.StaticFiles" NuGet 패키지가 필요합니다.

노트

기본 웹 루트는 wwwroot 디렉터리지만, UseWebRoot 메서드를 이용해서 다른 경로를 웹 루트로 지정할 수도 있습니다.

프로젝트 구조상 서비스하고자 하는 정적 파일이 다음과 같이 웹 루트 외부에도 존재한다고 가정해보겠습니다:

  • wwwroot
    • css
    • images
    • ...
  • MyStaticFiles
    • test.png

이런 경우, test.png 파일에 접근하기 위한 요청을 허용하려면, 다음과 같이 정적 파일 미들웨어를 구성해야 합니다:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles(); // For the wwwroot folder

    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")),
        RequestPath = new PathString("/StaticFiles")
    });
}

이제 http://<app>/StaticFiles/test.png를 요청하면 test.png 파일에 접근할 수 있습니다.

StaticFileOptions 클래스를 사용해서 응답 헤더를 설정할 수도 있습니다. 예를 들어서, 다음 코드는 wwwroot 폴더 하위에 존재하는 정적 파일을 서비스하도록 설정하고, 각 파일을 10분(600초) 동안 캐시할 수 있도록 Cache-Control 헤더를 설정합니다:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles(new StaticFileOptions()
    {
        OnPrepareResponse = ctx =>
        {
            ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=600");
        }
    });
}

정적 파일 권한 부여

정적 파일 모듈은 권한 점검 기능을 제공하지 않습니다. wwwroot 폴더 하위에 존재하는 파일을 비롯해서, 정적 파일 미들웨어로 서비스되는 모든 파일들은 공개적으로 사용할 수 있습니다. 권한에 따라 파일을 서비스하려면 다음과 같이 작업을 수행하십시오:

  • 대상 파일을 wwwroot 및 정적 파일 미들웨어가 접근할 수 있는 모든 디렉터리의 외부에 저장합니다. 그리고

  • 권한 부여가 적용된 컨트롤러의 액션에서 FileResult를 반환하는 방식으로 파일을 서비스합니다.

디렉터리 브라우징 활성화시키기

디렉터리 브라우징을 활성화시키면 웹 응용 프로그램의 사용자가 특정 디렉터리 내의 디렉터리 및 파일 목록을 확인할 수 있습니다. 기본적으로 디렉터리 브라우징은 보안상의 이유로 비활성화되어 있습니다 (고려 사항 절을 참고하시기 바랍니다). 디렉터리 브라우징을 활성화시키려면, 먼저 Startup.Configure 메서드에서 UseDirectoryBrowser 확장 메서드를 호출합니다:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles(); // For the wwwroot folder

    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot", "images")),
        RequestPath = new PathString("/MyImages")
    });

    app.UseDirectoryBrowser(new DirectoryBrowserOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot", "images")),
        RequestPath = new PathString("/MyImages")
    });
}

그런 다음, Startup.ConfigureServices 메서드에서 AddDirectoryBrowser 확장 메서드를 호출하여 필요한 서비스를 추가합니다:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDirectoryBrowser();
}

이 코드는 http://<app>/MyImages라는 URL을 이용한 wwwroot/images 폴더의 디렉터리 브라우징을 허용하며, 하위의 각 파일 및 폴더에 대한 링크를 제공합니다:

directory browsing

디렉터리 브라우징을 허용할 경우 발생할 수 있는 보안상의 위험에 대해서는 고려 사항 절을 참고하시기 바랍니다.

이 코드에서 app.UseStaticFiles 확장 메서드를 두 번 호출한다는 점에 유의하십시오. 첫 번째 호출은 wwwroot 폴더 하위의 CSS, images 및 JavaScript 디렉터리를 서비스하기 위한 호출이고, 두 번째 호출은 http://<app>/MyImages URL을 이용한 wwwroot/images 폴더의 디렉터리 브라우징을 허용하기 위한 호출입니다:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles(); // For the wwwroot folder

    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot", "images")),
        RequestPath = new PathString("/MyImages")
    });

    app.UseDirectoryBrowser(new DirectoryBrowserOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot", "images")),
        RequestPath = new PathString("/MyImages")
    });
}

기본 문서 제공하기

기본 홈 페이지를 설정하면 사용자가 사이트를 방문할 때 처음 시작할 페이지를 지정할 수 있습니다. 사용자가 완전한 URI를 지정하지 않더라도 웹 응용 프로그램에서 기본 페이지를 제공하려면, 다음과 같이 Startup.Configure 메서드에서 UseDefaultFiles 확장 메서드를 호출합니다:

public void Configure(IApplicationBuilder app)
{
    app.UseDefaultFiles();
    app.UseStaticFiles();
}
노트

기본 파일을 제공하기 위해서는 반드시 UseDefaultFiles 확장 메서드를 UseStaticFiles 확장 메서드보다 먼저 호출해야 합니다. UseDefaultFiles 확장 메서드는 실제로는 파일을 제공하지 않는 URL 재작성자입니다. 따라서 해당 파일을 서비스하기 위해서는 정적 파일 미들웨어를 활성화시켜야만 (UseStaticFiles) 합니다.

UseDefaultFiles 확장 메서드를 적용하면, 폴더에 대한 요청 시 다음 파일들을 검색합니다:

  • default.htm
  • default.html
  • index.htm
  • index.html

이 목록에서 발견된 첫 번째 파일이, 마치 URI가 완전하게 지정된 요청인 것처럼 제공됩니다 (비록 브라우저에 나타나는 URI는 여전히 처음 요청된 URI 그대로지만).

다음 코드는 기본 파일의 이름을 mydefault.html 로 변경하는 방법을 보여줍니다:

public void Configure(IApplicationBuilder app)
{
    // Serve my app-specific default file, if present.
    DefaultFilesOptions options = new DefaultFilesOptions();
    options.DefaultFileNames.Clear();
    options.DefaultFileNames.Add("mydefault.html");
    app.UseDefaultFiles(options);
    app.UseStaticFiles();
}

UseFileServer

UseFileServer 확장 메서드는 UseStaticFiles, UseDefaultFilesUseDirectoryBrowser 확장 메서드의 기능을 한꺼번에 제공합니다.

다음 코드는 정적 파일 기능과 기본 파일 기능은 제공하지만, 디렉터리 브라우징은 허용하지 않습니다:

app.UseFileServer();

반면, 다음 코드는 정적 파일, 기본 파일 및 디렉터리 브라우징 기능을 모두 허용합니다:

app.UseFileServer(enableDirectoryBrowsing: true);

디렉터리 브라우징을 허용할 경우 발생할 수 있는 보안상의 위험에 대해서는 고려 사항 절을 참고하시기 바랍니다. 웹 루트 외부에 존재하는 파일들을 대상으로 UseStaticFiles, UseDefaultFilesUseDirectoryBrowser 확장 메서드의 기능을 한꺼번에 제공하려면, FileServerOptions 개체의 인스턴스를 생성하고 구성한 다음, 이를 UseFileServer 확장 메서드에 매개 변수로 전달하면 됩니다. 예를 들어서, 웹 응용 프로그램의 디렉터리 계층 구조가 다음과 같다고 가정해보겠습니다:

  • wwwroot
    • css
    • images
    • ...
  • MyStaticFiles
    • test.png
    • default.html

이 예제 계층 구조를 사용하면서 MyStaticFiles 디렉터리를 대상으로 정적 파일, 기본 파일 및 디렉터리 브라우징 기능을 제공해야 할 수도 있습니다. 다음 코드는 FileServerOptions 확장 메서드를 단 한 번만 호출해서 해당 기능들을 활성화시키는 방법을 보여줍니다:

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles(); // For the wwwroot folder

    app.UseFileServer(new FileServerOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"MyStaticFiles")),
        RequestPath = new PathString("/StaticFiles"),
        EnableDirectoryBrowsing = true
    });
}

그리고 enableDirectoryBrowsing 속성을 true로 설정하는 경우에는 Startup.ConfigureServices 메서드에서 AddDirectoryBrowser 확장 메서드를 호출해야만 합니다:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDirectoryBrowser();
}

다음의 표는 위의 파일 계층 구조와 코드를 사용할 경우의 요청 및 응답을 보여줍니다:

URI 응답
http://<app>/StaticFiles/test.png MyStaticFiles/test.png
http://<app>/StaticFiles MyStaticFiles/default.html

만약 MyStaticFiles 디렉터리에 기본 파일이 존재하지 않으면, http://<app>/StaticFiles URI는 클릭 가능한 링크가 제공되는 디렉터리 목록을 반환합니다:

Static files list

노트

UseDefaultFilesUseDirectoryBrowser 확장 메서드는 슬래시로 끝나지 않는 http://<app>/StaticFiles 같은 URL을 전달받으면, 후행 슬래시를 추가한 http://<app>/StaticFiles/ 같은 URL로 클라이언트 측 재지정을 수행합니다. URL이 슬래시로 끝나지 않으면 문서 내의 상대 URL이 올바르지 않게 됩니다.

FileExtensionContentTypeProvider 클래스

FileExtensionContentTypeProvider 클래스에는 파일 확장자와 MIME 콘텐츠 형식을 매핑하는 컬렉션이 담겨 있습니다. 다음 예제에서는 몇 가지 파일 확장자를 일반적으로 알려진 MIME 형식으로 등록하고, ".rtf" 확장자에 대한 MIME 형식은 대체하고, ".mp4" 확장자는 제거하고 있습니다.

public void Configure(IApplicationBuilder app)
{
    // Set up custom content types -associating file extension to MIME type
    var provider = new FileExtensionContentTypeProvider();
    // Add new mappings
    provider.Mappings[".myapp"] = "application/x-msdownload";
    provider.Mappings[".htm3"] = "text/html";
    provider.Mappings[".image"] = "image/png";
    // Replace an existing mapping
    provider.Mappings[".rtf"] = "application/x-msdownload";
    // Remove MP4 videos.
    provider.Mappings.Remove(".mp4");

    app.UseStaticFiles(new StaticFileOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot", "images")),
        RequestPath = new PathString("/MyImages"),
        ContentTypeProvider = provider
    });

    app.UseDirectoryBrowser(new DirectoryBrowserOptions()
    {
        FileProvider = new PhysicalFileProvider(
            Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot", "images")),
        RequestPath = new PathString("/MyImages")
    });
}

MIME content types 문서를 참고하시기 바랍니다.

비표준 콘텐츠 형식

ASP.NET 정적 파일 미들웨어는 거의 400 여개의 알려진 파일 콘텐츠 형식을 인식할 수 있습니다. 사용자가 알 수 없는 파일 유형의 파일을 요청하면 정적 파일 미들웨어는 HTTP 404 (찾을 수 없음) 응답을 반환합니다. 디렉토리 브라우징이 활성화된 경우에도 파일에 대한 링크는 표시되지만, 해당 URI는 HTTP 404 오류를 반환합니다.

다음 코드는 알 수 없는 형식의 파일에 대한 서비스를 활성화시키고, 알 수 없는 파일을 이미지 형식으로 렌더합니다.

public void Configure(IApplicationBuilder app)
{
    app.UseStaticFiles(new StaticFileOptions()
    {
        ServeUnknownFileTypes = true,
        DefaultContentType = "image/png"
    });
}

이 코드를 사용하면, 알 수 없는 콘텐츠 형식의 파일에 대한 요청이 이미지 형식으로 반환됩니다.

경고

이런 방식으로 ServeUnknownFileTypes 기능을 사용하면 보안상 위험하므로 권장되지 않습니다. 이전 절에서 살펴본 FileExtensionContentTypeProvider 클래스가 비표준 확장자를 가진 파일을 서비스하기 위한 보다 안전한 방식의 대안을 제공해줍니다.

고려 사항

경고

UseDirectoryBrowserUseStaticFiles 기능은 보안에 취약합니다. 운영 환경에서는 디렉터리 브라우징 기능을 비활성화시키는 것을 권장합니다. UseStaticFiles이나 UseDirectoryBrowser를 이용해서 전체 디렉터리를 활성화시키고 모든 하위 디렉터리를 접근할 수 있게 허용할 디렉터리를 결정할 때는 극도로 주의해야 합니다. 공개 콘텐츠는 <content root>/wwwroot 같은 자체적인 디렉터리에 저장하고, 응용 프로그램 뷰, 구성 파일 등으로부터 격리하는 것을 권장합니다.

  • UseDirectoryBrowserUseStaticFiles를 통해서 노출되는 콘텐츠의 URL은 기반 파일 시스템의 대소문자 구분 및 문자 제한의 영향을 받습니다. 가령, Windows는 대소문자를 구분하지 않지만, Mac과 Linux는 대소문자를 구분합니다.

  • IIS에서 호스팅되는 ASP.NET Core 응용 프로그램은 ASP.NET Core 모듈을 통해서 정적 파일에 관한 요청을 비롯한 모든 요청을 응용 프로그램에 전달합니다. IIS의 정적 파일 처리기는 ASP.NET Core 모듈이 요청을 처리하기 전에 먼저 요청을 처리할 수 있는 기회가 없으므로 전혀 사용되지 않습니다.

  • 서버 수준이나 웹사이트 수준에서 IIS 정적 파일 처리기를 제거하려면 IIS(인터넷 정보 서비스) 관리자에서 다음의 과정을 수행하십시오:

    • 모듈(Modules) 기능으로 이동합니다.

    • 목록에서 StaticFileModule 모듈을 선택합니다.

    • 우측의 작업(Actions) 패인에서 제거(Remove) 링크 버튼을 클릭합니다.

경고

만약 IIS의 정적 파일 처리기가 활성화되어 있고, ASP.NET Core 모듈(ANCM)이 정상적으로 구성되어 있지 않다면 (가령 web.config 파일이 배포되지 않은 경우), IIS 정적 파일 처리기에 의해서 정적 파일이 서비스 될 것입니다.

  • C#이나 Razor 같은 코드 파일은 반드시 프로젝트의 웹 루트 (기본값 wwwroot) 외부에 위치해야 합니다. 그래야만, 응용 프로그램의 클라이언트 측 콘텐츠와 서버 측 코드 간에 명확한 분리를 제공하고, 서버 측 코드가 유출되는 것을 방지할 수 있습니다.

추가 자료