I want to return a zipped file from my server-side java using JAX-RS to the client.
I tried the following code,
#GET
public Response get() throws Exception {
final String filePath = "C:/MyFolder/My_File.zip";
final File file = new File(filePath);
final ZipOutputStream zop = new ZipOutputStream(new FileOutputStream(file);
ResponseBuilder response = Response.ok(zop);
response.header("Content-Type", "application/zip");
response.header("Content-Disposition", "inline; filename=" + file.getName());
return response.build();
}
But i'm getting exception as below,
SEVERE: A message body writer for Java class java.util.zip.ZipOutputStream, and Java type class java.util.zip.ZipOutputStream, and MIME media type application/zip was not found
SEVERE: The registered message body writers compatible with the MIME media type are:
*/* ->
com.sun.jersey.core.impl.provider.entity.FormProvider
What is wrong and how can I fix this?
You are delegating in Jersey the knowledge of how to serialize the ZipOutputStream. So, with your code you need to implement a custom MessageBodyWriter for ZipOutputStream. Instead, the most reasonable option might be to return the byte array as the entity.
Your code looks like:
#GET
public Response get() throws Exception {
final File file = new File(filePath);
return Response
.ok(FileUtils.readFileToByteArray(file))
.type("application/zip")
.header("Content-Disposition", "attachment; filename=\"filename.zip\"")
.build();
}
In this example I use FileUtils from Apache Commons IO to convert File to byte[], but you can use another implementation.
You can write the attachment data to StreamingOutput class, which Jersey will read from.
#Path("/report")
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response generateReport() {
String data = "file contents"; // data can be obtained from an input stream too.
StreamingOutput streamingOutput = outputStream -> {
ZipOutputStream zipOut = new ZipOutputStream(new BufferedOutputStream(outputStream));
ZipEntry zipEntry = new ZipEntry(reportData.getFileName());
zipOut.putNextEntry(zipEntry);
zipOut.write(data); // you can set the data from another input stream
zipOut.closeEntry();
zipOut.close();
outputStream.flush();
outputStream.close();
};
return Response.ok(streamingOutput)
.type(MediaType.TEXT_PLAIN)
.header("Content-Disposition","attachment; filename=\"file.zip\"")
.build();
}
In Jersey 2.16 file download is very easy
Below is the example for the ZIP file
#GET
#Path("zipFile")
#Produces("application/zip")
public Response getFile() {
File f = new File(ZIP_FILE_PATH);
if (!f.exists()) {
throw new WebApplicationException(404);
}
return Response.ok(f)
.header("Content-Disposition",
"attachment; filename=server.zip").build();
}
I'm not sure I it's possible in Jersey to just return a stream as result of annotated method. I suppose that rather stream should be opened and content of the file written to the stream. Have a look at this blog post. I guess You should implement something similar.
Related
I'm trying to use Spring WebClient to make some basic REST API calls. I'm getting an error that the request is malformed, but I can't tell exactly why. Is there any way to easily log the contents of the request (really, just the request body)? Everything I find online is super complicated. Here's what I have:
LinkedMultiValueMap params = new LinkedMultiValueMap();
params.add("app_id", getOneSignalAppId());
params.add("included_segments", inSegment);
params.add("content_available", true);
params.add("contents", new LinkedMultiValueMap() {{
add("en", inTitle);
}});
BodyInserters.MultipartInserter inserter = BodyInserters.fromMultipartData(params);
WebClient client = WebClient.builder()
.baseUrl("https://onesignal.com")
.defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + getOneSignalKey())
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
Mono<NotificationResponse> result = client
.post()
.uri("/api/v1/notifications")
.body(inserter)
.retrieve()
.bodyToMono(NotificationResponse.class);
I just want a string of the JSON that will be inserted into the request body.
You can create your own wrapper/proxy class around the JSON encoder (assuming you're using JSON) and intercept the serialized body before it is sent into the intertubes.
If your request is going to send JSON.
Specifically, you would extend the encodeValue method (or encodeValues in case of streaming data) of Jackson2JsonEncoder (the default encoder). Then you can do with that data what you wish, such as logging etc. And you could even do this conditionally based on environment/profile.
This custom logging-encoder can be specified when creating the WebClient, by providing it as a codec:
CustomBodyLoggingEncoder bodyLoggingEncoder = new CustomBodyLoggingEncoder();
WebClient.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(bodyLoggingEncoder);
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(new ObjectMapper(), MediaType.APPLICATION_JSON));
})
...
I made a blog post about this. You might be able to find the encoder for Multipart data and apply similar principles
For completeness, the encoder might look something like this:
public class CustomBodyLoggingEncoder extends Jackson2JsonEncoder {
#Override
public DataBuffer encodeValue(final Object value, final DataBufferFactory bufferFactory,
final ResolvableType valueType, #Nullable final MimeType mimeType, #Nullable final Map<String, Object> hints) {
// Encode/Serialize data to JSON
final DataBuffer data = super.encodeValue(value, bufferFactory, valueType, mimeType, hints);
// This is your code:
SomethingAmazing.doItWithThisData(extractBytes(data));
// Return the data as normal
return data;
}
private byte[] extractBytes(final DataBuffer data) {
final byte[] bytes = new byte[data.readableByteCount()];
data.read(bytes);
// We've copied the data above to our array, but must reset the buffer for actual usage
data.readPosition(0);
return bytes;
}
}
Hope that helps somehow!
I'm trying to POST a multipart/form-data using Spring RestTemplate with a byte array as the file to upload and it keeps failing (Server rejects with different kinds of errors).
I'm using a MultiValueMap with ByteArrayResource. Is there something I'm missing?
Yes there is something missing.
I have found this article:
https://medium.com/#voziv/posting-a-byte-array-instead-of-a-file-using-spring-s-resttemplate-56268b45140b
The author mentions that in order to POST a byte array using Spring RestTemplate one needs to override getFileName() of the ByteArrayResource.
Here is the code example from the article:
private static void uploadWordDocument(byte[] fileContents, final String filename) {
RestTemplate restTemplate = new RestTemplate();
String fooResourceUrl = "http://localhost:8080/spring-rest/foos"; // Dummy URL.
MultiValueMap<String, Object> map = new LinkedMultiValueMap<String, Object>();
map.add("name", filename);
map.add("filename", filename);
// Here we
ByteArrayResource contentsAsResource = new ByteArrayResource(fileContents) {
#Override
public String getFilename() {
return filename; // Filename has to be returned in order to be able to post.
}
};
map.add("file", contentsAsResource);
// Now you can send your file along.
String result = restTemplate.postForObject(fooResourceUrl, map, String.class);
// Proceed as normal with your results.
}
I tried it and it works!
I added an issue to send a request from java client to Python service in FastApi and sending a ByteArrayResource instaead of simple byte[] fixed the issue.
FastAPI server returned: "Expected UploadFile, received: <class 'str'>","type":"value_error""
I have the below class that tries to return some data in the form of an excel spreadsheet. I'm getting the error
MessageBodyWriter not found for media type=application/octet-stream, type=class org.apache.poi.xssf.usermodel.XSSFWorkbook
I've also tried #Produces("application/vnd.ms-excel"), but have gotten similar errors. Anyone have a suggestion as to how I can get this to return a spreadsheet? The last time I got an error message similar to this (complaining that a message body writer couldn't be found for arraylist) I just wrapped it in a generic entity. That trick didn't work this time.
#PermitAll
#Path("uploadWorkbook")
public class ExcelUploadResource {
#Context
ResourceContext resourceContext;
#Inject
JobService jobService;
#GET
#Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response list() {
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet("Job definitions");
int rowNum = 0;
for(Job job : jobService.list()){
Row row = sheet.createRow(rowNum++);
int cellNum = 0;
for(String field : job.toList()){
Cell cell = row.createCell(cellNum++);
cell.setCellValue(field);
}
}
GenericEntity<XSSFWorkbook> entity = new GenericEntity<XSSFWorkbook>(workbook) {};
ResponseBuilder response = Response.ok(entity);
response.header("Content-Disposition",
"attachment; filename=jobs.xls");
return response.build();
}
}
You can't just use arbitrary objects with the data type application/octet-stream. What you first need to understand is how objects are serialized. This is done with the use of MessageBodyWriters. You can learn more about them in JAX-RS Entity Providers.
How the writer works is that it is passed the entity and the response stream. The writer is supposed to take the entity and write the contents of the entity to the response stream. The writers are looked up by the type of entity we return and the media type expected, in your case you want it to be application/octet-stream.
What the error is saying is that there is no writer to handle the conversion of your XSSFWorkbook. When you talk about application/octet-stream, you're mostly dealing with binary files. XSSFWorkbook is not a binary file. When working with application/octet-stream, you'll mostly be working with byte[], File, InputStream, and StreamingOutput entity types. So if you want to use application/octet-stream, then you would need to change the entity to be one of those types.
I've never used Apache POI, but just going through a quick tutorial, it looks like what you probably want to use for this case is the StreamingOutput, you can just use the XSSFWorkbook#write(OutputStream) method to write the workbook to the StreamingOutput
public Response getExcelFile() {
XSSFWorkbook workbook = new XSSFWorkbook();
...
StreamingOutput output = new StreamingOutput() {
#Override
public void write(OutputStream out)
throws IOException, WebApplicationException {
workbook.write(out);
out.flush();
}
};
return Response.ok(output)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=jobs.xls")
.build();
}
This question already has answers here:
SpringBoot: Large Streaming File Upload Using Apache Commons FileUpload
(5 answers)
Closed 2 years ago.
Spring boot's default MultiPartResolver interface handles the uploading of multipart files by storing them on the local file system. Before the controller method is entered, the entire multipart file must finish uploading to the server.
We are storing all of our uploaded files directly to a database and our servers have a very small disk quota, so if a large file is uploaded, we are seeing an IOExeption - Disk quota exceeded.
Is there a way to get the stream directly from the client's incoming request before Spring's MultiPartResolver stores the file on the local filesystem so the we can stream directly to our db?
You could use apache directly, as described here https://commons.apache.org/proper/commons-fileupload/streaming.html.
#Controller
public class UploadController {
#RequestMapping("/upload")
public String upload(HttpServletRequest request) throws IOException, FileUploadException {
ServletFileUpload upload = new ServletFileUpload();
FileItemIterator iterator = upload.getItemIterator(request);
while (iterator.hasNext()) {
FileItemStream item = iterator.next();
if (!item.isFormField()) {
InputStream inputStream = item.openStream();
//...
}
}
}
}
Make sure to disable springs multipart resolving mechanism.
application.yml:
spring:
http:
multipart:
enabled: false
Actually it is not trivial task. If you would like to write stream from client right to the database, you have to process request manually. There are some libraries, that can make this task simpler. One of them is "Apache Commons FileUpload". Below very simple example, how can you process incoming multipart/form-data request by this library.
#Controller
public class Controller{
#RequestMapping("/upload")
public String upload(HttpServletRequest request){
String boundary = extractBoundary(request);
try {
MultipartStream multipartStream = new MultipartStream(request.getInputStream(),
boundary.getBytes(), 1024, null);
boolean nextPart = multipartStream.skipPreamble();
while(nextPart) {
String header = multipartStream.readHeaders();
if(header.contains("filename")){
//if input is file
OutputStream output = createDbOutputStream();
multipartStream.readBodyData(output);
output.flush();
output.close();
} else {
//if input is not file (text, checkbox etc)
ByteArrayOutputStream output = new ByteArrayOutputStream();
multipartStream.readBodyData(output);
String value = output.toString("utf-8");
//... do something with extracted value
}
nextPart = multipartStream.readBoundary();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private String extractBoundary(HttpServletRequest request) {
String boundaryHeader = "boundary=";
int i = request.getContentType().indexOf(boundaryHeader)+
boundaryHeader.length();
return request.getContentType().substring(i);
}
}
Header for file field will looks like:
Content-Disposition: form-data; name="fieldName"; filename="fileName.jpg"
Content-Type: image/jpeg
Header for simple field will looks like:
Content-Disposition: form-data; name="fieldName";
Note, that this snippet is just simplified example to show you direction. There is no some details like: extract field name from header, create database output stream etc. You can implement all of this stuff by your own.
Examples of multipart request's field headers you can find in RFC1867. Information about multipart/form-data RFC2388.
I'm trying to serve images from mongodb GridFS. My Controller.
#RequestMapping(value = "{id}", method = RequestMethod.GET)
public void getPhoto (#PathVariable String id, HttpServletResponse response, HttpServletRequest request) {
log.info("#getPhoto > ip of request: " + request.getRemoteAddr() + ", id: " + id);
final InputStream inputStream = resourceService.getMediaResourceById(id);
try {
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
} catch (IOException | NullPointerException e) {
log.error("#getPhoto > error with request for objectId: " + id, e);
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
}
The result:
This only happens using Spring Boot. as a test when using Spring and running the exact same code i'm getting:
Writing directly to a response is discouraged in controller methods for various reasons. You are essentially responsible for almost everything yourself. The preferred way is to return something that gets converted as needed.
You already use ResponseEntity<byte[]> now. But your source is a stream and you have to create an unnecessary byte array. You can use Resource instead that wraps all sorts of input streams, be it from files or already opened input streams.
InputStreamResource inputStream = new InputStreamResource(resourceService.getMediaResourceById(id));
return new ResponseEntity<>(inputStream, HttpStatus.OK);
or as of Spring 4.1
return ResponseEntity.ok(inputStream);
Please note that produces = MediaType.IMAGE_JPEG_VALUE doesn't actually set a content type. It's used for content negotiation.