Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

No handler found exception and static resources in spring boot

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!

like image 556
Vadim Dissa Avatar asked Oct 11 '16 09:10

Vadim Dissa


2 Answers

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
like image 136
James Avatar answered Oct 22 '22 02:10

James


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!

like image 23
SebastianK_91 Avatar answered Oct 22 '22 02:10

SebastianK_91