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

댓글을 달아 주세요