2014-04-22 14 views
5

Tôi cần một bộ lọc servlet sẽ chụp tất cả đầu vào, sau đó mangle đầu vào đó, chèn một mã thông báo đặc biệt vào mọi biểu mẫu. Hãy tưởng tượng rằng bộ lọc được gắn với tất cả các yêu cầu (Ví dụ: url-pattern=*). Tôi có mã để nắm bắt nội dung, nhưng có vẻ như không phải là RequestWrapper đủ mạnh để nắm bắt tất cả đầu vào. Một số đầu vào trả về 0 byte và sau đó tôi không thể "stream" nội dung đó cho người dùng. Ví dụ, chúng tôi vẫn đang sử dụng Struts 1.3.10 và bất kỳ mã Struts nào không "nắm bắt" đúng cách, chúng tôi nhận được nội dung byte không. Tôi tin rằng đó là vì cách Struts xử lý tiền đạo. Nếu có sự chuyển tiếp liên quan đến yêu cầu, tôi tự hỏi liệu mã chụp bên dưới có hoạt động không. Đây là tất cả các mã, bạn có một cách tiếp cận mà sẽ nắm bắt bất kỳ loại nội dung có nghĩa là để streaming cho người dùng.Bộ lọc servlet bắt tất cả nội dung đầu vào HTML để thao tác, chỉ hoạt động liên tục

<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); 
      } 
     } 
    } 

} 

Đây là những gì một ví dụ Struts ứng dụng có thể như thế nào, đối với một số ứng dụng (không Struts), chúng tôi có thể nắm bắt được nội dung. Nhưng đối với các ứng dụng như dưới đây, không có byte nào được trả lại cho nội dung HTML nhưng phải có nội dung.

<%@ 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> 

Tôi nghi ngờ rằng MyResponseWrapperMyPrintWriter không đủ mạnh mẽ để nắm bắt tất cả các loại nội dung.


Ví dụ servlet rằng sẽ làm việc (một):

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

Ví dụ servlet đó sẽ không hoạt động (b):

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

Ví dụ một sẽ nhận được ảnh chụp, ví dụ b sẽ không.

Đây là lớp trình bao bọc được cải thiện, hầu hết các ứng dụng sẽ hoạt động nhưng NGAY BÂY GIỜ một số ứng dụng thanh chống, chỉ MỘT SỐ phản hồi được gửi tới trình duyệt.

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(); 
     } 
    } 
} 

Tôi tìm thấy một giải pháp khả thi, trong phương pháp getInputStream, có vẻ như nếu tôi gọi là 'gần' trên tất cả các đối tượng, ví dụ như outStream.flush()outStream.close() và sau đó out.flush()out.close() ... nó trông giống như trận chung kết byte được viết đúng cách. nó không trực quan nhưng có vẻ như nó hoạt động.

+0

Hiện chúng tôi biết bạn đăng ký bộ lọc của bạn. –

+0

Được thêm ở trên cùng, phần bộ lọc hoạt động, ảnh chụp là phần bị hỏng. –

+0

Vì vậy, về cơ bản bạn muốn nắm bắt tất cả các bài viết dạng Struts và thao tác nó? Điều gì về các tập tin tải lên (tôi cần rõ ràng về những gì bạn đang cố gắng để đạt được)? Đừng quên rằng Struts ánh xạ tất cả các thuộc tính biểu mẫu của bạn tới các thuộc tính 'ActionForm' của bạn. Nếu nó hoạt động, thì sẽ không có vấn đề gì. –

Trả lời

5

cách tiếp cận ban đầu bạn không thành công vì PrintWriterwraps sự trao ByteArrayOutputStream với một BufferedWriter trong đó có một internal character buffer of 8192 characters, và bạn không bao giờ flush() bộ đệm trước khi nhận được byte từ ByteArrayOutputStream. Nói cách khác, khi ít hơn ~ 8KB dữ liệu được ghi vào getWriter() của phản hồi, gói ByteArrayOutputStream thực sự không bao giờ bị lấp đầy; cụ thể là tất cả mọi thứ vẫn còn trong bộ đệm nhân vật bên trong, chờ đợi để được đỏ mặt.

Một sửa chữa sẽ được thực hiện một cuộc gọi flush() trước toByteArray() trong MyPrintWriter của bạn:

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

Bằng cách này, bộ đệm ký tự nội bộ sẽ được rửa (tức là nó sẽ thực viết tất cả mọi thứ cho luồng dữ liệu gói). Điều này cũng hoàn toàn giải thích lý do tại sao nó hoạt động khi bạn viết thư cho getOutputStream(), bước này cụ thể là không sử dụng PrintWriter và không có gì được đệm trong một số bộ đệm bên trong.


Không liên quan cho vấn đề cụ thể: phương pháp này có một số vấn đề nghiêm trọng. Nó không phải là tôn trọng response character encoding trong khi xây dựng PrintWriter (bạn thực sự nên bọc ByteArrayOutputStream trong một OutputStreamWriter thay vì có thể mã hóa ký tự) và dựa vào nền tảng mặc định, nói cách khác, bất kỳ ký tự Unicode nào có thể kết thúc bằng Mojibake và do đó cách tiếp cận này chưa sẵn sàng cho World Domination.

Ngoài ra, phương pháp này có thể gọi cả hai số getWriter()getOutputStream() trên cùng một phản hồi, trong khi đó là considered an illegal state (chính xác để tránh loại sự cố đệm và mã hóa này).


Cập nhật theo những nhận xét, đây là một viết lại đầy đủ các wrapper phản ứng, thể hiện đúng cách, hy vọng một cách tự giải thích hơn mã bạn đã cho đến nay:

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()); 
    } 

} 

đây là cách bạn đang phải sử dụng nó:

@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

Các triển khai khác duy nhất tôi đã thấy rằng dường như đúng cách làm điều đó, sử dụng mảng byte nội bộ của riêng mình để thu thập tất cả các thông tin byte. Tôi không muốn đi vào mã hóa IO nhiều, trừ khi tôi cần. Nhưng tôi đoán tôi có thể phải làm vậy. –

+0

Nó không phải là khó khăn/nhiều nếu bạn biết phải làm gì. Tôi đã cập nhật câu trả lời bằng đoạn mã khởi động đang hoạt động. – BalusC

Các vấn đề liên quan