2014-04-22 7 views
5

Ho bisogno di un filtro servlet che acquisisca tutto l'input, quindi maneggi quell'input, inserendo un token speciale in ogni forma. Immagina che il filtro sia legato a tutte le richieste (ad esempio url-pattern=*). Ho il codice per l'acquisizione dei contenuti, ma non sembra che lo RequestWrapper sia abbastanza robusto da catturare tutto l'input. Alcuni input restituiscono zero byte e quindi non posso "trasmettere" il contenuto all'utente. Ad esempio, stiamo ancora utilizzando Struts 1.3.10 e qualsiasi codice Struts non "cattura" correttamente, otteniamo zero byte di contenuto. Credo che sia a causa di come Struts maneggia in avanti. Se c'è un forward coinvolto nella richiesta, mi chiedo se il codice di cattura qui sotto funzionerà. Ecco tutto il codice, hai un approccio che catturerà qualsiasi tipo di contenuto destinato allo streaming all'utente.Il filtro servlet catch-all che dovrebbe acquisire TUTTO il contenuto di input HTML per la manipolazione, funziona solo a intermittenza

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

} 

Questo è ciò che un esempio Struts app può sembrare, per alcune applicazioni (non Struts), possiamo catturare il contenuto. Ma per app come quella qui sotto, vengono restituiti zero byte per il contenuto HTML, ma dovrebbero esserci dei contenuti.

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

Ho il sospetto che il MyResponseWrapper e MyPrintWriter non sono abbastanza robusto per catturare tutti i tipi di contenuti.


Esempio servlet che avrebbe funzionato (un):

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

Esempio servlet che non avrebbe funzionato (B):

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

Esempio un otterrebbe una cattura, esempio b non lo faranno.

Ecco una classe wrapper migliorata, la maggior parte delle applicazioni funzionerà, ma ora alcune delle applicazioni struts, solo una parte della risposta viene inviata al browser.

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

ho trovato una possibile soluzione, nel metodo getInputStream, sembra che se io chiamo 'stretta' su tutti gli oggetti, ad esempio outStream.flush() e outStream.close() e poi out.flush() e out.close() ... Sembra che la finale i byte vengono scritti correttamente. non è intuitivo, ma sembra che funzioni.

+0

ci mostrano come si registra il filtro. –

+0

Aggiunto in alto, la parte del filtro funziona, la cattura è il pezzo rotto. –

+0

Quindi, in pratica, vuoi catturare tutti i messaggi del form di Struts e manipolarlo? Che dire dei file caricati (ho bisogno di chiarezza su ciò che stai cercando di ottenere)? Non dimenticare che Struts mappa tutti gli attributi del tuo form agli attributi 'ActionForm'. Se funziona, non ci dovrebbero essere problemi. –

risposta

5

Il tuo approccio iniziale non è riuscita perché PrintWriterwraps dato ByteArrayOutputStream con un BufferedWriter che ha un internal character buffer of 8192 characters, e non avete mai flush() il buffer prima di ottenere i byte dal ByteArrayOutputStream. In altre parole, quando vengono scritti meno di ~ 8 KB di dati su getWriter() della risposta, il ByteArrayOutputStream impacchettato non viene mai riempito; cioè tutto è ancora in quel buffer di caratteri interno, in attesa di essere svuotato.

Una soluzione potrebbe essere quella di effettuare una chiamata flush() prima toByteArray() nella vostra MyPrintWriter:

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

In questo modo il buffer di carattere interno sarà lavata (vale a dire che verrà realtà scrivere tutto al flusso incapsulato). Questo spiega anche il motivo per cui funziona quando si scrive su getOutputStream(), questo passaggio non utilizza lo PrintWriter e niente viene bufferizzato in un buffer interno.


Estranei al problema concreto: questo approccio ha alcuni gravi problemi. Non sta rispettando lo response character encoding durante la costruzione di PrintWriter (dovresti in realtà includere ByteArrayOutputStream in un OutputStreamWriter invece che può richiedere una codifica di caratteri) e fare affidamento sull'impostazione predefinita della piattaforma, in altre parole, qualsiasi carattere scritto Unicode potrebbe finire in Mojibake modo e quindi questo approccio non è pronto per World Domination.

Inoltre, questo approccio permette di chiamare sia getWriter() e getOutputStream() sulla stessa risposta, mentre questo è considered an illegal state (proprio per evitare questo tipo di problemi buffering e codifica).


Aggiornamento come per il commento, ecco una riscrittura completa del involucro risposta, che mostra la strada giusta, si spera in un modo più autoesplicativo rispetto al codice che avete finora:

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

} 

Ecco come si suppone di usarlo:

@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

Le uniche altre implementazioni che ho visto sembrano funzionare correttamente, usano i loro array di byte interni per raccogliere tutte le informazioni di byte. Non voglio entrare in quella stessa codifica IO, a meno che non sia necessario. Ma credo che potrei doverlo fare. –

+0

Non è così difficile/molto se sai cosa fare. Ho aggiornato la risposta con uno snippet di kickoff funzionante. – BalusC

Problemi correlati