使用表单体的Spring MVC Controller方法映射 [英] Spring MVC Controller method mapping using form body
问题描述
我正在构建一个小型应用程序,作为此处工作的某些第三方库的客户端。 API声明需要 Webhook
来响应某些异步事件,但除了更改 _method 所有方法外,所有方法都具有相同的签名code>调用之间的字段。例如,我有一个
_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 Webhook
is 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
或消息$这样的动词c $ c> 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 ping
or message
on `RequestMethod) I firstly had to deactivate the filter. This could be done by two ways:
-
我可以阻止Spring Boot自动配置Spring MVC,跳过
加载
。你可以想象这是一个很大,很大,BIIIIG NO ,因为,好吧,东西可能会发生。ApplicationContext
时加载WebMvcAutoConfiguration
I could stop Spring Boot from automagically configuring Spring MVC, skipping
WebMvcAutoConfiguration
from being loaded when theApplicationContext
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
仅适用于GET URI上的请求参数,例如上的
属性@RequestMapping 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屋!