如何在保持@FormParam绑定的同时阅读JBoss Resteasy servlet请求两次?

时间:2014-03-11 22:12:47

标签: servlets jboss jax-rs resteasy

我正在使用JBoss的Resteasy作为我们的JAX-RS提供商。我们需要读取servlet请求体以进行身份​​验证,问题是一旦在请求中读取了InputStream,就无法再次读取它,因此@FormParam将无法工作,除非我能以某种方式“放回内容”。我尝试了以下两个选项:

  1. 使用Resteasy的PreProcessInterceptor,我能够读取正文,但无法重置InputStream或添加包装类型。 documentation没有提及任何相关内容。根据JBoss'issue tracker,目前不可能。

  2. 使用Servlet过滤器+ Wrapper类型apporach(see example here),我能够在@javax.ws.rs.core.Context HttpServletRequest request中获取请求正文,但所有@FormParam仍然返回null。

    < / LI>

    这是PreProcessorInterceptor的片段:

    @Provider
    @ServerInterceptor
    public class SomePreprocessor implements PreProcessInterceptor {
    
        public ServerResponse preProcess(HttpRequest request, ResourceMethod method)
                throws Failure, WebApplicationException {
            try{
                StringWriter writer = new StringWriter();
                IOUtils.copy(request.getInputStream(), writer, "UTF-8");
                System.out.println("Request Body: "+writer.toString());
    
                // What can I do to reset the request body?
            }
            catch(Exception ex){
                ex.printStackTrace();
            }
    
            return null;
        }
    
    }
    

    以下是其余方法的片段:

    @POST
    @Path("/something")
    @Produces("application/xml")
    public Response doSomething(
      @FormParam("name") String name,
      @javax.ws.rs.core.Context HttpServletRequest request) {
    
      // name is always null
      System.out.println(name);
    
      // prints nothing in approach 1, returns the body in approach 2
      java.io.StringWriter writer = new java.io.StringWriter();
      org.apache.commons.io.IOUtils.copy(request.getReader(), writer);
      System.out.println(writer.toString());
    }
    

1 个答案:

答案 0 :(得分:3)

如果有人仍然对答案感兴趣,请按照以下方式解决问题:

创建一个扩展HttpServletRequestWrapper的自定义类型。请务必覆盖

  • 的getInputStream()
  • getReader()
  • 的getParameter()
  • getParameterMap()
  • getParameterNames()
  • getParameterValues()

这是因为当Resteasy尝试使用@ Event,@ ActionParam和@QueryParam等进行绑定时,它会调用Resteasy类上的getParameter()方法,然后将其委托给底层请求,在我的例子中,Apache&# 39; s Coyote Servlet请求。因此仅仅重写getInputStream()和getReader()是不够的,你必须确保getParameter()也使用新的输入流。

如果要存储主体供以后使用,则必须通过解析查询字符串和url编码的表单主体来自己构建param映射。它很容易实施,但它有自己的风险。我建议阅读Coyote对相同方法的实现。

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URLDecoder;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.ws.rs.core.MediaType;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * Wrapper class that supports repeated read of the request body and parameters.
 */
public class CustomHttpServletRequest extends HttpServletRequestWrapper {

    private static final Logger logger = Logger.getLogger(CustomHttpServletRequest.class);

    // A typical url encoded form is "key1=value&key2=some%20value"
    public final static Pattern urlStrPattern = Pattern.compile("([^=&]+)=([^&]*)[&]?");

    // Cached request body
    protected ByteArrayOutputStream cachedBytes;

    protected String encoding;
    protected String requestBody;

    // Cached form parameters
    protected Map<String, String[]> paramMap = new LinkedHashMap<String, String[]>();

    // Cached header names, including extra headers we injected.
    protected Enumeration<?> headerNames = null;

    /**
     * 
     * @param request
     */
    public CustomHttpServletRequest(HttpServletRequest request) {
        super(request);

        // Read the body and construct parameters
        try{
            encoding = (request.getCharacterEncoding()==null)?"UTF-8":request.getCharacterEncoding();

            // Parameters in query strings must be added to paramMap
            String queryString = request.getQueryString();
            logger.debug("Extracted HTTP query string: "+queryString);
            if(queryString != null && !queryString.isEmpty()){
                addParameters(queryString, encoding);
            }

            // Parse the request body if this is a form submission. Clients must set content-type to "x-www-form-urlencoded".
            requestBody = IOUtils.toString(this.getInputStream(), encoding);
            if (StringUtils.isEmpty(requestBody)) {requestBody = null;}
            logger.debug("Extracted HTTP request body: "+requestBody);
            if(request.getContentType() != null && request.getContentType().toLowerCase().contains(MediaType.APPLICATION_FORM_URLENCODED)){
                addParameters(requestBody, encoding);
            }
        }
        catch(IOException ex){
            throw new RuntimeException(ex);
        }
    }

    /**
     * 
     * @param requestBody
     * @param encoding
     * @throws IOException
     */
    private void addParameters(String requestBody, String encoding) throws IOException {
        if(requestBody == null){
            return;
        }
        Matcher matcher = urlStrPattern.matcher(requestBody);
        while(matcher.find()){
            String decodedName = URLDecoder.decode(matcher.group(1), encoding);
            // If there's no value, matcher.group(2) returns an empty string instead of null
            String decodedValue = URLDecoder.decode(matcher.group(2), encoding);
            addParameter(decodedName, decodedValue);
            logger.debug("Parsed form parameter, name = "+decodedName+", value = "+decodedValue);
        }
    }

    /**
     * 
     * @param name
     * @param value
     */
    private void addParameter(String name, String value) {
        String[] pv = paramMap.get(name);
        if (pv == null) {
            pv = new String[]{value};
            paramMap.put(name, pv);
        }
        else {
            String[] newValue = new String[pv.length+1];
            System.arraycopy(pv, 0, newValue, 0, pv.length);
            newValue[pv.length] = value;
            paramMap.put(name, newValue);
        }
    }

    /*
     * (non-Javadoc)
     * @see javax.servlet.ServletRequestWrapper#getInputStream()
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (cachedBytes == null){
            cachedBytes = new ByteArrayOutputStream();
            IOUtils.copy(super.getInputStream(), cachedBytes);
        }

        // Return a inner class that references cachedBytes
        return new CachedServletInputStream();
    }

    /*
     * (non-Javadoc)
     * @see javax.servlet.ServletRequestWrapper#getReader()
     */
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }


    /**
     * 
     * @return
     */
    public String getRequestBody() {
        return requestBody;
    }

    /*
     * (non-Javadoc)
     * @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String)
     */
    @Override
    public String getParameter(String name) {
        if(paramMap.containsKey(name)){
            String[] value = (String[]) paramMap.get(name);
            if(value == null){
                return null;
            }
            else{
                return value[0];
            }
        }
        return null;
    }

    /*
     * (non-Javadoc)
     * @see javax.servlet.ServletRequestWrapper#getParameterMap()
     */
    @Override
    public Map<String, String[]> getParameterMap() {
        return Collections.unmodifiableMap(paramMap);
    }

    /*
     * (non-Javadoc)
     * @see javax.servlet.ServletRequestWrapper#getParameterNames()
     */
    @Override
    public Enumeration<?> getParameterNames() {
        return Collections.enumeration(paramMap.keySet());
    }

    /*
     * (non-Javadoc)
     * @see javax.servlet.ServletRequestWrapper#getParameterValues(java.lang.String)
     */
    @Override
    public String[] getParameterValues(String name) {
        if(paramMap.containsKey(name)){
            return paramMap.get(name);
        }
        return null;
    }




    /**
     * Inner class that reads from stored byte array
     */
    public class CachedServletInputStream extends ServletInputStream {
        private ByteArrayInputStream input;

        public CachedServletInputStream() {
            input = new ByteArrayInputStream(cachedBytes.toByteArray());
        }

        @Override
        public int read() throws IOException {
            return input.read();
        }

        @Override
        public int read(byte[] b) throws IOException {
            return input.read(b);
        }

        @Override
        public int read(byte[] b, int off, int len) {
            return input.read(b, off, len);
        }
    }
}

添加一个过滤器来包装原始请求:

public class CustomFilter implements Filter {

    private static final Logger logger = Logger.getLogger(CustomFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if(request!=null && request instanceof HttpServletRequest){
            HttpServletRequest httpRequest = (HttpServletRequest) request;

                            logger.debug("Wrapping HTTP request");
                request = new CustomHttpServletRequest(httpRequest);
        }

        chain.doFilter(request, response);
    }
}