Why byte array becomes string while transferring it via rest template in Java - spring-boot

These are two SpringBoot projects. A is for api service providing, and B is consuming A's service via rest template. It is OK while transferring string. While transferring Excel file via byte array, B receives a string, not byte array.
The api method in A.
#GetMapping(value = "/export")
#ResponseBody
public HSResult exportFile(#RequestParam(value = "fileName", defaultValue = "-1") String fileName,
#RequestParam(value = "provider", defaultValue = "-1") String channelId,
#RequestParam(value = "fileStatus", defaultValue = "-5") int fileStatus,
#RequestParam(value = "cdate", defaultValue = "") String cdate,
HttpServletRequest request, HttpServletResponse response) {
// ...... some logic code
InputStream inputStream=new FileInputStream(fullPath);
long fileSize=new File(fullPath).length();
byte[] allBytes = new byte[(int) fileSize];
inputStream.read(allBytes);
result = HSResult.build(200, "exportFile success",allBytes);
return result;
}
The consuming method in B.
public ResponseEntity downLoadFile(int businessType, String fileName, int fileStatus, HttpServletResponse response) {
//.......
ResponseEntity<HSResult> responseEntity = restTemplate.getForEntity(url, HSResult.class);
HSResult apiResult = responseEntity.getBody();
byte[] fileData = (byte[]) apiResult.getData();
//.......
}
A reads an excel file from disk into a byte array before transferring.
But while receving the result in B side, it is string like UEsDBC0AAAAIADhMuVAKVjbC//////////8LABQAX3Jl
Why did this happen? And how to transfer byte array through rest template correctly? Thanks.
PS: The class of HSResult
public class HSResult {
private Integer status;
private String msg;
private Object data;
//gets and setters
//......
}

Finally, I find the root cause. Share it with who may encounter the same issue.
In A side, it puts a byte array in the data field of HSResult, this field is in type object.
While receving this in B side, rest template is trying to cast all the data back into HSResult. When it comes to the byte array, the receving field data is in type object. So rest template does not konw what the specific type it is, then convert the byte array into a string, with some decode. I don't know whether it is exactly UTF8 or GB2312 or else.
How to resolve this? Just specify the receving field with specific type, not object.
public class HSResult {
private Integer status;
private String msg;
private byte[] data;
//gets and setters
}
Then everythiing is OK.
Thanks for Ken's reminder. Http is a text protocal, it cannot transfer byte array directly, so byte array must be converted into string at first.

I have used the Gson() class to convert object to json! and it has no problem with byte arrays.
I have this problem when I want to move my codes to spring, so I solved it by serializing byte[] filed:
class ByteArraySerializer: JsonSerializer<ByteArray>() {
override fun serialize(bytes: ByteArray, jsonGenerator: JsonGenerator, serializerProvider: SerializerProvider?) {
val intArray = intArray(bytes)
jsonGenerator.writeArray(intArray, 0, intArray.size)
}
private fun intArray(input: ByteArray): IntArray {
val ret = IntArray(input.size)
for (i in input.indices) ret[i] = input[i].toInt() // & 0xff -> 0-255
return ret
}
}
convert byte array to int array
and then use it in my class:
data class VideoInfo(val durationMS: Int = 0): Serializable {
#JsonSerialize(using = ByteArraySerializer::class)
var bytes: ByteArray? = null
}
It will return json object as a below:
{
"bytes": [-1,-40, ..., 0],
"durationMS": 8870
}
It is kotlin, You can easily convert it to java :)

Related

Stream data in response for Spring MVC 4.3 Using Java 8, Tomcat 7

Below is the piece of code that causes OutOfMemory issue when I run my xyz.war in tomcat 7 with Java 8.
In Below code I am creating a CSV response of the data that was fetched from MongoDB via cursor.
#RequestMapping(value = "/elements/{elementname}/records", method = RequestMethod.GET)
public ModelAndView getAllRecords(
HttpServletRequest request, HttpServletResponse response,
#RequestParam(value = "customerid", required = true) long customerId,
#RequestParam(value = "userid", required = true) long userId,
#RequestParam(value = "query", required = false) String query,
throws Exception {
Map < String, Object > model = new HashMap < String, Object > ();
JsonObject records = elementService.searchRecords(query);
ModelAndViewData msvd = elementService.commonRestService
.getModelAndView("dataObject", "streamingView");
return new ModelAndView(msvd.getViewName(), handleCsvReportTypeRequest(records, customerId, userId));
}
public Map < String, Object > handleCsvReportTypeRequest(JsonObject records,
String elementName, long customerId, long userId) throws Exception {
StringBuffer csvData = new StringBuffer();
// create csv data
ModelAndViewData modelAndStreamingViewData = commonRestService.getModelAndView(
"dataObject", "streamingView");
byte[] byteArray = String.valueOf(csvData).getBytes();
InputStream byteArrayInputStream = new ByteArrayInputStream(byteArray);
model.put(modelAndStreamingViewData.getModelAttributeName(), byteArrayInputStream);
model.put(DownloadConstants.CONTENT_TYPE, DownloadConstants.CSV_CONTENT_TYPE);
model.put(DownloadConstants.FILENAME, "XYZ.csv");
model.put(DownloadConstants.LAST_MODIFIED, new Date(System.currentTimeMillis()));
model.put(DownloadConstants.CONTENT_LENGTH, Integer.valueOf(byteArray.length));
return model;
}
How can I stream CSV data back to the user without creating a huge data in memory and then passing to the user?
Use a Buffered read and write the response in HttpResponse object.
Try this way:
Spring MVC : large files for download, OutOfMemoryException

Source code in spring which takes in a JSON String(key-value Pair ) as an input and gives out the Updated JSON string

I need help in the coding of a program in spring which takes in a JSON String(key-value Pair ) as an input and gives out the Updated JSON string
I'm assuming you are asking in terms of REST, if yes then refer below snippet -
#Controller
public class JSONTest {
// Below method will take iput as JSON
#RequestMapping(value="/getData",consumes = MediaType.APPLICATION_JSON_VALUE)
public void consumeJson(#RequestBody String s) {
System.out.println("json body : " + s);
}
// Below method will produce output as JSON
#RequestMapping(value="/sendData",produces= MediaType.APPLICATION_JSON_VALUE)
public String sendJson() {
//Create jsonObject
//do some things with jsonObject, put some header information in jsonObject
return jsonObject.toString();
}
}

Download A File On click of a link using spring mvc

When I click on any link the content should be downloaded
But this is what I get.
MastercourseController.java
#RequestMapping(value = { ControllerUriConstant.download_file }, method = RequestMethod.GET)
#ResponseBody
public void downloadingAFileById(#RequestParam("id") String id, Model model, HttpServletRequest request)
throws TechnoShineException, IOException {
String filePath = "D:/dev/testFIle.txt";
long download = Long.parseLong(id);
byte[] b = masterCourseFileFormService.getAllDownloadable(download);
OutputStream outputStream = new FileOutputStream(filePath);
outputStream.write(b);
outputStream.close();
}
MasterCourseService
public byte[] getAllDownloadable(long id) throws TechnoShineException
{
return masterCourseFormUploadDao.getAllDownloadableFiles(id);
}
MasterCourseDao
public byte[] getAllDownloadableFiles(long id) throws TechnoShineException
{
return masterCourseFormUploadMapper.getAllDownloadable(id);
}
MasterCourseMapper
public byte[] getAllDownloadable(long id) throws TechnoShineException;
You are writing the data returned by getAllDownloadable(..) to a hard-coded file. Are you sure that is what you want? I think you want to write the content returned by getAllDownloadable(..) to be written into the response. That can be done by adding a method parameter of the type HttpServletResponse to your mapping and writing into the output stream returned by HttpServletResponse#getOutputStream() and flushing (not closing!) that stream at the end.
Furthermore you have to remove the #ResponseBody annotation as this is meant to be used if the value that is returned by the mapping method returns the data that should directly be sent to the client (i.e. when sending a JSON data object or a string) without passing it to the template engine. As you are not returning anything you can remove this annotation.
Furthermore you have to set the content type of your response by invoking HttpServletResponse#setContentType(contentType: String).
In your case, the invocation would be the following:
response.setContentType("text/plain");
You complete method would look like this:
#RequestMapping(
value = ControllerUriConstant.download_file,
method = RequestMethod.GET
)
public void downloadingAFileById(#RequestParam("id") String id, HttpServletResponse response)
throws TechnoShineException, IOException {
long download = Long.parseLong(id);
byte[] b = masterCourseFileFormService.getAllDownloadable(download);
response.getOutputStream().write(b);
response.getOutputStream().flush();
}

Spring RestRemplate postforobject with request parameter having integer value

I have a method in Spring rest service.
#RequestMapping(value = "test/process", method = RequestMethod.POST)
public #ResponseBody MyResponse processRequest(String RequestId, int count)
I am using Spring RestTemplate to call this service like this.
RestTemplate restTemplate = this.getRestTemplate();
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("RequestId", RequestId);
map.add("count", count);
restTemplate.postForObject(url, map,MyResponse.class);
When I try to invoke the client method I get the exception that no suitable HttpMessageConverter found for request type [java.lang.Integer]
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [java.lang.Integer]
at org.springframework.http.converter.FormHttpMessageConverter.writePart(FormHttpMessageConverter.java:310)
at org.springframework.http.converter.FormHttpMessageConverter.writeParts(FormHttpMessageConverter.java:270)
at org.springframework.http.converter.FormHttpMessageConverter.writeMultipart(FormHttpMessageConverter.java:260)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:200)
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:1)
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:596)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:444)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:409)
at org.springframework.web.client.RestTemplate.postForObject(RestTemplate.java:287)
I know one of the ways is to pass all the parameters as String. But I might need to pass complex data types as parameters later.
What is the ways to achieve this.
I have googled and some option seem to be writing my own converters. How should I start about solving this problem.
The root cause of this error is that by specifying an Integer in the LinkedMultiValueMap, the RestTemplate will take that to mean that your request is a multipart request. There is no HttpMessageConverter registered by default that can handle writing values of type Integer to a request body.
As you said, you can handle this situation by changing the count to be a String. After all, there is no Integer type in HTTP request parameters. However, you were worried
But I might need to pass complex data types as parameters later.
Assume something like this
public #ResponseBody MyResponse processRequest(String RequestId, int count, Complex complex) {
with
public class Complex {
private String someValue;
private int intValue;
public String getSomeValue() {
return someValue;
}
public void setSomeValue(String someValue) {
this.someValue = someValue;
}
public int getIntValue() {
return intValue;
}
public void setIntValue(int intValue) {
this.intValue = intValue;
}
public String toString() {
return someValue + " " + intValue;
}
}
The the following will work just fine
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("RequestId", "asd");
map.add("count", "42");
map.add("someValue", "complex");
map.add("intValue", "69");
restTemplate.postForObject(url, map,MyResponse.class);
Remember that the request parameters are used to populate the fields of model attributes by their names.
An even better solution would have you using a serialization standard like JSON or XML.

Ignore exceptions while converting a #RequestParam value

I have an endpoint that takes a request parameter: http://localhost/test?parameter=123
When somebody calls this endpoint with a string instead of an integer he gets a BAD_REQUEST response because the string can not be converted.
Is it possible to ignore conversion exceptions on a request parameter and just leave it empty?`
Currently my code looks like this:
#RequestMapping(value = "/test")
public void doSomething(#RequestParam(required = false) Integer parameter) {...}
You should take the parameter as String and convert yourself.
By saying it should be Integer in your method signature you require this indeed to be an integer. If it was not it is indeed BAD_REQUEST. If you want other custom scenario you should implement it yourself.
#RequestMapping(value = "/test")
public void doSomething(#RequestParam(required = false) String parameter) {
Integer parameterValue = null;
if (parameter != null) {
try {
parameterValue = Integer.valueOf(parameter);
} catch (NumberFormatException ex) {
// No-op as already null
}
}
// At this point the parameterValue is either null if not specified or specified as non-int, or has the integer value in it
}

Resources