Spring URL mapping conflicts - spring

At the moment I am bussy with implementing a new url structure for our webshop. The new url structure should be more optimized for search engines. We also want that our old structure will still be working and will use a 301 to redirect to a the new structure.
The problem is: the new structure sometimes conflicts with the old urls.
Example of the old url mapping:
#RequestMapping(value = "/brand/{categoryCode}/{categoryName}/{brandGroup}.do", method = RequestMethod.GET)
New structure:
#RequestMapping(value = "/brand/{brandGroup}/{superCategoryName}/{categoryName}.do", method = RequestMethod.GET)
As you can see the url's have the same amount of values, so the old mapping will catch the new one and vice versa.
What is the best way to fix this? Using a url filter to rewrite the old ones to the new url structure?

You could use an URL router in Spring MVC; you can define conflicting routes within your app and handle them with route prorities (first route to match the request wins) and refine request matching.
Your routes configuration file could look like:
GET /brand/{<[0-9]+>categoryCode}/{categoryName}/{brandGroup}.do oldcontroller.oldAction
GET /brand/{<[a-zA-Z]+>brandGroup}/{superCategoryName}/{categoryName}.do newController.newAction

In spring boot, regular expressions can be used when mapping the #PathVariable, and this can be useful to resolve url conflicts:
#RestController
public class TestController {
#PutMapping("/test/{id:^[1-9][0-9]*}") // id must be a number greater that 1
public void method1(#PathVariable long id, #RequestBody DataDto1 data) {
}
#PutMapping("/test/foo")
public void method1(#Valid #RequestBody DataDto2 data) {
}
}

Related

Can a Spring RESTful endpoint know its own URL 'template' [duplicate]

I'd like to create URLs based on the URL used by the client for the active request. Is there anything smarter than taking the current HttpServletRequest object and it's getParameter...() methods to rebuilt the complete URL including (and only) it's GET parameters.
Clarification: If possible I want to resign from using a HttpServletRequest object.
Well there are two methods to access this data easier, but the interface doesn't offer the possibility to get the whole URL with one call. You have to build it manually:
public static String makeUrl(HttpServletRequest request)
{
return request.getRequestURL().toString() + "?" + request.getQueryString();
}
I don't know about a way to do this with any Spring MVC facilities.
If you want to access the current Request without passing it everywhere you will have to add a listener in the web.xml:
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
And then use this to get the request bound to the current Thread:
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
Instead of using RequestContextHolder directly, you can also use ServletUriComponentsBuilder and its static methods:
ServletUriComponentsBuilder.fromCurrentContextPath()
ServletUriComponentsBuilder.fromCurrentServletMapping()
ServletUriComponentsBuilder.fromCurrentRequestUri()
ServletUriComponentsBuilder.fromCurrentRequest()
They use RequestContextHolder under the hood, but provide additional flexibility to build new URLs using the capabilities of UriComponentsBuilder.
Example:
ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromCurrentRequestUri();
builder.scheme("https");
builder.replaceQueryParam("someBoolean", false);
URI newUri = builder.build().toUri();
Java's URI Class can help you out of this:
public static String getCurrentUrl(HttpServletRequest request){
URL url = new URL(request.getRequestURL().toString());
String host = url.getHost();
String userInfo = url.getUserInfo();
String scheme = url.getProtocol();
String port = url.getPort();
String path = request.getAttribute("javax.servlet.forward.request_uri");
String query = request.getAttribute("javax.servlet.forward.query_string");
URI uri = new URI(scheme,userInfo,host,port,path,query,null)
return uri.toString();
}
in jsp file:
request.getAttribute("javax.servlet.forward.request_uri")
You can also add a UriComponentsBuilder to the method signature of your controller method. Spring will inject an instance of the builder created from the current request.
#GetMapping
public ResponseEntity<MyResponse> doSomething(UriComponentsBuilder uriComponentsBuilder) {
URI someNewUriBasedOnCurrentRequest = uriComponentsBuilder
.replacePath(null)
.replaceQuery(null)
.pathSegment("some", "new", "path")
.build().toUri();
//...
}
Using the builder you can directly start creating URIs based on the current request e.g. modify path segments.
See also UriComponentsBuilderMethodArgumentResolver
If you need the URL till hostname and not the path use Apache's Common Lib StringUtil, and from URL extract the substring till third indexOf /.
public static String getURL(HttpServletRequest request){
String fullURL = request.getRequestURL().toString();
return fullURL.substring(0,StringUtils.ordinalIndexOf(fullURL, "/", 3));
}
Example: If fullURL is https://example.com/path/after/url/ then
Output will be https://example.com
System.out.println(((HttpServletRequest)request).getRequestURI());
I used it. hope it's useful.

REST API endpoint selection in Spring Boot

Given a controller like this:
#RestController
#RequestMapping("/cars") {
public class CarController{
#RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<Cars>> getCars() { //logic }
#RequestMapping(method = RequestMethod.GET")
public ResponseEntity<List<Cars>> searchCar(#RequestParam("name") String name, #RequestParam("value") String value) { //logic}
}
If the url is like this localhost/cars I would like to access the getCars() method.
But if the url is :
localhost/cars?name=something&value=100 or
localhost/cars?name=something or
localhost/cars?value=100
I would like the second method to be accessed.
Is this possible to do?
You are still asking for the same list of resources, cars, only thing is that you are adding a filter or search / query criteria.
It would be beneficial to develop a query filter / criteria to support something like:
/cars?q=make+eq+acura (meaning make=acura)
/cars?q=price+lt+25000 (meaning price <25000)
and so on.
No it is not possible. because when a request comes to container then it will 1st scan all the URL and check uniqueness of the URL. If there is duplicate URL present then container will throws exception.
In your case you are using class level URL mapping, but you are not using method level URL mapping.
To access your getCars() method you need to use some URL like below
#RequestMapping(value = "/", method = RequestMethod.GET)
To access your 2nd method you need to use another mapping URL
#RequestMapping(values="/test", method = RequestMethod.GET")
You can't access
localhost/cars?name=something&value=100 or
localhost/cars?name=something or
localhost/cars?value=100
as you are using 2 parameters like #RequestParam("name") String name, #RequestParam("value") String value
you need to pass two parameter in your url like below
localhost/cars/test?name=something&value=100
if you don't want to pass any of two parameter then just pass it as null and check it inside your method

Spring MVC #PathVariable to catch only first level

I have a Spring Boot app that includes some controllers and static resources. I need to be able to have a controller that matches:
/hello
and
/hello/
but not
/wonder/hello
(or anything else). It seems that when I use the following mapping:
#RequestMapping(value = "/{slug}", method = RequestMethod.GET)
public String mapping(#PathVariable("slug") String slug)
it does a "catch-all" whereas I only need it to catch the first level. This causes issues with the static resource mapping.
use #RequestMapping("/hello") in the starting of controller

Accessing multiple controllers with same request mapping

Please find my HomeController and DemoController
class HomeController{
#RequestMapping(value="index")
public void home(){
}
}
class DemoController{
#RequestMapping(value="index")
public void demo(){
}
}
when I try to send a request to index, which one will get executed?
I wanted to know how can we have same request mapping value for multiple controllers
https://stackoverflow.com/a/34590355/2682499 is only partially correct at this point.
You can have multiple controller methods use the same URI so long as you provide Spring enough additional information on which one it should use. Whether or not you should do this is a different question. I would certainly not recommend using the same URI in two separate controller classes to avoid confusion, though.
You can do something like this:
class HomeController{
#RequestMapping(value="/index", params = {"!name", "!foo"})
public List<Something> listItems(){
// retrieve Something list
}
#RequestMapping(value="/index", params = "name")
public List<Something> listItems(String name) {
// retrieve Something list WHERE name LIKE %name%
}
#RequestMapping(value="/index", params = {"!name", "foo"})
public List<Something> listItems(String foo) {
// Do something completely different
}
}
For the full documentation on what is possible when overloading URIs you should reference the #ReqeustMapping documentation: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html. And, specifically https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestMapping.html#params-- for the section request parameters.
In Spring Web MVC this is not possible. Each mapping must be unique in your context. If not, you will receive a RuntimeException during context initialization.
You cannot even use parameters to differentiate your endpoints because they are not evaluated while searching for a suitable handler (applicable for Servlet environments). From #RequestMapping javadoc:
In a Servlet environment, parameter mappings are considered as restrictions that are enforced at the type level. The primary path mapping (i.e. the specified URI value) still has to uniquely identify the target handler, with parameter mappings simply expressing preconditions for invoking the handler.
Note that you can do the opposite, so multiple URLs can point to the same handler. Have a look at Spring MVC: Mapping Multiple URLs to Same Controller
Unfortunately, this is not possible. The request mapping has to be unique otherwise the application can't determine which method the incoming request should be mapped to.
What you can do instead is to extend the request mapping:
class HomeController{
#RequestMapping(value="home/index")
public void home(){
}
}
class DemoController{
#RequestMapping(value="demo/index")
public void demo(){
}
}

Spring MVC: How to treat html suffix like any RequestMapping

I have a legacy system and some url rewrite rule that we want to get rid. One of which is a rule to change is /tools/lookup.html?what=this and change it to /tools/search?what=this and actually returns json and not html !
I'm trying to find a way to have a #Controller to support the legacy format lookup.html, but it fails with HTTP 406 "The resource identified by this request is only capable of generating responses with characteristics not acceptable according to the request "accept" headers.". I'm wondering if anyone as done something similar?
My controller methods looks like:
#RequestMapping(value = "/tools/lookup.html", method = RequestMethod.GET)
public #ResponseBody Result lookup() {
return result;
}
Thanks in advance
Sylvain
Removing the reponsebody annotation will stop the controlle method returning json.
Take a closer look at #RequestMapping, which supports a produces element. E.g.,
#RequestMapping(value = "/tools/search", produces = "application/json")
#ResponseBody
public Result search(...) { ... }
The issue comes from how spring is treating pathvariables. The default behavior will cut off the last dot in a url (.html) to find the request mappings.
This effect happens only for the last pathvariable.
I havent found a property yet to change this globally but one way is to tell your pathvariable mapping to use the regex {pathvariable:.+}.
#Requestmapping("/somepath/{varwithextention:.+}")
public String method(#Pathvariable String varwithextension) {
...
}
Edit: i see that you do not even use pathvars. Probably its still the same effect for the last url part though?
Hi finally found something that should work in my case, note that my app doesn't need to support real html (REST only app), so this shouldn't have to much side effects. In my WebMvcConfigurerAdapter I added the following media type for html.
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("html",MediaType.APPLICATION_JSON);
configurer.mediaType("html",MediaType.APPLICATION_XML);
super.configureContentNegotiation(configurer);
}
I now I get my JSON or XML content back in the client. No more 406 error.

Resources