2014-04-22 5 views
5

내가 모든 입력을 캡처 한 후 모든 형태의 특별 토큰을 삽입, 그 입력을 난도질하는 서블릿 필터를 필요로 작동합니다. 필터가 모든 요청에 ​​묶여 있다고 가정 해 보겠습니다 (예 : url-pattern=*). 콘텐츠 캡처 용 코드가 있지만 RequestWrapper처럼 모든 입력을 캡처 할 수있을만큼 강력하지는 않습니다. 일부 입력은 0 바이트를 반환하고 그 내용을 다시 사용자에게 "스트리밍"할 수 없습니다. 예를 들어, 우리는 여전히 Struts 1.3.10을 사용하고 있으며 Struts 코드는 적절하게 "포착"되지 않습니다. 우리는 0 바이트 내용을 얻습니다. Struts가 전달을 처리하는 방식 때문이라고 생각합니다. 요청에 포워드가 포함 된 경우 아래 캡처 코드가 작동하는지 궁금합니다. 모든 코드는 다음과 같습니다. 사용자에게 스트리밍하기위한 모든 유형의 콘텐츠를 캡처하는 접근 방식이 있습니까? 캐치 - 모든 서블릿 필터는 간헐적으로 만

<filter> 
    <filter-name>Filter</filter-name> 
    <filter-class>mybrokenCaptureHtml.TokenFilter</filter-class> 
</filter> 
<filter-mapping> 
    <filter-name>Filter</filter-name> 
    <url-pattern>/*</url-pattern> 
</filter-mapping> 

package mybrokenCaptureHtml; 

import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.PrintWriter; 

import javax.servlet.Filter; 
import javax.servlet.FilterChain; 
import javax.servlet.FilterConfig; 
import javax.servlet.ServletException; 
import javax.servlet.ServletOutputStream; 
import javax.servlet.ServletRequest; 
import javax.servlet.ServletResponse; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import javax.servlet.http.HttpServletResponseWrapper; 

public class TokenFilter implements Filter {  
    @Override 
    public void destroy() { 
    } 

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { 
     HttpServletRequest request = (HttpServletRequest) servletRequest;    
     HttpServletResponse response = (HttpServletResponse) servletResponse; 
     try {                      
      final MyResponseWrapper responseWrapper = new MyResponseWrapper((HttpServletResponse) response); 
      chain.doFilter(request, responseWrapper);      

      // **HERE DEPENDING ON THE SERVLET OR APPLICATION CODE (STRUTS, WICKET), the response returns an empty string // 
      // Especiall struts, is there something in their forwards that would cause an error? 
      final byte [] bytes = responseWrapper.toByteArray(); 
        // For some applications that hit this filter 
        // ZERO BYTE DATA is returned, this is bad, but SOME 
        // CODE, the data is captured. 
      final String origHtml = new String(bytes); 

      final String newHtml = origHtml.replaceAll("(?i)</(\\s)*form(\\s)*>", "<input type=\"hidden\" name=\"zval\" value=\"fromSiteZ123\"/></form>");   
      response.getOutputStream().write(newHtml.getBytes()); 

     } catch(final Exception e) {    
      e.printStackTrace(); 
     } 
     return; 
    } 

    @Override 
    public void init(FilterConfig filterConfig) throws ServletException {   
    } 

    static class MyResponseWrapper extends HttpServletResponseWrapper {  
     private final MyPrintWriter pw = new MyPrintWriter();    
     public byte [] toByteArray() {    
      return pw.toByteArray();   
     } 
     public MyResponseWrapper(HttpServletResponse response) { 
      super(response);  
     } 

     @Override 
     public PrintWriter getWriter() { 
      return pw.getWriter(); 
     } 
     @Override 
     public ServletOutputStream getOutputStream() { 
      return pw.getStream(); 
     }  
     private static class MyPrintWriter { 
      private ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
      private PrintWriter pw = new PrintWriter(baos); 
      private ServletOutputStream sos = new MyServletStream(baos); 
      public PrintWriter getWriter() { 
       return pw; 
      } 
      public ServletOutputStream getStream() { 
       return sos; 
      } 
      byte[] toByteArray() { 
       return baos.toByteArray(); 
      } 
     }  
     private static class MyServletStream extends ServletOutputStream { 
      ByteArrayOutputStream baos; 
      MyServletStream(final ByteArrayOutputStream baos) { 
       this.baos = baos; 
      } 
      @Override 
      public void write(final int param) throws IOException { 
       baos.write(param); 
      } 
     } 
    } 

} 

이 예는 (스트럿츠를하지 않음) 일부 응용 프로그램처럼 보일 수 있습니다 응용 프로그램을 스트럿츠 무엇

, 우리는 콘텐츠를 캡처 할 수 있습니다. 그러나 아래의 앱과 같은 경우 0 바이트가 HTML 콘텐츠로 반환되지만 콘텐츠는 있어야합니다.

<%@ page contentType="text/html;charset=UTF-8" language="java" %> 
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> 
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> 
<%@ taglib uri="/WEB-INF/c.tld" prefix="c" %> 
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> 
<%@ taglib uri="/WEB-INF/struts-tiles.tld" prefix="tiles" %> 
<%@ taglib uri="/WEB-INF/struts-nested.tld" prefix="nested"%> 
<html:html> 
<head> 
<title><bean:message key="myApp.customization.title" /></title> 
<LINK rel="stylesheet" type="text/css" href="../theme/styles.css"> 
</head> 
<body> 
<html:form styleId="customizemyAppForm" method="post" action="/customizemyApp.do?step=submit"> 
<html:submit onclick="javascript:finish(this.form);" styleClass="input_small">&nbsp;&nbsp;<bean:message key="myApp.customization.submit" />&nbsp;</html:submit> 
<input type="button" styleClass="input_small" width="80" style="WIDTH:80px" name="<bean:message key="myApp.customization.cancel" />" value="<bean:message key="myApp.customization.cancel" />" onclick="javascript:cancel();"> 

</html:form> 
</body> 
</html:html> 

나는 MyResponseWrapperMyPrintWriter (가) 모든 유형의 콘텐츠를 캡처 할 수있을만큼 강력하지 않은 것으로 판단됩니다. 일하는 것이


예 서블릿 () :

response.getOutputStream().write(str.getBytes()); 

예 서블릿 작동하지 않을 것입니다 (B) :

response.getWriter().println("<html>data</html>"); 

얻을 것이다 캡처, 예제 b은 그렇지 않습니다.

여기에 대부분의 응용 프로그램은 작동하지만 지금은 스트럿 응용 프로그램의 일부는 만 응답의 일부는 브라우저로 전송되어, 개선 된 래퍼 클래스입니다.

import java.io.BufferedReader; 
import java.io.ByteArrayInputStream; 
import java.io.ByteArrayOutputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.OutputStreamWriter; 
import java.io.PrintWriter; 

import javax.servlet.ServletOutputStream; 
import javax.servlet.http.HttpServletResponse; 
import javax.servlet.http.HttpServletResponseWrapper; 

public class ByteArrayResponseWrapper extends HttpServletResponseWrapper { 
    private PrintWriter output = null; 
    private ServletOutputStream outStream = null; 
    private static final String NL = System.getProperty("line.separator"); 

    public ByteArrayResponseWrapper(final HttpServletResponse response) { 
     super(response); 
    } 

    public String getDocument() {   
     InputStream in = null; 
     try {    
      in = this.getInputStream();    
      if (in != null) {    
       return getDocument(in); 
      }   
     } catch(final Exception ee) { 
      // ee.print;StackTrace(); 
     } finally {     
      if (in != null) { 
       try { 
        in.close(); 
       } catch (IOException e) { 
        //e.prin;tStackTrace(); 
       } 
      } 
     } 
     return "";  
    } 

    protected String getDocument(final InputStream in) { 
     final StringBuffer buf = new StringBuffer(); 
     BufferedReader br = null; 
     try { 
      String line = ""; 
      br = new BufferedReader(new InputStreamReader(getInputStream(), this.getCharacterEncoding()));    
      while ((line = br.readLine()) != null) { 
       buf.append(line).append(NL);     
      } 
     } catch(final IOException e) { 
      //e.print;StackTrace(); 
     } finally { 
      try { 
       if (br != null) { 
        br.close(); 
       } 
      } catch (IOException ex) {    
      } 
     } 
     return buf.toString(); 
    } 

    @Override 
    public PrintWriter getWriter() throws IOException { 
     if (output == null) { 
      output = new PrintWriter(new OutputStreamWriter(getOutputStream(), this.getCharacterEncoding())); 
     } 
     return output; 
    } 

    @Override 
    public ServletOutputStream getOutputStream() throws IOException { 
     if (outStream == null) { 
      outStream = new BufferingServletOutputStream(); 
     } 
     return outStream; 
    } 

    public InputStream getInputStream() throws IOException { 
     final BufferingServletOutputStream out = (BufferingServletOutputStream) getOutputStream();   
     return new ByteArrayInputStream(out.getBuffer().toByteArray()); 
    } 

    /** 
    * Implementation of ServletOutputStream that handles the in-memory 
    * buffering of the response content 
    */ 
    public static class BufferingServletOutputStream extends ServletOutputStream { 
     ByteArrayOutputStream out = null; 

     public BufferingServletOutputStream() { 
      this.out = new ByteArrayOutputStream(); 
     } 

     public ByteArrayOutputStream getBuffer() { 
      return out; 
     } 

     public void write(int b) throws IOException { 
      out.write(b); 
     } 

     public void write(byte[] b) throws IOException { 
      out.write(b); 
     } 

     public void write(byte[] b, int off, int len) throws IOException { 
      out.write(b, off, len); 
     } 
     @Override 
     public void close() throws IOException { 
      out.close(); 
      super.close(); 
     } 
     @Override 
     public void flush() throws IOException { 
      out.flush(); 
      super.flush(); 
     } 
    } 
} 

내가 가능한 해결책을 발견의 getInputStream 방법, 내가 예를 들어 outStream.flush()outStream.close() 다음 out.flush()out.close() ... 그것은 최종처럼 보이는 모든 개체에 닫기를 호출하는 경우처럼 보인다 바이트가 제대로 기록됩니다. 직관적이지는 않지만 작동하는 것처럼 보입니다.

+0

당신이 당신의 필터를 등록하는 방법을 우리에게 보여줍니다. –

+0

상단에 필터 부품이 추가되어 캡쳐가 깨졌습니다. –

+0

그래서 기본적으로 모든 Struts 양식 게시물을 캡처하고 조작하고 싶습니까? 업로드 된 파일은 무엇입니까? (달성하고자하는 것에 대해 명확하게 설명해야합니까?) Struts가 모든 폼 속성을'ActionForm' 속성으로 매핑한다는 것을 잊지 마십시오. 그게 효과가 있다면 아무런 문제가 없어야합니다. –

답변

5

귀하의 초기 접근 방식 때문에 internal character buffer of 8192 characters이있는 BufferedWriterPrintWriterwraps 주어진 ByteArrayOutputStream, 그리고 결코 flush() 버퍼 ByteArrayOutputStream에서 바이트를 얻기 전에 실패했습니다. 즉, ~ 8KB 미만의 데이터가 응답의 getWriter()에 쓰여지면 랩 된 ByteArrayOutputStream은 실제로 채워지지 않습니다. 즉 모든 것이 플러시되기를 기다리는 내부 문자 버퍼에 남아 있습니다.

수정 사항은 MyPrintWritertoByteArray() 전에 flush() 호출을 수행하는 것입니다 :

byte[] toByteArray() { 
    pw.flush(); 
    return baos.toByteArray(); 
} 

내부 문자 버퍼가 플러시됩니다이 방법은 (즉,이 실제로은, 랩 된 스트림에 모든 것을 기록합니다). 이것은 또한 getOutputStream()에 쓸 때 이것이 작동하는 이유를 완전히 설명합니다. 즉,이 단계는 PrintWriter을 사용하지 않으며 일부 내부 버퍼에서 버퍼링되지 않습니다. 구체적인 문제에


관련없는 :이 방법은 몇 가지 심각한 문제가있다. PrintWriter의 건설 중 response character encoding을 존중하지 않습니다. 실제로는 ByteArrayOutputStream을 문자 인코딩을 사용할 수있는 OutputStreamWriter으로 감싸고 플랫폼 기본값에 의존해야합니다. 즉, 작성된 유니 코드 문자는 모두 Mojibake이 될 수 있습니다. 이 접근법은 세계 지배에 대한 준비가되어 있지 않습니다. 즉 (버퍼링 및 인코딩 어려움이 종류 않도록 정확하게) considered an illegal state있는 동안

또한, 이러한 접근 방식은 그것이 가능한 한 동일한 반응에 모두 getWriter()getOutputStream()을 호출 할 수있다.

public class CapturingResponseWrapper extends HttpServletResponseWrapper { 

    private final ByteArrayOutputStream capture; 
    private ServletOutputStream output; 
    private PrintWriter writer; 

    public CapturingResponseWrapper(HttpServletResponse response) { 
     super(response); 
     capture = new ByteArrayOutputStream(response.getBufferSize()); 
    } 

    @Override 
    public ServletOutputStream getOutputStream() { 
     if (writer != null) { 
      throw new IllegalStateException("getWriter() has already been called on this response."); 
     } 

     if (output == null) { 
      output = new ServletOutputStream() { 
       @Override 
       public void write(int b) throws IOException { 
        capture.write(b); 
       } 
       @Override 
       public void flush() throws IOException { 
        capture.flush(); 
       } 
       @Override 
       public void close() throws IOException { 
        capture.close(); 
       } 
      }; 
     } 

     return output; 
    } 

    @Override 
    public PrintWriter getWriter() throws IOException { 
     if (output != null) { 
      throw new IllegalStateException("getOutputStream() has already been called on this response."); 
     } 

     if (writer == null) { 
      writer = new PrintWriter(new OutputStreamWriter(capture, getCharacterEncoding())); 
     } 

     return writer; 
    } 

    @Override 
    public void flushBuffer() throws IOException { 
     super.flushBuffer(); 

     if (writer != null) { 
      writer.flush(); 
     } 
     else if (output != null) { 
      output.flush(); 
     } 
    } 

    public byte[] getCaptureAsBytes() throws IOException { 
     if (writer != null) { 
      writer.close(); 
     } 
     else if (output != null) { 
      output.close(); 
     } 

     return capture.toByteArray(); 
    } 

    public String getCaptureAsString() throws IOException { 
     return new String(getCaptureAsBytes(), getCharacterEncoding()); 
    } 

} 
:


주석에 따라 업데이트, 여기에 지금까지했습니다 코드보다 더 자기 설명하는 방법으로 희망, 올바른 방법을 보여주는 응답 래퍼의 전체 재 작성의

은 여기가 그것을 사용하는 거 야 방법은 다음과 같습니다

@Override 
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 
    CapturingResponseWrapper capturingResponseWrapper = new CapturingResponseWrapper((HttpServletResponse) response); 
    chain.doFilter(request, capturingResponseWrapper); 
    String content = capturingResponseWrapper.getCaptureAsString(); // This uses response character encoding. 
    String replacedContent = content.replaceAll("(?i)</form(\\s)*>", "<input type=\"hidden\" name=\"zval\" value=\"fromSiteZ123\"/></form>"); 
    response.getWriter().write(replacedContent); // Don't ever use String#getBytes() without specifying character encoding! 
} 
+0

필자가 보았던 유일한 구현은 모든 바이트 정보를 수집하기 위해 내부 바이트 배열을 사용하는 것입니다. 내가 필요로하지 않는 한 많은 IO 코딩을하고 싶지 않습니다. 그러나 나는해야 할 것 같아요. –

+0

무엇을 해야할지 아는 것이 그리 어렵지 않습니다. 나는 워킹 킥오프 발췌 문장으로 대답을 업데이트했다. – BalusC