使用表单体的Spring MVC Controller方法映射 [英] Spring MVC Controller method mapping using form body

查看:2462
本文介绍了使用表单体的Spring MVC Controller方法映射的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个小型应用程序,作为此处工作的某些第三方库的客户端。 API声明需要 Webhook 来响应某些异步事件,但除了更改 _method 调用之间的字段。例如,我有一个 _method = ping media 等等。

I'm building a small application to serve as a client for some third party library here at work. The API states that a Webhookis needed to respond some asynchronous events, but all their methods have the very same signature, apart from a changing _method field between the calls. For example, I have a _method = ping, media, etc.

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

I'd like to have separate methods on my controller to respond for each one of these methods. If the app allowed me to specify different URLs for each method it would be easy to use Spring MVC's @RequestMapping for each one of them. But I have to specify a single endpoint to receive all calls.

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

Is there a way (for example using Spring's HttpMessageConverter or something like that) to map different controller methods based on what the Request Body is? I've already tried with @RequestBody, @RequestParam but didn't seem to find anything.

我真的,真的不想使用一堆案例,切换方法前端控制器根据我的POST数据附带的 _method 字段调度操作,所以我碰巧认为有人之前遇到过这个问题并且智能地解决了这个问题。

I really, really didn't want to use a bunch of case, switch methods on a front controller to dispatch actions based on my _method field that comes with my POST data, so I happen to believe someone had this problem before and solved it intelligently.

非常感谢!

@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"
}


推荐答案

是的,我搞定了。答案有点棘手所以如果有人有这样的问题,我想在这里注册。

Right, I got it working. The answer is a bit tricky so I wanted to register it here should anyone have such problem.

@Neil McGuigan在他的评论中向我指出了正确的方向,但我没有'首先要注意。这里的主要罪魁祸首是我们远程应用程序端的非常非常非常错误的API设计。

@Neil McGuigan pointed me on the right direction on his comment but I didn't pay attention at first. The main culprit here is a very, very, very bad API design on our remote application's side.

_method 是一个用于指定非标准HTTP谓词的字段,例如 PUT PATCH DELETE TRACE 等等。此字段由 HiddenHttpMethodFilter 过滤, HttpServletRequest 包含此新方法。你可以在文件来源如何运作。

_method is a field used to specify non-standard HTTP verbs such as PUT, PATCH, DELETE, TRACE and so on. This field is filtered by HiddenHttpMethodFilter and the HttpServletRequest is wrapped with this 'new' method. You can see at the file's source how it works.

我想要这个 _method 字段来通过过滤器而不修改整个请求(并导致错误,因为没有像 ping 消息 on`RequestMethod)我首先必须停用过滤器。这可以通过两种方式完成:

As I wanted this _method field to get thru the filter without modifying the whole request (and causing the errors because there's no such verb as pingor message on `RequestMethod) I firstly had to deactivate the filter. This could be done by two ways:


  1. 我可以阻止Spring Boot自动配置Spring MVC,跳过加载 ApplicationContext 时加载WebMvcAutoConfiguration 。你可以想象这是一个很大,很大,BIIIIG NO ,因为,好吧,东西可能会发生。

  1. I could stop Spring Boot from automagically configuring Spring MVC, skipping WebMvcAutoConfiguration from being loaded when the ApplicationContext was loaded. As you can imagine this is a BIG, BIG, BIIIIG NO because, well, things could happen.

我可以使用 FilterRegistrationBean 来禁用错误过滤器。非常简单明了,这是我选择使用的方法:

I could use a FilterRegistrationBean to disable the bad filter. Pretty simple and straightforward, this was the method I chose to use:

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


最后但并非最不重要,我决定给 HiddenHttpMethodFilter 一个小扩展,以某种方式改进请求是如何通过的。 Java EE规范在Servlet规范命令中非常清楚,它指出:

Last but not least, I decided to give HiddenHttpMethodFilter a little extension to somehow improve how the requests were getting thru. The Java EE Spec is pretty clear on the Servlet Spec Commandments where it states:


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

Thou should not alter your request on your side. You must respect the sender (something like that)

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

Though I agree with this, for the sake of my mental stability I decided to alter it anyway. To achieve this, we can use a simple HttpServletRequestWrapper, override the chosen methods and filter the original request with the wrapped part. I ended up doing something like this:

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 标头包装请求。有了这个,我可以轻松地使用 @RequestMapping headers 属性,并将请求映射到正确的控制器方法。

So, what this does is to wrap the request with a new x-whatools-method header when the header is in my whatoolsMethods list. With this, I can easily use @RequestMapping's headers property and map the requests to the correct controller methdods.

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

Back to the initial question, I'm almost sure (well, 99,95% should be completely sure but let's not risk it) the params property on @RequestMapping works only for request parameters on GET URIs, e.g http://foo.bar/?baz=42. It won't work filtering parameters sent on the request's body.

感谢Neil的指导,即使很小!我希望这有助于某人。

Thanks Neil for your guidance, even if small! I hope this helps someone.

这篇关于使用表单体的Spring MVC Controller方法映射的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆