Spring 5 - Read JSON or MultipartFile - spring-mvc

I ran into what looks like a really simple problem. I have an http endpoint which should accept either JSON body or uploaded file.
Here is definition of controller method:
#PostMapping(value = "/api/endpoint")
public CompletableFuture<ResponseEntity<Void>> createResource(
#RequestParam(name = "file", required = false) MultipartFile file,
#RequestBody(required = false) Command command){
}
Command is a POJO class with Jackson annotations for deserialisation.
When I pass JSON body with Content-Type: application/json, it works fine. But when I pass file with Content-Type: multipart/form-data, I get 415 Unsupported Media Type.
Here is raw http request when passing just JSON body.
POST /api/devices?= HTTP/1.1
Host: localhost:8080
Content-Type: application/json
cache-control: no-cache
{"foo": "bar"}------WebKitFormBoundary7MA4YWxkTrZu0gW--
Any ideas what is causing this, thanks.

Try the below code.
#PostMapping(value = "/api/endpoint",consumes = {"multipart/form-data"})
public CompletableFuture<ResponseEntity<Void>> createResource(
#RequestPart("file") MultipartFile file,
#RequestPart Command command){
}

You might need two separate methods with different parameters and annotations. Which, of course, can each just call a shared internal method for their common behaviors.

Related

spring mvc Always JSON repsonse

I'm trying to learn Spring MVC and i'm stuck at this program behaving weirdly can any one please help.
#Controller
public class GreetingController {
#RequestMapping(value="/greeting", consumes={"application/json", "application/xml"},produces = {"application/json","application/xml"}, headers = "Content-type=*/*")
public #ResponseBody Greeting greeting(
#RequestParam(value="name", required=false, defaultValue="I'm default") String name) {
Greeting obj = new Greeting("1",name);
Address address = new Address("CA, US");
obj.setAddress(address);
return obj;
}
}
Greeting and Address are just POJO's.
here is the output from 'curl' i.e irrespective of Content-type output is in JSON format.
curl --header "Content-type: application/xml" http://host.com:8080/javamvc/greeting
{"id":1,"content":"Hello, I'm default!","address":{"addr":"CA, US"}}
curl --header "Content-type: application/json" http://host.com:8080/javamvc/greeting
{"id":1,"content":"Hello, I'm default!","address":{"addr":"CA, US"}}
and then when i use 'RestClient' from mozilla i get output as xml always as irrespective of my Content-type=application/json or Content-type=application/xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><greeting><id>1</id><content>Hello, I'm default!</content><address><addr>CA, US</addr></address></greeting>
Can anyone please help?
In your curl requests, you are specifying the Content-Type of the request body, not the content type you are expecting in the response body.
For that, you need to specify the Accept header.
Spring is always producing application/json, because it's the first in the list of produces values.
You'll have to be more specific about what you are doing with Mozilla, but it seems like it's requesting with the Accept header being application/xml.

spring-mvc The request sent by the client was syntactically incorrect. for json post

I am trying to build a springmvc app. On one of the action I want it to take a POST of JSON content. I am sending the following values:
POST /TestResults/create.json HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache
[ { "test_name": "value1", "test_time": "111"} ]
However, when I send it I am getting the following error:
The request sent by the client was syntactically incorrect.
My action looks like this:
#RequestMapping(value="/create", method=RequestMethod.POST, consumes = "application/json")
#ResponseBody
public ModelAndView createTest(#RequestBody TestResult test_result) {
When I change the action to bepublic ModelAndView createTest(#RequestBody String test_result) { the action does succeed but I am using "String" at that time.
Is it possible to know what I might be doing incorrectly?
The JSON you are receiving is a JSON array. A JSON array unless you have a custom deserializer cannot be mapped to a type like TestResult, unless it's some subtype of Collection. You'll want to use something like
public ModelAndView createTest(#RequestBody List<TestResult> testResults) {
For #RequestBody String it works because all Spring has to do is read from the request body and convert the bytes into a String.

Apache Camel: How to get parameters sent to a Camel Servlet using a HTTP POST request by a smart way?

I'm writing a route that may receive a TXT file with some parameters using a Camel Servlet Component.
According to the Camel documentation I'm supposed to have the parameter set in the header of the message.
But in the the case of a HTTP POST it seems that the header is not populated as expected.
I found that the body contains all the request, the file and the parameters.
Here is an example of content :
------WebKitFormBoundaryC9GDMXt2OAHARCZj
Content-Disposition: form-data; name="upfile"; filename="user.txt"
Content-Type: text/plain
hello world...
...
------WebKitFormBoundaryC9GDMXt2OAHARCZj
Content-Disposition: form-data; name="userdata" testtest
------WebKitFormBoundaryC9GDMXt2OAHARCZj
Content-Disposition: form-data; name="id" 12344
------WebKitFormBoundaryC9GDMXt2OAHARCZj--
Does someone know a way to get the parameters by a smart way?
Do I have to make my own parser in my processor?
ericg,
If you use HTTP POST, the parameters are indeed set in the message body. Maybe you should implements a processor in your route to get the parameters from the body and set them in the exchange headers if it's what you need
Best regards,
In the camel we can get HTTP post parameters, I am not sure to get the Content-Disposition. if we want to get Content-Disposition, we can write own processor method.
But sure can get the parameters, your mentioned body content is your uploaded files content.
from(HTTP_INBOUND_URL)
.log("The message : ${in.body}")
.doTry()
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
// TODO Auto-generated method stub
String templateId = exchange.getIn().getHeader("templateId",
String.class);
}
})
Where templateId is your HTTP post parameter.

WebAPI not returning XML even with correct Accepts header

I'm using ASP.NET WebAPI RC, and hosting an API controller with nothing fancy about it. Everything works fine with JSON, but I'm testing requesting different formats using the Accepts header, and that's where I'm having trouble.
I'm using jQuery to issue an AJAX request, and setting the 'dataType' parameter of the request. This correctly sets the appropriate Accept header as you will see below.
$.ajax({
type: method,
url: url,
dataType: "xml",
data: data || null,
success: function (data) {
// omitted
}
});
Here is a save of the fiddler request/response. As you can see the Accept header says application/xml, but WebAPI returned JSON. I have also tried manually setting the Accept header to just "application/xml" (so it doesn't have the text/html stuff as well), but to no avail.
What the heck am I missing? (note: I've snipped some confidential info in the data but didn't tweak it otherwise)
GET http://localhost/insp**snip**6716 HTTP/1.1
Host: localhost
Connection: keep-alive
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.60 Safari/537.1
Accept: application/xml, text/xml, */*; q=0.01
Referer: http://localhost/inspector/api/test?
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-US,en;q=0.8
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: m=34e2:|2c69:t|47ba:t|4e99:t; .INSPECTOR3COOKIE=08BA683091E2A457B1832E9B*snip*F911D9ED97076
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/7.5
X-AspNet-Version: 4.0.30319
Persistent-Auth: true
X-Powered-By: ASP.NET
Date: Fri, 03 Aug 2012 22:27:42 GMT
Content-Length: 1816
{"Id":2416716,"ProjectId":36,"Url":"http://ins *snip, but obviously this is JSON not XML *
I'd like to point out I'm not tweaking any formatters in AppStart or anything, so as far as I understand, the JSON and XML formatters should be enabled by default.
UPDATE: I figured it out -- check my own answer below
I figured it out!
I had this in my AppStart, because I wanted the Xml serializer not the DataContract serializer:
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
HOWEVER... apparently there is something about my model that makes the Xml Serializer think it can't serialize it. I am guessing that is causing WebAPI to decide to use the JSON formatter instead.
It's completely non-intuitive that this harmless looking setting could actually affect which formatter is used. Hope the WebAPI people see this :)
Some kind of tool that let you understand the inputs and outputs of content negotiation process so you can debug issues like this would be nice.
I had the same issue but fixed it by adding default constructors to all the models that I was returning.
The XML serializer creates blank model objects and then populates it via the setters on the properties. If the setters are protected or private then that property will not get serialized either
The current answers in this thread already call out a lot of the reasons but just to summarize, the XmlSerializer only supports a limited number of types.
When looking for the "best" formatter, the DefaultContentNegotiator, as correctly described by AASoft, asks each of the formatters whether they can support a particular type. It then matches those formatters against the accept headers in the request.
If it doesn't find any match based on the accept headers then it picks the first that can serialize the type, in this case the JSON formatter. However, you can configure the DefaultContentNegotiator to instead of returning a default format then return a 406 None Accepted status code. This indicates to the client that no matching representation could be found and instead of sending data that the client may not be able to use it generates an error response.
Setting this option is described in the blog "ASP.NET Web API Updates – May 14" [1] under the section "Content Negotiation Improvements".
Hope this helps,
Henrik
[1] http://blogs.msdn.com/b/henrikn/archive/2012/05/14/asp-net-web-api-updates-may-14.aspx
The answer has already been provided but I thought I'd put my findings so that it might be helpful to anyone coming later.
The culprit was IEnumerable. For example, returning object of Class Album containing IEnumerable and never getting XML return - only JSON.
I used
GlobalConfiguration.Configuration.Formatters.XmlFormatter.UseXmlSerializer = true;
in the Global.asax as well. Which is actually necessary for xml returns. Still I wasn't getting XML back.
Then I Changed the IEnumerable to List and it worked fine. Looks like the XML Formatter in Web API cannot process the IEnumerable in return objects.
Hope this helps.
Just as a followup to this. We had this problem when we had a List of objects in our return model but the object in the list didn't have a parameterless constructor. Our code looked like this:
public class ReturnClass
{
public int Value { get; set; }
public List<OtherClass> ListOfThings { get; set; }
}
public class OtherClass
{
public int OtherValue { get; set; }
public OtherClass(OtherObject o)
{
this.OtherValue = o.OtherValue;
}
}
We simply had to add a parameterless constructor for the SubClass object.
public class ReturnClass
{
public int Value { get; set; }
public List<OtherClass> ListOfThings { get; set; }
}
public class OtherClass
{
public int OtherValue { get; set; }
public OtherClass(OtherObject o)
{
this.OtherValue = o.OtherValue;
}
public OtherClass()
{
}
}
Be careful using nullable int for any of the properties you may be serializing. A nullable int with config.Formatters.XmlFormatter.UseXmlSerializer = true set will cause Web API to return JSON no matter what your accept header says
I just want to add one more reason this might happen, properties with internal get or set. The kind that are generated by VS when you are adding properties to a class by pressing Ctrl-.
like public string Foo {get; internal set;}

ASP.NET MVC ignoring Content-Length?

I've been having some problems with missing post data in ASP.NET MVC which has lead me to investigate how ASP.NET MVC deals with invalid content lengths. I had presumed that a post with a invalid content length should be ignored by MVC.NET but this doesn't seem to be the case.
As an example, try creating a new ASP.NET MVC 2 web application and add this action to the HomeController:
public ActionResult Test(int userID, string text)
{
return Content("UserID = " + userID + " Text = " + text);
}
Try creating a simple form that posts to the above action, run fiddler and (using "Request Builder") modify the raw data so that some of the form data is missing (e.g. remove the text parameter). Before executing the request, remember to un-tick the "Fix Content-Length header" checkbox under the Request Builder options then set a break point on the code above and execute the custom http request.
I find that the request takes a lot longer than normal (30 seconds or so) but to my amazement is still processed by the controllers action. Does anyone know if this is expected behavior and, if so, what would you recommend to safeguard against invalid content-lengths?
ASP.NET does not ignore the Content-Length request header. Consider the following controller action as an example which simply echoes back the foo parameter:
[HttpPost]
public ActionResult Index(string foo)
{
return Content(foo, "text/plain");
}
Now let's make a valid POST request to it:
using (var client = new TcpClient("127.0.0.1", 2555))
using (var stream = client.GetStream())
using (var writer = new StreamWriter(stream))
using (var reader = new StreamReader(stream))
{
writer.Write(
#"POST /home/index HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:2555
Content-Length: 10
Connection: close
foo=foobar");
writer.Flush();
Console.WriteLine(reader.ReadToEnd());
}
As expected this prints the response HTTP headers (which are not important) and in the body we have foobar. Now try reducing the Content-Length header of the request:
POST /home/index HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: localhost:2555
Content-Length: 5
Connection: close
foo=foobar
Which returns a single f in the response body. So as you can see an invalid HTTP request could lead to incorrect parsing of the parameters.

Resources