2009-03-25 4 views
8

Я работаю над устаревшим кодом и должен сделать патч.Изменить тело HttpServletRequest

Проблема: старое приложение отправляет плохие HTTP-запросы POST. Один из параметров не кодируется URL. Я знаю, что этот параметр всегда приходит последним, и я знаю, что это имя. Теперь я пытаюсь исправить это на стороне сервера, которая работает внутри tomcat.

Этот параметр недоступен с помощью стандартного метода getParameter метода HttpServletRequest, поскольку он неверен. Метод просто возвращает null. Но когда я вручную считываю весь запрос с помощью ServletInputStream, все остальные параметры исчезают. Похоже, что базовые классы не могут анализировать содержимое ServletInputStream, поскольку он исчерпан.

До сих пор мне удалось создать оболочку, которая считывает все параметры из тела и переопределяет все методы доступа к параметрам. Но если какой-либо фильтр в цепочке перед моим будет пытаться получить доступ к любому параметру, все будет ломаться, поскольку ServletInputStream будет пустым.

Могу ли я как-то избежать этой проблемы? Может быть, есть другой подход?

Подводя итог, если я прочитаю необработанное тело запроса в фильтре, параметры исчезнут из запроса. Если я прочитаю единственный параметр, ServletInputStream станет пустым, и ручная обработка будет невозможна. Более того, невозможно прочитать неверный параметр с помощью метода getParameter.

ответ

9

Решение я нашел:

Это не достаточно просто переопределить параметры доступа к методам. Необходимо сделать несколько вещей.

  1. Фильтр необходим, когда запрос будет обернут.
  2. Пользовательский HttpRequestWrapper необходим при переопределении всех методов доступа к параметрам. Тело запроса должно анализироваться в конструкторе и храниться как поле.
  3. getInputStream и Методы getReader также должны быть переопределены. Они возвращают значения в зависимости от сохраненного тела запроса.
  4. Пользовательский класс, расширяющийся ServletInputStream требуется, поскольку этот абстрактный.

Этот 4 комбинированный позволит использовать getParameter без вмешательства getInputStream и getReader методами.

Учтите, что разбор синтаксического запроса вручную может усложниться с помощью многопрофильных запросов. Но это еще одна тема.

Чтобы уточнить, я переопределил методы доступа к параметрам, потому что мой запрос был поврежден, как указано в вопросе. Возможно, вам это не понадобится.

+0

Не могли бы вы предоставить более подробную информацию о ручном анализе параметров запроса вручную? Разве это недостаточно, чтобы просто переопределить getInputStream и getReader? –

+0

Хорошо, я посмотрел. Мне приходилось во многом анализировать параметры, потому что один из них был поврежден, как описано в вопросе. Если они в порядке, просьба не требуется. – clorz

+2

См. Здесь для полной реализации: http://stackoverflow.com/a/1048123/535203 –

8

Вместо того, чтобы переопределять методы, почему бы вам не установить фильтр сервлета, который переписывает запрос?

У Джейсона Хантера довольно хорошее article on filters.

1

Вы можете написать свой собственный фильтр сервлетов и надеяться, что он появится первым в цепочке. Затем заверните объект ServletRequest в то, что будет обрабатывать переписывание там, где это необходимо. Посмотрите раздел Программирование Индивидуальные запросы и ответы http://java.sun.com/products/servlet/Filters.html

------ Обновление ------

Я должен что-то отсутствует. Вы говорите, что можете прочитать тело запроса и сами прочитать параметры. Не могли бы вы обеспечить, чтобы ваш фильтр был первым, оберните объект ServletRequest, прочитайте, обработайте и сохраните параметры, передайте объект запроса вверх по цепочке и предложите параметры, которые вы сохранили, вместо оригинальных?

+0

Я могу прочитать все параметры, кроме сломанного. Я могу читать только сломанный параметр через ServletInputStream. Когда я использую входной поток, все остальные параметры исчезают. Если я разбираю все параметры из входного потока, иногда сервлеты позже в цепочке прерываются. Это сложно =) – clorz

5

я сделал более полную оболочку, которая позволяет еще получить доступ к содержимому в случае Content-Type является применение/х-WWW-форм-urlencoded и вы уже назвали один из методов 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

Зачем вам нужно переопределять все методы, делегированные объектам req, когда базовый класс уже это сделал? – Divick

+0

Я понял, что вы реализуете HttpServletRequest вместо расширения HttpServletRequestWrapper, поэтому вам нужно реализовать все необходимые методы. Но почему бы не перейти от HttpServletRequestWrapper, который каким-либо образом делегирует вызов исходному объекту запроса? – Divick

+0

Методы getParameterValues, getParameter полагаются на исходный поток, поэтому оболочка не будет делать этого. Но я уступаю использованию оболочки, чтобы избежать реализации всех методов потока. Фактически, отладчик откажется от этого без дополнительной автономной конфигурации. –

3

Я хочу опубликовать это как комментарий, но мне не хватает репутации. Ваше решение недостаточно в том, что ServletInputStreamWrapper вернет отрицательные целые числа. Например, высмеивать запрос с входной кодировкой UTF-16, большой или малой величиной. Ввод может начинаться с отметки байтового заказа, указывающей endianess, и при тестировании моего заявления, пожалуйста, сконфигурируйте для этого макет запроса. http://en.wikipedia.org/wiki/Byte_order_mark#UTF-16 Каждая из этих спецификаций содержит байт 0xFF. Поскольку java не имеет беззнакового байта, этот 0xFF возвращается как -1. Чтобы обойти это, просто измените функцию чтения так, как показано на рисунке

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

Я немного как ваше решение, потому что оно хорошо работает с весной. Сначала я попытался устранить часть кода делегирования, которую вы написали, перейдя из HttpServletRequestWrapper. Однако Spring делает что-то интересное: когда он встречает запрос типа ServletRequestWrapper, он разворачивает его, вызывая getRequest(). Проблема в том, что мой метод getRequest(), скопированный из вашего кода, возвращает новый класс, который простирается от HttpServletRequestWrapper ... прополоскать и повторить бесконечно. Так что грустно говорить, что вы можете выиграть за не использование интерфейсов!

+0

Я также нашел еще одну проблему, где req.getContentType(). Равно ("применение/х-WWW-форм-urlencoded") должно быть req.getContentType(). StartsWith ("применение/х-WWW -форма-urlencoded ") Как выясняется, Firefox может отправлять« application/x-www-form-urlencoded; charset = UTF-8 », тогда как Chrome и IE отправляют только« application/x-www-form-urlencoded », – DanielKWinsor

Смежные вопросы