How to resolve MethodArgumentConversionNotSupportedException with MockMvc? - spring-mvc

I'm writing a unit test for a controller method that accepts a MultipartFile
and a custom object MessageAttachment. So far I can see that the MultipartFile is the correct format for the request but the MessageAttachment is not.
The parsing of the messageAttachment throws a server side 500 error with MethodArgumentConversionNotSupportedException.
It seem to be an issue with converting the MessageAttachment to a MockMultipartFile in the test. This is similar to the example shown here - https://stackoverflow.com/a/21805186
Question:
How can you resolve a MethodArgumentConversionNotSupportedException with MockMvc?
Controller method under test
#RequestMapping(value = "/", method = RequestMethod.POST, consumes = "multipart/form-data", produces = "application/json")
public ResponseEntity<MessageAttachment> handleFileUpload(#RequestParam(value = "file", required = true) MultipartFile file, #RequestParam(value = "messageAttachment") MessageAttachment messageAttachment) {
//do stuff with the file and attachment passed in..
MessageAttachment attachment = new MessageAttachment();
return ResponseEntity.accepted().header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getOriginalFilename() + "\"").body(attachment);
}
MockMvc Test
#Test
public void shouldSaveUploadedFile() throws Exception {
// Given
ObjectMapper mapper = new ObjectMapper();
MessageAttachment messageAttachment = new MessageAttachment();
messageAttachment.setTimestamp(new Date());
MockMultipartFile multipartFile = new MockMultipartFile("file", "test.txt", "text/plain",
"Spring Framework".getBytes());
//Mapping the msgAttachment to a MockMultipartFile HERE
MockMultipartFile msgAttachment = new MockMultipartFile("messageAttachment", "","application/json",
mapper.writeValueAsString(messageAttachment).getBytes());
// When
this.mockMvc.perform(MockMvcRequestBuilders.multipart("/media/")
.file(multipartFile)
.file(msgAttachment)).andDo(MockMvcResultHandlers.print());
}
Console output of MockMvcResultHandlers.print()
MockHttpServletRequest:
HTTP Method = POST
Request URI = /media/
Parameters = {}
Headers = {Content-Type=[multipart/form-data]}
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.fizz.buzz.fizzapi.controller.MediaUploadController
Method = public org.springframework.http.ResponseEntity<com.fizz.buzz.fizzapi.model.MessageAttachment> com.fizz.buzz.fizzapi.controller.MediaUploadController.handleFileUpload(org.springframework.web.multipart.Mu
ltipartFile,com.fizz.buzz.fizzapi.model.MessageAttachment,javax.servlet.http.HttpServletRequest)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException
ModelAndView:
View name = null
View = null
Model = null

You'll want to use #RequestPart instead of #RequestParam for the part of the request that is application/json. The javadoc for #RequestPart states
Supported method argument types include MultipartFile in conjunction
with Spring's MultipartResolver abstraction,
javax.servlet.http.Part in conjunction with Servlet 3.0 multipart
requests, or otherwise for any other method argument, the content of
the part is passed through an HttpMessageConverter taking into
consideration the 'Content-Type' header of the request part. This is
analogous to what #RequestBody does to resolve an argument based on
the content of a non-multipart regular request.
Note that #RequestParam annotation can also be used to associate the
part of a "multipart/form-data" request with a method argument
supporting the same method argument types. The main difference is that
when the method argument is not a String, #RequestParam relies on type
conversion via a registered Converter or PropertyEditor while
#RequestPart relies on HttpMessageConverters taking into consideration
the 'Content-Type' header of the request part. #RequestParam is likely
to be used with name-value form fields while #RequestPart is likely to
be used with parts containing more complex content (e.g. JSON, XML).
Presumably, you haven't registered a Converter, nor a PropertyEditor, to parse the content of that part, whereas an HttpMessageConverter for JSON is automatically registered (depending on your Spring MVC/Boot version) if you have Jackson on the classpath.

Related

Minimal API and XML formatters

Trying out minimal APIs in .NET 6 and can't make it work with XML content type. If I use standard controllers, using .AddXmlSerializerFormatters() extension does the job:
builder.Services.AddControllers().AddXmlSerializerFormatters();
But when I switch from controller to .MapPost(..), I start getting 415 HTTP responses.
app.MapPost("/endpoint", ([FromBody] Request request) => {})
.Accepts<Request>("text/xml");
HTTP response: 415 Microsoft.AspNetCore.Http.BadHttpRequestException: Expected a
supported JSON media type but got "text/xml"
Is there any other way I can declare XML formatters that will work with minimal APIs?
As suggested by the post linked by guru-stron, it's possible to pass XML documents by implementing your own wrapping model that provides a BindAsync method.
internal sealed class XDocumentModel
{
public XDocumentModel(XDocument document) => Document = document;
public XDocument Document { get; init; }
public static async ValueTask<XDocumentModel?> BindAsync(HttpContext context, ParameterInfo parameter)
{
if (!context.Request.HasXmlContentType())
throw new BadHttpRequestException(
message: "Request content type was not a recognized Xml content type.",
StatusCodes.Status415UnsupportedMediaType);
return new XDocumentModel(await XDocument.LoadAsync(context.Request.Body, LoadOptions.None, CancellationToken.None));
}
}
I added a extension method to HttpRequest for convenient Content-Type validation.
internal static class HttpRequestXmlExtensions
{
public static bool HasXmlContentType(this HttpRequest request)
=> request.Headers.TryGetValue("Content-Type", out var contentType)
&& string.Equals(contentType, "application/xml", StringComparison.InvariantCulture);
}
You can then use the model directly as a paramter by your minimal API endpoint.
app.MapGet("/xml-test", (XDocumentModel model) =>
{
// model.Document <- your passed xml Document
return Results.Ok(new { Value = model.Document.ToString() });
})
Some final thoughts: This implementation enables you to pass a generic XML document to the endpoint. However, if you expect a certain document structure, you could implement this by making the XDocumentModel expect a generic type parameter and extracting this type's properties from the XDocument instance.
I did it this way:
app.MapPost("/endpoint", (HttpContext c) =>
{
var reader = new StreamReader(c.Request.Body);
var xml = reader.ReadToEndAsync().Result;
// You can do with your xml string whatever you want
return Results.Ok();
}).Accepts<HttpRequest>("application/xml");

Consume Html response using restTemplate

I want to consume html response using restTemplate and rerun that response to my html so i can add those content to my html page but getting below error tried so many alternatives but not luck
I want to consume every type of response and return that as it is to my html/jsp via ajax and render that content in a div.
HTML (ajax call) --- Spring MVC (rest call to 3rd party) --- application returns html
Code
#RequestMapping(value = "/xyz-service/**", method = {RequestMethod.GET, RequestMethod.PUT},produces="application/json;charset=UTF-8")
public Object mirrorRest(HttpServletRequest request) {
String url = request.getRequestURI();
return restTemplate.getForObject("http://xyz-service:8080"+url , String.class);
}
I am able to invoke my serive method that retuning html as respose but getting error
"Could not extract response: no suitable HttpMessageConverter found for response type [class java.lang.String] and content type [text/html;charset=UTF-8]"
]
The exception seem to have occurred because your request was missing the header parameters.
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + apikey);
headers.set("Charset", "utf-8");
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Request> entity = new HttpEntity<Request>(
req, headers); //incase if your request have a request body
try {
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); //if no request body you could simply use headers parameter directly
logger.info(response.toString());
return response.getBody().toString();
} catch (HttpStatusCodeException exception) {
logger.info("API failed"+exception);
return null;
}
No you can't. An HTML page is not a json object: REST template is designed to consume RestServices.
You should use a URLConnection from jdk

how can return json using response.senderror

In my app,I use springMVC and tomcat,my controller return object,but when something wrong,I only want return some string message with content tye json,so I use response.error, but it not work,the return is a html.
my controller:
#RequestMapping(value = "{id}/{name}" ,method=RequestMethod.POST,produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody UserBean login(#PathVariable String id,#PathVariable("name") String userName,
#RequestHeader(value = "User-Agent") String user_agen,
#CookieValue(required = false) Cookie userId,
HttpServletRequest request,HttpServletResponse response,#RequestBody UserBean entity
) throws IOException {
System.out.println("dsdsd");
System.out.print(userName);
response.setContentType( MediaType.APPLICATION_JSON_VALUE);
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "somethind wrong");
return null;
According to the Javadoc for the HttpServletReponse#sendError method:
Sends an error response to the client using the specified status. The
server defaults to creating the response to look like an
HTML-formatted server error page containing the specified message,
setting the content type to "text/html", leaving cookies and other
headers unmodified...
So sendError will generate an HTML error page using the message that you supplied and will override the content type to text/html.
Since the client end is expecting a JSON response, you may be better to manually set the response code and the message yourself using fields on your UserBean - assuming it can support it. That will then be serialized to a JSON response that your clientside Javascript can evaluate.
#RequestMapping(value = "{id}/{name}" ,method=RequestMethod.POST,produces = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody UserBean login(#PathVariable String id,#PathVariable("name") String userName,
#RequestHeader(value = "User-Agent") String user_agen,
#CookieValue(required = false) Cookie userId,
HttpServletRequest request,HttpServletResponse response,#RequestBody UserBean entity
) throws IOException {
System.out.println("dsdsd");
System.out.print(userName);
response.setContentType( MediaType.APPLICATION_JSON_VALUE);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
UserBean userBean = new UserBean();
userBean.setError("something wrong"); // For the message
return userBean;
There is also the option of using the Tomcat property org.apache.coyote. USE_CUSTOM_STATUS_MSG_IN_HEADER which will place the message into a custom response header. See this post and the Tomcat docs for more info.

modify HTTP request URI and HTTP request method with a CXF interceptor

I want to modify HTTP request URI and HTTP request method using a CXF interceptor in a HTTP client.
I have developed something like this:
public class MyInterceptor extends AbstractPhaseInterceptor<Message> {
public MyInterceptor() {
super(Phase.PRE_PROTOCOL);
}
public void handleMessage(Message message) {
// this returns me correct path and method
// String path = (String) message.getExchange().getOutMessage().get(Message.REQUEST_URI);
// String method = (String) message.getExchange().getOutMessage().get(Message.HTTP_REQUEST_METHOD);
// this does not work as expected
String path = (String) message.get(Message.REQUEST_URI);
String method = (String) message.get(Message.HTTP_REQUEST_METHOD);
// do things here
}
}
Why do need I to use exchange/OutMessage to obtain data about current message and I can not use message directly?
How can I edit both values? I tried using message.put(<key>, <value>) and the same with exchange/OutMessage, but nothing is modified.
Coming to the path, you'd always get that value as null, I believe.
You can try following code, to get the actual value of your uri:
String requestURI = (String) message.get(Message.class.getName() + ".REQUEST_URI");

How to set content type dynamically in a Spring MVC controller (depending on presence of request param)?

I have a REST API that until now always returned JSONP (JSON data wrapped in whatever function call client wanted):
static final String JAVASCRIPT = "application/javascript;charset=UTF-8";
#RequestMapping(value = "/matches", produces = JAVASCRIPT)
#ResponseBody
public String matches(#RequestParam String callback) {
String json = jsonService.getCachedJson("/matches");
return toJsonp(callback, json);
}
Now, things have changed so that I need to return either JSON or JSONP: if client provides a callback function name, we return JSONP and otherwise pure JSON.
With regards to content type, I'd like to be as correct as possible and use application/json for JSON and application/javascript for JSONP.
So, something like this:
#RequestMapping(value = "/matches")
#ResponseBody
public String matches(#RequestParam(required = false) String callback) {
String json = jsonService.getCachedJson("/matches");
// TODO: if callback == null, set content type to "application/json",
// otherwise to "application/javascript"
return jsonOrJsonp(callback, json);
}
String jsonOrJsonp(String callback, String json) {
return Strings.isNullOrEmpty(callback) ? json : toJsonP(callback, json);
}
Looks like I can no longer use produces attribute of #RequestMapping. What's the simplest way to set content type with Spring MVC in the scenario above?
I'd like to avoid defining HttpMessageConverters (or other Spring hassle) or changing the method return type, if at all possible! And obviously I wouldn't like duplicated method declarations where produces value is the only significant difference. What I'm looking for is minimal changes to the above code.
Latest Spring (3.2.3).
Have you tried just using two request handler methods?
#RequestMapping(value = "/matches", produces = JAVASCRIPT, params="callback")
#ResponseBody
public String Jsonp(#RequestParam String callback) {
return toJsonp(callback, jsonService.getCachedJson("/matches"));
}
#RequestMapping(value = "/matches", produces = JSON)
#ResponseBody
public String json() {
return toJson(jsonService.getCachedJson("/matches"));
}
The first method with the params parameter will only be mapped to requests where the callback param is present.

Resources