【问题标题】:Spring MVC Controller method mapping using form body使用表单主体的 Spring MVC Controller 方法映射
【发布时间】:2015-11-24 04:04:09
【问题描述】:

我正在构建一个小型应用程序,作为工作中某个第三方库的客户端。 API 声明需要 Webhook 来响应一些异步事件,但它们的所有方法都具有相同的签名,除了调用之间的 _method 字段不断变化。比如我有一个_method=pingmedia

我想在我的控制器上有单独的方法来响应这些方法中的每一种。如果应用程序允许我为每种方法指定不同的 URL,则可以很容易地为每种方法使用 Spring MVC 的 @RequestMapping。但我必须指定一个端点来接收所有呼叫。

有没有办法(例如使用 Spring 的 HttpMessageConverter 或类似的东西)根据请求正文映射不同的控制器方法?我已经尝试过@RequestBody@RequestParam,但似乎没有找到任何东西。

我真的,真的不想在前端控制器上使用一堆 case, switch 方法来根据我的 POST 数据附带的 _method 字段调度操作,所以我碰巧相信有人以前遇到过这个问题并聪明地解决了它。

非常感谢!

编辑1:提供源代码

@Controller
@RequestMapping("/webhooks")
public class WebhookController {

    @RequestMapping(method = RequestMethod.POST, params = {"_method=ping"})
    @ResponseBody
    public String ping(){
        return "pong";
    }

    @RequestMapping(method = RequestMethod.POST, params = {"_method=media"})
    @ResponseBody
    public String media(){
        return "media";
    }
}

这就是答案:

{
  "timestamp": 1440875190389,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.bind.UnsatisfiedServletRequestParameterException",
  "message": "Parameter conditions \"_method=ping\" not met for actual request parameters: ",
  "path": "/webhooks"
}

【问题讨论】:

    标签: spring spring-mvc jackson spring-boot


    【解决方案1】:

    好吧,我搞定了。答案有点棘手,所以如果有人遇到此类问题,我想在这里注册它。

    @Neil McGuigan 在他的评论中为我指出了正确的方向,但一开始我并没有注意。这里的罪魁祸首是我们的远程应用程序端的非常、非常、非常糟糕的 API 设计。

    _method是用于指定非标准HTTP动词的字段,例如PUTPATCHDELETETRACE等。此字段由HiddenHttpMethodFilter 过滤,HttpServletRequest 使用此“新”方法包装。您可以在 the file's source 了解它是如何工作的。

    因为我希望这个 _method 字段通过过滤器而不修改整个请求(并导致错误,因为在 `RequestMethod 上没有 pingmessage 这样的动词)我首先必须停用过滤器.这可以通过两种方式完成:

    1. 我可以阻止 Spring Boot 自动配置 Spring MVC,在加载 ApplicationContext 时跳过加载 WebMvcAutoConfiguration。正如您所想象的那样,这是一个 BIG、BIG、BIIIIG NO,因为things 可能会发生。

    2. 我可以使用FilterRegistrationBean 来禁用坏过滤器。非常简单直接,这是我选择使用的方法:

      @Bean
      public FilterRegistrationBean registration(HiddenHttpMethodFilter filter) {
          FilterRegistrationBean registration = new FilterRegistrationBean(filter);
          registration.setEnabled(false);
          return registration;
      }
      

    最后但同样重要的是,我决定给HiddenHttpMethodFilter 一个小扩展,以某种方式改进请求是如何通过的。 Java EE Spec 在 Servlet Spec Commandments 中非常清楚地指出:

    不应该改变你的请求。你必须尊重发件人(类似的东西)

    虽然我同意这一点,但为了我的精神稳定,我还是决定改变它。为此,我们可以使用简单的HttpServletRequestWrapper,覆盖选择的方法并使用包装部分过滤原始请求。我最终做了这样的事情:

    public class WhatoolsHiddenHttpMethodFilter extends OrderedHiddenHttpMethodFilter {
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            String paramValue = request.getParameter(OrderedHiddenHttpMethodFilter.DEFAULT_METHOD_PARAM);
            if("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH);
                List<String> whatoolsMethods = Arrays.asList("ping", "message", "carbon", "media", "media_carbon", "ack");
                if(whatoolsMethods.contains(paramValue)){
                    WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
                            .HttpMethodRequestWrapper(request, "POST", paramValue);
                    filterChain.doFilter(wrapper, response);
                } else {
                    WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
                            .HttpMethodRequestWrapper(request, method, null);
                    filterChain.doFilter(wrapper, response);
                }
            } else {
                filterChain.doFilter(request, response);
            }
        }
    
        private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
            private final String method;
    
            private final String whatoolsMethod;
    
            public HttpMethodRequestWrapper(HttpServletRequest request, String method, String whatoolsMethod) {
                super(request);
                this.method = method;
                this.whatoolsMethod = whatoolsMethod;
            }
    
            @Override
            public String getMethod() {
                return this.method;
            }
    
            @Override
            public String getHeader(String name) {
                if("x-whatools-method".equals(name)){
                    return this.whatoolsMethod;
                }
                return super.getHeader(name);
            }
    
            @Override
            public Enumeration<String> getHeaderNames() {
                List<String> names = Collections.list(super.getHeaderNames());
                if(this.whatoolsMethod != null){
                    names.add("x-whatools-method");
                }
                return Collections.enumeration(names);
            }
        }
    }
    

    所以,当标头在我的whatoolsMethods 列表中时,它的作用是用新的x-whatools-method 标头包装请求。有了这个,我可以轻松地使用@RequestMappingheaders 属性并将请求映射到正确的控制器方法。

    回到最初的问题,我几乎可以肯定(好吧,99,95% 应该完全确定,但我们不要冒险)@RequestMapping 上的params 属性仅适用于 GET URI 上的请求参数,例如http://foo.bar/?baz=42。它无法过滤请求正文中发送的参数。

    感谢尼尔的指导,即使很小!我希望这对某人有所帮助。

    【讨论】:

      【解决方案2】:

      您可以在请求映射中使用参数:

      @RequestMapping(value="/foo", params={"_method=ping"})

      假设这些是post参数

      params 确实适用于 POST,我向你保证

      这是我的控制器:

      @Controller
      @RequestMapping("/test1")
      public class ParamTestController {
      
          @RequestMapping(method = RequestMethod.POST)
          @ResponseBody String getA(){
              return "A";
          }
      
          @RequestMapping(method = RequestMethod.POST, params = {"b"})
          @ResponseBody String getB(){
              return "B";
          }
      }
      

      这是我的测试:

      【讨论】:

      • 这很奇怪,尼尔。那没有用。一开始我也是这么想的,但是params好像只带GET参数,不是吗?
      • 为我工作。你有 HiddenHttpMethodFilter 吗?
      • 我在主要问题上发布了结果。没用!
      猜你喜欢
      • 1970-01-01
      • 2023-02-01
      • 2017-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-11-19
      • 1970-01-01
      相关资源
      最近更新 更多