지난번 아티클의 HttpCompression 소스 코드를 공개합니다.
 
아직 개선의 여지는 있지만, 필요하신 분들이 많으신 줄 압니다. 코드를 수정하셔서 사용하셔도 무방하나, 상업적 용도나 재배포가 불가능 하오니, 이점 숙지하시기 바랍니다.
 
실제 적용하기 위해서 Umc.Core.Configuration 어셈블리가 필요하지만 조금 수정하시면 자신의 사이트에 맞게 수정하시면 Umc.Core.Configuration 어셈블리는 필요하지 않습니다. 이 부분에 대해선 문의를 받지 않습니다.
 
현재는 매뉴얼이 제공되지 않습니다.

Posted by 땡초 POWERUMC

댓글을 달아 주세요




 
3. 문제 해결
 
이전 아티클에서 살펴보았듯이, ASP.NET 에서 보다 효과적인 HTTP Compression 을 위해 WebResource 와 ScriptResource 의 압축의 필요성을 알아보았습니다.
 
그럼 하나하나 해결의 실마리를 풀어 보도록 하겠습니다.
 
3.1 WebResource 압축
 
WebResource 는 포함 리소스로 지정된 파일과 WebResourceAttribute 을 통해 리소스의 콘텐츠를 사용할 수 있습니다.
 
 


 
첫번째, 일반적으로 압축 되지 않은 콘텐츠이기 때문에, 임의로 GZip 알고리즘을 이용하여 압축된 바이너리를 사용하여 Response Header 의 Content-Encoding 을 Gzip 으로 해 주셔도 됩니다. 하지만 불편하겠죠?
 
두번째, 요청 파일명이 WebResource.axd 인지를 검사하여, GZip 압축을 수행할 수 있습니다.
 
세번째, ASP.NET 파이프라인에서 요청에 대한 HttpHandler 타입을 검사하는 방법입니다.
WebResource 는 IHttpHandler를 구현하는 System.Web.Handlers.AssemblyResourceLoader 클래스입니다. 그래서 Context 개체의 CurrentHandler 의 개체 타입을 검사하는 방법으로 WebResource 인지 판단할 수 있습니다.
 
하지만, 문제는 여기서 그치지 않습니다. 만약 리소스가 image/jpeg 라고 가정할 때, 이러한 검사방법으로 압축하였음에도 Content-Encoding 이 text/html 로 내보내지게 됩니다. 이미지가 깨진다는 말이죠.
 
구글링을 통해 알아본 봐, 아래와 같은 코드를 사용하면 됩니다. 대략, HttpHandler 에게 현재 요청의 ASP.NET 파이프라인의 Response 개체를 참조하도록 하여, 포함 리소스의 Content-Type 을 참조되도록 한 코드인 듯 합니다.
 
// 맴버 선언
private static FieldInfo _HttpWriterFieldInfo = typeof(HttpResponse).GetField("_httpWriter", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
 
private static FieldInfo _IgnoreFurtherWritersFieldInfo = typeof(HttpWriter).GetField("_ignoringFurtherWrites", BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
 
if (context.CurrentHandler is System.Web.Handlers.AssemblyResourceLoader)
{
           HttpWriter httpWriter = (HttpWriter)_HttpWriterFieldInfo.GetValue(context.Response);
       _IgnoreFurtherWritersFieldInfo.SetValue(httpWriter, false);}
 
위의 코드를 사용하여 WebResource 의 정상적인 Content-Type 내보낼 수 있게 됩니다.
 
 
3.2 ScriptResource 압축
 
ScriptResourceAttribute 은 System.Web.Extensions 어셈블리에 정의되어 있고, 이름에서 알 수 있듯이 는 AJAX.NET 이 참조하는 스크립트 리소스 입니다. ScriptResource 는 WebResource 로 정의된 포함 리소스를 ScriptResource 로 변환하여 줄 수 있습니다.
 
MSDN 예제에 다음과 같이 사용하라고 나와 있습니다.
 
[assembly:System.Web.UI.WebResource("LocalizingScriptResources.CheckAnswer.js", "application/x-javascript")]
[assembly:System.Web.UI.ScriptResource("LocalizingScriptResources.CheckAnswer.js", "LocalizingScriptResources.VerificationResources", "Answer")]
 

 
ScriptResource 는 Context.ApplicationInstance.Modules 컬렉션에서 로드된 HttpModule 을 확인할 수 있습니다.
 
ScriptResource 는 ScriptResourceHandler 를 구현한 Handler 이며, 다음과 같은 코드로 타입을 알 수 있습니다.
 
if (context.CurrentHandler is System.Web.Handlers.ScriptResourceHandler)
{
       // 처리
}
 
하지만, 위 방법은 그다지 권장하지 않습니다. AJAX.NET 을 사용하지 않는 웹 프로젝트도 타입 검사를 위해 System.Web.Extensions 어셈블리를 참조를 한다는 것은 그다지 좋은 방법이 아니기 때문에, Type.GetType() 을 이용하여 타입 검사를 하는 것이 바람직 합니다.
 
ScriptResource 를 압축하기 위해 WebResource 보다 좀 더 이슈가 많은 놈입니다.
 
해결 방법을 보기 앞서, 이러한 리소스를 호출하는 URL 은 참 독특하게 생겼습니다.
 
http://localhost:11762/ScriptResource.axd?d=OPgKDvRlavpQaVqOD0g1boX7qB-1ghFWxrumbN3vsX9V6HIuwC8ttnL5kS-GN-I8lyEsM4Cv4jlmkKjiWk5FpbGKpocj2jsQpab1931xYKi73VoRqj6NfhWnGtvW1Qk00&t=633419587306805934
 
위와 같이, QueryString 의 d 라는 키값이 가지는 알 수 없는 문자들이 덕지덕지 붙어 있습니다. 이 문자열은 암호화되어 인코딩된 문자입니다. 이 문자열은 System.Web.Page 클래스의 EncryptString 과 DecryptString 이라는 internal 로 선언된 정적 메서드를 통해 암복호화를 수행합니다. 그리고 암복호화에 사용되는 토큰키는 HttpHandler 에 PublicKeyToken 에 정의되어 있습니다.
 
위의 d 쿼리스트링을 복호화 해보면
 
ZSystem.Web.Extensions,3.5.0.0,,31bf3856ad364e35|MicrosoftAjaxWebForms.debug.js|ko
 
이러한 알아봄직한 단어들이 나옵니다. 바로 맨 앞의 Z 로 ScriptResource 가 GZip 압축 되었다는 것을 의미합니다. a u 는 압축이 되지 않았다는 것을 알려주기도 합니다.
 
그리고 t 쿼리스트링은 어셈블리의 DateTime 의 Tick 입니다. 캐시에 동작하기 위해 어셈블리의 날짜를 URI 에 포함됩니다.
 
그럼 어떻게 압축 여부를 판단하는지 알았습니다. ScriptResource 의 압축 여부를 판단하기 위해 다음과 같은 코드를 만들 수 있습니다.
 
string param = context.Request.QueryString["d"];
string decrypt      = HttpUtil.DecryptString(param);
if( decrypt.ToUpper().StartsWith("Z") )
       return;
 
코드는 쿼리스트링의 d 값을 PublicKeyToken 을 이용하여 복호화하여 Z 로 시작하면 이미 압축된 콘텐츠 이므로, 압축을 수행하지 않도록 return 하는 코드입니다.
 
만약 위의 코드로 ScriptResource 의 이미 압축었는지의 여부를 체크하지 않으면, 서버는 압축된 콘텐츠를 다시 한번 GZip 압축하기 때문에, 브라우져는 GZip 압축을 해제하였지만 콘텐츠는 깨져버립니다. ( 두 번 압축하였으니, 두 번 풀어줘야 정상적인 콘텐츠가 되겠죠? )
 
 
4. 웹 사이트에 진정한 날개를…
 
자신이 개발하거나 운영하고 있는 웹 사이트가 있고, AJAX.NET 이나 다양한 컴포넌트를 이용하였다면 반드시 웹 캡춰 툴을 이용하여 확인해 보시기 바랍니다. 그리고, 자신이 그러한 웹 사이트를 개발하거나 관리한다면 위의 방법을 이용하여 적용해 보시기 바랍니다. text/html 이나 text/javascript 와 더불어 트래픽의 부하를 가져오는 다량의 WebResource 와 ScriptResource 가 가벼워지면서 획기적으로 줄어든 트래픽을 확인할 수 있을 것입니다.
 
그리고, 직접 개발하기 벅차신 분을 위해 개발중인 Umc.Core.Web.HttpCompression 어셈블리를 함께 드리도록 하겠습니다. 전체적으로 개발이 완료되지 않았으므로 테스트 용도로만 사용시면 됩니다. (어떠한 버그도 책임지지 않는답니다^^;)
 
아래의 내용을 각각 맞는 Config Section 에 추가해서 넣으시면 동작합니다. 그럼 오늘도 좋은 하루 되세요 …
 
<configSections>
       <sectionGroup name="Umc.Core" type="Umc.Core.Configuration.UmcConfigSectionGroup, Umc.Core.Configuration">
             <section name="Umc.Core.Web" type="Umc.Core.Configuration.UmcWebConfigSection, Umc.Core.Configuration" />
             <section name="Setting" type="Umc.Core.Configuration.UmcSettingConfigSection, Umc.Core.Configuration" />
       </sectionGroup>
</configSections>
 
<system.web>
       <httpModules>
             <!-- HttpModule 매핑-->
             <!--<add name="UmcHttpModule" type="Umc.Core.Web.UmcHttpModule, Umc.Core.Web" />-->
             <add name="UmcHttoCompressionModule" type="Umc.Core.Web.HttpCompression.HttpCompressionModule, Umc.Core.Web.HttpCompression" />
       </httpModules>
</system.web>
 
<Umc.Core>
       <!-- DefaultExtension 웹폼이사용하는기본확장자입니다.-->
       <Umc.Core.Web DefaultExtension=".aspx">
             <RewritePath Enabled="True" /> <!--웹사이트의 rewritepath 적용할지여부-->
             <HttpCompressionFile>
                    <add Name="/\.(gif|jpg|jpeg|png)$/i" Type="GZip" Enabled="False"  NameType="RegexExpression"></add>
             </HttpCompressionFile>
             <HttpCompressionMime>
                    <!--디폴트속성들입니다-->
                    <add Name="text/html" Type="GZip" MinimumLength="512" Enabled="True"/>
                    <add Name="text/css"/>
                    <add Name="text/javascript"/>
                    <add Name="application/x-javascript"/>
                    <add Name="image/bmp"/>
             </HttpCompressionMime>
       </Umc.Core.Web>
</Umc.Core>
 
 
참고 URL
 
Posted by 땡초 POWERUMC

댓글을 달아 주세요



 
 
1.    서론
 
HTTP Compression 은 웹사이트의 응답 성능을 향상 시키는데 많은 일조를 합니다. 압축 정도와 데이터의 타입에 따라 압축율은 다르지만, 최소 30% 이상의 체감 속도의 향상을 가져온다고 합니다. 제가 운영하고 있는 [Umc Projects/Umc Blog] - [UmcBlog] 블로그 소스 다운로드 의 메인화면의 HTML 만해도 101,430 Bytes 지만, GZip 압축을 적용한 결과 15,450 Bytes 로 7배 정도로 작아진 트래픽이 발생합니다.
 

[그림1] Umc Blog 메인 화면을 HTTP 압축한 비교표
 
위 압축은 ShartZipLib 를 이용하여 최고 압축율로 압축한 결과 입니다.
자~ 어떻습니까? 이제 HTTP 압축을 자신의 사이트에 도입해 보려고 한다면, 우선 다음의 강좌를 참고하여 선수 지식을 익히시기 바랍니다.
 
최고의 가이드 문서인 유경상님의 HTTP 압축 (1) : 성능 향상을 위한 다른 접근 기법
서동진님의 [1000원짜리] GZip 이용한 웹 페이지의 압축 전달
 
제가 아는 포스팅은 이것 뿐인지, 아니면 제가 못 찾는 것인지 모르겠지만, 어쨌든 왜 써야 하며, 어떻게 구현하는지는 보셔야 다음 내용을 이해할 수 있습니다.
 
 
2.    문제 제기
 
위 내용 중 특히 유경상님의 문서에서 볼 수 있듯이 HTML 과 XML Web Service 에서 압축방법과 압축해제 방법을 너무나 잘 설명해 주셨습니다. (쵝오!) .NET Framework 가 버전업이 되면서 편리한 API 와 옵션들이 추가되어 깊은 이해보다 코드 한 줄로 해결할 수 있는 방법들이 너무나 다양해 졌습니다.
 
좋습니다. 어찌되어건, HTTP Compression 은 서버의 CPU 가 가져가는 부하가 경미하기 때문에 보다 훨씬 많은 이점이 있습니다. 그렇기 때문에, 웹 사이트는 특별한 상황이 아니라면 HTTP Compression 을 활용하도록 해야 합니다.
 
IIS HTTP Compression 을 사용하지 말아야 할 때
IIS 서비스 압축은 기본적으로 DLL 도 GZip 압축 하도록 되어있습니다. 스마트클라이언트 시나리오에서는 클라이언트가 GZip 압축된 어셈블리(*.dll) 등을 다운로드 하였을 때 다운로드 된 클라이언트는 GZip 압축을 복호화 하지 못합니다. 유경상님의 HTTP Compression (III) : IIS Support 을 참고 하세요.
 
자.. 문제는 여기서 부터입니다. HTTP Compression 의 구현은 쉽지만, HTML(CSS/JS) 이나 XML Web Service 에 국한된 포스팅 입니다. 특히, 클라이언트 어플케이션일 경우 의 GZip 압축의 범위가 XML Web Service 에 국한됩니다. 하지만, 불특정 다수의 웹 서비스 사이트를 생각한다면 HTML Compression 만으로 부족한 것을 알 수 있습니다.
 

[그림2] HTML 뿐만 아니라 다양한 데이터를 클라이언트는 내려받습니다 (클릭하면 확대 됩니다)
 
우리가 웹 사이트를 접속하였을 때, 보여지는 것은 HTML/CSS/JS 만 있는 것이 아닙니다. 수십 개의 콘텐츠 중 고작, 10여개의 텍스트 용량을 줄였다고 체감속도가 향상 되었다는 것은 억지에 불과합니다.(그나마 안하는 것보다는 나은듯^^;) 왜냐하면, 웹 사이트를 접속할 때 XML, BMP 등과 같은 압축되지 않은 다양한 콘텐츠도 다운로드 되기 때문 입니다.
 
물론, HTTP Compression 을 하느냐 안하느냐도 중요합니다. 하지만 압축이 전부는 아닙니다. 자신이 개발하거나 관리하는 사이트가 이런 문제로 고민을 한다면 다양한 웹 사이트 최적화 기법을 통해서 빠른 응답을 기대할 수 있습니다.
 
웹 사이트 최적화 튜닝 방법
 
ASP.NET 개발 환경을 무척 편리해 졌습니다. .NET Framework 2.0 부터 추가된 WebResource 와 ScriptResource 를 이용하여 컴포넌트 배포시에 별도의 스크립트 파일이 없이도 동작하도록 어셈블리의 리소스에 포함시킬 수 있게 되었습니다. 무척 배포가 쉬워졌지요.
 
하지만, ASP.NET 웹사이트로 구축된 기업의 인트라넷의 경우 다양한 컴포넌트를 사용하여 개발하게 될 때, 수십여 개의 WebResource 와 ScriptResource 를 뱉어내게 됩니다. 이 리소스들은 압축되었다는 보장을 할 수 없고, 실제로 압축되지 않은 리소스들이 더 많습니다. 이런 웹 사이트의 성능 저하 주 원인이 바로 리소스입니다.
 
문제는 여기에서 생깁니다. 어떻게 이 리소스들을 압축하시겠습니까?
AJAX.NET 의 ScriptManager 컨트롤은 3개의 ScriptResource 를 포함합니다. ScriptResource 는 아무런 작업을 하지 않아도 gzip 압축이 되는데, 특정 컴포넌트의 ScriptResource 는 gzip 압축을 하지 않습니다.
 

[그림3] 아무 작업 없이도 ScriptResource 는 압축이 되기도 합니다.
 
특히 ScriptResource 의 경우는 아무런 생각 없이 압축을 수행하다가는 이미 압축된 리소스가 추가적으로 GZip 압축이 되어, 브라우져는 중복 압축된 콘텐츠를 제대로 복호화 하지 못합니다. 결국 사이트는 바보가 됩니다.
 
Next… (다음 아티클)
Posted by 땡초 POWERUMC

댓글을 달아 주세요