【问题标题】:How to get form parameters in request filter如何在请求过滤器中获取表单参数
【发布时间】:2013-08-09 01:32:35
【问题描述】:

我正在尝试在请求过滤器中获取请求的表单参数:

@Override
public ContainerRequest filter(final ContainerRequest request) {

    final Form formParameters = request.getFormParameters();

    //logic

    return request;
}

但是,表单似乎总是为空。 HttpRequestContext.getFormParameters() 文档说:

获取请求实体的表单参数。

此方法将确保请求实体被缓冲,以便应用程序可以使用它。

返回: 表单参数,如果有请求实体且内容类型为“application/x-www-form-urlencoded”,则返回不包含参数的实例。

我的资源使用 @Consumes("application/x-www-form-urlencoded") 进行了注释,尽管它在请求过滤器之后才会被匹配 - 这就是为什么这不起作用吗?

我尝试进行一些研究,但找不到任何确凿证据证明这是否可行。有this 4-year old discussion,其中 Paul Sandoz 说:

如果您使用 Jersey 过滤器或使用 HttpRequestContext,您可以获得如下表单参数:[到 Jersey 1.1.1 HttpRequestContext.getFormParameters 的链接断开]

我还发现了 this 3-year-old discussion 关于如何在请求过滤器中获取 multipart/form-data 表单字段的信息。在其中,Paul Sandoz 使用了以下代码:

// Buffer
InputStream in = request.getEntityInputStream();
if (in.getClass() != ByteArrayInputStream.class) {
    // Buffer input
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    try {
        ReaderWriter.writeTo(in, baos);
    } catch (IOException ex) {
        throw new ContainerException(ex);
    }
    in = new ByteArrayInputStream(baos.toByteArray());
    request.setEntityInputStream(in);
}

// Read entity
FormDataMultiPart multiPart = request.getEntity(FormDataMultiPart.class);

我尝试为Form 模拟这种方法,但request.getEntityInputStream() 的结果始终是一个空流。看看the source of getFormParameters,这个方法实际上已经在做同样的事情了:

@Override
public Form getFormParameters() {
    if (MediaTypes.typeEquals(MediaType.APPLICATION_FORM_URLENCODED_TYPE, getMediaType())) {
        InputStream in = getEntityInputStream();
        if (in.getClass() != ByteArrayInputStream.class) {
            // Buffer input
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            try {
                ReaderWriter.writeTo(in, byteArrayOutputStream);
            } catch (IOException e) {
                throw new IllegalArgumentException(e);
            }

            in = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            setEntityInputStream(in);
        }

        ByteArrayInputStream byteArrayInputStream = (ByteArrayInputStream) in;
        Form f = getEntity(Form.class);
        byteArrayInputStream.reset();
        return f;
    } else {
        return new Form();
    }
}

在我了解实体输入流之前,我无法弄清楚是什么在消耗实体输入流。 Jersey 中的某些东西一定在消耗它,因为表单参数稍后会传递到资源方法中。我在这里做错了什么,或者这是不可能的(为什么)?

编辑:以下是发送请求的示例:

POST /test/post-stuff HTTP/1.1
Host: local.my.application.com:8443
Cache-Control: no-cache
Content-Type: application/x-www-form-urlencoded

form_param_1=foo&form_param_2=bar

这是(有点多余的)request logging

INFO: 1 * Server in-bound request
1 > POST https://local.my.application.com:8443/test/post-stuff
1 > host: local.my.application.com:8443
1 > connection: keep-alive
1 > content-length: 33
1 > cache-control: no-cache
1 > origin: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
1 > user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
1 > content-type: application/x-www-form-urlencoded
1 > accept: */*
1 > accept-encoding: gzip,deflate,sdch
1 > accept-language: en-US,en;q=0.8
1 > cookie: [omitted]
1 > 

这是该请求的响应标头,包括Jersey Trace

Content-Type →application/json;charset=UTF-8
Date →Fri, 09 Aug 2013 18:00:17 GMT
Location →https://local.my.application.com:8443/test/post-stuff/
Server →Apache-Coyote/1.1
Transfer-Encoding →chunked
X-Jersey-Trace-000 →accept root resource classes: "/post-stuff"
X-Jersey-Trace-001 →match path "/post-stuff" -> "/post\-stuff(/.*)?", [...], "(/.*)?"
X-Jersey-Trace-002 →accept right hand path java.util.regex.Matcher[pattern=/post\-stuff(/.*)? region=0,11 lastmatch=/post-stuff]: "/post-stuff" -> "/post-stuff" : ""
X-Jersey-Trace-003 →accept resource: "post-stuff" -> @Path("/post-stuff") com.application.my.jersey.resource.TestResource@7612e9d2
X-Jersey-Trace-004 →match path "" -> ""
X-Jersey-Trace-005 →accept resource methods: "post-stuff", POST -> com.application.my.jersey.resource.TestResource@7612e9d2
X-Jersey-Trace-006 →matched resource method: public javax.ws.rs.core.Response com.application.my.jersey.resource.TestResource.execute(java.lang.String,java.lang.String)
X-Jersey-Trace-007 →matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider@b98df1f
X-Jersey-Trace-008 →matched message body writer: java.lang.String@f62, "application/json" -> com.sun.jersey.core.impl.provider.entity.StringProvider@1c5ddffa

这是(不起眼的)servlet 配置:

<servlet>
    <servlet-name>jersey</servlet-name>
    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>com.sun.jersey.config.property.packages</param-name>
        <param-value>com.application.my.jersey</param-value>
    </init-param>
    <init-param>
        <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
        <param-value>com.application.my.jersey.MyFilterFactory</param-value>
    </init-param>
    <init-param>
        <param-name>com.sun.jersey.config.feature.Trace</param-name>
        <param-value>true</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

这是示例资源:

@Path("/post-stuff")
@Produces(MediaType.APPLICATION_JSON)
public final class TestResource {

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response execute(
            @FormParam("form_param_1") final String formParam1,
            @FormParam("form_param_2") final String formParam2
    ) {
        return Response.created(URI.create("/")).entity("{}").build();
    }
}

我使用的是 Jersey 1.17。


对于那些感兴趣的人,我正在尝试推出自己的必需参数验证,如JERSEY-351 中所述。我的解决方案 here 适用于查询、cookie 和标头参数 - 表单参数对我不利。

【问题讨论】:

  • 您能否向我们展示您发送到其余端点的请求。
  • @michal.gajdos 看看我的编辑,让我知道我还能提供什么。
  • formParam1formParam2 在您的资源方法中也是 null 吗?只是为了确保 MyFilterFactory 包含您的第一个 sn-p 中的 #filter 方法?
  • @michal.gajdos formParam1formParam1 按预期传递到资源方法(不是 null)。是的,MyFilterFactory 创建了资源过滤器,该过滤器创建了具有 filter 实现的容器请求过滤器。

标签: java jersey http-post http-request jersey-1.0


【解决方案1】:

这是一个棘手的问题。我删除了其他 Jersey 过滤器以消除问题,但错过了隐藏在 web.xml 底部的普通 servlet 过滤器:

<filter>
    <filter-name>myFilter</filter-name>
    <filter-class>com.application.my.MyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>myFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

删除此过滤器解决了问题 - 表单参数出现在 Jersey 过滤器中。但为什么?我深入挖掘,将问题缩小到MyFilter 中的单个语句:

request.getParameter("some_param")

我试图通过删除 MyFilter 并在 Jersey 过滤器中进行相同的调用(通过注入 HttpServletRequest)来进一步简化问题 - 但表单参数仍然出现。当在传递给javax.servlet.Filter.doFilterorg.apache.catalina.connector.RequestFacade 实例上调用getParameter 时,该问题似乎特别发生。那么这实际上是一个 Tomcat 错误吗?

ServletRequest.getParameter 的文档说:

如果参数数据是在请求正文中发送的,例如发生在 HTTP POST 请求中,那么直接通过getInputStream()getReader() 读取正文可能会干扰此方法的执行。

所以也许反过来也是如此——调用getParameter 可能会被允许干扰实体输入流?我不清楚该方法的合同是否允许这种行为,以及它是否表明 Tomcat、Jersey 中存在错误,或者两者都没有。

无论如何,实际上并不需要那个旧过滤器,所以我的问题得到了解决,只是将其删除。


这是问题的完整再现(Tomcat 7.0):

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
    <display-name>test</display-name>
    <servlet>
        <servlet-name>jersey</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>com.application.my</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
            <param-value>com.application.my.TestFilterFactory</param-value>
        </init-param>
        <init-param>
            <param-name>com.sun.jersey.config.feature.Trace</param-name>
            <param-value>false</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>jersey</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
    <filter>
        <filter-name>servletFilter</filter-name>
        <filter-class>com.application.my.TestServletFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>servletFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

TestServletFilter.java:

package com.application.my;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public final class TestServletFilter implements Filter {

    @Override
    public void init(FilterConfig config) { }

    @Override
    public void doFilter(
            final ServletRequest request,
            final ServletResponse response,
            final FilterChain chain
    ) throws IOException, ServletException {
        System.out.println("calling getParameter on " + request.getClass().getName());
        request.getParameter("blah");
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() { }
}

TestFilterFactory.java:

package com.application.my;

import java.util.Collections;
import java.util.List;

import com.sun.jersey.api.model.AbstractMethod;
import com.sun.jersey.spi.container.ContainerRequest;
import com.sun.jersey.spi.container.ContainerRequestFilter;
import com.sun.jersey.spi.container.ContainerResponseFilter;
import com.sun.jersey.spi.container.ResourceFilter;
import com.sun.jersey.spi.container.ResourceFilterFactory;

public final class TestFilterFactory implements ResourceFilterFactory {

    @Override
    public List<ResourceFilter> create(final AbstractMethod method) {
        return Collections.<ResourceFilter>singletonList(new ResourceFilter() {
            @Override
            public ContainerRequestFilter getRequestFilter() {
                return new ContainerRequestFilter() {
                    @Override
                    public ContainerRequest filter(final ContainerRequest request) {

                        System.out.println("form: " + request.getFormParameters());

                        return request;
                    }
                };
            }
            @Override
            public ContainerResponseFilter getResponseFilter() {
                return null;
            }
        });
    }
}

TestResource.java:

package com.application.my;

import java.net.URI;

import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;


@Path("/post-stuff")
@Produces(MediaType.APPLICATION_JSON)
public final class TestResource {

    @POST
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response execute(
            @FormParam("form_param_1") final String formParam1,
            @FormParam("form_param_2") final String formParam2
    ) {
        System.out.println("form param_1: " + formParam1);
        System.out.println("form param_2: " + formParam2);

        return Response.created(URI.create("/")).entity("{}").build();
    }
}

【讨论】:

    【解决方案2】:

    确保您的ResourceFilterFactoryTestResource#execute 方法创建ResourceFilter 的实例,然后创建ContainerRequestFilter 实例:

    public class MyFilterFactory implements ResourceFilterFactory {
    
        @Override
        public List<ResourceFilter> create(final AbstractMethod am) {
            return new ArrayList<ResourceFilter>() {{
    
                add(new ResourceFilter() {
                    @Override
                    public ContainerRequestFilter getRequestFilter() {
                        return new ContainerRequestFilter() {
                            @Override
                            public ContainerRequest filter(final ContainerRequest request) {
                                System.out.println(request.getFormParameters());
                                return request;
                            }
                        };
                    }
    
                    @Override
                    public ContainerResponseFilter getResponseFilter() {
                        return null;
                    }
                });
            }};
        }
    }
    

    从您提供的trace,我不确定您的ContainerRequestFilter 是否被调用。应该还有一个包含以下内容的跟踪标头:

    →matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider@b98df1f
    

    我的测试的整个跟踪:

    HTTP/1.1 201 Created
    Location: http://localhost:8080/helloworld-webapp/helloworld/
    Content-Type: text/plain
    X-Jersey-Trace-000: accept root resource classes: "/helloworld"
    X-Jersey-Trace-001: match path "/helloworld" -> "/application\.wadl(/.*)?", "/helloworld(/.*)?"
    X-Jersey-Trace-002: accept right hand path java.util.regex.Matcher[pattern=/helloworld(/.*)? region=0,11 lastmatch=/helloworld]: "/helloworld" -> "/helloworld" : ""
    X-Jersey-Trace-003: accept resource: "helloworld" -> @Path("/helloworld") com.sun.jersey.samples.helloworld.resources.HelloWorldResource@7449df0f
    X-Jersey-Trace-004: match path "" -> ""
    X-Jersey-Trace-005: accept resource methods: "helloworld", POST -> com.sun.jersey.samples.helloworld.resources.HelloWorldResource@7449df0f
    X-Jersey-Trace-006: matched resource method: public javax.ws.rs.core.Response com.sun.jersey.samples.helloworld.resources.HelloWorldResource.execute(java.lang.String,java.lang.String)
    X-Jersey-Trace-007: matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider@6bc1b916
    X-Jersey-Trace-008: matched message body reader: class com.sun.jersey.api.representation.Form, "application/x-www-form-urlencoded" -> com.sun.jersey.core.impl.provider.entity.FormProvider@6bc1b916
    X-Jersey-Trace-009: matched message body writer: java.lang.String@f62, "text/plain" -> com.sun.jersey.core.impl.provider.entity.StringProvider@4aae6c4e
    Transfer-Encoding: chunked
    Server: Jetty(6.1.24) 
    

    编辑 1:

    启用请求LoggingFilter:

    <init-param>
        <param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name>
        <param-value>com.sun.jersey.api.container.filter.LoggingFilter</param-value>
    </init-param>
    

    编辑 2:

    还要确保没有其他 Servlet 或 Jersey 过滤器之前读取过 InputStream。在这种情况下,实体输入流可能不再可用(但您仍然可以将 @FormParam 注入到您的资源方法中 - 就像本例一样)。

    【讨论】:

    • 过滤器肯定被实例化并被调用——这就是我验证Form在其执行过程中为空的方式。我附加了 Jersey 源代码并通过getFormParameters 跟踪它 - 它正确地将输入流写入ByteArrayOutputStream,但流是空的。我不确定为什么您会看到额外的消息正文阅读器跟踪,而我没有 - 一个来自您的电话 getFormParameters,但另一个来自什么?
    • 另外,为了清楚起见,您使用的是什么版本的泽西岛?
    • 我正在使用1.18-SNAPSHOT。第一个 matched message body reader 来自 getEntity(Form.class) in ContainerRequest#getFormParameters() ,第二个来自表单参数注入器在进入资源方法之前。请您启用LoggingFilter(请参阅我的回答中的EDIT 1)并提供日志吗?
    • 当然,请参阅我的最新编辑。所以我想知道为什么我没有看到参数注入器的痕迹?参数清楚地显示在资源方法中......
    • 你能用可重现的测试用例向我们的 JIRA (java.net/jira/browse/JERSEY) 提交错误吗?我需要调试这个。谢谢。
    猜你喜欢
    • 2014-10-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-22
    • 1970-01-01
    • 2016-08-30
    • 1970-01-01
    相关资源
    最近更新 更多