How to measure REST payload size? - spring

i am using Spring-REST and JSON payload, is there an easy way to measure the payload size ?
My Rest controller returns a list of object, so i don't have a direct access to the JSON String sent to the client.
#RestController
#RequestMapping("/films")
public class FilmController {
#RequestMapping(method = RequestMethod.GET)
public List<Film> filmAll() {
List<Film> films = filmDao.findAllManyToOneQueryDsl();
return films;
}
}

There is the Content-Length HTTP header, though it may or may not be there. It is supplied by the other side (client or server). You will also have to trust the other side to provide the correct value for Content-Length. Other than that, the payload is usually presented to you as a byte stream (or character stream). You can consume the stream and count the bytes (or characters).

Related

Sending multipart form data does not appear to work correctly

I'm trying to call an endpoint that accepts PUT requests and expects to be passed 3 different MultipartFile paramters. Let's call them A, B and C.
When I make a request to the same enpoint from Postman it works as intended. When I do it via the the reactor-netty lib I get back Error 400 Bad Request:
"Required request part 'A' is not present"
HttpClient
.create()
// skipping baseUrl and headers headers
.put()
.uri(ENDPOINT_URI)
.sendForm((req, form) -> form
.multipart(true)
.file("A", FILE_A, "application/json)
.file("B", FILE_B, "application/json)
.file("C", FILE_C, "application/json))
.response()
I could not find much info online to establish if this is the best way to achieve what I need. Can you please point me to where I'm going wrong or perhaps towards an alternative solution?
Thanks
After looking throught the source of the HttpClientForm (the class in which .file is called) I found this:
default HttpClientForm file(String name, InputStream stream, #Nullable String contentType) {
return file(name, "", stream, contentType);
}
as well as this:
default HttpClientForm file(String name, File file, #Nullable String contentType) {
return file(name, file.getName(), file, contentType);
}
Somehow I thought that the first paramter 'name' is the one that is matched with the #RequestParam value. By the looks of it its actually the second.
Also if using an input stream instead of a File I had to call the the file method with 4 paramters and pass the name explicitly as the second parameter like so:
file(name, "A", stream, contentType)

Empty request body gives 400 error

My Spring controller method looks something like this:
#RequestMapping(method=RequestMethod.PUT, value="/items/{itemname}")
public ResponseEntity<?> updateItem(#PathVariable String itemname, #RequestBody byte[] data) {
// code that saves item
}
This works fine except when a try to put a zero-length item, then I get an HTTP error: 400 Bad Request. In this case my method is never invoked. I was expecting that the method should be invoked with the "data" parameter set to a zero-length array.
Can I make request mapping work even when Content-Length is 0?
I am using Spring framework version 4.1.5.RELEASE.
Setting a new byte[0] will not send any content on the request body. If you set spring MVC logs to TRACE you should see a message saying Required request body content is missing as a root cause of your 400 Bad Request
To support your case you should make your #RequestBody optional
#RequestMapping(method=RequestMethod.PUT, value="/items/{itemname}")
public ResponseEntity<?> updateItem(#PathVariable String itemname, #RequestBody(required = false) byte[] data) {
// code that saves item
}

FileSystemResource is returned with content type json

I have the following spring mvc method that returns a file:
#RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public FileSystemResource getFiles(#PathVariable String fileName){
String path="/home/marios/Desktop/";
return new FileSystemResource(path+fileName);
}
I expect a ResourceHttpMessageConverter to create the appropriate response with an octet-stream type according to its documentation:
If JAF is not available, application/octet-stream is used.
However although I correctly get the file without a problem, the result has Content-Type: application/json;charset=UTF-8
Can you tell me why this happens?
(I use spring version 4.1.4. I have not set explicitly any message converters and I know that spring loads by default among others the ResourceHttpMessageConverter and also the MappingJackson2HttpMessageConverter because I have jackson 2 in my classpath due to the fact that I have other mvc methods that return json.
Also if I use HttpEntity<FileSystemResource> and set manually the content type, or specify it with produces = MediaType.APPLICATION_OCTET_STREAM it works fine.
Note also that in my request I do not specify any accept content types, and prefer not to rely on my clients to do that)
I ended up debugging the whole thing, and I found that AbstractJackson2HttpMessageConverter has a canWrite implementation that returns true in case of the FileSystemResource because it just checks if class is serializable, and the set media type which is null since I do not specify any which in that case is supposed to be supported by it.
As a result it ends up putting the json content types in a list of producible media types. Of course ResourceHttpMessageConverter.canWrite implementation also naturally returns true, but the ResourceHttpMessageConverter does not return any producible media types.
When the time to write the actual response comes, from the write method implementation, the write of the ResourceHttpMessageConverter runs first due to the fact that the ResourceHttpMessageConverter is first in the list of the available converters (if MappingJackson2HttpMessageConverter was first, it would try to call write since its canWrite returns true and throw exception), and since there was already a producible content type set, it does not default to running the ResourceHttpMessageConverter.getDefaultContentType that would set the correct content type.
If I remove json converter all would work fine, but unfortunately none of my json methods would work. Therefore specifying the content type is the only way to get rid of the returned json content type
For anyone still looking for a piece of code:
You should wrap your FileSystemResource into a ResponseEntity<>
Then determine your image's content type and append it to ResponseEntity as a header.
Here is an example:
#GetMapping("/image")
public #ResponseBody ResponseEntity<FileSystemResource> getImage() throws IOException {
File file = /* load your image file from anywhere */;
if (!file.exists()) {
//TODO: throw 404
}
FileSystemResource resource = new FileSystemResource(file);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(/* determine your image's media type or just set it as a constant using MediaType.<value> */);
headers.setContentLength(resource.contentLength());
return new ResponseEntity<>(resource, headers, HttpStatus.OK);
}

Handling batched sendgrid events using the asp.net web api

I'm attempting to use an asp.net web api application to handle batched SendGrid events and I've run into a stumbling block due to the way SendGrid handles the content type header of the post it sends.
From their documentation:
Batched event POSTs have a content-type header of application/json,
and contain exactly one JSON string per line, with each line
representing one event. Please note that currently the POST headers
define this post as application/json, though it’s not; each line is a
valid JSON string, but the overall POST body is not.
So, given a controller:
public class SendGridController : ApiController
{
// POST api/values
public void Post([FromBody]string value)
{
// do something with value
}
}
Making a post to it as SendGrid does will result in "value" being null.
string URI = "http://localhost:3018/api/sendgrid/";
string myParameters =
#"={""email"":""foo#bar.com"",""timestamp"":1322000095,""user_id"":""6"",""event"":""bounced""}
{""email"":""foo#bar.com"",""timestamp"":1322000096,""user_id"":""9"",""event"":""bounced""}";
using (var wc = new System.Net.WebClient())
{
wc.Headers[System.Net.HttpRequestHeader.ContentType] = "application/json"; // I work fine if "application/x-www-form-urlencoded" is used.
wc.UploadString(URI, myParameters);
}
If I change the content type in my client example to "application/x-www-form-urlencoded", everything works as expected.
Is there an easy way for me to override this convention such that I can handle the badly formed "json" that sendgrid provides as a string in my controller method?
Ok, I finally figured it out. The trick was to remove the "value" param and work with the request object directly.
So something like:
public class SendGridController : ApiController
{
// POST api/values
public void Post()
{
var value = Request.Content.ReadAsStringAsync().Result;
// do something with value
}
}

Is there a limit size of Spring RestTemplate postForObject?

For example, if the post url is:
http://www.wolf.com/pcap/search?stime={stime}&etime=${etime}&bpf=${bpf}
then can we do this:
Map<String, String> vars = new HashMap<String, String>();
vars.put("bpf", bpf);
...
responseString = restTemplate.postForObject(url, null, String.class,vars);
If bpf is a String, then is there a limitation of the size of bpf? Can it be any size?
Unfortunately the answer is: "It depends".
More precisely: as you append the bpf as a parameter to the URL it does not really matter if you are doing a POST or a GET. Sometimes there are restrictions on the length of an URL a server will handle, but that depends on what the server accepts, and cannot be determined from the RestTemplate, which is the client.
For example if the server you send the REST request to is a tomcat, then the maximal value of the complete header (URL, HTTP-Header etc) is by default 8kB for tomcat 6.0 or higher; see e.g. https://serverfault.com/questions/56691/whats-the-maximum-url-length-in-tomcat
Just in case if you have control over the server side, too, you can change the expected interface by not sending the bpf as parameter, but as request body, like:
Map<String, String> vars = new HashMap<String, String>();
// vars.put("bpf", bpf); <--- not needed
responseString = restTemplate.postForObject(url, bpf, String.class, vars);
(and then of course get the bpf on the server from the request body instead).
Otherwise you are out of luck and have to limit the length of the URL. Maybe use a proxy or network sniffer to see what extra Headers are actually send and subtract that from the 8kB limit to get the maximal length of the URL.

Resources