Thursday, March 29, 2018

Making Http Post request with MultipartForm data

My requirement was to upload a file and pass some JSON in an API in Java. So I googled and found some code as below:

HttpClient httpclient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(UPLOAD_URI);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
builder.setCharset(Charset.defaultCharset());
String text = "{\"name\":\"manish\",\"occupation\":\"engineer\"}";
builder.addTextBody("info", text);
builder.addPart("doc", new FileBody(file));
httpPost.addHeader("content-type", MediaType.MULTIPART_FORM_DATA);
httpPost .setEntity(builder.build()); httpclient.execute(httpPost);

When I used this code then I got 400 error with reason as "Bad Request" and empty response body. The target server was also in my control and was running Jersey framework on Tomcat. I checked the logs and found just error as 400. Absolutely no message or other info on the reason of the error.
When I ran the API from CURL then it worked with no issues. Thats makes me believe that the issue is in code above.

After a lot of hit and trial I found that the missing piece is the "boundary". Boundary was not added into the "content-type" header. I changed the code above to:

HttpClient httpclient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(UPLOAD_URI);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
builder.setCharset(Charset.defaultCharset());
String text = "{\"name\":\"maniildersh\",\"occupation\":\"engineer\"}";
builder.setBoundary("------------------------f8ee5b62c27a1db3");
builder.addTextBody("info", text);
builder.addPart("doc", new FileBody(file));
httpPost.addHeader("content-type", MediaType.MULTIPART_FORM_DATA+ "; boundary=------------------------f8ee5b62c27a1db3");
httpPost .setEntity(builder.build()); httpclient.execute(httpPost);

Thats it !  Code started working. Later I changed the boundary to be generated dynamically instead of being static.

Sunday, March 25, 2018

Ansible: ERROR: tag(s) not found in playbook:

I was getting this error when I was running an ansible playbook from my java application. And surprisingly when I copy the exact same command on the command line then it ran just fine.  Every time I am getting an error as shown below:
ERROR: tag(s) not found in playbook:  foobar.  possible values: foobar
I spent many hours but could not understand why it is failing to run from java app but runs fine from command line. Then I started playing with the command in java and placed the "-t foobar" at different places, such as after the "ansible-playbook" command or at the end of the statement, or along with an extra variable. Every time it failed with same error.
Then I noticed that there are two spaces before the "foobar" in the error.  And that was like the eureka moment for me. I changed the java code to use "-tfoobar" (without space between "-t" and tagname). That's it, it worked.
It seems the library I was using (commons-exec from apache) passes the arguments in such a way that ansible does not like space after "-t" option. But space works just fine when running the command via command line.

Learnings in handling input and output streams in Java

I learned it hard way that working with streams in Java can be tricky, specially when you are writing to an output stream. It happened to me a couple of times that I found that although I am writing to stream but still I lost some data. That happened because I forgot to close the stream. And sometimes I closed a stream which I should not have. Below I am providing some points that I have learnt while working with streams:

Flush the streams frequently: Main benefit of working with streams is you process the data as you receive from input stream and push the processed data into the output stream. So, you must flush your data into the output stream as you process. This ensures that the listener on the other end of the output stream gets the data continuously. And also make sure you flush the data when the processing is done to ensure even the last bits are sent to the output stream.

Close the stream only if you open it: It is a rule of thumb that you close any stream (input or output) only if you have open it. Simple reason is, if the caller method has opened the stream and passed it to you( in your method) then it is possible that caller can use it after your method call. Example is, input and output stream in a servlet is opened by the application container and then passed to the servlet, you don't have to close these streams. Container closes these streams when the http request is completed.
This includes the stream that you have created as wrapper on another stream.




Tuesday, February 6, 2018

Execute different methods based on accept header and set priority when no accept header provided

I had a REST API which was accepting a file upload and in response generating another file. This was working fine without any issue with below code:
@POST 
@Path("/convert")
@Produces("application/octet-stream")
@Consumes(MediaType.MULTIPART_FORM_DATA)
 But, later I had to add a requirement where in certain cases the response in NOT stream but a JSON object. I wanted to keep the API same but behave differently only for limited number of cases. After looking into some Jersey docs and some testing below is what implemented
@POST
@Path("/convert")
@Produces("application/octet-stream;qs=1")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@POST
@Path("/convert")
@Produces("application/json;qs=.5")
@Consumes(MediaType.MULTIPART_FORM_DATA) 
Note that both of the APIs have signature, and only difference is in response type. With these signatures client when passes "accept" header as  "application/octet-stream" then first method is called. And when "accept" header is "application/json" then second method is called.
In addition to this, I also used the "quality" (note qs), this is set to 1 for first method and 0.5 for the second method. Because of this my code change was backward compatible. Which means, when no accept header is provided. In that case the first method is called because qs=1.