I want to serve a .yaml file via a REST endpoint with Spring, I know that it cannot be directly displayed in a browser (just talking about Chrome here), since it doesn't support display of yaml files.
I have included what I think is the necessary library for this purpose
compile group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.9.9'.
If I open the endpoint /v2/api-doc in the browser, it will prompt me, to download a file named exactly as the endpoint /v2/api-doc. It contains the correct content.
Question: Is there a way to correctly transfer the .yaml file, so that the user will be prompted to safe myfile.yaml?
#RequestMapping(value = "/v2/api-doc", produces = "application/x-yaml")
public ResponseEntity<String> produceApiDoc() throws IOException {
byte[] fileBytes;
try (InputStream in = getClass().getResourceAsStream("/restAPI/myfile.yaml")) {
fileBytes = IOUtils.toByteArray(in);
}
if (fileBytes != null) {
String data = new String(fileBytes, StandardCharsets.UTF_8);
return new ResponseEntity<>(data, HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
You should set a Content-Disposition header (and I recommend using ResourceLoader to load resources in Spring Framework).
Example:
#RestController
public class ApiDocResource {
private final ResourceLoader resourceLoader;
public ApiDocResource(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
#GetMapping(value = "/v2/api-doc", produces = "application/x-yaml")
public ResponseEntity produceApiDoc() throws IOException {
Resource resource = resourceLoader.getResource("classpath:/restAPI/myfile.yaml");
if (resource.exists()) {
return ResponseEntity
.ok()
.contentType(MediaType.parseMediaType("application/x-yaml"))
.header("Content-Disposition", "attachment; filename=myfile.yaml")
.body(new InputStreamResource(resource.getInputStream()));
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
}
Related
Good afternoon.
I can't get the browser to download the file from the server. I took the code from a previous project and it doesn't work. Please explain why.
On the server, files are collected in a zip archive. It is necessary to download the archive. I am using this:
My controller
#SneakyThrows
#GetMapping("/report/UploadDocuments")
#ResponseBody
public ResponseEntity<InputStreamResource> uploadDocuments(HttpServletResponse response,
#RequestParam("check") String check){
deleteAllFilesFolder(Directories.DYRECTORY_EXPORT);
ArrayList<Long> idDocuments = Converter.arrayStringInLong(check);
List<PaymentOrderArchive> documents = paymentOrderArchiveService.findAllById(idDocuments);
for (PaymentOrderArchive p : documents){
UploadingFiles.dowloadFileInDirectory(p);
}
//"method" create zip-file and return path
String s = method(documents);
//Problem here
UploadingFiles up = new UploadingFiles();
return up.downloadFile1(servletContext);
}
My method for dowload
public ResponseEntity<InputStreamResource> downloadFile1(ServletContext servletContext) throws IOException {
MediaType mediaType = MediaTypeUtils.getMediaTypeForFileName(servletContext, Directories.NAME_ZIP);
File file = new File(Directories.DYRECTORY_EXPORT + Directories.NAME_ZIP);
InputStreamResource resource = new InputStreamResource(new FileInputStream(file));
return ResponseEntity.ok()
// Content-Disposition
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + Directories.NAME_ZIP)
// Content-Type
.contentType(mediaType)
// Contet-Length
.body(resource);
}
public class MediaTypeUtils {
public static MediaType getMediaTypeForFileName(ServletContext servletContext, String
fileName) {
String mineType = servletContext.getMimeType(fileName);
try {
MediaType mediaType = MediaType.parseMediaType(mineType);
return mediaType;
} catch (Exception e) {
return MediaType.APPLICATION_OCTET_STREAM;
}
}
Everything is working. The program does not give errors. But the file is not downloading. And I can't understand why.
We have a RestController with the below endpoint
#PostMapping(path = "/downloadFile", produces = MediaType.MULTIPART_FORM_DATA_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
public FileDownloadResponse downloadFile(#RequestBody FileDownloadRequest request) {
FileDownloadResponse downloadResponse = new FileDownloadResponse();
File file = new File("c:/fileLocation/"+request.getFileName());
try (InputStream stream = new FileInputStream(file)) {
byte[] bytes = IOUtil.toByteArray(stream);
downloadResponse.setFileName(file.getName());
downloadResponse.setCheckSum(calculateCheckSum(bytes));
downloadResponse.setFileContents(new FileSystemResource(bytes, file.getName()));
} catch (Exception e) {
e.printStackTrace();
}
return downloadResponse;
}
public class FileDownloadResponse {
private String fileName;
private Long checkSum;
private Resource fileContents;
}
public static class FileSystemResource extends ByteArrayResource {
private String fileName;
public FileSystemResource(byte[] byteArray , String filename) {
super(byteArray);
this.fileName = filename;
}
public String getFilename() { return fileName; }
public void setFilename(String fileName) { this.fileName= fileName; }
}
And on the Client Side we have the below code,
public class FileDownloadResponseClient {
private String fileName;
private Long checkSum;
private MultipartFile fileContents;
}
public FileDownloadResponseClient download(FileDownloadRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Collections.singletonList(Mediatype.ALL));
HttpEntity<FileDownloadRequest> requestEntity = new HttpEntity<>(request, headers);
return restTemplate.postForEntity(downloadUrl, requestEntity, FileDownloadResponseClient.class);
}
When we run the Rest Client above, we are getting the below error,
org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : [no body]
Is it possible to download a multipartfile along with other additional fields? If yes, what is that we are missing here, please let us know.
Thanks in Advance!
org.springframework.web.multipart has a method boolean isEmpty() to find if the file has no content. Best put that check there and redirect to a message about such a file multipart form.
Of [no body] i have found that message on test requests to http server but in entirety generally means there is nothing in the form or no extra information needed for the server to complete the request.
For now i presume the spring framework handles all the url decoding and boundary marker stripping (on both ends) of uploaded files.
I have some webservice endpoints that should offer json data by default. Therefore configuring as follows:
#Configuration
public class ContentNegotiationConfiguration implements WebMvcConfigurer {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
Problem: now I want to create an endpoint that offers a file download (thus is not json).
#RestController
public class FileServlet {
#GetMapping(value = "/docs/{filename}", consumes = MediaType.ALL_VALUE, produces = APPLICATION_OCTET_STREAM_VALUE)
public Object download(#Pathvariable filename) {
File file = fileservice.resolve(filename);
return new FileSystemResource(file);
}
}
Accessing this endpoint from the browser works fine. I can download the files.
But: when using native clients that are not setting any http headers like content-type, accept-header etc, the access fails with:
WARN o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver: Resolved
[org.springframework.web.HttpMediaTypeNotAcceptableException:
Could not find acceptable representation]
All of them result in the exception:
curl localhost:8080/docs/testfile.txt
curl -O localhost:8080/docs/testfile.txt
wget localhost:8080/docs/testfile.txt
This is probably because I set the default content type to json above in ContentNegotiationConfiguration. I cannot change that due to all the other endpoints that should be json by default.
Question: how can I explicit ignore that default json setting on that single endpoint, and always just offer the download stream?
Try custom ContentNegotiationStrategy with AntPathMatcher something like:
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// configurer.defaultContentType(MediaType.APPLICATION_JSON,MediaType.APPLICATION_OCTET_STREAM);
configurer.defaultContentTypeStrategy(
new ContentNegotiationStrategy() {
private UrlPathHelper urlPathHelper = new UrlPathHelper();
AntPathMatcher antPathMatcher = new AntPathMatcher();
#Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request == null) {
return null;
}
String path = this.urlPathHelper.getLookupPathForRequest(request);
if (antPathMatcher.match("/docs/*", path)) {
return Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM);
} else {
return Collections.singletonList(MediaType.APPLICATION_JSON);
}
}
});
}
With the hint from #M. Deinum, I got it working as follows:
#GetMapping(value = "/docs/{filename}")
public void download(#Pathvariable filename) {
FileSystemResource file = new FileSystemResource(fileservice.resolve(filename));
rsp.setHeader("Content-Disposition", "attachment; filename=" + file.getFilename());
ResourceHttpMessageConverter handler = new ResourceHttpMessageConverter();
handler.write(file, MediaType.APPLICATION_OCTET_STREAM, new ServletServerHttpResponse(rsp));
}
That way writing directly to the stream bypassing the content negotiation, while still relying on the Spring class ResourceHttpMessageConverter for not having to implement the response writer myself.
We are using spring controllers to handle file uploads:
For example:
#RequestMapping(value = "/scan", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ScanResult scan(HttpServletRequest request) throws IOException, FileUploadException {
return scanService.scanFile(parseMultipart(request));
}
But we are not using any multipart resolver, we are streaming the files from the servlet request input stream. We need to start processing the file immediately for performance reasons.
When doing this this way, we can't seem to use the typical detection/configuration for multipart files. I know Springfox (which we use to generate our swagger docs) will generate the appropriate swagger controls if it sees a MultipartFile as a controller parameter, which will not be the case for us.
Are there any other config options available to hint to springfox that we want a file upload here?
Regarding breaking changes in Springfox v2.7.0:
You need to use dataType = "__file" instead of file as commented in https://github.com/springfox/springfox/issues/1285
Found my answer here: https://github.com/springfox/springfox/issues/1285
The following implicit params give me what I need:
#ApiImplicitParams (value = {
#ApiImplicitParam(dataType = "file", name = "file", required = true,paramType = "form")}
#RequestMapping(value = "/scan", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ScanResult scan(HttpServletRequest request) throws IOException, FileUploadException {
return scanService.scanFile(parseMultipart(request));
}
This adds a simple file picker to the API. To make things more confusing, turns out this functionality was broken in Springfox 2.4 - the version I was using. Adding that annotation and updating versions was all I needed to do.
That's right
https://stackoverflow.com/a/44385675/3810914
In Controller It should be:
#ApiOperation(value = "Upload file", response = String.class)
#ApiResponses({
#ApiResponse(code = 500, message = "Internal Server Error"),
#ApiResponse(code = 400, message = "Bad request")
})
#ApiImplicitParams (value = {
#ApiImplicitParam(dataType = "__file", name = "fileData", required = true,paramType = "form")})
#PostMapping(value = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<?> uploadFileSimple(UploadFile form) {
// Create folder to save file if not exist
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
MultipartFile fileData = form.getFileData();
String name = fileData.getOriginalFilename();
if (name != null && name.length() > 0) {
try {
// Create file
File serverFile = new File(UPLOAD_DIR + "/" + name);
BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(serverFile));
stream.write(fileData.getBytes());
stream.close();
return ResponseEntity.ok("/file/" + name);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error when uploading");
}
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Bad request");
}
And In Model:
package com.xxx.xxx.request;
import lombok.*;
import org.springframework.web.multipart.MultipartFile;
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class UploadFile {
private MultipartFile fileData;
}
I have a Spring Boot web application where I catch my custom exceptions in ControllerAdvice class. The problem is that Spring Boot doesn't throw exception by default if no handler is found (it sends json back to a client).
What I want is to catch NoHandlerFoundException in my ControllerAdvice class. To make this possible I explicitly configured
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
This trick does the job and I can catch NoHandlerFoundException now but it disables Spring to auto-configure path to static resources. So all my static resources are not available for a client now. I tried to resolve this using one more configuration which doesn't help
spring.resources.static-locations=classpath:/resources/static/
Could anybody please advise how to map static resources in Spring Boot when auto-configuration was disabled with spring.resources.add-mappings=false?
Thanks!
If your static resources are limited to specific URL paths, you can configure only those paths to be handled by the Spring static resources handler. In this example, the /doc URL path is served by static resources in the /resources/static/doc/ folder in the classpath:
spring.mvc.static-path-pattern=/doc/**
spring.resources.static-locations=classpath:/resources/static/doc/
You'll need to remove this configuration:
spring.resources.add-mappings=false
I experienced the same issue and after some research, I found out that it is obviously not possible to have both options enabled (i.e. throwing NoHandlerFoundException by setting spring.mvc.throw-exception-if-no-handler-found=true AND serving static resources automatically).
Enabling the option to throw NoHandlerFoundException requires one to set spring.resources.add-mappings to false, otherwise it would not work. Furthermore, in my test setup it was not possible to disable spring.resources.add-mappings and specify the URLs for static resources manually (e.g. via application properties spring.mvc.static-path-pattern and spring.resources.static-locations or programmatically by overriding public void addResourceHandlers(ResourceHandlerRegistry registry)), because then the spring.resources.add-mappings=false setting seems to be overruled.
Finally, I implemented the following workaround for serving static resources manually via my own controller implementation:
#Controller
public class StaticWebContentController {
private Map<String, byte[]> cache = new HashMap<String,byte[]>();
#RequestMapping(value = "/css/{file}", method = RequestMethod.GET)
public ResponseEntity<byte[]> getCssFile(#PathVariable("file") String name){
ResponseEntity<byte[]> responseEntity = loadResource(".\\static\\css\\"+name,"text/css");
return responseEntity;
}
#RequestMapping(value = "/img/bootstrap-icons-1.1.0/{file}", method = RequestMethod.GET)
public ResponseEntity<byte[]> getimgFile(#PathVariable("file") String name){
ResponseEntity<byte[]> responseEntity = loadResource(".\\static\\img\\bootstrap-icons-1.1.0\\"+name,"image/svg+xml");
return responseEntity;
}
#RequestMapping(value = "/js/{file}", method = RequestMethod.GET)
public ResponseEntity<byte[]> getJsFile(#PathVariable("file") String name){
ResponseEntity<byte[]> responseEntity = loadResource(".\\static\\js\\"+name,"text/javascript");
return responseEntity;
}
private ResponseEntity<byte[]> loadResource(String path, String contentType){
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", contentType);
if(hasCachedContent(path)){
return new ResponseEntity<byte[]>(getCachedContent(path),responseHeaders,HttpStatus.OK);
}else{
Resource resource = new ClassPathResource(path);
if(resource.exists()){
try{
InputStream inputStream = resource.getInputStream();
byte[] content = inputStream.readAllBytes();
putCache(path, content);
return new ResponseEntity<byte[]>(content,responseHeaders,HttpStatus.OK);
}catch(IOException e){
throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR,e.getMessage());
}
}else{
throw new ResponseStatusException(HttpStatus.NOT_FOUND,"The requested resource '"+path+"' does not exist'");
}
}
}
private byte[] getCachedContent(String path){
return cache.get(path);
}
private boolean hasCachedContent(String path){
return cache.containsKey(path);
}
private void putCache(String path, byte[] content){
cache.put(path, content);
}
}
In my application, I have three types of static resources located in three different sub folders. Each type is handled by a separate endpoint in order to set the Content-Type header properly. Moreover, the controller caches each resource in order to avoid to reload the requested resource from hard disk again.
Probably, this is not the best solution, however, a feasible workaround in case of my application. Any recommendations for improvement are highly appreciated!
Instead of adding below lines to config properties
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
write your custom Error Attributes as below:
#Configuration
public class CustomErrorAttributes extends DefaultErrorAttributes {
#Bean
public ErrorAttributes errorAttributes() {
return new DefaultErrorAttributes() {
#Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> errorAttributes = super.getErrorAttributes(requestAttributes, includeStackTrace);
Map<String, Object> newErrorAttributes = new LinkedHashMap<String, Object>();
Object errorMessage = requestAttributes.getAttribute(RequestDispatcher.ERROR_MESSAGE, RequestAttributes.SCOPE_REQUEST);
if (errorMessage != null) {
newErrorAttributes.put("response-type", "error");
newErrorAttributes.put("error-code", errorAttributes.get("status"));
newErrorAttributes.put("message", errorAttributes.get("message"));
newErrorAttributes.put("error-message", errorAttributes.get("error"));
}
return newErrorAttributes;
}
};
}
}