I have two services returning two different ResponseEntity.
public ResponseEntity<InputStreamResource> getA(...) {
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_PDF)
.contentLength(out.size())
.body(new InputStreamResource(bis)); }
public ResponseEntity<InputStreamResource> getB(...) {
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_PDF)
.contentLength(out.size())
.body(new InputStreamResource(bis)); }
Each service has a controller that calls and returns.
public ResponseEntity<InputStreamResource> getA(...) {
return aService.getA(...) }
public ResponseEntity<InputStreamResource> getB(...) {
return bService.getB(...) }
I'm trying to create another controller which does and return both services at once.
public ResponseEntity<InputStreamResource> getAB(...) {
return aService.getA(...) *and* bService.getB(...) ?????? }
not sure how to combine two ResponseEntities returns into one.
Returning ResponseEntity from a Service method is not a good idea.
It's the Controller layer which should be responsible for generating ResponseEntity Object. That is his business not of the Service Layer. Similarly, the responsibility of the Service Layer is to prepare some kind of a DTO object based on the given input and then the Controller will wrap around that DTO and send it as a response.
So, I suggest to do some structural change here.
Service Layer
public InputStreamResource getA(...) {
return A
}
public InputStreamResource getB(...) {
return B
}
Controller Layer
public ResponseEntity<InputStreamResource> getA(...) {
return new ResponseEntity<>(aService.getA(...) (
}
public ResponseEntity<InputStreamResource> getB(...) {
return new ResponseEntity<>(bService.getB(...) )
}
To merge 2 streams
If you are targeting to stream 2 different pdf documents one by one, then I think the option will be to merge pdf documents in memory first with whatever pdf library you might be using. Then create a single InputStreamResource as Response.
But, If the streams can be run in sequence, then below is a working example to merge 2 streams using SequenceInputStream -
#RequestMapping(
path = "/sayHello",
method = RequestMethod.GET,
produces = MediaType.TEXT_PLAIN_VALUE
)
public ResponseEntity<InputStreamResource> get() {
byte[] inputBytes1 = "Hello".getBytes();
// 1st stream has "Hello" text
ByteArrayInputStream baos1 = new ByteArrayInputStream(inputBytes1);
byte[] inputBytes2 = "World".getBytes();
// 2nd stream has "World" text
ByteArrayInputStream baos2 = new ByteArrayInputStream(inputBytes2);
// combined stream will have "HelloWorld" text
SequenceInputStream combinedStream = new SequenceInputStream(baos1, baos2);
InputStreamResource inputStreamResource = new InputStreamResource(combinedStream);
return ResponseEntity.ok().body(inputStreamResource);
}
Output ->
curl -X GET http://localhost:8083/sayHello
HelloWorld
You can try via this way:
public ResponseEntity<List<InputStreamResource> getAAndB(...) {
private List<InputStreamResource> result = new ArrayList<>();
result.add(aService.getA(...));
result.add(bService.getB(...));
return ResponseEntity.ok().body(result)); }
}
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import com.itextpdf.text.Document;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfCopy;
import com.itextpdf.text.pdf.PdfReader;
#RestController
public class ReportController {
private static final Logger LOGGER = ...
#Autowired
private ReportManager manager;
#GetMapping("/reportcard/students")
public ResponseEntity<InputStreamResource> getStudentReportCard(parameters...) throws ServiceException {
List<InputStream> studentReportList = manager.getStudentReports(parameters.... );
HttpHeaders headers = getHeaders(Constants.STUDENTS_REPORT_CARD);
Document document = new Document(PageSize.LETTER);
ByteArrayOutputStream outputStream = null;
try {
outputStream = new ByteArrayOutputStream();
PdfCopy copy = new PdfCopy(document, outputStream);
document.open();
for (InputStream file : studentReportList) {
copy.addDocument(new PdfReader(file)); // writes directly to the output stream
}
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (document.isOpen()) {
document.close();
}
try {
if (outputStream != null) {
outputStream.close();
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
InputStreamResource inputStreamResource = new InputStreamResource(new ByteArrayInputStream(outputStream.toByteArray()));
return ResponseEntity.ok().headers(headers).contentType(MediaType.APPLICATION_PDF).body(inputStreamResource);
}
private HttpHeaders getHeaders(String fileName) {
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
headers.set("Content-disposition", "attachment; filename=" + fileName);
return headers;
}
public class ReportManagerImpl implements ReportManager {
private static final Logger LOGGER = ...
#Autowired
private HttpClient httpClient;
#Override
public List<InputStream> getStudentReports(parameters...)
throws ServiceException {
List<InputStream> studentReportList = new Vector<InputStream>();
List<String> learnersList = //list of students
String reportUrl = //url
for (String learner : learnersList) {
studentReportList.add(getHttpResponse(reportUrl + learner));
}
return studentReportList;
}
private InputStream getHttpResponse(String url) throws ServiceException {
try {
HttpResponse response = httpClient.execute(new HttpGet(url));
HttpEntity entity = response.getEntity();
return entity.getContent();
} catch (IOException e) {
throw new ServiceException(e.getMessage());
}
}
Related
I'm currently writing a post API that gets a list of invoices number and then calls another API with resttamplate to obtain a pdf for every invoice number after that I concatenate all these pdf files to one file and return this file as a response, the problem here that there are invoices have an invalid invoice number so when I send this invoice number to the rest API can't get pdf so I want to get the failed invoices and send them back to the caller of my rest API, how to return pdf of the successful invoice and JSON object that contain a list of failed invoices number. Thanks in advance
that's my postApi
#PostMapping(value = "/gen-report")
public ResponseEntity<byte[]> generateReport(
#RequestHeader(value = HttpHeaders.AUTHORIZATION) String headerAuthorization) {
byte[] res = null;
List<String> failedInvoices = new ArrayList<>();
ResponseEntity<byte[]> response = null;
ArrayList<RequestParameters> requests = new ArrayList<>();
RequestParameters rp1 = new RequestParameters("360", "3600382368", "N");
RequestParameters rp2 = new RequestParameters("360", "3600382367", "N");
requests.add(rp1);
requests.add(rp2);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
List<byte[]> responses = new ArrayList<>();
for (RequestParameters parameter : requests) {
MultiValueMap<String, String> map = mobileOrderReportService.genrateReportService(parameter);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map, headers);
response = null;
byte[] content = null;
content = requestReportByInvoiceNumber(entity);
if (content != null) {
responses.add(content);
} else {
failedInvoices.add(parameter.getOrderNum());
}
}
try {
res = mergePDF(responses);
} catch (DocumentException ex) {
Logger.getLogger(MobileOrderReportController.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(MobileOrderReportController.class.getName()).log(Level.SEVERE, null, ex);
}
headers.setContentType(MediaType.parseMediaType("application/pdf"));
String filename = "pdf1.pdf";
headers.add("content-disposition", "inline;filename=" + filename);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
response = new ResponseEntity<byte[]>(res, headers, HttpStatus.OK);
return response;
}
this method returns the byte[] with the successful invoice or null with the failed invoice
public byte[] requestReportByInvoiceNumber(HttpEntity<MultiValueMap<String, String>> entity) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<byte[]> response = null;
try {
response = restTemplate.exchange(mobileOrderReportService.getUrl(), HttpMethod.POST, entity,
byte[].class);
byte[] content = response.getBody();
return content;
} catch (RestClientException ex) {
logger.error("request to UReport failed in requestReportByInvoiceNumber method !...");
return null;
}
}
method merge pdf and return one pdf
public byte[] mergePDF(List<byte[]> pdfFilesAsByteArray) throws DocumentException, IOException {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
Document document = null;
PdfCopy writer = null;
for (byte[] pdfByteArray : pdfFilesAsByteArray) {
try {
PdfReader reader = new PdfReader(pdfByteArray);
int numberOfPages = reader.getNumberOfPages();
if (document == null) {
document = new Document(reader.getPageSizeWithRotation(1));
writer = new PdfCopy(document, outStream); // new
document.open();
}
PdfImportedPage page;
for (int i = 0; i < numberOfPages;) {
++i;
page = writer.getImportedPage(reader, i);
writer.addPage(page);
}
} catch (Exception e) {
e.printStackTrace();
}
}
document.close();
outStream.close();
return outStream.toByteArray();
}
You already tagged "Multipart", which could be a solution. You're currently not sending one but just a byte array i.e. a file. With a multipart response, you could indeed have multiple (or in your case 2) parts:
Part with the merged PDF
List of failed invoice numbers either as plain text, JSON, or however you would like to send it.
A multipart response looks like this https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html (scroll down to the example)
An easier and "dirtier" way would be, to just include the faulty invoice numbers in your header response. You can define custom headers, so feel free to name it as you wish.
Either way, your client needs to be adapted, either by being able to read a multipart response (for which you need to write an HttpMessageConverter if your client isn't communicating reactively (Webflux)) or by reading the custom header.
I have a controller in gateway microservice that accepts the MultipartFile and resends to the service behind it
#PostMapping
public ResponseEntity upload(#ApiParam(name = "file", value = "File", required = true) MultipartFile file)
throws BaseException {
if (Objects.isNull(file)){
throw new CheckFieldException("file", MultipartFile.class);
}
if (megabyte * maxFileSize - file.getSize() < 0){
return ResponseEntity.accepted().body(new DocumentResponseDTO(false, "File size exceeds " + maxFileSize + "MB"));
}
DiscoveryConfig.CashTracking config = discoveryConfig.getCashTracking();
UriComponents uriStatementUpload = UriComponentsBuilder.newInstance().scheme(config.getScheme())
.host(config.getHost()).port(config.getPort()).path(config.getExcelNominalOperationsPath()).build(true);
try {
HttpEntity<byte[]> fileEntity = new HttpEntity(file.getBytes());
ResponseEntity<DocumentResponseDTO> entity = restTemplate.postForEntity(uriStatementUpload.toUri(), fileEntity, DocumentResponseDTO.class);
return entity;
} catch (HttpClientErrorException e) {
return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsString());
} catch (IOException e) {
return ResponseEntity.status(500).body("IOException while getting bytes stream from file");
}
}
and in CashTracking service there is also file upload like that:
#PostMapping(value = "/upload")
public ResponseEntity uploadExcelNominalOperationsFile(#ApiParam(name = "file", value = "File", required = true) MultipartFile file) throws IOException {
try (InputStream is = file.getInputStream()) {
log.info("Processing incoming Excel file with nominal operations");
Workbook workbook = new XSSFWorkbook(is);
log.info("Processing workbook");
Sheet sheet = workbook.getSheetAt(0);
log.info("Processing the first sheet");
List<NominalOperationVO> nominalOperationVOs = new ArrayList<>();
List<String> fileHeaders = new ArrayList<>();
And when the file is actually uploaded to the gateway service, the service behind it starts processing the file upload, but the MultipartFile file is null. I have explicitly put it in the Entity I have sent to the service behind the gateway, the question, what I'm doing wrong if it is null? If I do upload to that microservice directly, it process the request correctly.
The main stuff I was missing was putting the Http headers per specific multipart form's parts. They should be identical to what has been sent to the gateway service.
public ResponseEntity upload(#ApiParam(name = "file", value = "Файл", required = true) MultipartFile file)
throws BaseException {
if (Objects.isNull(file)){
throw new CheckFieldException("file", MultipartFile.class);
}
if (megabyte * maxFileSize - file.getSize() < 0){
return ResponseEntity.accepted().body(new DocumentResponseDTO(false, "File size exceeds " + maxFileSize + "MB"));
}
DiscoveryConfig.CashTracking config = discoveryConfig.getCashTracking();
UriComponents uriStatementUpload = UriComponentsBuilder.newInstance().scheme(config.getScheme())
.host(config.getHost()).port(config.getPort()).path(config.getExcelNominalOperationsPath()).build(true);
try {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultipartBodyBuilder multipartBodyBuilder = new MultipartBodyBuilder();
//here is the really needed stuff with 2 headers
Resource resource = new ByteArrayResource(file.getBytes());
multipartBodyBuilder.part("file", resource)
.header("Content-Type",file.getContentType())
.header("Content-Disposition","form-data; name=\"file\"; filename=\""+file.getOriginalFilename()+"\"");
// multipart/form-data request body
MultiValueMap<String, HttpEntity<?>> body = multipartBodyBuilder.build();
HttpEntity<MultiValueMap<String, HttpEntity<?>>> requestEntity
= new HttpEntity<>(body, headers);
ResponseEntity<DocumentResponseDTO> entity = restTemplate.postForEntity(uriStatementUpload.toUri(), requestEntity, DocumentResponseDTO.class);
return entity;
} catch (HttpClientErrorException e) {
return ResponseEntity.status(e.getStatusCode()).body(e.getResponseBodyAsString());
} catch (IOException e) {
return ResponseEntity.status(500).body("IOException while getting bytes stream from file");
}
}
Right now I have 2 Spring App.
App A will have a controller that will receive a video file ad Multipart file and sending the file to App B via rest template.
Some Code from App A that handle sending request to App B.
#RestController
public class AppAController {
#Autowired
private final AppBService service;
#PostMapping("/sendToB")
public ResponseEntity<String> contoller(#RequestParam("file") MultipartFile file) {
String result = service.sendToB(file);
return new ResponseEntity<>(result, HttpStatus.OK);
}
}
#Service
public class AppBService {
public String sendToB(MultipartFile file) throws ResponseStatusException {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("file", file);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
String serverUrl = "http://127.0.0.1:8090/makeFrames";
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
serverUrl,
HttpMethod.POST,
requestEntity,
String.class
);
if (response.getStatusCode() != HttpStatus.OK) {
throw new ResponseStatusException(response.getStatusCode(), response.getBody());
}
return response.getBody();
}
}
And for App B, it will receive a video and extracting key frames from a video using JavaCV.
#RestController
public class ProcessorController {
#PostMapping("/makeFrames")
public ResponseEntity<String> framesExtractorController(#RequestParam("file") MultipartFile file) {
try {
File result = FramesExtractor.grabFrames(file);
return new ResponseEntity<>(result.getAbsolutePath(), HttpStatus.OK);
} catch (IOException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
}
}
}
public class FramesExtractor {
private static final Logger LOG = LoggerFactory.getLogger(FramesExtractor.class);
private FramesExtractor() {
}
public static File grabFrames(MultipartFile video) throws IOException {
LOG.info("Extracting Frames from the video " + video.getName());
String directoryName = video.getOriginalFilename() + "-frames-result";
directoryAssurance(directoryName);
try (FFmpegFrameGrabber frameGrabber = new FFmpegFrameGrabber(video.getInputStream());
Java2DFrameConverter converter = new Java2DFrameConverter()) {
frameGrabber.setImageWidth(480);
frameGrabber.setImageHeight(360);
frameGrabber.start();
Frame frame;
int i = 0;
while ((frame = frameGrabber.grabKeyFrame()) != null) {
BufferedImage bi = converter.getBufferedImage(frame);
ImageIO.write(bi, "png", new File(directoryName + "/" + String.format("%03d", i) + ".png"));
i++;
}
frameGrabber.stop();
LOG.info("Finish Extracting Frames");
return new File(directoryName);
} catch (Exception e) {
LOG.error(e.getMessage());
throw e;
}
}
}
Both app A and B have these properties set to their application.properties files.
server.port={There respective port}
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=100MB
spring.servlet.multipart.max-request-size=100MB
And when i testing the application by using PostMan to send post request with video file in form-data body to App A /sendToB api i receive this error message.
"Type definition error: [simple type, class java.io.FileDescriptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile[\"inputStream\"]->java.io.FileInputStream[\"fd\"])"
So I want to ask what cause this problems and how I can fix it or sending video file to other Spring App running on other port properly.
I edited my previous answer because you can actually get parameters from a response body in a POST with #RequestParam annotation.
I would use JSON as content type. This is my solution:
public String sendToB(MultipartFile file) throws ResponseStatusException {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
byte[] bytesFile = file.getBytes();
String base64String = Base64.getEncoder().encodeToString(bytesFile);
Map<String, Object> body = new HashMap<>();
body.put("file", base64String);
body.put("directoryName", file.getOriginalFilename());
body.put("videoName", file.getName())
HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(body, headers);
String serverUrl = "http://127.0.0.1:8090/makeFrames";
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.exchange(
serverUrl,
HttpMethod.POST,
requestEntity,
String.class
);
if (response.getStatusCode() != HttpStatus.OK) {
throw new ResponseStatusException(response.getStatusCode(), response.getBody());
}
return response.getBody();
}
For App B, the code would be:
#PostMapping("/makeFrames")
public ResponseEntity<String> framesExtractorController(#RequestBody Map<String, Object> requestBody) {
try {
String base64String = requestBody.get("file").toString();
byte[] bytesFile = Base64.getDecoder().decode(base64String);
InputStream inputStream = new ByteArrayInputStream(bytesFile);
String directoryName = requestBody.get("directoryName").toString();
String videoName = requestBody.get("videoName").toString();
File result = FramesExtractor.grabFrames(inputStream, directoryName, videoName);
return new ResponseEntity<>(result.getAbsolutePath(), HttpStatus.OK);
} catch (IOException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.NOT_FOUND);
}
}
}
public static File grabFrames(InputStream inputStream, String directoryName, String videoName) throws IOException {
//Your code
}
I wrote a method that gets JSON, converts an object to java and writes JSON to a file.But writing to the file just does not work. Tell me what could be the reason?
public class ApiUtils {
public static HttpClient client = HttpClient.newHttpClient();
public static void getRequest(String url, String path) {
PostDTO postDTO = new PostDTO();
String pathJSONFile = "src/main/resources/Post.json";
List<PostPojo> postPojos = null;
HttpRequest request = HttpRequest.newBuilder()
.GET()
.header("accept", "application/json")
.uri(URI.create(url + path))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
ObjectMapper objectMapper = new ObjectMapper();
postPojos = objectMapper.readValue(response.body(), new TypeReference<List<PostPojo>>() {
});
objectMapper.writeValue(Paths.get("allPost.json").toFile(), response.body());
postDTO.setStatus(response.statusCode());
postDTO.setPosts(postPojos);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(postDTO);
}
}
File allPost.json located in the folder resources.
You're writing the wrong object I think - your code sample says
objectMapper.writeValue(Paths.get("allPost.json").toFile(), response.body());
which is trying to write the InputStream of response.body(). I presume you meant to try and write out postPojos via
objectMapper.writeValue(Paths.get("allPost.json").toFile(), postPojos);
instead.
Hi, I wanted to return a file from a resteasy server. For this purpose, I have a link at the client side which is calling a rest service with ajax. I want to return the file in the rest service. I tried these two blocks of code, but both didn't work as I wanted them to.
#POST
#Path("/exportContacts")
public Response exportContacts(#Context HttpServletRequest request, #QueryParam("alt") String alt) throws IOException {
String sb = "Sedat BaSAR";
byte[] outputByte = sb.getBytes();
return Response
.ok(outputByte, MediaType.APPLICATION_OCTET_STREAM)
.header("content-disposition","attachment; filename = temp.csv")
.build();
}
.
#POST
#Path("/exportContacts")
public Response exportContacts(#Context HttpServletRequest request, #Context HttpServletResponse response, #QueryParam("alt") String alt) throws IOException {
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=temp.csv");
ServletOutputStream out = response.getOutputStream();
try {
StringBuilder sb = new StringBuilder("Sedat BaSAR");
InputStream in =
new ByteArrayInputStream(sb.toString().getBytes("UTF-8"));
byte[] outputByte = sb.getBytes();
//copy binary contect to output stream
while (in.read(outputByte, 0, 4096) != -1) {
out.write(outputByte, 0, 4096);
}
in.close();
out.flush();
out.close();
} catch (Exception e) {
}
return null;
}
When I checked from the firebug console, both of these blocks of code wrote "Sedat BaSAR" in response to the ajax call. However, I want to return "Sedat BaSAR" as a file. How can I do that?
Thanks in advance.
There're two ways to to it.
1st - return a StreamingOutput instace.
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response download() {
InputStream is = getYourInputStream();
StreamingOutput stream = new StreamingOutput() {
public void write(OutputStream output) throws IOException, WebApplicationException {
try {
output.write(IOUtils.toByteArray(is));
}
catch (Exception e) {
throw new WebApplicationException(e);
}
}
};
return Response.ok(stream, MediaType.APPLICATION_OCTET_STREAM).header("content-disposition", "attachment; filename=\"temp.csv\"").build();
}
You can return the filesize adding Content-Length header, as the following example:
return Response.ok(stream, MediaType.APPLICATION_OCTET_STREAM).header("content-disposition", "attachment; filename=\"temp.csv\"").header("Content-Length", getFileSize()).build();
But if you don't want to return a StreamingOutput instance, there's other option.
2nd - Define the inputstream as an entity response.
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response download() {
InputStream is = getYourInputStream();
return Response.code(200).entity(is).build();
}