当前位置: 首页 > 知识库问答 >
问题:

无法再从HttpServletRequest SpringBoot 2.2、Jersey 2.29获取表单数据

穆俊杰
2023-03-14

我们有一个SpringBoot应用程序,并且正在使用jer来审核传入的HTTP请求。

我们实现了一个Jersey ContainerRequestFilter来检索传入的HttpServletRequest,并使用HttpServletRequest的getParameterMap()方法来提取查询和表单数据,并将其放入审计中。

这与getParameterMap()的javadoc一致:

"请求参数是随请求一起发送的额外信息。对于HTTP servlet,参数包含在查询字符串或发布的表单数据中。"

这里是有关过滤器的留档:

https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/user-guide.html#filters-and-interceptors

更新SpringBoot时,我们发现getParameterMap()不再返回表单数据,但仍返回查询数据。

我们发现SpringBoot 2.1是支持我们代码的最后一个版本。在SpringBoot 2.2中,Jersey的版本更新为2.29,但在查看发行说明后,我们没有看到任何与此相关的内容。

什么改变了?我们需要改变什么来支持SpringBoot 2.2/泽西2.29?

这是我们代码的简化版本:

JerseyRequestFilter-我们的过滤器

import javax.annotation.Priority;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ResourceInfo;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
...

@Provider
@Priority(Priorities.AUTHORIZATION)
public class JerseyRequestFilter implements ContainerRequestFilter {

    @Context
    private ResourceInfo resourceInfo;

    @Context
    private HttpServletRequest httpRequest;
    ...
    
    public void filter(ContainerRequestContext context) throws IOException {
        ...
        requestData =  new RequestInterceptorModel(context, httpRequest, resourceInfo);
        ...
    }   
    ...
}   

RequestInterceptorModel-映射没有填充表单数据,只有查询数据

import lombok.Data;
import org.glassfish.jersey.server.ContainerRequest;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ResourceInfo;
...

@Data
public class RequestInterceptorModel {

    private Map<String, String[]> parameterMap;
    ...
    
    public RequestInterceptorModel(ContainerRequestContext context, HttpServletRequest httpRequest, ResourceInfo resourceInfo) throws AuthorizationException, IOException {
        ...
        setParameterMap(httpRequest.getParameterMap());
        ...
    }
    ...     
}

JerseyConfig-我们的配置

import com.xyz.service.APIService;
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.ApiListingResource;
import io.swagger.jaxrs.listing.SwaggerSerializers;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.wadl.internal.WadlResource;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
...

@Component
public class JerseyConfig extends ResourceConfig {
    ...

    public JerseyConfig() {
        this.register(APIService.class);
        ...
        // Access through /<Jersey's servlet path>/application.wadl
        this.register(WadlResource.class);
        this.register(AuthFilter.class);
        this.register(JerseyRequestFilter.class);
        this.register(JerseyResponseFilter.class);
        this.register(ExceptionHandler.class);
        this.register(ClientAbortExceptionWriterInterceptor.class);
    }

    @PostConstruct
    public void init() 
        this.configureSwagger();
    }

    private void configureSwagger() {
        ...
    }
}

完整示例

以下是使用我们的示例项目重新创建的步骤:

  1. 在此处从github下载源代码:
 git clone https://github.com/fei0x/so-jerseyBodyIssue
 mvn -Prun
  curl -X POST \
  http://localhost:8012/api/jerseyBody/ping \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d param=Test%20String
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.15.RELEASE</version>

<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.9.RELEASE</version>
 mvn -Prun
  curl -X POST \
  http://localhost:8012/api/jerseyBody/ping \
  -H 'content-type: application/x-www-form-urlencoded' \
  -d param=Test%20String

共有2个答案

长孙作人
2023-03-14

我将发布这个答案,即使@Amir Schnell已经发布了一个有效的解决方案。原因是我不太清楚为什么这个解决方案有效。当然,我宁愿有一个只需要向属性文件添加属性的解决方案,而不是像我的解决方案那样修改代码。但我不确定我是否对一个与我的逻辑所认为的相反的解决方案感到满意。这就是我的意思。在当前代码(SBv 2.1.15)中,如果您提出请求,请查看日志,您将看到一个Jersey日志

2020-12-15 11:43:04.858WARN 5045---[nio-8012-exec-1]o. g. j. s. WebComponent:对URIhttp://localhost:8012/api/jerseyBody/ping的servlet请求在请求正文中包含表单参数,但请求正文已被访问请求参数的servlet或servlet过滤器使用。只有使用@FormParam的资源方法才能按预期工作。通过其他方式使用请求正文的资源方法将无法按预期工作。

这是Jersey的一个已知问题,我在这里看到一些人问他们为什么不能从HttpServletRequest中获取参数(这个消息几乎总是在他们的日志中)。然而,在你的应用程序中,即使这是记录的,你也能够获得参数。只有在升级SB版本,然后没有看到日志后,参数才不可用。所以你明白我为什么困惑了。

这是另一个不需要弄乱过滤器的解决方案。您可以使用Jersey用于获取FormParam的相同方法。只需将以下方法添加到您的RequestInterceptorModel类中

private static Map<String, String[]> getFormParameterMap(ContainerRequestContext context) {
    Map<String, String[]> paramMap = new HashMap<>();
    ContainerRequest request = (ContainerRequest) context;
    if (MediaTypes.typeEqual(MediaType.APPLICATION_FORM_URLENCODED_TYPE, request.getMediaType())) {
        request.bufferEntity();
        Form form = request.readEntity(Form.class);
        MultivaluedMap<String, String> multiMap = form.asMap();
        multiMap.forEach((key, list) -> paramMap.put(key, list.toArray(new String[0])));
    }
    return paramMap;
}

为此,您根本不需要HttpServletRequest。现在,您可以通过调用此方法来设置参数映射

setParameterMap(getFormParameterMap(context));

希望有人能解释这个令人困惑的案例。

桓修能
2023-03-14

好吧,在大量调试代码和挖掘github存储库后,我发现了以下内容:

如果请求是后请求,则有一个过滤器读取请求的正文输入流,使其无法用于进一步使用。这是隐藏的HttpMethodFilter。但是,如果正文内容是应用程序/x-www-form-urlencoded,则该过滤器将其放入请求的parameterMap中。

查看此github问题:https://github.com/spring-projects/spring-framework/issues/21439

该过滤器在spring boot 2.1中默认处于活动状态。十、

由于这种行为在大多数情况下是不需要的,因此创建了一个属性来启用/禁用它,并使用spring boot 2.2。默认情况下,它已停用。

由于代码依赖于此筛选器,因此可以通过以下属性启用它:

spring.mvc.hiddenmethod.filter.enabled=true

我在当地测试了它,它对我有用。

编辑:

以下是该解决方案的工作原理

HiddenHttpomeodFilter调用

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

    HttpServletRequest requestToUse = request;

    if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
        String paramValue = request.getParameter(this.methodParam);
    ...

request.getParameter检查参数是否已经被解析,如果不是这样的话,也会这样做。
此时,请求主体输入流还没有被调用,所以请求数字也解析主体:
org.apache.catalina.connector.Request#parseParameters

protected void parseParameters() {

    parametersParsed = true;

    Parameters parameters = coyoteRequest.getParameters();
    boolean success = false;
    try {
        ...
        // this is the bit that parses the actual query parameters
        parameters.handleQueryParameters();
            
        // here usingInputStream is false, and so the body is parsed aswell
        if (usingInputStream || usingReader) {
            success = true;
            return;
        }
        ... // the actual body parsing is done here 

问题是,在这个场景中使用InputStream是false,因此该方法在解析查询参数后不会返回<仅当第一次检索请求正文的输入流时,code>usingInputStream才设置为true。这只有在我们从过滤链的末端掉下来并为请求提供服务之后才能完成。jersey在org中初始化ContainerRequest时调用inputStream。玻璃鱼。运动衫servlet。网络组件#initContainerRequest

private void initContainerRequest(
            final ContainerRequest requestContext,
            final HttpServletRequest servletRequest,
            final HttpServletResponse servletResponse,
            final ResponseWriter responseWriter) throws IOException {

    requestContext.setEntityStream(servletRequest.getInputStream());
    ...

请求#获取输入流

public ServletInputStream getInputStream() throws IOException {
    ...
    usingInputStream = true;
    ...

由于HiddenHttpMethodFilter是唯一访问参数的过滤器,没有这个过滤器,在调用请求之前,永远不会解析参数。getParameterMap()在RequestInterceptorModel中。但当时,请求主体的inputStream已经被访问,因此

 类似资料:
  • 我一直在尝试使用nativescript创建一个android应用程序。我正在使用fetch模块从服务器获取响应。当我试图从httpbin获得响应时。org/get,没关系。但当我试图从本地服务器获取响应时,网络请求失败。错误 发送到httpbin。组织/获取- 发送到本地主机:8000/api- 当我尝试从纯节点中的localhost:8000/api获取响应时。js通过请求模块。它工作得很好。

  • 我是Thymeleaf的新手,我尝试使用Thymeleaf和Spring MVC执行一个简单的表单提交示例。我根据Thymeleaf留档编写了代码。但是我在控制器中得到空值。 我的控制器是 我的模型课是

  • 这是我收到的错误: 请求的资源上不存在“< code > Access-Control-Allow-Origin ”标头。因此,不允许访问源“https://s.codepen.io”。如果不透明响应满足您的需要,请将请求的模式设置为' < code>no-cors ',以便在禁用cors的情况下获取资源。 我将模式设置为但仍然没有运气。

  • 我正在尝试从MySQL数据库中的表用户获取数据。使用下面的代码,我可以登录到数据库(不返回错误),但当我试图从表中读取数据时,它会显示警告(在这种情况下,我只是选择行来检查代码是否有效)。代码如下: 通过上面的代码,我得到以下结果: 警告:mysql_query()[function.mysql query]:在/home/adamjudk/public_html/airlinesimulator

  • 问题内容: 我似乎无法恢复发送到我的Node.js服务器的发布请求的表单数据。我在服务器代码和发布请求(在chrome中使用postman发送)的下方放置了: 发布请求 NodeJS服务器代码 登录方法尝试获取,但是始终为空。我在SO上看到过其他描述这种行为的案例,但是没有相关的答案在这里适用。 感谢您的帮助。 问题答案: 通常,快速应用程序需要指定适当的主体解析器中间件才能包含主体。 [编辑]

  • 我有一个简单的web应用程序,我从应用程序的前端获取json。我有一个对象,它包含很少的属性和一个对象数组。 它看起来像这样: 我通过使用。我用向我的PHP-Laravel后端发送了一个请求,我想对它进行解析。 这就是我所拥有的: 我尝试像这样获取json数据: 但它就是不起作用。我总是遇到一个错误,我试图访问对象上的,但它不存在。我也试过这样做: 但是我也有同样的问题。我做错了什么,我看到了关于