如何在维护@FormParam绑定的同时两次读取 JBoss Resteasy 的 servlet 请求?



我正在使用jboss'Resteasy作为我们的JAX-RS提供商。我们需要读取用于身份验证目的的servlet请求主体的要求,问题是一旦在请求中读取了输入流,就无法再读取它,因此@formparam不起作用,除非我可以以某种方式"将内容放回"。我尝试了以下两个选项:

  1. 使用Resteasy的预处理访问者,我能够阅读身体,但是没有办法重置InputStream或添加包装器类型。该文档对此没有任何提及。根据JBOSS的"问题跟踪器",目前不可能。

  2. 使用Servlet Filter 包装器类型Apporach(请参见此处),我能够在@javax.ws.rs.core.Context HttpServletRequest request中获取请求主体,但是所有@formparam仍然返回null。

这是预处理互动器的片段:

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

如果任何人仍然对答案感兴趣,这就是我解决的方式:

创建一个扩展httpservletrequestwrapper的自定义类型。确保您覆盖

  • getInputstream()
  • getReader()
  • getParameter()
  • getParameTermap()
  • getParameternames()
  • getParameTervalues()

这是因为当Resteasy尝试使用@Form,@formparam和@queryparam等绑定时,它将调用Resteasy类上的GetParameter()方法,然后将其委派给基础请求,就我而言,就Apache的土狼而言,servlet请求。因此,仅用的GetInputStream()和getReader()是不够的,您必须确保GetParameter()也使用新的输入流。

如果要存储身体以备后用,则必须通过解析查询字符串和编码形式的主体来构造参数映射。实施非常直接,但它具有自身的风险。我建议阅读土狼的实现相同方法。

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

最新更新