2

I'm attempting to use WebJars-Locator with a Spring-Boot application to map JAR resources. As per their website, I created a RequestMapping like this:

@ResponseBody
@RequestMapping(method = RequestMethod.GET, value = "/webjars-locator/{webjar}/{partialPath:.+}")
public ResponseEntity<ClassPathResource> locateWebjarAsset(@PathVariable String webjar, @PathVariable String partialPath)
{

The problem with this is that the partialPath variable is supposed to include anything after the third slash. What it ends up doing, however, is limiting the mapping itself. This URI is mapped correctly:

http://localhost/webjars-locator/angular-bootstrap-datetimepicker/datetimepicker.js

But this one is not mapped to the handler at all and simply returns a 404:

http://localhost/webjars-locator/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css

The fundamental difference is simply the number of components in the path which should be handled by the regular expression (".+") but does not appear to be working when that portion has slashes.

If it helps, this is provided in the logs:

2015-03-03 23:03:53.588 INFO 15324 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/webjars-locator/{webjar}/{partialPath:.+}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity app.controllers.WebJarsLocatorController.locateWebjarAsset(java.lang.String,java.lang.String) 2

Is there some type of hidden setting in Spring-Boot to enable regular expression pattern matching on RequestMappings?

robross0606
  • 544
  • 1
  • 9
  • 19
  • 2
    Are you sure this is resolving correctly? http://localhost/webjars-locator/angular-bootstrap/datetimepicker/datetimepicker.js – minion Mar 04 '15 at 04:49
  • I created a sample project to reproduce the issue. This URL is not working `http://localhost:8080/webjars-locator/angular-bootstrap/datetimepicker/datetimepicker.js`. But, `http://localhost:8080/gradle-spring-mvc-web-project/webjars-locator/angular-bootstrap/datetimepicker.js` is working fine. @robross - Please confirm the same. – Mithun Mar 04 '15 at 07:17
  • I am absolutely positive which items are resolving correctly and which are not. However, I did note that I had a character typo in the example and fixed it. Sorry about that. – robross0606 Mar 04 '15 at 12:04
  • @Mithun, your project appears to be somewhat different than mine. I do not need to put the path to my "project" in the URL. – robross0606 Mar 04 '15 at 12:05

2 Answers2

9

The original code in the docs wasn't prepared for the extra slashes, sorry for that!

Please try this code instead:

@ResponseBody
@RequestMapping(value="/webjarslocator/{webjar}/**", method=RequestMethod.GET)
public ResponseEntity<Resource> locateWebjarAsset(@PathVariable String webjar, 
        WebRequest request) {
    try {
        String mvcPrefix = "/webjarslocator/" + webjar + "/";
        String mvcPath = (String) request.getAttribute(
                HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
        String fullPath = assetLocator.getFullPath(webjar, 
                mvcPath.substring(mvcPrefix.length()));
        ClassPathResource res = new ClassPathResource(fullPath);
        long lastModified = res.lastModified();
        if ((lastModified > 0) && request.checkNotModified(lastModified)) {
            return null;
        }
        return new ResponseEntity<Resource>(res, HttpStatus.OK);
    } catch (Exception e) {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}

I will also provide an update for webjar docs shortly.

Updated 2015/08/05: Added If-Modified-Since handling

philippn
  • 1,850
  • 1
  • 14
  • 11
  • You would like to add `Last-Modified` header attribute in response to avoid the transfer of the resources every time the page is loaded. `URLConnection conn = classPathResource.getURL().openConnection();` and then use `headers.setContentLength(conn.getContentLengthLong());` and `headers.setLastModified(conn.getLastModified());` – ciri-cuervo Aug 03 '15 at 15:40
  • @ciri-cuervo: Good point! I have updated the code accordingly. Thanks for your feedback! – philippn Aug 05 '15 at 12:46
  • I don't think it works with return `null`, cause Spring would send a **200 OK** response status and the site wouldn't load the resources. If you set the `Last-Modified` attribute in headers, Spring will automatically reply **304 Not Modified** (and not the resource) when it finds `If-Modified-Since` in the request. Look at my example: [WebJarsLocatorController.java](https://bitbucket.org/ciri-cuervo/spring-boot-with-thymeleaf/src/909ba6fc6e05e8e61077b550401472f23c523de7/src/main/java/com/example/web/WebJarsLocatorController.java?at=master) – ciri-cuervo Aug 05 '15 at 23:20
  • I have tested it with Chrome and it seems to do the job fine. From what I read in the docs, checkNotModified() will transparently set the appropriate response headers (including status). Source: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/context/request/WebRequest.html#checkNotModified-long- – philippn Aug 06 '15 at 16:43
1

It appears that you cannot have a PathVariable to match "the remaining part of the url". You have to use ant-style path patterns, i.e. "**" as described here:

Spring 3 RequestMapping: Get path value

You can then get the entire URL of the request object and extract the "remaining part".

Community
  • 1
  • 1
ci_
  • 8,594
  • 10
  • 39
  • 63
  • Please refer to http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-uri-templates-regex – robross0606 Mar 04 '15 at 13:08
  • I have read the docs. I know that it doesn't explicitly state that a `@PathVariable` can only be applied to one element in the path, but it seems that this is the case. Nowhere in the docs could I find an example of a regexp that matches multiple path elements. – ci_ Mar 04 '15 at 14:02
  • The regex is not matching multiple path variables. Just one. The link i provided takes you directly to such an example. Similar examples can be found all over the web. I suspect Spring Boot is to blame. Regardless, I tested with one element and that didn't work either. – robross0606 Mar 04 '15 at 15:40
  • I don't get it, you have a `PathVariable` with a regex, and you want it to match "integration/bootstrap/3/dataTables.bootstrap.css", which is _more than one_ path element. The link you provided shows how it matches _one_ path element. And yes, it does work in that case. – ci_ Mar 04 '15 at 16:03
  • Maybe another way of expressing this is, your regexp cannot match '/'. – ci_ Mar 04 '15 at 16:06
  • I think I understand. The example provided from webjars is inadequate. I will use the workaround to obtain the path. – robross0606 Mar 04 '15 at 17:03