ASP.NET 코어 의 응답 압축 실현

소개
응답 압축 기술 은 현재 웹 개발 분야 에서 비교적 자주 사용 되 는 기술 로 대역 폭 자원 이 제 한 된 상황 에서 압축 기술 을 사용 하 는 것 이 대역 폭 부 하 를 향상 시 키 는 최 우선 방안 이다.우리 가 잘 아 는 웹 서버,예 를 들 어 IIS,Tomcat,Nginx,Apache 등 은 모두 압축 기술 을 사용 할 수 있 습 니 다.자주 사용 하 는 압축 유형 은 Brotli,Gzip,Deflate 를 포함 합 니 다.그들 은 CSS,JavaScript,HTML,XML 과 JSON 등 유형 에 대한 효과 가 비교적 뚜렷 하지만 그림 자체 가 압축 형식 이기 때 문 입 니 다.그 다음으로 약 150-1000 바이트 이하 의 파일(구체 적 으로 파일 의 내용 과 압축 의 효율 에 달 려 있 고 작은 파일 을 압축 하 는 비용 은 압축 되 지 않 은 파일 보다 더 큰 압축 파일 이 생 길 수 있 습 니 다.ASP.NET Core 에서 우 리 는 매우 간단 한 방식 으로 응답 압축 을 사용 할 수 있다.
사용 방법\#
ASP.NET Core 에서 압축 에 응답 하 는 방식 을 사용 하 는 것 은 비교적 간단 하 다.우선,Configure Services 에 services.AddResponse Compression 주입 응답 압축 과 관련 된 설정 을 추가 합 니 다.예 를 들 어 사용 하 는 압축 유형,압축 단계,압축 목표 유형 등 입 니 다.그 다음으로 Configure 에 app.UseResponse Compression 차단 요청 을 추가 하여 압축 이 필요 한 지 여 부 를 판단 합 니 다.대체적으로 사용 방식 은 다음 과 같 습 니 다.

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
    services.AddResponseCompression();
  }

  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
    app.UseResponseCompression();
  }
}

사용자 정의 설정 이 필요 하 다 면 압축 도 수 동 으로 설정 할 수 있 습 니 다.

public void ConfigureServices(IServiceCollection services)
{
  services.AddResponseCompression(options =>
  {
    //          ,               
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
    //         
    options.Providers.Add<MyCompressionProvider>();
    //     MimeType       
    options.MimeTypes = 
      ResponseCompressionDefaults.MimeTypes.Concat(
        new[] { "application/json" });
  });
  //         ,         
  services.Configure<GzipCompressionProviderOptions>(options => 
  {
    //           ,              
    options.Level = CompressionLevel.Fastest;

    //       
    //options.Level = CompressionLevel.NoCompression;

    //           ,             
    //options.Level = CompressionLevel.Optimal;
  });
}

압축 에 응답 하 는 대체적인 작업 방식 은 Http 요청 을 할 때 Request Header 에 Accept-Encoding:gzip 또는 원 하 는 압축 형식 을 추가 하면 여러 종 류 를 전달 할 수 있 습 니 다.서버 에서 요청 을 받 아 Accept-Encoding 을 가 져 와 서 이 유형의 압축 방식 을 지원 하 는 지 판단 합 니 다.지원 하면 압축 출력 내용 과 관련 되 고 Content-Encoding 을 현재 사용 하고 있 는 압축 방식 으로 설정 하여 함께 되 돌려 줍 니 다.클 라 이언 트 가 응답 을 받 은 후에 Content-Encoding 을 가 져 와 서 서버 가 압축 기술 을 사 용 했 는 지 판단 하고 해당 하 는 값 에 따라 어떤 압축 유형 을 사 용 했 는 지 판단 한 다음 에 해당 하 는 압축 해제 알고리즘 을 사용 하여 원시 데 이 터 를 얻 었 습 니 다.
소스 코드 탐구\#
위의 소 개 를 통 해 여러분 들 이 Response Compression 에 대해 어느 정도 알 게 되 었 을 것 이 라 고 믿 습 니 다.그 다음 에 우 리 는 소스 코드 를 보 는 방식 으로 그의 대체적인 작업 원 리 를 알 아 보 겠 습 니 다.
AddResponseCompression#
먼저 주입 과 관련 된 코드 를 살 펴 보 겠 습 니 다.구체 적 인 코드 는 Response Compression Services Extensions 확장 클래스 에 탑재 되 어 있 습 니 다.[클릭 하여 원본 코드 보기👈]

public static class ResponseCompressionServicesExtensions
{
  public static IServiceCollection AddResponseCompression(this IServiceCollection services)
  {
    services.TryAddSingleton<IResponseCompressionProvider, ResponseCompressionProvider>();
    return services;
  }

  public static IServiceCollection AddResponseCompression(this IServiceCollection services, Action<ResponseCompressionOptions> configureOptions)
  {
    services.Configure(configureOptions);
    services.TryAddSingleton<IResponseCompressionProvider, ResponseCompressionProvider>();
    return services;
  }
}

주로 ResponseCompressionProvider 와 ResponseCompressionOptions 를 주입 합 니 다.먼저 ResponseCompressionOptions[클릭 하여 원본 코드 보기👈]에 대해 알 아 보 겠 습 니 다.

public class ResponseCompressionOptions
{
  //          
  public IEnumerable<string> MimeTypes { get; set; }

  //           
  public IEnumerable<string> ExcludedMimeTypes { get; set; }

  //     https  
  public bool EnableForHttps { get; set; } = false;

  //       
  public CompressionProviderCollection Providers { get; } = new CompressionProviderCollection();
}
이런 종류 에 대해 서 는 소 개 를 많이 하지 않 는 것 이 비교적 간단 하 다.Response Compression Provider 는 우리 가 응답 압축 알고리즘 을 제공 하 는 핵심 클래스 로 구체 적 으로 압축 알고리즘 을 자동 으로 선택 하 는 방법 은 모두 그것 이 제공 하 는 것 입 니 다.이런 유형의 코드 가 비교적 많 기 때문에 우 리 는 한 가지 방법 으로 설명 하지 않 을 것 이다.구체 적 인 소스 코드 는 스스로[클릭 하여 원본 코드 보기👈]을 조회 할 수 있다.먼저 우 리 는 Response Compression Provider 의 구조 함 수 를 먼저 볼 수 있다.

public ResponseCompressionProvider(IServiceProvider services, IOptions<ResponseCompressionOptions> options)
{
  var responseCompressionOptions = options.Value;
  _providers = responseCompressionOptions.Providers.ToArray();
  //              Br Gzip    
  if (_providers.Length == 0)
  {
    _providers = new ICompressionProvider[]
    {
      new CompressionProviderFactory(typeof(BrotliCompressionProvider)),
      new CompressionProviderFactory(typeof(GzipCompressionProvider)),
    };
  }
  //  CompressionProviderFactory         Provider  GzipCompressionProvider
  for (var i = 0; i < _providers.Length; i++)
  {
    var factory = _providers[i] as CompressionProviderFactory;
    if (factory != null)
    {
      _providers[i] = factory.CreateInstance(services);
    }
  }
  //              text/plain、text/css、text/html、application/javascript、application/xml
  //text/xml、application/json、text/json、application/was
  var mimeTypes = responseCompressionOptions.MimeTypes;
  if (mimeTypes == null || !mimeTypes.Any())
  {
    mimeTypes = ResponseCompressionDefaults.MimeTypes;
  }
  //   MimeType  HashSet
  _mimeTypes = new HashSet<string>(mimeTypes, StringComparer.OrdinalIgnoreCase);
  _excludedMimeTypes = new HashSet<string>(
    responseCompressionOptions.ExcludedMimeTypes ?? Enumerable.Empty<string>(),
    StringComparer.OrdinalIgnoreCase
  );
  _enableForHttps = responseCompressionOptions.EnableForHttps;
}
그 중에서 Brotli copression Provider,Gzip Compression Provider 는 구체 적 으로 압축 방법 을 제공 하 는 곳 입 니 다.우 리 는 비교적 자주 사용 하 는 Gzip 의 Provider 의 대체적인 실현[클릭 하여 원본 코드 보기👈]을 살 펴 보 겠 습 니 다.

public class GzipCompressionProvider : ICompressionProvider
{
  public GzipCompressionProvider(IOptions<GzipCompressionProviderOptions> options)
  {
    Options = options.Value;
  }

  private GzipCompressionProviderOptions Options { get; }

  //    Encoding  
  public string EncodingName { get; } = "gzip";

  public bool SupportsFlush => true;

  //                       GZipStream
  //      Level               
  public Stream CreateStream(Stream outputStream)
    => new GZipStream(outputStream, Options.Level, leaveOpen: true);
}

Response Compression Provider 와 관련 된 다른 방법 에 대해 서 는 UseResponse Compression 미들웨어 를 설명 할 때 구체 적 으로 볼 때 사용 하 는 방법 을 설명 합 니 다.이 종 류 는 압축 에 응 하 는 핵심 류 이기 때문에 지금 미리 말 하면 미들웨어 가 사용 하 는 곳 에서 잊 어 버 릴 수 있 습 니 다.다음은 UseResponse Compression 의 대략적인 실현 을 살 펴 보 겠 습 니 다.
UseResponseCompression#
UseResponse Compression 은 구체 적 으로 인삼 이 없 는 확장 방법 이 고 간단 합 니 다.설정 과 작업 이 모두 주 입 된 곳 에서 이 루어 졌 기 때문에 우 리 는 중간 부품 의 실현 을 직접 살 펴 보고 중간 부품 의 위 치 를 찾 습 니 다 Response Compression Middleware[클릭 하여 원본 코드 보기👈]

public class ResponseCompressionMiddleware
{
  private readonly RequestDelegate _next;
  private readonly IResponseCompressionProvider _provider;

  public ResponseCompressionMiddleware(RequestDelegate next, IResponseCompressionProvider provider)
  {
    _next = next;
    _provider = provider;
  }

  public async Task Invoke(HttpContext context)
  {
    //      Accept-Encoding   ,         "     "
    if (!_provider.CheckRequestAcceptsCompression(context))
    {
      await _next(context);
      return;
    }
    //      Body
    var originalBodyFeature = context.Features.Get<IHttpResponseBodyFeature>();
    var originalCompressionFeature = context.Features.Get<IHttpsCompressionFeature>();
    //       Body
    var compressionBody = new ResponseCompressionBody(context, _provider, originalBodyFeature);
    //     Body
    context.Features.Set<IHttpResponseBodyFeature>(compressionBody);
    context.Features.Set<IHttpsCompressionFeature>(compressionBody);

    try
    {
      await _next(context);
      await compressionBody.FinishCompressionAsync();
    }
    finally
    {
      //    Body
      context.Features.Set(originalBodyFeature);
      context.Features.Set(originalCompressionFeature);
    }
  }
}
이 중간 부품 은 Response Compression Body 를 초기 화 하 는 것 이 매우 간단 하 다.여기 서 보면 궁금 할 수도 있 습 니 다.호출 압축 과 관련 된 코드 가 트리거 되 지 않 았 습 니 다.Response Compression Body 도 Finish Compression Async 만 호출 되 었 습 니 다.서 두 르 지 마 세 요.Response Compression Body 류 의 구 조 를 보 겠 습 니 다.

internal class ResponseCompressionBody : Stream, IHttpResponseBodyFeature, IHttpsCompressionFeature
{
}
이 종 류 는 IHttpResponse Body Feature 를 실 현 했 습 니 다.우리 가 사용 하 는 Response.Body 는 사실 얻 은 HttpResponse Body Feature.stream 속성 입 니 다.우리 가 사용 하 는 Response.Write Async 와 관련 된 방법 은 내부 적 으로 모두 PipeWriter 를 호출 하여 쓰기 작업 을 하고 있 으 며,PipeWriter 는 Http Response Body Feature.Writer 속성 에서 나 온 것 입 니 다.출력 과 관련 된 작업 의 핵심 은 IHttpResponse Body Feature 를 조작 하 는 것 으로 요약 된다.관심 있 는 사람 은 HttpResponse 와 관련 된 소스 코드 를 직접 찾 아 보면 관련 정 보 를 알 수 있 습 니 다.그래서 저희 Response Compression Body 는 출력 작업 과 관련 된 방법 을 다시 썼 습 니 다.즉,Response 와 관련 된 Write 나 Body 와 관련 된 것 을 호출 하면 본질은 IHttp Response Body Feature 를 조작 하 는 것 이다.우 리 는 응답 출력 과 관련 된 미들웨어 를 열 었 기 때문에 IHttp Response Body Feature 의 실현 류 Response Compression Body 와 관련 된 방법 으로 출력 을 완성 할 것 이다.우리 가 일반적으로 이해 하 는 것 과 차이 가 있다.일반적인 상황 에서 우 리 는 출력 된 Stream 에 대해 조작 을 하면 된다 고 생각 하지만 압축 미들웨어 에 응답 하여 출력 과 관련 된 조작 을 다시 썼 다.
이것 을 알 게 된 후 에는 모두 가 그다지 의문 이 없 을 것 이 라 고 믿는다.Response Compression Body 는 출력 과 관련 된 조작 을 다시 썼 기 때문에 코드 가 상대 적 으로 많 기 때문에 하나씩 붙 이지 않 습 니 다.우 리 는 압축 핵심 과 관련 된 코드 만 볼 수 있 습 니 다.Response Compression Body 소스 코드 와 관련 된 세부 사항 에 관심 이 있 으 면[클릭 하여 원본 코드 보기👈]을 조회 할 수 있 습 니 다.수출 의 본질은 모두 Write 방법 을 호출 하 는 것 입 니 다.Write 방법 과 관련 된 실현 을 살 펴 보도 록 하 겠 습 니 다.

public override void Write(byte[] buffer, int offset, int count)
{
  //                   
  OnWrite();
  //_compressionStream    OnWrite   
  if (_compressionStream != null)
  {
    _compressionStream.Write(buffer, offset, count);
    if (_autoFlush)
    {
      _compressionStream.Flush();
    }
  }
  else
  {
    _innerStream.Write(buffer, offset, count);
  }
}
위의 코드 를 통 해 우 리 는 OnWrite 방법 이 핵심 조작 이라는 것 을 보 았 다.우 리 는 OnWrite 방법 이 실현 되 는 지 직접 보 았 다.

private void OnWrite()
{
  if (!_compressionChecked)
  {
    _compressionChecked = true;
    //               
    if (_provider.ShouldCompressResponse(_context))
    {
      //  Vary       
      var varyValues = _context.Response.Headers.GetCommaSeparatedValues(HeaderNames.Vary);
      var varyByAcceptEncoding = false;
      //  Vary     Accept-Encoding
      for (var i = 0; i < varyValues.Length; i++)
      {
        if (string.Equals(varyValues[i], HeaderNames.AcceptEncoding, StringComparison.OrdinalIgnoreCase))
        {
          varyByAcceptEncoding = true;
          break;
        }
      }
      if (!varyByAcceptEncoding)
      {
        _context.Response.Headers.Append(HeaderNames.Vary, HeaderNames.AcceptEncoding);
      }
      //     ICompressionProvider        
      var compressionProvider = ResolveCompressionProvider();
      if (compressionProvider != null)
      {
        //         ,  Content-Encoding    
        //       Content-Encoding                 
        _context.Response.Headers.Append(HeaderNames.ContentEncoding, compressionProvider.EncodingName);
        //     ,  Content-MD5      ,                。
        _context.Response.Headers.Remove(HeaderNames.ContentMD5); 
        //     ,  Content-Length      ,           ,         。
        _context.Response.Headers.Remove(HeaderNames.ContentLength);
        //         
        _compressionStream = compressionProvider.CreateStream(_innerStream);
      }
    }
  }
}

private ICompressionProvider ResolveCompressionProvider()
{
  if (!_providerCreated)
  {
    _providerCreated = true;
    //  ResponseCompressionProvider             
    _compressionProvider = _provider.GetCompressionProvider(_context);
  }
  return _compressionProvider;
}

위의 논 리 를 통 해 알 수 있 듯 이 압축 과 관련 된 논 리 를 실행 하기 전에 압축 과 관련 된 방법 을 만족 시 키 는 지 판단 해 야 한다.Should CompressResponse.이 방법 은 Response Compression Provider 의 방법 이다.여 기 는 코드 를 붙 이지 않 는 다.원래 논 리 를 판단 하 는 것 이다.내 가 직접 정리 한 것 은 몇 가지 상황 이다.
  • 요청 이 Https 인 경우 Https 상황 에서 압축 을 허용 하 는 설정,즉 ResponseCompressionOptions 의 EnableForHttps 속성 설정
  • 을 설 정 했 는 지 여부
  • Response.Head 에는 Content-Range 헤드 정보 가 포함 되 어 있 지 않 습 니 다.
  • Response.Head 에는 Content-Encoding 헤드 정보 가 포함 되 어 있 지 않 습 니 다.
  • Response.Head 에 Content-Type 헤더 정보 가 포함 되 어야 합 니 다.
  • 되 돌아 오 는 MimeType 에는 압축 할 필요 가 없 는 설정 형식,즉 ResponseCompressionOptions 의 Excluded MimeTypes
  • 이 포함 되 어 있 지 않 습 니 다.
  • 되 돌아 오 는 MimeType 에는 압축 해 야 할 설정 유형,즉 ResponseCompressionOptions 의 MimeTypes
  • 이 포함 되 어 있어 야 합 니 다.
  • 위의 두 가지 상황 을 만족 시 키 지 못 하면 돌아 오 는 MimeType 에*/*가 포함 되 어 있 고 응답 압축
  • 을 실행 할 수 있 습 니 다.
    다음은 Response Compression Provider 의 GetCompression Provider 방법 을 살 펴 보고 어떤 압축 형식 으로 돌아 가 는 지 확인 합 니 다.
    
    public virtual ICompressionProvider GetCompressionProvider(HttpContext context)
    {
      var accept = context.Request.Headers[HeaderNames.AcceptEncoding];
      //         Accept-Encoding  
      if (StringValues.IsNullOrEmpty(accept))
      {
        Debug.Assert(false, "Duplicate check failed.");
        return null;
      }
      //  Accept-Encoding   ,      gzip、br、identity ,       
      if (!StringWithQualityHeaderValue.TryParseList(accept, out var encodings) || !encodings.Any())
      {
        return null;
      }
      //                  
      var candidates = new HashSet<ProviderCandidate>();
      foreach (var encoding in encodings)
      {
        var encodingName = encoding.Value;
        //Quality            ,          
        var quality = encoding.Quality.GetValueOrDefault(1);
        //quality   0
        if (quality < double.Epsilon)
        {
          continue;
        }
        //      encodingName    providers     EncodingName     
        //                providers        
        for (int i = 0; i < _providers.Length; i++)
        {
          var provider = _providers[i];
          if (StringSegment.Equals(provider.EncodingName, encodingName, StringComparison.OrdinalIgnoreCase))
          {
            candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
          }
        }
        //      EncodingName *          providers     
        if (StringSegment.Equals("*", encodingName, StringComparison.Ordinal))
        {
          for (int i = 0; i < _providers.Length; i++)
          {
            var provider = _providers[i];
            candidates.Add(new ProviderCandidate(provider.EncodingName, quality, i, provider));
          }
          break;
        }
        //      EncodingName identity   ,         
        if (StringSegment.Equals("identity", encodingName, StringComparison.OrdinalIgnoreCase))
        {
          candidates.Add(new ProviderCandidate(encodingName.Value, quality, priority: int.MaxValue, provider: null));
        }
      }
    
      ICompressionProvider selectedProvider = null;
      //              
      if (candidates.Count <= 1)
      {
        selectedProvider = candidates.FirstOrDefault().Provider;
      }
      else
      {
        //          Quality   Priority          
        selectedProvider = candidates
          .OrderByDescending(x => x.Quality)
          .ThenBy(x => x.Priority)
          .First().Provider;
      }
      //       selectedProvider  identity       null
      if (selectedProvider == null)
      {
        return null;
      }
      return selectedProvider;
    }
    
    
    이상 의 소 개 를 통 해 우 리 는 압축 에 응 하 는 대체적인 작업 방식 을 대충 알 수 있 고 간단하게 정리 할 수 있다.
  • 먼저 압축 과 관련 된 알고리즘 유형 이나 압축 목 표를 설정 한 Mime Type
  • 그 다음 에 우 리 는 압축 단 계 를 설정 할 수 있 는데 이것 은 압축 의 질과 압축 성능 을 결정 할 것 이다
  • 은 압축 미들웨어 에 응답 함으로써 우 리 는 우선 순위 가 가장 높 은 압축 알고리즘 을 얻어 압축 할 수 있다.이런 상황 은 주로 여러 가지 압축 유형 에 대한 상황 이다.이 압축 알고리즘 은 내부 메커니즘 과 등록 압축 알고리즘 의 순서 와 일정한 관계 가 있 기 때문에 최종 적 으로 가장 큰 반환 을 선택 할 것 이다.
  • 응답 압축 미들웨어 의 핵심 작업 클래스 Response Compression Body 는 IHttp Response Body Feature 를 실현 하고 출력 과 관련 된 방법 을 재 작성 하여 응답 에 대한 압축 을 실현 합 니 다.관련 방법 을 수 동 으로 호출 하지 않 고 기본 출력 방식 을 교체 합 니 다.응답 압축 을 설정 하고 응답 압축 을 만족 시 키 기 를 요청 하면 출력 을 호출 하 는 곳 은 기본적으로 Response Compression Body 에서 압축 과 관련 된 방법 을 실행 하 는 것 이지 구체 적 인 출력 을 차단 하 는 것 이 아 닙 니 다.왜 그 랬 는 지 에 대해 서 는 아직 디자이너 의 진정한 고려 를 이해 하지 못 했다.
  • 총화
    관련 코드 를 보기 전에 압축 에 응 하 는 논리 가 매우 간단 할 것 이 라 고 생각 했 는데 소스 코드 를 보고 나 서 야 자신 이 생각 하 는 것 이 너무 간단 하 다 는 것 을 알 게 되 었 다.그 중에서 자신의 생각 과 가장 큰 차이 가 있 는 것 은 Response Compression Middleware 미들웨어 미들웨어 에서 출력 흐름 을 통일 적 으로 차단 하여 압축 작업 을 하 는 줄 알 았 는데 전체 출력 작업 을 재 작성 하 는 것 이 라 고 생각 하지 못 했다.이전에 우리 가 Asp.Net 관련 프레임 워 크 를 사용 할 때 Filter 나 HttpModule 을 통일 적 으로 써 서 처 리 했 기 때문에 사고방식 이 존재 한다.아마도 Asp.Net 코어 디자이너 가 더 깊 은 이 해 를 가지 고 있 을 것 입 니 다.제 가 아직 철저하게 이해 하지 못 했 기 때문에 이렇게 하 는 장점 이 무엇 인지 이해 할 수 없습니다.만약 에 더 좋 은 이해 가 있 거나 답 이 있 으 면 댓 글 에 댓 글 을 달 아 의혹 을 풀 어 주 십시오.
    여기 서 ASP.NET Core 의 응답 압축 실현 에 관 한 글 을 소개 합 니 다.더 많은 관련 ASP.NET Core 응답 압축 내용 은 예전 의 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 도 많은 응원 부 탁 드 리 겠 습 니 다!

    좋은 웹페이지 즐겨찾기