Spring-boot MVC file suffix regression? - spring-boot

Previous to Boot 2.0.0 we used:
#RequestMapping(method = RequestMethod.GET, value = "/{ipAddress:.+}", produces = MediaType.APPLICATION_JSON_VALUE)
which allowed us to pass in an IP address and not have it drop the last octet thinking it was a file name suffix, this is documented on several posts.
In 2.0.0, this still works, except for addresses ending in .123. which cause a 406.
Have managed to workaround using tips from https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-config-content-negotiation and Spring MVC #PathVariable with dot (.) is getting truncated but might be a useful catch for someone else?
Regards

Related

Influencing order of RequestMapping processing

In short this is what I'm searching for: I want to create a RequestMapping that catches all URLs except a small list I don't want to catch.
The minimum to exclude is everything below /webjars/, I also would like to exclude other URLS like /actuator/ and probably more.
Background information
We need to replace an old CMS which has literally thousands of different URLs. All URLs need to be detected and checked against a database and then the users shall be presented with a landing page, this landing page will then redirect the user to the new CMS target URL.
The logic that needs to be applied is too complicated for some Apache / nginx magic, therefore I wrote a Spring Boot application that can accomplish this.
I've created a #RequestMapping(value = "**", method = RequestMethod.GET) that catches all GET requests (these are the one I want to grab and react on) and a #RequestMapping(value = "**") for the other http verbs like POST and DELETE which I simply abort by sending a http error status code.
This works fine and the (simplified) code looks like this:
#RequestMapping(value = "**", method = RequestMethod.GET)
public String catchAccepted(HttpServletRequest request, HttpServletResponse httpServletResponse, Model model) {
model.addAttribute("targetUrl", ua.deriveNewUrl(request));
return "redirect";
}
#RequestMapping(value = "**")
#ResponseBody
public ResponseEntity<Object> catchDenied(HttpServletRequest request, HttpServletResponse httpServletResponse) {
return new ResponseEntity<Object>(HttpStatus.I_AM_A_TEAPOT);
}
The page that gets displayed for all the GET requests is based on a Thymeleaf template which uses Bootstrap in order to do the layout job.
In order to include Bootstrap I use the webjars-locator and org.webjars.bootstrap, the resources are included by specifying <script src="/webjars/bootstrap/js/bootstrap.min.js"></script> in the redirect.html Thymeleaf template.
Problem
The problem is, that my ** mapping on GET also gets applied to the /webjars/... call and instead of the desired js code I get whatever my redirect template returns.
I found no way to tell Spring Boot about the desired order.
First I would like to have Spring Boot handle the webjars mapping, then my other mapping.
Attempts so far
I checked other posts on SO but they only work when I have access to the sourcecode where the mapping is made. And I don't have access to the webjars locator / see no point in changing it just to solve this issue.
I also tried to set up a "anything that is not related to webjars" mapping like this:
#RequestMapping(value = "^(?!webjars$|actuator$).*", method = RequestMethod.GET)
But this doesn't have the desired effect because the RequestMapping only seems to support ant-stlye paths, which doesn't support negations because Ant (in contrast to Spring Boot) has a field for includes and excludes: https://ant.apache.org/manual/dirtasks.html
Negating the mapping seems only to be possible for params, not for the path: Change #RequestMapping order
I didn't yet find a way to influence the order if other mappings come from code I can not incluence.
But I found a way to configure "catch all except of ...":
#RequestMapping(value = { "", "/", "{url:(?!webjars$|actuator$).*}/**" }, method = RequestMethod.GET)
This configures three mappings. The first two are there to handle calls to the root of the webserver. The third configures a path pariable which I could also put into a #PathVariable but in my scenario the value doesn't matter. If you configure a path variable you need to give it a default because the pattern will only be satisfied depending on the value of your URL.
The regex tells Spring Boot only to react if the url doesn't contain webjars or actuator. The regex itself is best explained by using regex101:

Mapping of missing URI variables to Request Mapping

I've developed a Spring API /getFileData, which accepts three URI parameters viz. businessDate/fileName/recordId. It is possible to have any of them can be passed as null. But I still want my API to be working in this case also. How can I achieve this?
I've tried using #GetMapping("getFileData/{businessDate}/{fileName}/{recordId}", "getFileData/{businessDate}//", "getFileData/{businessDate}/{fileName}/")..so on like this for all possible combinations.
#RequestMapping(value = "/getFileData/{businessDate}/{fileName}/{recordId}", method = RequestMethod.GET)
I want this API to be working for all the combination of URI parameters if something get missed out. for example someone requested,
/getFileData///22 or
/getFileData/22Dec2018/ or
/getFileData//treasure/22
You can do that with a #RequestParam of type java.util.Map.
With your design, you will have various #PathVariable params in the controller method as well as the order of path variables /{var1}/{var2}... constructs the url so I don't think it would be possible to skip a path variable in the url and still call the same controller method.

What is String:.+ in spring request mapping's path param

While I was modifying the code written by other developer I come across an end point #RequestMapping(value = "/ICD/{icdcode:.+} and wanted to know what is :.+ in the path variable.
This has already been answered
Spring MVC #PathVariable getting truncated
Spring MVC #PathVariable with dot (.) is getting truncated
Spring - Path variable truncate after dot - annotation
Basically, it is a regular expression. Spring considers that anything behind the last dot is an extension and get rid of it.
If you have a mapping to /somepath/{email} and try /somepath/test#gmail.com the value for the path parameter email will be test#gmail
Using the regular expression {pathparam:.+} everything is considered part of the value, even what is behind the last dot.

SEO friend urls for spring mvc application

I have an application with urls like site.com/article/1/title.htm
I have #RequestMapping /article/{id}/{title}.htm that server this request and gets the article.
What I am looking achieve is to have a url like site.com/title.htm but cant think of a way to do that using Spring MVC because I need id of the article. any ideas? Thanks in advance
When you create an article, you need to create the SEO-friendly URL also and persist it, along with the article. Now you need to have a repository method that allows you to retrieve articles by permalink, and a Spring MVC endpoint that calls that repository method.
Using the title may not be a good idea, as the title is usually not URL-friendly, and may eventually be non-unique. But it is a good idea to use the title as the input for the permalink.
Here's a sample permalink algorithm:
Take the title
replace all occurrences of one or more space or punctuation with a single dash
replace all non-ascii characters with their ascii neighbors
check whether that permalink already exists
if it does, add a counter
This is how the read path could look like:
#Autowired
private ArticleRepository ar;
#RequestMapping(value="/article/{id}/{ignored}") #ResponseBody
public Article getByIdAndIgnorePermalink(#PathVariable String id, #PathVariable String ignored){
return ar.getById(id);
}
#RequestMapping(value="/article/{title}.html") #ResponseBody
public Article getByPermalink(#PathVariable String permalink){
return ar.getByPermalink(permalink);
}
There is no way send hidden id obviously, so it has to be done through a permalink of the article or simply via title, to achieve site.com/title.html you need to get rid of all the fixed bits by adding this request mapping rule:
#RequestMapping(value = "/**/{articleTitle}.html"
but to get the article you can obviously use id as its not there in the URL and have to work with that articleTitle or generate a permalink as suggested by #Sean above.

rest url - having '/' as a path VALUE

I think this is not possible, but I need confirmation before throwing it away...
I have a GET REST endpoint with this pattern
/networks/{networkId}/publishers/{publisherId}/ratings
the problem I am facing is, publisherId could have '/' in its id, like the id could be "opt/foo/bar" (we have not control over this id, it is given to us by our clients).
So
/networks/68/publishers/opt/foo/bar/ratings - obviously does not work, getting a url not fond error.
/networks/68/publishers/opt%2ffoo%2fbar/ratings - also does not work. same error.
I know passing it as a query param will work. But I want to know if there is a way to make it work having it as a path param?
Thanks!
URL encoding is the right way to go but it looks like your container is decoding the slash before Jersey receives it.
Assuming you are using Tomcat, you can attempt to persuade Tomcat to allow the encoding, try:
tomcat/bin/setenv.bat
set
CATALINA_OPTS="-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true"
I don't know if other containers have similar issues and settings.
I have not tried this, but theoretically this should work in Jersey:
#Path("/networks/{networkId}/publishers/")
#GET
public String get(#PathParam("networkId") String networkId, #Context UriInfo ui) {
java.util.List<PathSegment> segments = ui.getPathSegments();
// Last segment is "ratings", the rest is your publisherId.
}

Resources