2009-03-25 28 views
8

Estoy trabajando en el código heredado y necesito hacer un parche.Modificar el cuerpo HttpServletRequest

El problema: una aplicación antigua envía malas solicitudes HTTP POST. Uno de los parámetros no tiene codificación URL. Sé que este parámetro siempre viene último y sé que es su nombre. Ahora estoy tratando de arreglarlo en el lado del servidor que se ejecuta dentro de Tomcat.

Este parámetro no es accesible a través del método getParameter estándar de HttpServletRequest, ya que está mal formado. El método simplemente devuelve null. Pero cuando leo manualmente todo el cuerpo de la solicitud a través de ServletInputStream, todos los otros parámetros desaparecen. Parece que las clases subyacentes no pueden analizar el contenido de ServletInputStream porque está agotado.

Hasta ahora he logrado hacer un envoltorio que lee todos los parámetros del cuerpo y anula todos los métodos de acceso a los parámetros. Pero si algún filtro en la cadena antes que el mío intentará acceder a cualquier parámetro, todo se romperá ya que ServletInputStream estará vacío.

¿Puedo de alguna manera evadir este problema? Puede ser que haya un enfoque diferente?

En resumen, si leeré el cuerpo de la solicitud sin procesar en el filtro, los parámetros desaparecerán de la solicitud. Si leo un solo parámetro, ServletInputStream se vaciará y el procesamiento manual será imposible. Además, es imposible leer el parámetro mal formado a través del método getParameter.

Respuesta

9

solución que he encontrado:

No es suficiente con sólo redefinir los métodos de acceso a los parámetros. Se deben hacer varias cosas.

  1. Se necesita un filtro donde se envuelva la solicitud.
  2. Se necesita un personalizado HttpRequestWrapper con todos los métodos de acceso a los parámetros anulados. El cuerpo de la solicitud debe analizarse en el constructor y almacenarse como un campo.
  3. getInputStream y getReader métodos deben redefinirse también. Devuelven valores que dependen del cuerpo de solicitud almacenado.
  4. Se requiere extensión de clase personalizada ServletInputStream ya que este es abstracto.

Esta 4 combinada permitirá utilizar getParameter sin interferencia con getInputStream y getReader métodos.

mente que el manual de solicitud de parámetros de análisis puede complicarse con las peticiones de varias partes. Pero ese es otro tema.

Para aclarar, que redefinió los parámetros de acceso a métodos ya mi petición fue dañado como se indica en la pregunta. Puede que no lo necesites.

+0

¿Podría proporcionar más detalles sobre el análisis de parámetros de solicitud manual? ¿No es suficiente anular getInputStream y getReader? –

+0

Ok, lo busqué. Tuve que analizar muchos parámetros porque uno de ellos se dañó como se describe en una pregunta. Si están bien en su solicitud, no hay necesidad de hacer eso. – clorz

+2

Consulte aquí para la implementación completa: http://stackoverflow.com/a/1048123/535203 –

8

En lugar de anular los métodos, ¿por qué no instala un filtro de servlet que reescribe la solicitud?

Jason Hunter tiene una muy buena article on filters.

1

Puede escribir su propio filtro Servlet y asegurarse de que aparezca primero en la cadena. Luego ajuste el objeto ServletRequest en algo que pueda reescribir donde sea necesario. Echar un vistazo a la sección de programación personalizada solicitudes y respuestas de http://java.sun.com/products/servlet/Filters.html

------ ------ actualización

Debo estar perdiendo algo. Usted dice que puede leer el cuerpo de la solicitud y leer los parámetros usted mismo. ¿No podría asegurarse entonces de que su filtro es el primero, ajuste el objeto ServletRequest, lea, procese y almacene los parámetros, pase su objeto de solicitud a la cadena y ofrezca los parámetros que almacenó en lugar de los originales?

+0

Puedo leer todos los parámetros excepto uno quebrado. Solo puedo leer el parámetro roto a través de ServletInputStream. Cuando uso la transmisión de entrada, todos los demás parámetros desaparecen. Si analizo todos los parámetros desde la secuencia de entrada, algunas veces los servlets se rompen después. Es complicado =) – clorz

5

hice un envoltorio más completa que le permite todavía tener acceso al contenido en el caso de Content-Type es application/x-www-form-urlencoded y ya llamó a uno de los métodos getParameterXXX:

import java.io.BufferedReader; 
import java.io.ByteArrayInputStream; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.UnsupportedEncodingException; 
import java.net.URLDecoder; 
import java.security.Principal; 
import java.util.Enumeration; 
import java.util.HashMap; 
import java.util.LinkedList; 
import java.util.Locale; 
import java.util.Map; 
import java.util.StringTokenizer; 

import javax.servlet.RequestDispatcher; 
import javax.servlet.ServletInputStream; 
import javax.servlet.http.Cookie; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpSession; 

/** 
* This class implements the Wrapper or Decorator pattern.<br/> 
* Methods default to calling through to the wrapped request object, 
* except the ones that read the request's content (parameters, stream or reader). 
* <p> 
* This class provides a buffered content reading that allows the methods 
* {@link #getReader()}, {@link #getInputStream()} and any of the getParameterXXX to be  called 
* safely and repeatedly with the same results. 
* <p> 
* This class is intended to wrap relatively small HttpServletRequest instances. 
* 
* @author pgurov 
*/ 
public class HttpServletRequestWrapper implements HttpServletRequest { 

private class ServletInputStreamWrapper extends ServletInputStream { 

    private byte[] data; 
    private int idx = 0; 
    ServletInputStreamWrapper(byte[] data) { 
     if(data == null) 
      data = new byte[0]; 
     this.data = data; 
    } 
    @Override 
    public int read() throws IOException { 
     if(idx == data.length) 
      return -1; 
     return data[idx++]; 
    } 

} 

private HttpServletRequest req; 
private byte[] contentData; 
private HashMap<String, String[]> parameters; 

public HttpServletRequestWrapper() { 
    //a trick for Groovy 
    throw new IllegalArgumentException("Please use HttpServletRequestWrapper(HttpServletRequest request) constructor!"); 
} 

private HttpServletRequestWrapper(HttpServletRequest request, byte[] contentData, HashMap<String, String[]> parameters) { 
    req = request; 
    this.contentData = contentData; 
    this.parameters = parameters; 
} 

public HttpServletRequestWrapper(HttpServletRequest request) { 
    if(request == null) 
     throw new IllegalArgumentException("The HttpServletRequest is null!"); 
    req = request; 
} 

/** 
* Returns the wrapped HttpServletRequest. 
* Using the getParameterXXX(), getInputStream() or getReader() methods may interfere 
* with this class operation. 
* 
* @return 
*  The wrapped HttpServletRequest. 
*/ 
public HttpServletRequest getRequest() { 
    try { 
     parseRequest(); 
    } catch (IOException e) { 
     throw new IllegalStateException("Cannot parse the request!", e); 
    } 
    return new HttpServletRequestWrapper(req, contentData, parameters); 
} 

/** 
* This method is safe to use multiple times. 
* Changing the returned array will not interfere with this class operation. 
* 
* @return 
*  The cloned content data. 
*/ 
public byte[] getContentData() { 
    return contentData.clone(); 
} 

/** 
* This method is safe to use multiple times. 
* Changing the returned map or the array of any of the map's values will not 
* interfere with this class operation. 
* 
* @return 
*  The clonned parameters map. 
*/ 
public HashMap<String, String[]> getParameters() { 
    HashMap<String, String[]> map = new HashMap<String, String[]>(parameters.size() * 2); 
    for(String key : parameters.keySet()) { 
     map.put(key, parameters.get(key).clone()); 
    } 
    return map; 
} 

private void parseRequest() throws IOException { 
    if(contentData != null) 
     return; //already parsed 

    byte[] data = new byte[req.getContentLength()]; 
    int len = 0, totalLen = 0; 
    InputStream is = req.getInputStream(); 
    while(totalLen < data.length) { 
     totalLen += (len = is.read(data, totalLen, data.length - totalLen)); 
     if(len < 1) 
      throw new IOException("Cannot read more than " + totalLen + (totalLen == 1 ? " byte!" : " bytes!")); 
    } 
    contentData = data; 
    String enc = req.getCharacterEncoding(); 
    if(enc == null) 
     enc = "UTF-8"; 
    String s = new String(data, enc), name, value; 
    StringTokenizer st = new StringTokenizer(s, "&"); 
    int i; 
    HashMap<String, LinkedList<String>> mapA = new HashMap<String, LinkedList<String>>(data.length * 2); 
    LinkedList<String> list; 
    boolean decode = req.getContentType() != null && req.getContentType().equals("application/x-www-form-urlencoded"); 
    while(st.hasMoreTokens()) { 
     s = st.nextToken(); 
     i = s.indexOf("="); 
     if(i > 0 && s.length() > i + 1) { 
      name = s.substring(0, i); 
      value = s.substring(i+1); 
      if(decode) { 
       try { 
        name = URLDecoder.decode(name, "UTF-8"); 
       } catch(Exception e) {} 
       try { 
        value = URLDecoder.decode(value, "UTF-8"); 
       } catch(Exception e) {} 
      } 
      list = mapA.get(name); 
      if(list == null) { 
       list = new LinkedList<String>(); 
       mapA.put(name, list); 
      } 
      list.add(value); 
     } 
    } 
    HashMap<String, String[]> map = new HashMap<String, String[]>(mapA.size() * 2); 
    for(String key : mapA.keySet()) { 
     list = mapA.get(key); 
     map.put(key, list.toArray(new String[list.size()])); 
    } 
    parameters = map; 
} 

/** 
* This method is safe to call multiple times. 
* Calling it will not interfere with getParameterXXX() or getReader(). 
* Every time a new ServletInputStream is returned that reads data from the begining. 
* 
* @return 
*  A new ServletInputStream. 
*/ 
public ServletInputStream getInputStream() throws IOException { 
    parseRequest(); 

    return new ServletInputStreamWrapper(contentData); 
} 

/** 
* This method is safe to call multiple times. 
* Calling it will not interfere with getParameterXXX() or getInputStream(). 
* Every time a new BufferedReader is returned that reads data from the begining. 
* 
* @return 
*  A new BufferedReader with the wrapped request's character encoding (or UTF-8 if null). 
*/ 
public BufferedReader getReader() throws IOException { 
    parseRequest(); 

    String enc = req.getCharacterEncoding(); 
    if(enc == null) 
     enc = "UTF-8"; 
    return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(contentData), enc)); 
} 

/** 
* This method is safe to execute multiple times. 
* 
* @see javax.servlet.ServletRequest#getParameter(java.lang.String) 
*/ 
public String getParameter(String name) { 
    try { 
     parseRequest(); 
    } catch (IOException e) { 
     throw new IllegalStateException("Cannot parse the request!", e); 
    } 
    String[] values = parameters.get(name); 
    if(values == null || values.length == 0) 
     return null; 
    return values[0]; 
} 

/** 
* This method is safe. 
* 
* @see {@link #getParameters()} 
* @see javax.servlet.ServletRequest#getParameterMap() 
*/ 
@SuppressWarnings("unchecked") 
public Map getParameterMap() { 
    try { 
     parseRequest(); 
    } catch (IOException e) { 
     throw new IllegalStateException("Cannot parse the request!", e); 
    } 
    return getParameters(); 
} 

/** 
* This method is safe to execute multiple times. 
* 
* @see javax.servlet.ServletRequest#getParameterNames() 
*/ 
@SuppressWarnings("unchecked") 
public Enumeration getParameterNames() { 
    try { 
     parseRequest(); 
    } catch (IOException e) { 
     throw new IllegalStateException("Cannot parse the request!", e); 
    } 
    return new Enumeration<String>() { 
     private String[] arr = getParameters().keySet().toArray(new String[0]); 
     private int idx = 0; 

     public boolean hasMoreElements() { 
      return idx < arr.length; 
     } 

     public String nextElement() { 
      return arr[idx++]; 
     } 

    }; 
} 

/** 
* This method is safe to execute multiple times. 
* Changing the returned array will not interfere with this class operation. 
* 
* @see javax.servlet.ServletRequest#getParameterValues(java.lang.String) 
*/ 
public String[] getParameterValues(String name) { 
    try { 
     parseRequest(); 
    } catch (IOException e) { 
     throw new IllegalStateException("Cannot parse the request!", e); 
    } 
    String[] arr = parameters.get(name); 
    if(arr == null) 
     return null; 
    return arr.clone(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getAuthType() 
*/ 
public String getAuthType() { 
    return req.getAuthType(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getContextPath() 
*/ 
public String getContextPath() { 
    return req.getContextPath(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getCookies() 
*/ 
public Cookie[] getCookies() { 
    return req.getCookies(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String) 
*/ 
public long getDateHeader(String name) { 
    return req.getDateHeader(name); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String) 
*/ 
public String getHeader(String name) { 
    return req.getHeader(name); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getHeaderNames() 
*/ 
@SuppressWarnings("unchecked") 
public Enumeration getHeaderNames() { 
    return req.getHeaderNames(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String) 
*/ 
@SuppressWarnings("unchecked") 
public Enumeration getHeaders(String name) { 
    return req.getHeaders(name); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String) 
*/ 
public int getIntHeader(String name) { 
    return req.getIntHeader(name); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getMethod() 
*/ 
public String getMethod() { 
    return req.getMethod(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getPathInfo() 
*/ 
public String getPathInfo() { 
    return req.getPathInfo(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getPathTranslated() 
*/ 
public String getPathTranslated() { 
    return req.getPathTranslated(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getQueryString() 
*/ 
public String getQueryString() { 
    return req.getQueryString(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getRemoteUser() 
*/ 
public String getRemoteUser() { 
    return req.getRemoteUser(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getRequestURI() 
*/ 
public String getRequestURI() { 
    return req.getRequestURI(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getRequestURL() 
*/ 
public StringBuffer getRequestURL() { 
    return req.getRequestURL(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getRequestedSessionId() 
*/ 
public String getRequestedSessionId() { 
    return req.getRequestedSessionId(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getServletPath() 
*/ 
public String getServletPath() { 
    return req.getServletPath(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getSession() 
*/ 
public HttpSession getSession() { 
    return req.getSession(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getSession(boolean) 
*/ 
public HttpSession getSession(boolean create) { 
    return req.getSession(create); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#getUserPrincipal() 
*/ 
public Principal getUserPrincipal() { 
    return req.getUserPrincipal(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie() 
*/ 
public boolean isRequestedSessionIdFromCookie() { 
    return req.isRequestedSessionIdFromCookie(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL() 
*/ 
public boolean isRequestedSessionIdFromURL() { 
    return req.isRequestedSessionIdFromURL(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl() 
*/ 
@SuppressWarnings("deprecation") 
public boolean isRequestedSessionIdFromUrl() { 
    return req.isRequestedSessionIdFromUrl(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid() 
*/ 
public boolean isRequestedSessionIdValid() { 
    return req.isRequestedSessionIdValid(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String) 
*/ 
public boolean isUserInRole(String role) { 
    return req.isUserInRole(role); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getAttribute(java.lang.String) 
*/ 
public Object getAttribute(String name) { 
    return req.getAttribute(name); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getAttributeNames() 
*/ 
@SuppressWarnings("unchecked") 
public Enumeration getAttributeNames() { 
    return req.getAttributeNames(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getCharacterEncoding() 
*/ 
public String getCharacterEncoding() { 
    return req.getCharacterEncoding(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getContentLength() 
*/ 
public int getContentLength() { 
    return req.getContentLength(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getContentType() 
*/ 
public String getContentType() { 
    return req.getContentType(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getLocalAddr() 
*/ 
public String getLocalAddr() { 
    return req.getLocalAddr(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getLocalName() 
*/ 
public String getLocalName() { 
    return req.getLocalName(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getLocalPort() 
*/ 
public int getLocalPort() { 
    return req.getLocalPort(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getLocale() 
*/ 
public Locale getLocale() { 
    return req.getLocale(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getLocales() 
*/ 
@SuppressWarnings("unchecked") 
public Enumeration getLocales() { 
    return req.getLocales(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getProtocol() 
*/ 
public String getProtocol() { 
    return req.getProtocol(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getRealPath(java.lang.String) 
*/ 
@SuppressWarnings("deprecation") 
public String getRealPath(String path) { 
    return req.getRealPath(path); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getRemoteAddr() 
*/ 
public String getRemoteAddr() { 
    return req.getRemoteAddr(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getRemoteHost() 
*/ 
public String getRemoteHost() { 
    return req.getRemoteHost(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getRemotePort() 
*/ 
public int getRemotePort() { 
    return req.getRemotePort(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String) 
*/ 
public RequestDispatcher getRequestDispatcher(String path) { 
    return req.getRequestDispatcher(path); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getScheme() 
*/ 
public String getScheme() { 
    return req.getScheme(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getServerName() 
*/ 
public String getServerName() { 
    return req.getServerName(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#getServerPort() 
*/ 
public int getServerPort() { 
    return req.getServerPort(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#isSecure() 
*/ 
public boolean isSecure() { 
    return req.isSecure(); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#removeAttribute(java.lang.String) 
*/ 
public void removeAttribute(String name) { 
    req.removeAttribute(name); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object) 
*/ 
public void setAttribute(String name, Object value) { 
    req.setAttribute(name, value); 
} 

/* (non-Javadoc) 
* @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String) 
*/ 
public void setCharacterEncoding(String env) 
     throws UnsupportedEncodingException { 
    req.setCharacterEncoding(env); 
} 

} 
+0

¿Por qué necesita anular todos los métodos que se delegan a los objetos de solicitud cuando la clase base ya lo haría? – Divick

+0

Me di cuenta de que implementa HttpServletRequest en lugar de extender HttpServletRequestWrapper, por lo tanto, debe implementar todos los métodos necesarios. Pero ¿por qué no extender desde HttpServletRequestWrapper que de todos modos delega la llamada al objeto de solicitud original? – Divick

+0

Los métodos getParameterValues, getParameter dependen de la secuencia original, por lo que un contenedor por sí solo no lo hará. Pero cedo al uso de la envoltura para evitar implementar todos los métodos de la transmisión. De hecho, undertow rechazará esto sin una configuración extra independiente. –

3

Quería publicar esto como un comentario, pero no tengo suficiente representante. Su solución es insuficiente porque ServletInputStreamWrapper devolverá números enteros negativos. Por ejemplo, simula una solicitud con codificación de entrada UTF-16, ya sea grande o pequeña endian. La entrada puede comenzar con la marca de orden de bytes que indica endianess, y cuando pruebe mi declaración, construya el contenido de la solicitud de prueba para hacerlo. http://en.wikipedia.org/wiki/Byte_order_mark#UTF-16 Cualquiera de estas listas de materiales contiene un byte 0xFF. Como java no tiene un byte sin signo, este 0xFF se devuelve como -1. Para evitar esto, simplemente cambie la función de lectura como

public int read() throws IOException { 
     if (index == data.length) { 
      return -1; 
     } 
     return data[index++] & 0xff; 
    } 

Me gusta algo su solución porque funciona bien con Spring. Al principio traté de eliminar parte del código de delegación que escribiste al extender desde HttpServletRequestWrapper. Sin embargo, Spring hace algo interesante: cuando encuentra una solicitud de tipo ServletRequestWrapper lo desenvuelve llamando a getRequest(). El problema es que mi método getRequest(), copiado de su código, devuelve una nueva clase que se extiende desde HttpServletRequestWrapper ... enjuague y repita infinitamente. ¡Así que es triste decirlo, apúntese una victoria por no usar interfaces!

+0

También encontré otro problema, donde req.getContentType(). Equals ("application/x-www-form-urlencoded") deben ser req.getContentType(). StartsWith ("application/x-www -form-urlencoded ") Como resultado, Firefox puede enviar" application/x-www-form-urlencoded; charset = UTF-8 "mientras que Chrome e IE solo envían" application/x-www-form-urlencoded " – DanielKWinsor

Cuestiones relacionadas