Passing Parameters in Spring MVC 4.0 - spring-mvc

I have read multiple posts, and have yet to find an answer to my issue. I have the following controller using Spring 4.
Because of the examples I have found, I have gleaned how to use my WellTest POJO to automatically bind via the #RequestParam. I can tell you that many times the "Experts" assume way too much of what they themselves do easily every day when explaining this stuff, and I am not a newby by any means. However, I don't work services very much and in Spring's case, sometimes your only as good as your last version you used. Mine was around 2.5 or so.
Here is my controller method - it's just for testing the passing of Variables.
#RequestMapping(value = "/update/{WellTestID}", method = RequestMethod.POST,headers="Accept=application/json")
public String setWellTestByWellTestID( #RequestParam(defaultValue = "99999") Long wellTestID,
#RequestParam(defaultValue = "99999") Long wellFacilityID,
#RequestParam(defaultValue = "101") int operatorPersonID,
#RequestParam(defaultValue = "67845") int durationSeconds,
#RequestParam(defaultValue = "100") long grossRate,
#RequestParam(defaultValue = "400") long waterCut,
#RequestParam(defaultValue = "30") long percentGas,
#RequestParam(defaultValue = "500") long temperature,
#RequestParam(defaultValue = "60") long flowLinePressure,
#RequestParam(defaultValue = "50") long casingPressure,
#RequestParam(defaultValue = "0") int allocation,
#RequestParam(defaultValue = "3") int wellTestTypeID,
#RequestParam(defaultValue = "you") String createUser,
#RequestParam(defaultValue = "me-me") String lastModifiedUser,
#RequestParam(defaultValue = "4-4-2014") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date createDate,
WellTest wellTest) throws IOException
{
/* try {
wellTestService.updateWellTest(wellTest);
} catch (ParseException e) {
e.printStackTrace();
}*/
String param1 = new String(wellFacilityID.toString());
return param1;
}
The app is for retrieving and editing Well Tests for Oil Wells. I was able to get every parameter to pass, except when I used a Date datatype. I am positive it's related to conversion within the binding, but the service will not even error within debugger - The error tells me the request is not syntactically correct and it never gets to the request method.
The error is...
Blockquote
400 Bad Request Show explanation Loading time: 18
Request headers
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_4) AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/37.0.2062.120 Safari/537.36
Origin: chrome-extension://hgmloofddffdnphfgcellkdfbfbjeloo
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip,deflate
Accept-Language: en-US,en;q=0.8,de;q=0.6
Response headers
X-Powered-By: Servlet/3.1 JSP/2.3 (GlassFish Server Open Source Edition 4.0 Java/Oracle Corporation/1.8)
Server: GlassFish Server Open Source Edition 4.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 3600
Access-Control-Allow-Headers: x-requested-with
Content-Language:
Content-Type: text/html
Date: Wed, 17 Sep 2014 21:29:47 GMT
Connection: close
Content-Length: 1105
My question is simple, and please if you are going to comment or answer, be thorough in both. Please assume i know next to nothing, and you are the one who can explain it.
How are passed parameters datatypes converted within Spring MVC? Please explain what to do as well as the concepts behind the why.

Related

Streaming mp4 requests via http with range header in grails

I'm on an old grails 2.5.1 app and I noticed mp4 video files served from the server don't play in Safari. I looked up the issue on SO and got some hints that it has to do with the range header. But I suspect the way I'm handling the range header isn't quite right.
So far, what I've found is Mac OS Safari 11.0 (11604.1.38.1.7) (I don't care about ios Safari right now) sends two GET requests. Firstly, it sends one with:
host: localhost:8080
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Safari/604.1.38
accept-language: en-us
accept-encoding: gzip, deflate
x-request-time: t=****
x-forwarded-for: *.*.*.*
x-forwarded-host: *.com
x-forwarded-server: *.com
connection: Keep-Alive
cookie: ...TOO BIG TO SHOW HERE
<- "GET /.../videos/lol.mp4 HTTP/1.1" 200 186ms
Subsequently, it sends second GET request:
host: localhost:8080
language: en-us
playback-session-id: 03F1B4E6-F97E-****
bytes=0-1
accept: */*
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Safari/604.1.38
https://.../videos/lol.mp4
encoding: identity
request-time: t=****
forwarded-for: *.*.*.*
forwarded-host: *.com
forwarded-server: *.com
connection: Keep-Alive
cookie: ...TOO BIG TO SHOW HERE
<- "GET /uiv2/videos/lol.mp4 HTTP/1.1" 206 149ms
Debugging this is hard because Safari web inspector doesn't show you much. In fact, it doesn't even show you all the headers it sends so I had to get this from the back end.
As can be seen, the difference between request 1 and 2 is the 2nd has playback-session-id and range.
The hard part is finding out how to please Safari in how range and playback-session-id are handled.
I've made a controller to return the range of bytes requested, if they're requested. But still no luck.
import grails.compiler.GrailsTypeChecked
import grails.plugin.springsecurity.annotation.Secured
import asset.pipeline.grails.AssetResourceLocator
import grails.util.BuildSettings
import org.codehaus.groovy.grails.commons.GrailsApplication
import org.springframework.core.io.Resource
class VideoController {
GrailsApplication grailsApplication
AssetResourceLocator assetResourceLocator
public index() {
Resource mp4Resource = assetResourceLocator.findAssetForURI('/../lol.mp4');
response.addHeader("Content-type", "video/mp4")
response.addHeader( 'Accept-Ranges', 'bytes')
String range = request.getHeader('range')
if(range) {
String[] rangeKeyValue = range.split('=')
String[] rangeEnds = rangeKeyValue[1].split('-')
if(rangeEnds.length > 1) {
int startByte = Integer.parseInt(rangeEnds[0])
int endByte = Integer.parseInt(rangeEnds[1])
int contentLength = (endByte - startByte) + 1
byte[] inputBytes = new byte[contentLength]
mp4Resource.inputStream.read(inputBytes, startByte, contentLength)
response.status = 206
response.addHeader( 'Content-Length', "${contentLength}")
response.outputStream << inputBytes
} else {
response.addHeader( 'Content-Length', "${mp4Resource.contentLength()}")
response.outputStream << mp4Resource.inputStream
}
} else {
log.info 'no range, so responding with whole mp4'
response.addHeader( 'Content-Length', "${mp4Resource.contentLength()}")
response.outputStream << mp4Resource.inputStream
}
}
}
In the Safari console, I get:
Failed to load resource: Plug-in handled load
Nothing else. And sadly lots of fields in the web inspector are blank even though they're obviously set in the server.
I've tried so many things at this point that any help, pointers, tips will be appreciated. Thanks guys :) !
After trying many things and scouring many posts, this formula worked. You need all four of those headers. Don't need to return anything in the first request. This may not work for all browsers but this works for safari. Additional modifications can ensure all browsers are handled
class VideoController {
GrailsApplication grailsApplication
AssetResourceLocator assetResourceLocator
public index() {
Resource mp4Resource = assetResourceLocator.findAssetForURI('/../lol.mp4')
String range = request.getHeader('range')
if(range) {
String[] rangeKeyValue = range.split('=')
String[] rangeEnds = rangeKeyValue[1].split('-')
if(rangeEnds.length > 1) {
int startByte = Integer.parseInt(rangeEnds[0])
int endByte = Integer.parseInt(rangeEnds[1])
int contentLength = (endByte - startByte) + 1
byte[] inputBytes = new byte[contentLength]
def inputStream = mp4Resource.inputStream
inputStream.skip(startByte) // input stream always starts at the first byte, so skip bytes until you get to the start of the requested range
inputStream.read(inputBytes, 0, contentLength) // read from the first non-skipped byte
response.reset() // Clears any data that exists in the buffer as well as the status code and headers
response.status = 206
response.addHeader("Content-Type", "video/mp4")
response.addHeader( 'Accept-Ranges', 'bytes')
response.addHeader('Content-Range', "bytes ${startByte}-${endByte}/${mp4Resource.contentLength()}")
response.addHeader( 'Content-Length', "${contentLength}")
response.outputStream << inputBytes
}
}
}
}

ArangoDB can't send request with curl

I can't unserstand what I am doing wrong, but when I am sending next request with curl, I am getting error:
echo {"id":1,"question":"aaa"},{"id":2,"question":"bbb?"} | curl -X POST --data-binary #- --dump - http://localhost:8529/_db/otest/_api/document/?collection=sitetestanswers
HTTP/1.1 100 (Continue)
HTTP/1.1 400 Bad Request
Server: ArangoDB
Connection: Keep-Alive
Content-Type: application/json; charset=utf-8
Content-Length: 100
{"error":true,"errorMessage":"failed to parse json object: expecting EOF","code":400,"errorNum":600}
Any ideas? I tied wrap it's to [...]. Nothing do not help.
With [...] validator mark this as valid
Same with D. Here is my code:
void sendQuestionsToArangoDB(Json questions)
{
string collectionUrl = "http://localhost:8529/_db/otest/_api/document/?collection=sitetestanswers";
auto rq = Request();
rq.verbosity = 2;
string s = `{"id":"1","question":"foo?"},{"id":2}`;
auto rs = rq.post(collectionUrl, s, "application/json");
writeln("SENDED");
}
--
POST /_db/otest/_api/document/?collection=sitetestanswers HTTP/1.1
Content-Length: 37
Connection: Close
Host: localhost:8529
Content-Type: application/json
HTTP/1.1 400 Bad Request
Server: ArangoDB
Connection: Close
Content-Type: application/json; charset=utf-8
Content-Length: 100
100 bytes of body received
For D I use this lib: https://github.com/ikod/dlang-requests
Same issue with vibed.
ArangoDB do not understand JSON if it's come ass array like [...]. It should be passed as key-value. So if you need pass array it should have key mykey : [].
Here is working code:
import std.stdio;
import requests.http;
void main(string[] args)
{
string collectionUrl = "http://localhost:8529/_db/otest/_api/document?collection=sitetestanswers";
auto rq = Request();
rq.verbosity = 2;
string s = `{"some_data":[{"id":1, "question":"aaa"},{"id":2, "question":"bbb"}]}`;
auto rs = rq.post(collectionUrl, s, "application/json");
writeln("SENDED");
}
otest - DB name
sitetestanswers - collection name (should be created in DB)
echo '[{"id":1,"question":"aaa"},{"id":2,"question":"bbb?"}]'
should do the trick. You need to put ticks around the JSON. The array brackets are necessary otherwise this is not valid JSON.
You are trying to send multiple documents. The data in the original question separates the documents by comma ({"id":1,"question":"aaa"},{"id":2,"question":"bbb?"}) which is invalid JSON. Thus the failed to parse json object answer from ArangoDB.
Putting the documents into angular brackets ([ ... ]) as some of the commentors suggested will make the request payload valid JSON again.
However, you're sending the data to a server endpoint that handles a single document. The API for POST /_api/document/?collection=... currently accepts a single document at a time. It does not work with multiple documents in a single request. It expects a JSON object, and whenever it is sent something different it will respond with an error code.
If you're looking for batch inserts, please try the API POST /_api/import, described in the manual here: https://docs.arangodb.com/HttpBulkImports/ImportingSelfContained.html
This will work with multiple documents in a single request. ArangoDB 3.0 will also allow sending multiple documents to the POST /_api/document?collection=... API, but this version is not yet released. A technical preview will be available soon however.

How to debug corrupt zip file generation?

We have a web page that is grabs a series of strings from a url, finds some pdfs associated with those strings, zips them up using DotNetZip, and returns them to the user. The page that does this is very simple - here's the Page_Load:
protected void Page_Load(object sender, EventArgs e)
{
string[] fileNames = Request.QueryString["requests"].Split(',');
Response.Clear();
Response.ClearHeaders();
Response.ContentType = "application/zip";
string archiveName = String.Format("MsdsRequest-{0}.zip", DateTime.Now.ToString("yyyy-mm-dd-HHmmss"));
Response.AddHeader("Content-Disposition", "attachment; filename=\"" + archiveName + "\"");
using (ZipFile zip = new ZipFile())
{
foreach (string fileName in fileNames)
{
zip.AddFile(String.Format(SiteSettings.PdfPath + "{0}.pdf", msdsFileName), "");
}
zip.Save(Response.OutputStream);
}
Response.Flush();
}
(Before you ask, it would be fine if someone put other values in this url...these are not secure files.)
This works fine on my development box. However, when testing on our QA system, it downloads the zipped file, but it is corrupt. No error is thrown, and nothing is logged in the event log.
It may be possible for me to find a way to interactively debug on the QA environment, but since nothing is actually failing by throwing an error (such as if the dll wasn't found, etc.), and it's successfully generating a non-empty (but corrupt) zip file, I'm thinking I'm not going to discover much by stepping through it.
Is it possible that this is some kind of issue where the web server is "helping" me by "fixing" the file in some way?
I looked at the http response headers where it was working on my local box and not working on the qa box, but while they were slightly different I didn't see any smoking gun.
As an other idea I rejected, the content length occured to me as a possibility since if the content length value was too small I guess that would make it corrupt...but I'm not clear why that would happen and I don't think that's exactly it since if I try to zip and download 1 file I get a small zip...while downloading several files gives me a much larger zip. So that, combined with the fact that no errors are being logged, makes me think that the zip utility is correctly finding and compressing files and the problem is elsewhere.
Here are the headers, to be complete.
The response header on my development machine (working)
HTTP/1.1 200 OK
Date: Wed, 02 Jan 2013 21:59:31 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Content-Disposition: attachment; filename="MsdsRequest-2013-59-02-165931.zip"
Transfer-Encoding: chunked
Cache-Control: private
Content-Type: application/zip
The response header on the qa machine (not working)
HTTP/1.1 200 OK
Date: Wed, 02 Jan 2013 21:54:37 GMT
Server: Microsoft-IIS/6.0
P3P: CP="NON DSP LAW CUR TAI HIS OUR LEG"
SVR: 06
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Content-Disposition: attachment; filename="MsdsRequest-2013-54-02-165437.zip"
Cache-Control: private
Content-Type: application/zip
Set-Cookie: (cookie junk removed);expires=Wed, 02-Jan-2013 21:56:37 GMT;path=/;httponly
Content-Length: 16969
Not sure how to approach this since nothing is claiming a failure. I feel like this could be a web server configuration issue (since I don't have any better ideas), but am not sure where to look. Is there a tact I can take?
As it is you have miss to give an End() to the page right after the Flush() as:
...
zip.Save(Response.OutputStream);
}
Response.Flush();
Response.End();
}
But this is not the correct way, to use a page to send a zip file, probably IIS also gZip the page and this may cause also issues. The correct way is to use a handler and also avoid extra gZip compression for that handler by ether configure the IIS, ether if you make the gZip compression avoid it for that one.
a handler with a name for example download.ashx for your case will be as:
public void ProcessRequest(HttpContext context)
{
string[] fileNames = Request.QueryString["requests"].Split(',');
context.Response.ContentType = "application/zip";
string archiveName = String.Format("MsdsRequest-{0}.zip", DateTime.Now.ToString("yyyy-mm-dd-HHmmss"));
context.Response.AddHeader("Content-Disposition", "attachment; filename=\"" + archiveName + "\"");
// render direct
context.Response.BufferOutput = false;
using (ZipFile zip = new ZipFile())
{
foreach (string fileName in fileNames)
{
zip.AddFile(String.Format(SiteSettings.PdfPath + "{0}.pdf", msdsFileName), "");
}
zip.Save(context.Response.OutputStream);
}
}

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