Sending multipart form data does not appear to work correctly - reactor-netty

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)

Related

Spring RestTemplate API query parameter encoding for doing a GET HTTP Request

The url-string contains a back-slash character that needs to be encoded. The url string is as follows.
String folder = "\\Foo\\Bar\\"; // some folder search path.
String urlString= "http://localhost:8081/certificates/?mypath=%5CFoo%5CBar%5C" // (after encoding)
Here I use Spring RestTemplate to do a GET request. I setup a mock-server to examine the request in detail (mock server setup using Mulesoft, if u must know!).
ResponseEntity<String> responseEntity = api.exchange(urlString, HttpMethod.GET, new HttpEntity<>(new HttpHeaders()), String.class);
Here I use plain vanilla Java URLConnection to perform the request. Attached image with detailed request snapshot.
// 2. Plain vanilla java URLConnection. "result.toString()" has certificate match.
StringBuilder result = new StringBuilder();
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestProperty("X-Venafi-Api-Key", apiKey);
conn.setRequestMethod("GET");
BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
result.append(line);
}
rd.close();
System.out.println(result.toString());
In the images, you can see that the queryString value is different for these two requests. One of them shows \\ while the other shows %5C, although the parsed parameter value for myPath is still the same.
I am having to deal with an api that seems to work if-and-only-if the queryString looks like the former (i.e. "\\"). Why does the parsed queryString for Spring show "%5C" while this value shows double-backslash for requests originating from plain Java, curl, and even a simple browser?
What baffles me EVEN more, is that just about everything about the two HTTP Requests are IDENTICAL! And yet, why does the queryString/requestUri parse differently for these two requests? Shouldn't it be that a HTTP GET method is completely defined by its header contents and the requestUri? What am I missing to capture in these two GET requests?
Lots of questions. Spent an entire day, but at least I could verify that the way the requestUri/queryString is parsed seems to align with how the remote api-server responds.
Thanks.
Did some digging around the following morning. Turn out, with
ResponseEntity<String> responseEntity = api.exchange(urlString, HttpMethod.GET, new HttpEntity<>(new HttpHeaders()), String.class);
You should NOT have the "urlString" already encoded. The 'exchange' method does that encoding for you under-the-hood.

webapi controller return csv data

I have a webapi controller code looks like:
[HttpPost]
public HttpResponseMessage DownloadForms(FormCriteria criteria)
{
string downloadData = new FormsToCsvHelper(Umbraco, criteria).GetCsv();
return Request.CreateResponse(HttpStatusCode.OK, downloadData);
}
If i look at the data returned in debugger just before sending it back it looks like:
Created Date,IP,Form Name,Email address,Message,Full Name
31/05/2019 10:43:08,127.0.0.1,Contact form - test,test12#hotmail.com,test,Ismail Mayat
If I copy and paste it into file and save as csv it looks fine in excel.
However the data i actually get back looks like:
"Created Date,IP,Form Name,Email address,Message,Full Name\r\n31/05/2019 10:43:08,127.0.0.1,Contact form - test,test12#hotmail.com,test,Ismail Mayat"
The whole record set it wrapped in quote and the line feed is seen as a literal so when opening file in excel its all on one line.
There is a DelegatingHandler that fire but that is for a specific url request so for this request it does not do anything.
Anyone any ideas whats going on?
WebAPI will serialize the value as JSON by default, so that's why your value is enclosed in double quotes.
To get around this, you can use the StringContent class:
return new HttpResponseMessage(statusCode) {
Content = new StringContent(downloadData, Encoding.UTF8, "text/csv")
};

Web API content negotiated formatters with accept header and url parameter

I have implemented content negotiation so that a specific serializer will be used based on the accept header:
XmlFormatter fmtXml = new XmlFormatter();
fmtXml.SupportedMediaTypes.Add(new
System.Net.Http.Headers.MediaTypeHeaderValue("text/xml"));
JsonFormatter fmtJson = new JsonFormatter();
fmtJson.SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
config.Formatters.Insert(0, fmtJson);
config.Formatters.Insert(0, fmtXml);
I need to allow a client to specify the desired format using a url parameter, which would take precedence over the accept header.
To do this, I've started subclassing the DefaultContentNegogiator (although I don't know that it's the best idea.:
public class CustomContentNegotiator : DefaultContentNegotiator
{
public override ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
string sMimeType = HttpUtility.ParseQueryString(request.Url.Query).Get("_format");
if (!string.IsNullOrEmpty(sMimeType))
{
...
}
else
{
return base.Negotiate(type, request, formatters);
}
}
}
Then I replace the default content negotiator with mine:
GlobalConfiguration.Configuration.Services.Replace(typeof(IContentNegotiator), new CustomContentNegotiator());
The idea with the custom content negotiator is that if a content format has been specified as a url parameter, I would locate the formatter that matches, otherwise I would just fallback to the behavior of the DefaultContentNegotiator.
I'm just not sure how to match correctly on the supported media types, or if there is a better, simpler solution to this...
I determined that using a custom content negotiator was a red herring. Instead I was able to use a MediaTypeMapping which matches against a specific url parameter instead of the accept request header:
fmtJson.MediaTypeMappings.Add(new System.Net.Http.Formatting.QueryStringMapping("_format", "json", "application/json"));

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);
}

Resources