I am trying to render an image which I got from a Java service as InputStream, re-send it through NodeJS Express server and finally render it in Angular4
Here's what I do:
Java Jersey service:
#GET
#Path("thumbnail")
#ApiOperation(
value = "Gets document preview",
notes = "Gets document preview"
)
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Preview of the document")
})
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces("image/png")
public Response getDocThumbnail(
#ApiParam(value = "Entity UUID", required = true) #FormDataParam("uuid") String uuid
) throws RepositoryException, UnknowException, WebserviceException, PathNotFoundException, DatabaseException, AutomationException, AccessDeniedException, ConversionException, IOException {
RawDocument rawDocument = docCtrl.getDocThumbnail(uuid);
return Response
.ok(rawDocument.getInputStream(), "image/png")
.header("Content-Disposition", "attachment; filename=\" " + rawDocument.getName() + "\"")
.build();
}
the controller looks like:
public RawDocument getDocThumbnail(String uuid) throws IOException, AccessDeniedException, PathNotFoundException, WebserviceException, RepositoryException, DatabaseException, ConversionException, AutomationException, UnknowException {
return new RawDocument(
okmWebSrv.getOkmService().getThumbnail(uuid, ThumbnailType.THUMBNAIL_LIGHTBOX),
"whatever"
);
}
Basically it's call to OpenKM SDK to retreive document's thumbnail
This Java endpoint is called from NodeJS Express 4.15 that is pre-processing some requests for this Java backend.
Here's what I do:
...compose request options...
const vedica_res = await rp(options);
let buffered = new Buffer(vedica_res, 'binary');
res.writeHead(200, {
'Content-Type': 'image/png',
'Content-disposition': 'attachment;filename=' + 'thumb.png',
'Content-Length': buffered.length
});
return res.end(buffered, 'binary');
Finally with Angular4 being the initiator of this roundtrip I am trying to render the image like so:
this.rest.send('http://localhost:4200/vedica/api/document/thumbnail', RequestMethod.Get,
{uuid: '19516ea1-657e-4b21-8564-0cb87f29b064'}, true).subscribe(img => {
// this.preview = img
var urlCreator = window.URL;
var url = urlCreator.createObjectURL(img);
this.thumb.nativeElement.src = url;
})
The 'img' received is a Blob {size: 81515, type: "image/png"}. Console shows no errors but renders no image in the <img #thumb/> tag. But I can see that it sets the src=blob:http%3A//localhost%3A3000/4cf847d5-5af3-4c5a-acbc-0201e60efdb7 for it. Image just has a broken image icon.
When I try to read a cached response in a new tab, its accessible but renders nothing again.
Can you point out what I'm doing wrong? Have tried a lot, but no luck.
I think the problem is not the stream is closed early, the problem I think will be in the way is downloaded, take a look here:
https://docs.openkm.com/kcenter/view/sdk4j-1.1/document-samples.html#getContent
From the server side ( inde middle between OpenKM and your user interface ) the problem usualy is:
//response.setContentLength(is.available()); // Cause a bug, because at this point InputStream still has not its real size.
And you should use
response.setContentLength(new Long(doc.getActualVersion().getSize()).intValue());
resolved this by replacing request-promise with bare request package for making this request to the java BE and piping reply right into the wrapping response of the angular FE:
let reply = request(options);
reply.pipe(res);
Related
I'm making a program that captures webcam images and I need to be able to send them to a backend for text detection and image recognition.
I'm using react webcam for the screenshots. Component is declared like this:
<Webcam
mirrored="false"
audio={false}
screenshotFormat="image/jpeg"
ref={props.webcam}
style={{
marginLeft: "auto",
marginRight: "auto",
display: "block",
paddingTop: "10px",
paddingBottom: "10px",
}}
/>
then, I invoke webcamRef.current.getScreenshot() to get the Image as a Base64 encoding. bytes are sent to a java backend with the following logic:
var formData = new FormData();
formData.append("file", props.image);
formData.append("user", props.user);
axios
.post("http://localhost:8080/api/storeImage", formData, {
headers: { "Content-Type": "multipart/formdata" },
})
.catch((err) => {
throw err;
});
Everything works fine up to this point. Problem arises when I try to create an ImageBuffer from the java backend:
Contoller:
#PostMapping("/api/storeImage")
#ResponseBody
public String storeImage(#RequestParam("file") String file, #RequestParam Long user) throws IOException, InvalidDniException {
return service.storeImage(file, user);
}
Service:
public String storeImage(String source, Long user) throws IOException, InvalidDniException {
byte[] decodedSource = Base64.getMimeDecoder().decode(source);
BufferedImage image = ImageIO.read(new ByteArrayInputStream(decoded)); <-- this returns null ...
I need the BufferedImage to crop and extract information from the captured screenshot. Every answer I've found on similar questions do not seem to work here.
Turns out I had to remove this prefix: data:image/jpeg;base64 from the String received.
Image
I want to write a client code to consume an API. The API is expecting a text file. When I select the binary file option in the postman tool and select any text file from my local it worked. how to implement this in spring ?. I have tried MULTIPART_FORM_DATA but no luck.
If You mean file
#RestController
public class FileContentController {
#RequestMapping(value="/up", method = RequestMethod.POST)
public ResponseEntity<?> upload(#RequestParam("file") MultipartFile file)
throws IOException {
String contentType=file.getContentType());
InputStream i=file.getInputStream();
return new ResponseEntity<>(HttpStatus.OK);
}
return null;
}
also spring boot has multi part confs, you should enable it and set size and tempdir
,In Earlier version spring boot need to add:
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
spring.servlet.multipart.enabled=true
spring.servlet.multipart.location=${java.io.tmpdir}
However in your client code you should not set content-type application/json in your header post request
simple fetch should be such
const input = document.getElementById('uploadInput');
const data = new FormData();
data.append('file', input.files[0]);
var resp = await fetch('upload/', {
method: 'POST',
body: data
});
if (!resp.ok) {
throw new Error(`HTTP error! status: ${resp.status}`);
}
if (resp.ok) {
await this.images();
}
I've tried the various ways given in Stackoverflow, maybe I missed something.
I have an Android client (whose code I can't change) which is currently getting an image like this:
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.connect();
Where url is the location of the image (static resource on CDN). Now my Spring Boot API endpoint needs to behave like a file resource in the same way so that the same code can get images from the API (Spring boot version 1.3.3).
So I have this:
#ResponseBody
#RequestMapping(value = "/Image/{id:.+}", method = RequestMethod.GET, consumes = MediaType.ALL_VALUE, produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<byte[]> getImage(#PathVariable("id")String id) {
byte[] image = imageService.getImage(id); //this just gets the data from a database
return ResponseEntity.ok(image);
}
Now when the Android code tries to get http://someurl/image1.jpg I get this error in my logs:
Resolving exception from handler [public
org.springframework.http.ResponseEntity
com.myproject.MyController.getImage(java.lang.String)]:
org.springframework.web.HttpMediaTypeNotAcceptableException: Could not
find acceptable representation
Same error happens when I plug http://someurl/image1.jpg into a browser.
Oddly enough my tests check out ok:
Response response = given()
.pathParam("id", "image1.jpg")
.when()
.get("MyController/Image/{id}");
assertEquals(HttpStatus.OK.value(), response.getStatusCode());
byte[] array = response.asByteArray(); //byte array is identical to test image
How do I get this to behave like an image being served up in the normal way? (Note I can't change the content-type header that the android code is sending)
EDIT
Code after comments (set content type, take out produces):
#RequestMapping(value = "/Image/{id:.+}", method = RequestMethod.GET, consumes = MediaType.ALL_VALUE)
public ResponseEntity<byte[]> getImage(#PathVariable("id")String id, HttpServletResponse response) {
byte[] image = imageService.getImage(id); //this just gets the data from a database
response.setContentType(MediaType.IMAGE_JPEG_VALUE);
return ResponseEntity.ok(image);
}
In a browser this just seems to give a stringified junk (byte to chars i guess). In Android it doesn't error, but the image doesn't show.
I believe this should work:
#RequestMapping(value = "/Image/{id:.+}", method = RequestMethod.GET)
public ResponseEntity<byte[]> getImage(#PathVariable("id") String id) {
byte[] image = imageService.getImage(id);
return ResponseEntity.ok().contentType(MediaType.IMAGE_JPEG).body(image);
}
Notice that the content-type is set for ResponseEntity, not for HttpServletResponse directly.
Finally fixed this... I had to add a ByteArrayHttpMessageConverter to my WebMvcConfigurerAdapter subclass:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
final ByteArrayHttpMessageConverter arrayHttpMessageConverter = new ByteArrayHttpMessageConverter();
final List<MediaType> list = new ArrayList<>();
list.add(MediaType.IMAGE_JPEG);
list.add(MediaType.APPLICATION_OCTET_STREAM);
arrayHttpMessageConverter.setSupportedMediaTypes(list);
converters.add(arrayHttpMessageConverter);
super.configureMessageConverters(converters);
}
In case you don't know the file/mime type you can do this.... I've done this where i take an uploaded file and replace the file name with a guid and no extension and browsers / smart phones are able to load the image no issues.
the second is to serve a file to be downloaded.
#RestController
#RequestMapping("img")
public class ImageController {
#GetMapping("showme")
public ResponseEntity<byte[]> getImage() throws IOException{
File img = new File("src/main/resources/static/test.jpg");
return ResponseEntity.ok().contentType(MediaType.valueOf(FileTypeMap.getDefaultFileTypeMap().getContentType(img))).body(Files.readAllBytes(img.toPath()));
}
#GetMapping("thing")
public ResponseEntity<byte[]> what() throws IOException{
File file = new File("src/main/resources/static/thing.pdf");
return ResponseEntity.ok()
.header("Content-Disposition", "attachment; filename=" +file.getName())
.contentType(MediaType.valueOf(FileTypeMap.getDefaultFileTypeMap().getContentType(file)))
.body(Files.readAllBytes(file.toPath()));
}
}
UPDATE in java 9+ you need to add compile 'com.sun.activation:javax.activation:1.2.0' to your dependencies this has also been moved or picked up by jakarta.see this post
Using Apache Commons, you can do this and expose the image on an endpoint
#RequestMapping(value = "/image/{imageid}",method= RequestMethod.GET,produces = MediaType.IMAGE_JPEG_VALUE)
public #ResponseBody byte[] getImageWithMediaType(#PathVariable int imageid) throws IOException {
InputStream in = new ByteArrayInputStream(getImage(imageid));
return IOUtils.toByteArray(in);
}
All images will be served at endpoint /image/{imageid}
My problem is that I am getting the wrong sized file on the client side. Here is my #Controller ...
#RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
public ResponseEntity<?> download(final HttpServletRequest request,
final HttpServletResponse response,
#PathVariable("id") final int id) throws IOException {
try {
// Pseudo-code for retrieving file from ID.
Path zippath = getZipFile(id);
if (!Files.exists(zippath)) {
throw new IOException("File not found.");
}
ResponseEntity<InputStreamResource> result;
return ResponseEntity.ok()
.contentLength(Files.size(zippath))
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(new FileInputStream(zippath.toFile())));
} catch (Exception ex) {
// ErrorInfo is another class, unimportant
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorInfo(ex));
}
}
... and here is my client-side code using angular-file-saver ...
$http({url: "export/download/" + exportitem.exportId, withCredentials: true})
.then(function(response) {
function str2bytes(str) {
var bytes = new Uint8Array(str.length);
for (var i=0; i<str.length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
var blob = new Blob([str2bytes(response.data)], {type: 'application/octet-stream'});
FileSaver.saveAs(blob, "download.zip");
}, $exceptionHandler);
The original file is 935673 bytes but response.data is 900728 and passing it through the transformation to Uint8Array results in a Blob that is 900728 in size as well. Either way, the resulting saved file is 900728 bytes (34945 bytes shy). Also it is not quite the same in what gets written. It seems to slightly get bloated but then the last part just seems to be truncated. Any ideas what I might be doing wrong?
UPDATE
I just updated my controller method to be the following and got the exact same result. Grrr.
#RequestMapping(value = "/download/{id}", method = RequestMethod.GET)
public void download(final HttpServletRequest request,
final HttpServletResponse response,
#PathVariable("id") final int id) throws IOException {
// Pseudo-code for retrieving file from ID.
Path zippath = getZipFile(id);
if (!Files.exists(zippath)) {
throw new IOException("File not found.");
}
response.setContentType("application/zip");
response.setHeader("Content-Disposition",
"attachment; filename=download.zip");
InputStream inputStream = new FileInputStream(zippath.toFile());
org.apache.commons.io.IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
}
So the problem turned out to be angular's $http service. I also tried jQuery's ajax method. Both gave the same result. If I instead use the native XMLHttpRequest it works correctly. So the Java code was sound. I first verified this by exposing the file directly to the internet and then both using curl and directly accessing in the browser I managed to download the file of the correct size. Then I found this solution so that I could also download the file via javascript.
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = "blob";
xhr.withCredentials = true;
xhr.onreadystatechange = function (){
if (xhr.readyState === 4) {
var blob = xhr.response;
FileSaver.saveAs(blob, filename);
}
};
xhr.send();
Why does angular or jQuery give the wrong result? I still don't know but if anyone wishes to give an answer that uses those it would be appreciated.
responseType: blob
did the trick for a zip file
Angular 2 +
this.http.get('http://localhost:8080/export', { responseType: ResponseContentType.Blob })
.subscribe((res: any) => {
const blob = new Blob([res._body], { type: 'application/zip' });
saveAs(blob, "fileName.zip");
i just stumbled over the 'responseType' in $http requests, you are probably looking for 'blob': https://docs.angularjs.org/api/ng/service/$http#usage
I created an angular js program for downloading a file from the server here follows the code
HTML Code
<a download="fullList.csv" ng-href="{{ fullListUrl }}" type="button" class="btn btn-success btn-xs exec-batch" ng-click="exportCSVBulk(batchExec)">
<span class="glyphicon glyphicon-ok"></span> EXPORT AS CSV
</a>
AngularJS Controller
$scope.exportCSVBulk=function(){
var page = "../importExportService/exportBulkCSV/"+batchExec.id;
$http.get(page).success(function(response) {
$scope.fullListUrl = 'data:text/csv;charset=utf-8,' + escape(response);
});
}
Here what i am doing is when a user click on the EXPORT AS CSV link the function exportCSVBulk fires and from that function the url value (fullListUrl) sets. But this is an ajax request, so when a user click on the link the url, the response time become little bit long which results the url will not redirected properly. Is it possible to fix this problem? or is there is any alternative way to fix this?
I have faced the similar issue for downloading files such as .pdf, .xls, .xlsx etc through Ajax.
Its a fact that we cant download files through Ajax, even though i came up with a solution which downloads files through Ajax like.
You can use jquery.fileDownload - A jQuery File Download Plugin for Ajax like, feature rich file downloads.
Demo Working
Server Side
I am using Spring at the server side
#RequestMapping(value = "exportXLS", method = RequestMethod.POST, produces = APP_JSON)
#ResponseBody
public void getCSV(final HttpServletResponse response, #RequestParam(value = "empId", required = true) final String empId) throws IOException, Exception
{
final byte[] csv = ExportXLSUtil.getFileBytes(empId); // get the file bytes
final OutputStream output = getOutputStream(response);
response.setHeader("Content-Disposition", "attachment; filename=documents_" + new DateTime() + ".xls");
response.setContentType(CONTENT_TYPE);
response.setContentLength(csv.length);
write(output, csv);
}
Client Side
At the client side, I am using AngularJS
$downloadXLS = function(id)
{
$.fileDownload('/user/exportXLS',
{
httpMethod : "POST",
data : {
empId : id
}
}).done(function(e, response)
{
// success
}).fail(function(e, response)
{
// failure
});
}
Download Link - jquery.fileDownload.js
I created a more angular way solution. The server has to provide content-type and content-disposition if you want to sync with server info, although you could add type and download properties manually.
vm.export = function () {
//PopUps.showLoading()
$http.get(Url).then(function (result) {
//PopUps.hideLoading()
var headers = result.headers()
var blob = new Blob([result.data], { type: headers['content-type'] })
var windowUrl = (window.URL || window.webkitURL)
var downloadUrl = windowUrl.createObjectURL(blob)
var anchor = document.createElement("a")
anchor.href = downloadUrl
var fileNamePattern = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/
anchor.download = fileNamePattern.exec(headers['content-disposition'])[1]
document.body.appendChild(anchor)
anchor.click()
windowUrl.revokeObjectURL(blob)
})
}