从 Spring 的 rest 控制器同时支持 application/json 和 application/x-www-form-urlencoded
Posted
技术标签:
【中文标题】从 Spring 的 rest 控制器同时支持 application/json 和 application/x-www-form-urlencoded【英文标题】:Supporting application/json and application/x-www-form-urlencoded simultaneously from Spring's rest controller 【发布时间】:2018-06-04 02:26:09 【问题描述】:我正在编写一个 REST 端点,它需要同时支持 application/x-www-form-urlencoded 和 application/json 作为请求正文。我做了以下配置,
@RequestMapping(method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE , consumes =
MediaType.APPLICATION_FORM_URLENCODED_VALUE, MediaType.APPLICATION_JSON_VALUE , path = Constants.ACCESS_TOKEN_V1_ENDPOINT)
public OAuth2Authorization createAccessTokenPost(
@RequestBody(required = false) MultiValueMap<String, String> paramMap) ..
虽然它单独支持 application/x-www-form-urlencoded 或 application/json(当我从 consumes = 中注释掉一种内容类型时),但它不能同时支持两者。有什么想法吗?
【问题讨论】:
您在日志中看到了什么异常? 您好,感谢您的回复。 org.springframework.web.HttpMediaTypeNotSupportedException:org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:237) 不支持内容类型'application/json;charset=UTF-8' .springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:150) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor。 Accept和Content-Type请求头分别携带什么值? 感谢您的回复。接受:application/x-www-form-urlencoded;内容类型:application/x-www-form-urlencoded 请查看以下链接:***.com/questions/42462450/… 【参考方案1】:所以默认情况下 RestControllers 可以相当容易地处理 application/json
并且可以从 @RequestBody
注释参数创建请求 pojo,而 application/x-www-form-urlencoded
需要更多的工作。一个解决方案可能是创建一个额外的 RestController 方法,该方法具有相同的映射端点来处理传入的不同类型的请求(application/json, application/x-www-form-urlencoded
等)。这是因为application/x-www-form-urlencoded
端点需要使用@RequestParam
而不是@RequestBody
注释(用于application/json
)。
例如,如果我想为 /emp
托管一个将 application/json
或 application/x-www-form-urlencoded
作为 Content-Types 的 POST 端点并使用服务来做某事,我可以创建这样的重载方法
@Autowired
private EmpService empService;
@PostMapping(path = "/emp", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity createEmp(final @RequestHeader(value = "Authorization", required = false) String authorizationHeader,
final @RequestParam Map<String, String> map)
//After receiving a FORM URLENCODED request, change it to your desired request pojo with ObjectMapper
final ObjectMapper mapper = new ObjectMapper();
final TokenRequest tokenRequest = mapper.convertValue(map, CreateEmpRequest.class);
return empService.create(authorizationHeader, createEmpRequest);
@PostMapping(path = "/emp", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity createEmp(final @RequestHeader(value = "Authorization", required = false) String authorizationHeader,
final @RequestBody CreateEmpRequest createEmpRequest)
//Receieved a JSON request, the @RequestBody Annotation can handle turning the body of the request into a request pojo without extra lines of code
return empService.create(authorizationHeader, createEmpRequest);
【讨论】:
【参考方案2】:根据我的发现,spring 不支持同时支持内容类型“application/x-www-form-urlencoded
”、“application/json
”和“application/xml
”。
我想到的原因:Spring 通过解析 JSON 和 XML 类型并将其注入标有 @RequestBody
spring 注释的 java pojo 来处理它们。但是,x-www-form-urlencoded
必须注入到标有@RequestBody
的MultiValueMap<>
对象中。不会同时支持标有@RequestBody
的两种不同的java 类型,因为spring 可能不知道在哪里注入payload。
可行的解决方案:
“application/x-www-form-urlencoded
”可以在 API 中得到支持。也就是可以使用@RequestBody注解注入到spring的MultiValueMap<>
中。
为了在同一方法上支持 JSON 和 XML,我们可以利用 servlet 规范和基于它们构建的 spring 类将有效负载提取为流。
示例代码:
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.MultiValueMap;
// usual REST service class
@Autowired
private MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter;
@Autowired
private Jaxb2RootElementHttpMessageConverter jaxb2RootElementHttpMessageConverter;
public ResponseEntity<Object> authorizationRequestPost(HttpServletResponse response, HttpServletRequest request,@RequestBody(required = false) MultiValueMap<String, String> parameters)
// this MultiValueMap<String,String> will contain key value pairs of "application/x-www-form-urlencoded" parameters.
// payload object to be populated
Authorization authorization = null;
HttpInputMessage inputMessage = new ServletServerHttpRequest(request)
@Override
public InputStream getBody() throws IOException
return request.getInputStream();
;
if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE))
authorization = (Authorization) mappingJackson2HttpMessageConverter.read(Authorization.class, inputMessage);
else if (request.getContentType().equals(MediaType.APPLICATION_XML_VALUE))
authorization = (Authorization)jaxb2RootElementHttpMessageConverter.read(Authorization.class, inputMessage);
else
// extract values from MultiValueMap<String,String> and populate Authorization
// remaining method instructions
请注意,使用这种方法可以支持任何自定义数据类型/标记/格式。 Spring的org.springframework.http.converter.HttpMessageConverter<>
可以扩展写解析逻辑。
另一种可能的方法可能是执行相同逻辑的 AOP 样式解决方案:通过从 HttpServlet
输入流中提取有效负载并注入到有效负载对象中来解析有效负载。
第三种方法是编写一个过滤器来执行逻辑。
【讨论】:
【参考方案3】:不可能使用单个 Spring 控制器方法同时处理 application/json
和 application/x-www-form-urlencoded
请求。
Spring通过ServletRequest.getParameter(java.lang.String)获取application/x-www-form-urlencoded
数据,文档说:
对于 HTTP servlet,参数包含在查询字符串或发布的表单数据中。
如果参数数据是在请求正文中发送的,例如发生在 HTTP POST 请求中,那么直接通过 getInputStream() 或 getReader() 读取正文可能会干扰该方法的执行。
所以,如果你的方法参数被注解@RequestBody
,Spring会读取请求体并解析到方法参数对象。但是application/x-www-form-urlencoded
会导致Spring 通过调用ServletRequest.getParameter(java.lang.String) 来填充参数对象。
【讨论】:
【参考方案4】:只是为了做到这一点,上面的answer 不起作用,因为即使您不使用@RequestBody
注释MultiValueMap
,它也会始终检查contentType==MediaType.APPLICATION_FORM_URLENCODED_VALUE
,在其余情况下再次解析为@987654325 @。
【讨论】:
以上是关于从 Spring 的 rest 控制器同时支持 application/json 和 application/x-www-form-urlencoded的主要内容,如果未能解决你的问题,请参考以下文章
仅使用 Spring Security 自定义令牌无状态保护 REST 控制器,同时保持常规状态完整 Web 登录正常工作
Spring Webflux注解的rest控制器不支持ServerHttpRequest作为方法参数:java.lang.NoSuchMethodException