2010-02-17 9 views
3

저는 서버 측 XSLT 변환을 IIS HttpModule로 구현하려고했습니다. 필자의 기본적인 접근 방식은 BeginRequest에 새로운 필터를 설치하여 MemoryStream에 쓰기를 전환 한 다음 PreSendRequestContent에서 XSLT를 사용하여 문서를 변환하고 원본 출력 스트림에 쓰도록하는 것입니다. 그러나 변환을 수행하지 않고도 HttpModule이 첫 번째 페이지로드에서 작동하는 것처럼 보이고 응용 프로그램 풀을 다시 시작할 때까지 서버에서 전혀 응답을받지 못하기 때문에 분명히 잘못된 작업을 수행하고 있습니다. 변환을 통해 처음에는 빈 페이지를 얻었고 응답은 없었습니다. 나는 분명히 뭔가 어리석은 짓을하고있다. 그러나 이것은 수년 동안 (그리고 HttpModule에서 처음 시도한) C# 코드이며, 문제가 무엇인지 전혀 모른다. 어떤 실수를 저질렀습니까? (나는 아래의 코드에서 XSLT 부분을 주석 처리하고 응답을 캐시의 내용을 기록하는 라인을 주석 처리했습니다.)HttpModule에서 XSLT 변환을 수행하려면 어떻게해야합니까?

using System; 
using System.IO; 
using System.Text; 
using System.Web; 
using System.Xml; 
using System.Xml.Xsl; 

namespace Onyx { 

    public class OnyxModule : IHttpModule { 

     public String ModuleName { 
      get { return "OnyxModule"; } 
     } 

     public void Dispose() { 
     } 

     public void Init(HttpApplication application) { 

      application.BeginRequest += (sender, e) => { 
       HttpResponse response = HttpContext.Current.Response; 
       response.Filter = new CacheFilter(response.Filter); 
       response.Buffer = true; 
      }; 

      application.PreSendRequestContent += (sender, e) => { 

       HttpResponse response = HttpContext.Current.Response; 
       CacheFilter cache = (CacheFilter)response.Filter; 

       response.Filter = cache.originalStream; 
       response.Clear(); 

/*    XmlReader xml = XmlReader.Create(new StreamReader(cache), new XmlReaderSettings() { 
        ProhibitDtd = false, 
        ConformanceLevel = ConformanceLevel.Auto 
       }); 

       XmlWriter html = XmlWriter.Create(response.OutputStream, new XmlWriterSettings() { 
        ConformanceLevel = ConformanceLevel.Auto 
       }); 

       XslCompiledTransform xslt = new XslCompiledTransform(); 
       xslt.Load("http://localhost/transformations/test_college.xsl", new XsltSettings() { 
        EnableDocumentFunction = true 
       }, new XmlUrlResolver()); 
       xslt.Transform(xml, html); */ 

       response.Write(cache.ToString()); 

       response.Flush(); 

      }; 

     } 


    } 

    public class CacheFilter : MemoryStream { 

     public Stream originalStream; 
     private MemoryStream cacheStream; 

     public CacheFilter(Stream stream) { 
      originalStream = stream; 
      cacheStream = new MemoryStream(); 
     } 

     public override int Read(byte[] buffer, int offset, int count) { 
      return cacheStream.Read(buffer, offset, count); 
     } 

     public override void Write(byte[] buffer, int offset, int count) { 
      cacheStream.Write(buffer, offset, count); 
     } 

     public override bool CanRead { 
      get { return cacheStream.CanRead; } 
     } 

     public override string ToString() { 
      return Encoding.UTF8.GetString(cacheStream.ToArray()); 
     } 

    } 

} 
+1

아니 범죄 청소기,하지만 샘플 마일 떨어진 그 공상 "클린 코드"일에서이다. – Filburt

+0

@ Filburt - 좀 더 구체적으로 말하십시오. 아니면 스팸 메일 일뿐입니다. –

+0

@Jeff : 좋아요, 단지 두 가지 명백한 포인트가 있습니다. 1) 이름 지정 - (HttpApplication 컨텍스트) 오해의 소지가 있습니다. 2) 모든 것을 Init() 모듈로 초기화해야합니다. – Filburt

답변

3

당신은 위치에 당신의 MemoryStream을에 데이터를 읽는 완료되면 스트림의 끝. 에서는 StreamReader/XmlReader를에 스트림을 전송하기 전에 당신은 (심지어 스트림의 위치를 ​​재설정 한 후) 나는이 전혀 작동 조금 놀랐어요 0

stream.Position = 0; 
/* or */ 
stream.Seek(0, SeekOrigin.Begin); 
+0

두 번째 버전의 인수의 순서가 잘못되었습니다. 이제 코드를 수정하여 XSLT 출력을 한 번 얻습니다. 내가 " '', 16 진수 값 0x1F, 유효하지 않은 문자 인 Line 1, position 1"을 얻는 페이지를 두 번째로로드하면 확실히 앞으로 나아갑니다. – Rich

+0

내 코드를 수정했다 .. 인텔리 센스없이 얻은 것입니다.) 추가 디버깅을 위해 들어오는 스트림을 디스크에 덤프하고 실제로 유효한 XML인지 검사해야합니다. gzipped 일 수 있습니까? –

+0

0x1F는 utf-8로 인코딩 된 응답의 BOM 인 것 같습니다. BOM을 생략하도록 UTF8Encoding을 지시 할 수 있습니다. http://msdn.microsoft.com/en-us/library/s064f8w2.aspx – Filburt

2

에 위치를 재설정 할 필요가있다. HttpApplication 코드를 조금 찔렀다. 완전히 이해하지는 못했지만 요청 처리 과정에서 너무 늦게 출력 스트림을 수정하는 것처럼 보였다.

아직 파악하지 못했다면 PostReleaseRequestState - UpdateRequestCache 또는 PostUpdateRequestCache 중 하나의 이벤트에 두 번째 처리기 함수를 연결해보십시오. 특히 적합한 것은 아니지만 계속 읽으십시오!

어떤 이유로 든 MSDN documentation for HttpApplication은 이벤트 목록에 PreSendRequestContent을 포함하지 않지만 Reflector는 HttpResponse.Flush까지 핸들러가 호출되지 않는다고 표시합니다.

내가 코드를 잘 읽고 있어요 경우, Response.Flush 전에 핸들러가 호출되는 콘텐츠 길이를 계산, 그래서 그것은이 코드에 도달 할 때 응답이 비어 있다고 생각한다

if (contentLength > 0L) { 
    byte[] bytes = Encoding.ASCII.GetBytes(Convert.ToString(contentLength, 0x10) + "\r\n"); 
    this._wr.SendResponseFromMemory(bytes, bytes.Length); 
    this._httpWriter.Send(this._wr); 
    this._wr.SendResponseFromMemory(s_chunkSuffix, s_chunkSuffix.Length); 
} 

가 몇 가지 있습니다 진입 점과 초기 조건에 따라 호출 될 수있는 대체 경로 및 이것이 시간의 일부분은 작동하지만 다른 이유는 작동하지 않는 이유를 설명 할 수 있습니다. 하지만 하루가 끝나면 Flush에있는 응답 스트림을 수정하면 안됩니다.

좀 특이한 일을하고 있습니다. 기존의 감각 (일부 스트림을 다른 스트림으로 전달하는 곳)에서 응답 스트림을 필터링하지 않고 있기 때문에 약간의 해킹을해야 할 수도 있습니다 귀하의 현재 디자인 작업.

대안 대신 모듈 대신 IHttpHandler을 사용하여이를 구현하는 것입니다. a good example here이 있습니다. 데이터베이스 쿼리의 결과를 변환하는 것을 다루지 만 파일 시스템 데이터 소스에 쉽게 적응할 수 있어야합니다.

+0

핸들러를 다른 이벤트에 첨부하려했지만 아무 소용이 없었습니다. 나는 어떻게 든 스트림과 함께 뭔가 잘못하고 있다고 생각합니다. 이 경우 HttpHandler 구현은 파일, WCF 데이터 서비스, 다른 응용 프로그램의 출력 등을 투명하게 변환 할 수 있기를위한 최고의 옵션이 아닙니다. 현재 우리는 URL 재 작성기를 사용하여 요청을 다른 엔드 포인트로 전달한 다음, libxslt를 사용하여 응답을 변환하는 PHP 스크립트에 외부 URL을 매핑하지만 성능은 향상 될 수 있으며 투명성과는 거리가 멀습니다. – Rich

+0

폭발 - 아 글쎄, 나는 이것을 남겨두고 비슷한 생각을 가진 다른 사람들을 구별 할 것이다. –

1

msdn example을 고집하지 않아도 HttpApplication을 구현해야합니다. 대해 EndRequest :

context.EndRequest += (sender, e) => { 
    HttpResponse response = HttpContext.Current.Response; 
    response.Flush(); 
}; 

// ... 

public void Init(HttpApplication application) 
{ 
    // ... 
    application.EndRequest += (new EventHandler(this.Application_EndRequest)); 
} 

private void Application_EndRequest(object sender, EventArgs e) 
{ 
    HttpApplication application = (HttpApplication)source; 
    HttpContext context = application.Context; 
    context.Current.Response.Flush(); 
} 
+0

아이러니하게도, 이것은 내가 처음 시작한 방법 이었지만 람다 버전으로 바뀌 었습니다. 왜냐하면 그것이 더 깨끗하다고 ​​생각했기 때문입니다. – Rich

관련 문제