ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ASP.NET Core] HTTP Cache-Control Header 를 이용한 Get 응답(Response) 캐싱 및 파일 다운로드 예제
    .NET/ASP.NET Core 2021. 4. 12. 22:34
    반응형

    운영중인 웹 서비스에 대한 수정요청이 있어 작업하던 도중 파일 다운로드기능에 캐싱이 필요하여 응답 캐싱이 가능하도록 작업했다. 사실 사용자 수가 많지 않은 내부서비스라 딱히 적용안해도 되긴 하지만 돈(?) 안되는 트래픽은 줄일 수 있으면 줄이는 습관을 가지는게 좋은지라 적용했다.

     

    적용자체도 그리 어렵지 않다.

    1. Startup.cs에 AddResponseCaching() 및 UseResponseCaching() 적용

    Startup.cs 에 services.AddResponseCaching()과 app.UseResponseCaching() 함수를 이용하여 캐싱 기능을 적용한다.

    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.HttpsPolicy;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using Microsoft.OpenApi.Models;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace CacheExample
    {
        public class Startup
        {
            public Startup(IConfiguration configuration)
            {
                Configuration = configuration;
            }
    
            public IConfiguration Configuration { get; }
    
            // This method gets called by the runtime. Use this method to add services to the container.
            public void ConfigureServices(IServiceCollection services)
            {
                services.AddResponseCaching(); // 캐시사용을 위해 추가
                services.AddControllers();
                services.AddSwaggerGen(c =>
                {
                    c.SwaggerDoc("v1", new OpenApiInfo { Title = "CacheExample", Version = "v1" });
                });
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                    app.UseSwagger();
                    app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "CacheExample v1"));
                }
                app.UseResponseCaching();  // 캐시사용을 위해 추가
                app.UseHttpsRedirection();
    
                app.UseRouting();
    
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllers();
                });
            }
        }
    }
    

     

    2. 파일 다운로드 메소드에 ResponseCache 속성 적용

    캐싱이 필요한 응답메소드에 ResponseCache 속성을 추가해주면 끝난다. 추가할땐 Duration 값을 설정해줘야 오류가 나지 않는다.

    using Microsoft.AspNetCore.Http;
    using Microsoft.AspNetCore.Mvc;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace CacheExample.Controllers
    {
        [Route("api/[controller]")]
        [ApiController]
        public class DownloadController : ControllerBase
        {
            [HttpGet("{filename}")]
            [ResponseCache(Duration = 15)] // 15초동안 유지되는 캐시
            public async Task<IActionResult> GetDownloadResult(string filename)
            {
                var path = Path.Join("Download", filename);
                if (System.IO.File.Exists(path))
                {
                    byte[] bytes;
                    using (FileStream file = new FileStream(path: path, mode: FileMode.Open))
                    {
                        try
                        {
                            bytes = new byte[file.Length];
                            await file.ReadAsync(bytes);
                            return File(bytes, "application/octet-stream");
                        }
                        catch (Exception ex)
                        {
                            return StatusCode(StatusCodes.Status500InternalServerError);
                        }
                    }
    
    
                }
                else
                {
                    return NotFound();
                }
            }
        }
    }
    

    ResponseCache 속성의 Duration 값은 HTTP 표준 헤더 중 하나인 Cache-Control 의 max-age 설정이다. 따라서 조금 원시적인(?) 방법을 선호한다면 아래와 같은 방식도 가능하다.

    public async Task<IActionResult> GetDownloadResult(string filename)
    {
    	Response.Headers.Append("Cache-Control", "max-age=15");
        
        /* 이하 생략 */ 
    }
            

     

    3. 동작확인

    응답메소드에 중단점(breakpoint)을 설정하고 디버깅한 뒤, 파일다운로드를 실행해보면 처음에는 breakpoint에 걸리지만, 이후 15초 동안은 반복적으로 Request를 실행하여 파일을 다운로드 받아도 breakpoint에 걸리지 않는 것을 확인할 수 있다. 캐싱된 응답이 반응하기 때문이다.

     

    캐싱되지 않은 첫번째 응답과 이후 캐싱된 2~5번째 응답

    응답캐싱은 반드시 파일만 가능한 것이 아니다. 일반적인 Get 요청은 모두 가능하다. 아래 예제는 ASP.NET Core 개발자라면 프로젝트 생성시마다 자주 보게되는 WeatherForcast 예제이다.

     

    WeatherForcast 예제

     

    4. 유의사항

    1. 캐싱하지 말아야 할 응답은 구분해야한다.

    WeatherForcast 예제를 자주 접해봤다면 알겠지만, 예제는 매 응답마다 Random한 값의 날씨정보를 주는 메소드이다.

    [HttpGet]
    [ResponseCache(Duration = 15)]
      public IEnumerable<WeatherForecast> Get()
      {
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
          Date = DateTime.Now.AddDays(index),
          TemperatureC = rng.Next(-20, 55),
          Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }

    이 메소드는 매 요청마다 다른 응답값을 주는게 목표인데, 캐싱이 되어있으면 설정된 Duration 값 동안은 동일한 응답을 받을 수 밖에 없다.

     

    만약 캐싱이 반드시 필요하고, 캐싱응답시간 중간에 클라이언트(=프론트엔드)영역에서 새로운 값이 필요하다고 판단할 수 있는 구현이 되어있다면, 요청시 Request 헤더에 아래 정보를 포함시키기 바란다. 

    * 서버 측에 적용시키란 뜻이 아니다.

    Cache-Control : no-cache

     

     

    2. 캐싱은 Get만 쉽게 적용가능하다.

    캐싱은 Get 메소드만 쉽게 적용할 수 있다. 나머지 메소드(Post, Put, Delete 등)는 적용이 어렵다.

    stackoverflow.com/questions/626057/is-it-possible-to-cache-post-methods-in-http

     

    Is it possible to cache POST methods in HTTP?

    With very simple caching semantics: if the parameters are the same (and the URL is the same, of course), then it's a hit. Is that possible? Recommended?

    stackoverflow.com

     

    3. 클라이언트(프론트엔드) 영역의 지원여부

    캐싱은 브라우저의 기능과도 관련이 있으므로, 비브라우저 클라이언트 영역에선 해당 플랫폼에 따라 지원여부가 달라진다.

     

    이것으로 캐싱방법을 알아보았다.

     

    반응형

    댓글

Designed by Tistory.