RequestMapping with more than one path element - spring

I'm trying to write the simplest possible Spring application which uses more than one path element in a #RequestMapping. For example, /appcontext/blog/hello-world.html should work in the request mapping (obviously /appcontext is my application's context).
Can Spring do something like that? I have it working easily when it maps just one thing. For example:
#RequestMapping("hello-world")
works and will match /hello-world.do , /anything/hello-world.do , but my problem is, I'm trying to match only hello-world if it's in a /blog path, and whenever I use something like:
#RequestMapping("/blog/hello-world")
it doesn't ever trigger.
Server log for example:
INFO: Mapped "{[/blog/hello],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String mainweb.BlogController.foo()
That looks like it should work (and it's the only request mapping I have), but then:
WARNING: No mapping found for HTTP request with URI [/context/blog/hello.html] in DispatcherServlet with name 'dispatcher'
Can Spring do something like this? I don't want to put all my request mappings in /, because it's going to be a mess.
The only mapping I have gotten to work is:
#RequestMapping("/**")
From there, I could look at the HttpServletRequest object directly, but that seems to defeat the entire point of having #RequestMapping.
Here is my Controller:
#Controller
public class BlogController {
private static final Logger LOG = Logger.getLogger(BlogController.class.getName());
#RequestMapping("/blog/hello")
public String foo1() {
LOG.info("foo1");
return "nothing";
}
#RequestMapping("/blog/hello.html")
public String foo2() {
LOG.info("foo2");
return "nothing";
}
#RequestMapping("/blog/hello.*")
public String foo3() {
LOG.info("foo3");
return "nothing";
}
#RequestMapping("/blog/**")
public String foo4() {
LOG.info("foo4");
return "nothing";
}
#RequestMapping("/blog/{path}")
public String foo5(#PathVariable String path) {
LOG.info("foo5 " + path);
return "nothing";
}
// added this as a test - it's the only way that this controller works
#RequestMapping("/**")
public String foo6() {
LOG.info("foo6");
return "nothing";
}
}
If I don't have the foo6 mapping, nothing in this works at all, no matter what URLs I go to.
When I use that controller, this is what shows up in the server log:
INFO: Mapped "{[/blog/hello],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String mainweb.BlogController.foo1()
INFO: Mapped "{[/blog/hello.html],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String mainweb.BlogController.foo2()
INFO: Mapped "{[/blog/hello.*],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String mainweb.BlogController.foo3()
INFO: Mapped "{[/blog/**],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String mainweb.BlogController.foo4()
INFO: Mapped "{[/blog/{path}],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String mainweb.BlogController.foo5(java.lang.String)
INFO: Mapped "{[/**],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public java.lang.String mainweb.BlogController.foo6()
But again, nothing ever triggers except foo6 if I put it in.
Thank you!
Edit: I have created an extremely simple project, which has only one class, the Controller, with one method, the RequestMapping. This project does nothing except not work. There is simply no way to get #RequestMapping to do anything other than work with a wildcard. This seems like a critical bug in Spring; all the documentations says that #RequestMapping does more than just map one wildcard.

It looks to me that the reason it's not mapping in the /blog path is that you're not using that path to access it.
According to the errors that you posted in the question above, you have defined/mapped:
/blog/hello
Whereas you are trying to access:
/context/blog/hello.html
i.e. You are prefixing the URI with "/context" and suffixing it with ".html". That's why it's not responding.
I would recommend trying to access the URI that you have mapped. However, you may also need to ensure that your application's root context is correct.
Note that by adding the ".html" suffix, you are not using the path that you defined. If you wish to use random suffixes such as that, then it is also possible to use wildrcards in your mappings. i.e. #RequestMapping(value = "/blog/hello**")
However, I would generally recommend against using wildcards if you can avoid it, as they are likely to throw up a few surprises when you add more mappings to your application.
Edit - I also just spotted that your controller is messing up those mappings even more. Just read the following mappings together:
#RequestMapping("/blog/hello")
#RequestMapping("/blog/hello.html")
#RequestMapping("/blog/hello.*")
Your mappings are clashing with each other. The first mapping in that list is a subset of the others, so it will be used for any URLs that match any of those mappings. i.e. The second 2 are redundant. You could potentially fix that by putting the 'bare' /blog/hello after the the other 2, so that the more specific mappings are picked up first, however, relying on the order in which the methods are written seems like asking for trouble to me.

Figured it out. The servlet-mapping chops off part of the path! The servlet-mapping basically creates a mini-context. I didn't know it did that, and it's not intuitively obvious, but that's what's happening.

Related

Web Api Routing: Multiple controller types were found that match the URL for parameter VS constant paths

My issue is similar to Web Api Routing : Multiple controller types were found that match the URL but I want to keep them in separate controllers.
From the comments, 2 preexisting answers are good workarounds but do not solve the actual issue I'm trying to resolve.
The URLs I'm making up are similar to nested directories in a file system OR are very similar to Firebase URLs.
/BiggestSet/{BiggestSetCode}/Subset1/{Subset1Code}/SubsetOfSubset1/{SubsetOfSubset1}
... etc all the way down to where ever the tree stops. Think of it as a tree of data.
/Collection/{Instance}/Collection/{Instance}
The issue I have is that at the /Collection level I want to also provide specific collection level operations. Like Add and search and other collection specific Operations Collection/ProccessData
Collection Controller:
/Collection/Add
/Collection/ProcessDataOnTheColleciton
Instance Controller:
/Collection/{InstanceCode}
/Collection/{InstanceCode}/ProcessOnTheInstance
The problem I'm having is the Collection/ProcessData clashes with the instance Collection/{InstanceCode}
NOTE: 1 is an parameter and the other is a constant.
If you setup the controllers so that collection and Instance are in the same controller. the /{InstanceCode} doesn't clash with the /ProcessData
BUT
If you setup so the controllers are split into logical functions WebAPI gives the error Multiple controller types were found that match the URL.
Does anyone know how to modify attribute routing to somehow behave as if they are in the same controller OR to prioritize the constant over the parameter across controllers?
To keep two separate controllers and still have such routes you can use regular expression route constraints. This way you can specify for the instanceCode you accept everything except the actions from the other controller.
Here is a sample of how to configure routes like that:
public class CollectionController : ApiController
{
[HttpGet]
[Route("Collection/Add")]
public string Add()
{
return $"CollectionController = Collection/Add";
}
[HttpGet]
[Route("Collection/Process")]
public string Process()
{
return $"CollectionController = Collection/Process";
}
}
public class InstanceController : ApiController
{
[HttpGet]
[Route("Collection/{instanceCode:regex(^(?!Add$|Process$).*)}")]
public string Get(string instanceCode)
{
return $"InstanceController = Collection/{instanceCode}";
}
[HttpGet]
[Route("Collection/{instanceCode:regex(^(?!Add$|Process$).*)}/Process")]
public string Process(string instanceCode)
{
return $"InstanceController = Collection/{instanceCode}/Process";
}
}
Here is also a link to the post that explains the regular expression used in the sample.
An even better option would be if you have a specific format for the instanceCode and set the regular expression to accept only this specific format. Then you would not need to modify the regular expression for every new action added. I include also a link to the documentation for all available Route constraints. There you can see all the available options. For example if your instance code is a number you don't even need a regular expression you can just restrict with the int constraint like this [Route("Collection/{instanceCode:int}")].

How to get Swagger UI to display similar Spring Boot REST endpoints?

I have a controller class with two endpoints
#RestController
#RequestMapping
public class TestController {
#RequestMapping(
value= "/test",
method = RequestMethod.GET)
#ResponseBody
public String getTest() {
return "test without params";
}
#RequestMapping(
value= "/test",
params = {"param"},
method = RequestMethod.GET)
#ResponseBody
public String getTest(#PathParam("param") int param) {
return "test with param";
}
}
One has a parameter, one doesn't, and the both work.
If I use curl or a web browser to hit the endpoints
http://localhost:8081/test
returns
test without params
and
http://localhost:8081/test?param=1
returns
test with param
but the swagger ui only shows the one without a parameter.
If I change the value in the request mapping for the request with a parameter to
#RequestMapping(
value= "/testbyparam",
params = {"param"},
method = RequestMethod.GET)
Swagger UI displays both endpoints correctly, but I'd rather not define my endpoints based on what swagger will or won't display.
Is there any way for me to get swagger ui to properly display endpoints with matching values, but different parameters?
Edit for Clarification:
The endpoints work perfectly fine; /test and /test?param=1 both work perfectly, the issue is that swagger-ui won't display them.
I would like for swagger ui to display the endpoints I have defined, but if it can't, then I'll just have to live with swagger-ui missing some of my endpoints.
Edit with reference:
The people answering here: Proper REST formatted URL with date ranges
explicitly say not to seperate the query string with a slash
They also said "There shouldn't be a slash before the query string."
The issue is in your Request Mapping, The second method declaration is overriding the first method. As Resource Mapping value is same.
Try changing the second method to below. As you want to give input in QueryParam rather than path variable, you should use #RequestParam not #PathParam.
Note that you have to give /test/, In order to tell Spring that your mapping is not ambiguous. Hope it helps.
#RequestMapping(
value= "/test/",
method = RequestMethod.GET)
#ResponseBody
public String getTest (#RequestParam("param") int param) {
return "test with param"+param;
}
Upon reading clarifications, the issue here is that swagger-ui is doing the correct thing.
You have two controller endpoints, but they are for the same RESOURCE /test that takes a set of optional query parameters.
Effectively, all mapped controller endpoints that have the same method (GET) and request mapping (/test) represent a single logical resource. GET operation on the test resource, and a set of optional parameters which may affect the results of invoking that operation.
The fact that you've implemented this as two separate controller endpoints is an implementation detail and does not change the fact that there is a single /test resource that can be operated upon.
What would be the benefit to consumers of your API by listing this as two separate endpoints in swagger UI vs a single endpoint with optional parameters? Perhaps it could constrain the set of allowed valid query parameters (if you set ?foo you MUST set &bar) but this can also be done in descriptive text, and is a much more standard approach. Personally, I am unfamiliar with any publicly documented api that distinguishes multiple operations for the same resource differentiated by query params.
As per Open API Specification 3
OpenAPI defines a unique operation as a combination of a path and an
HTTP method. This means that two GET or two POST methods for the same
path are not allowed – even if they have different parameters
(parameters have no effect on uniqueness).
Reference - https://swagger.io/docs/specification/paths-and-operations/
This was also raised as an issue but it was closed because OAS3 doesn't allow that -
https://github.com/springdoc/springdoc-openapi/issues/859
Try including the param in the path as below.
#GetMapping("/test/{param}")
public String getTest(#PathVariable final int param) {
return "test with param";
}
I'm unclear exactly what you're attempting to do, but I'll give two solutions:
If you want to have PATH parameters e.g. GET /test & GET /test/123 you can do:
#GetMapping("/test")
public String getTest() {
return "test without params";
}
#GetMapping("test/{param}")
public String getTest(#PathVariable("param") int param) {
return "test with param";
}
If you want query parameters (GET /test and GET /test?param=123) then you need a single endpoint that takes an optional parameter:
#GetMapping("test")
public String getTest(#RequestParam("param") Integer param) {
if(param == null) {
return "test without params";
} else {
return "test with param";
}
}

reading appSettings from asp.net core webapi

I have an asp.net core 2 webapi service. I want to read appsettings.json but I cannot figure it out. As usual, when I search the internet I get a dozen completely different answers based on various versions of the framework that ultimately don't seem to work.
As an example appsettings.json:
"AppSettings": {
"Domain": "http://localhost"
}
Firstly I tried this:
Added via nuget Microsoft.Extensions.Configuration and Microsoft.Extensions.Configuration.Json
In my ConfigureServices method I added:
services.AddSingleton<IConfiguration>(Configuration);
In my Controller I injected the config:
IConfiguration iconfig;
public ValuesController(IConfiguration iConfig)
{
iconfig = iConfig;
}
To retrieve it:
return "value: " + iconfig.GetValue<string>("AppSettings:Domain");
I can see the controller constructor being called and passing in a config, but the value is always null.
I did notice that Startup already passes in an IConfiguration. Is it a case of yet another change in implementation and i need to do something different?
[edit]
I've now read
https://joonasw.net/view/aspnet-core-2-configuration-changes
Which says it's all change again and it's auto injected, but after following the code it doesn't actually say how you get your hands on the data in your controller.
You can take a look at my answer here: https://stackoverflow.com/a/46940811/2410655
Basically you create a class that matches the properties you define in appsettings.json file (In the answer it's the AppSettings class).
And then in ConfigureServices(), you inject that class using services.Configure<>. That class will be available in all Controllers by DI.
To access its data, you can take a look at #aman's original question post from the link above.
public class HomeController : Controller
{
private readonly AppSettings _mySettings;
public HomeController(IOptions<AppSettings> appSettingsAccessor)
{
_mySettings = appSettingsAccessor.Value;
}
}

How to override a web api route?

I am trying to standardize an extension model for our REST API development team. We need to provide default implementation of routes, while allowing for custom implementations of routes that replace the default as well.
As a simple example if we have a GET route api/users like this:
public class DefaultUsersController : ApiController
{
[HttpGet]
[Route("api/users", Order = 0)]
public IEnumerable<string> DefaultGetUsers()
{
return new List<string>
{
"DefaultUser1",
"DefaultUser2"
};
}
}
We expect the default work like this:
Now a developer wants to change the behavior of that route, he should be able to simply define the same route with some mechanism to imply their implementation should be the one used, instead of the default. My initial thinking was to use the Order property on the Route attribute since that's what it appears to be there for, as a way to provide a priority (in ascending order) when an ambiguous route is discovered. However it's not working that way, consider this custom implementation that we want to override the default api/users route:
public class CustomUsersController : ApiController
{
[HttpGet]
[Route("api/users", Order = -1)]
public IEnumerable<string> CustomGetUsers()
{
return new List<string>
{
"CustomUser1",
"CustomUser2"
};
}
}
Notice the Order property is set to -1 to give it a lower priority value than the default, which is set to 0. I would have thought this would be used by the DefaultHttpControllerSelector, but it isn't. From the DefaultHttpControllerSelector:
And we end up with this exception being returned in the response:
Is it possible Microsoft just missed the logic/requirement to use Order as a route disambiguator and this is a bug? Or is there another simple way to override a route, hopefully with an attribute?
I have pretty much the same problem. I am creating a starter site, but I want users to be able to redefine to behaviour of a Controller, especially if there is a bug.
I use Autofac to resolve the Controller, but even when I register the new controller as the old one, the original one gets selected.
What I'll do is probably go with URL Rewriting. Especially since this issue is temporary in my case. However, I would be interested if someone has a better option.

Loop in Spring Controller Method

I have some very strange behaviour in one of my spring controller which I can not explain.
So this is what I have. Very simple controller.
#RequestMapping(value="/doSomething")
public void doSomething(#RequestParam int value, HttpSession session) {
System.out.println("Lorem");
// Some stuff later on done here
System.out.println("ipsum");
}
When I request the mapped URL the controller behaves as it whould have an infiniate loop inside. So it starts with the output "Lorem" then "ipsum" and instead of leaving the method it starts right from the beginning of the method again. It is not called multiple times from external. Does anybody know this behaviour or has any clue? Furthermore I could observe that the output speed slows down as memory drastically increases up to about 1.5 GB and 100% CPU usage spread all over every single core.Thanks for your help.
Your handler method doesn't seem right to me. Typically you need to return a string that will be resolved into a View by ViewResolver, eg:
#RequestMapping(value="/doSomething")
public String doSomething(#RequestParam int value, HttpSession session) {
// ....
return "somethingdone";
}
This is my understanding.
If a controller declares a void return type , Spring will attempt to infer the view name from the request URL.
In your case, it will assume the view name is "doSomething", and proceed on that assumption.
It does this using an implementation of RequestToViewNameTranslator, The default implementation of RequestToViewNameTranslator is DefaultRequestToViewNameTranslator
See this

Resources