Spring 4 RestController Dispatcher url-pattern - java

I have an annotated rest controller, like the one below. I'm able to get the services to host fine, but only if I configure the full path for each individual service in web.xml:
#RestController
#RequestMapping("/service/")
public class StuffRestController
{
#RequestMapping("/getStuffList")
public List<Stuff> getStuffList() {
... make stuff ...
return stuffList;
}
... many other similar services ...
}
This is really the only spring resource in my application; although, we are using spring security.
The below are the only lines spring4-servlet.xml:
<mvc:annotation-driven />
<context:component-scan base-package="com.me.stuff.presentation.controller" />
<context:component-scan base-package="com.me.stuff.security" />
The StuffRestController class resides in the "...controller" package.
web.xml:
<servlet>
<servlet-name>spring4</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/spring4-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring4</servlet-name>
<url-pattern>/service/getStuffList</url-pattern>
</servlet-mapping>
All of the above actually all works fine and dandy, but there are 30 other services in this controller and I would rather not make a new servlet mapping for every method. The issue occurs when I attempt to "wildcard" the mapping. I've tried /service/, /service, and /service/*. And many other combinations.
Most all simply don't map properly, and I receive 404 errors. If I use /service/* it will engage the dispatcher servlet when /service/getStuffList is called, but it responds with:
WARNING: No mapping found for HTTP request with URI [/myapp/service/getStuffList] in DispatcherServlet with name 'spring4'
I'm sure this is something simple with how URL mappings are created, but it is eluding me.

The issue is you've included the path: /service/getStuffList in both your DispatcherServlet and the #RestController request mapping. So to access the rest controller method, you've to hit the following URL:
{contextPath}/service/getStuffList/service/getStuffList
So, either change the dispatcher servlet url-pattern to /, so it will handle every request coming to your application, and then based on path after myApp, will redirect to appropriate controller. Or, set the RestController mapping to /*. You should prefer the former approach.
If you want to have your servlet handle request coming at /service, then change the url-pattern to /service/*. But then you've to remove all the request mapping from class level. Else at current scenario, you've to hit the following url:
{contextPath}/service/service/getStuffList
However, if you want to include the dispatcher servlet url-pattern in path resolution (i.e., you want to map the class at /service and also map servlet to that path), you can set alwaysUseFullPath property to true of URL handler mapping. For that, add the following to your spring context xml file:
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name = "alwaysUseFullPath" value = "true" />
</bean>

Related

Spring webmvc controller accepts `.com/` but rejects `.com` in url with throwing 406 error

I am new to spring webmvc plz bear with me and I am not able to post exactly what to ask due to my lack of understanding on this subject. All I can say is spring mvc file extentions.
inherited maven + spring webmvc 5.1.2.RELEASE project
deployed mvn built fooapp.war to tomcat 8.5.58 on linux platform
The question I have is when I fire rest api call in a browser for the /mail/{address} endpoint.
Case 1)
Without / at the end of uri, it returns http status 406 - The target resource does not have a current representation that would be acceptable to the user agent, according to the proactive negotiation header fields received in the request, and the server is unwilling to supply a default representation.
http://localhost:<port>/fooapp/mail/john#bar.com
Case 2)
However with / at the end of uri, it returns proper response in json format.
http://localhost:<port>/fooapp/mail/john#bar.com/
I was not able to formulate a proper question to start with not understanding its cause but I did my best searching for a clue from www. I came to many threads but these caught my eyes:
How to change spring request mapping to disallow url pattern with suffix
Spring MVC; avoiding file extension in url?
This is what I tried.
Frist replaced / with /* for url-pattern in the web.xml.
<servlet>
<servlet-name>fooapp</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:fooapp.xml
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>fooapp</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
From the fooapp.xml, <mvc:path-matching suffix-pattern="false"/> is added.
<mvc:annotation-driven>
<mvc:path-matching suffix-pattern="false"/>
</mvc:annotation-driven>
And the watered down version of FooController.java.
#RestController
#RequestMapping("/mail/{address}")
public final class FooController {
#RequestMapping(method = RequestMethod.GET, produces = "application/json")
public String doSomething(
#PathVariable("address") String address, HttpServletResponse response) {
// execute code...
return josnData;
}
Not knowing what I am doing my attempts didn't change the outcome. Why .com vs .com/ is treated differently? And how to get a proper response back with .com?
update
I may found a clue: Spring does not ignore file extension
will give a try what it says.
With ^ suggestion, I was able to avoid http status 406 however when the FooController consume email address, it chops off .com so that data associated with the eamil address is not returned.
The suggestion I tried was adding following content to fooapp.xml.
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
</bean>
Found a solution
With this article: https://www.baeldung.com/spring-mvc-pathvariable-dot
By adding mail/{address:.+} in the controller along with ^ solution together fixed the problem.

RequestMapping to accept all urls in Rest Apis

I have a Controller which is designed to accept all requests and do all the required processing. However it is not working for all requests. My Controller is such:
#RequestMapping("/*")
public class GatewayDummyController
and my web.xml configuration is:
<servlet-name>sample-apis-servlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<servlet-mapping>
<servlet-name>sample-apis-servlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
The name of the war is sampleApi. I am able to access urls:
localhost/8080/sampleApi
localhost/8080/sampleApi/rest
the URL that I cannot access is:
localhost/8080/sampleApi/rest/v1.
I need to access all urls after sampleApi/. Please suggest a way to do that.
Add #RequestMapping("/**") to the controller to accept all urls

Spring error: No mapping found for HTTP request with URI [/myproject/] in DispatcherServlet with name 'appServlet'

After setting up the default security page with Spring security (in memory), I receive the following error:
No mapping found for HTTP request with URI [/myproject/] in DispatcherServlet with name 'appServlet'
Update:
This problem was fixed by moving the component-scan (which scans my security package) from the servlet-context.xml to root-context.xml
You error messaege tells No mapping found for HTTP request with URI [/addressbook/]. In other words, you have not mapped a request mapping for the URL /addressbook
If you want your project to have a base URL as /addressbook then change the web.xml as below
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
....
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/addressbook/*</url-pattern>
</servlet-mapping>
Controller
#Controller
public class HomeController {
#RequestMapping(value="/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
return "home";
}
}
Then goto the browser and type the below. There should be a / at end.
http://hostname:port/addressbook/
But if you want to change the context root, you cannot do this by web.xml. Context root isn't part of the standard web.xml file. You can change this by doing the following in the eclipse
In project properties click on Web Project Settings (assuming you are in a dynamic web project.) The only configuration value there is "Context root:"
Change "Context root" to /addressbook
Change the web.xml url-pattern to <url-pattern>/</url-pattern>
rebuild
remove the project from tomcat
redeploy.
Quoted from digitaljoel

Servlet Mapping Error appears when "#WebServlet" annotation is present

My web.xml looks like this:
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.ajayramesh.jrecycled.servlets.Login</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
I have a class called Login.java with an auto-generated annotation that says:
#WebServlet("/login")
right above my HttpServlet class extension declaration. When this annotation is present, I get the following error when I try to start my server:
The servlets named [LoginServlet] and [com.ajayramesh.jrecycled.servlets.Login] are both mapped to the url-pattern [/login] which is not permitted
I only have one definition in my web.xml and only a single web.xml. When I remove this annotation, the server works fine. To my understanding, annotations are not supposed to have an effect on the runtime of the program, and are only meant to optimize compilation? On a side note, what exactly does that annotation do?
You can't use the same mapping for both annotation and web.xml, you can use either one of it. App server treat it as duplicate url mapping.
Basically, declaring servlet and servlet-mapping elements in web.xml is equal to annotating a servlet class with #WebServlet.

Servlet Mapping: Is it possible to add servlet mappings at runtime?

Is it possible to add a servlet mapping at runtime? either through a ContextListener or within a Servlet's init() method?
Within the application I am using, all requests are handled through a single Servlet and their mappings are defined in the web.xml file. The number of mappings are increasing and adding a new mapping requires a new release.
Mapping the servlet to /* would not work since requests dispatched to JSPs and static content would also be routed to this Servlet.
Using a filter might be an option nevertheless, it will add a requirement for the filter to know what path to prefix to the RequestDispatcher.
For example:
Dispatcher Servlet Mapping
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/action/*</url-pattern>
</servlet-mapping>
Routing Filter Definition
<filter>
<filter-name>RoutingFilter</filter-name>
<filter-class>com.ssv.web.filter.RoutingFilter</filter-class>
<init-param>
<param-name>exclude</param-name>
<param-value>/static/**:/action/**:/**/*.jsp</param-value>
</init-param>
<init-param>
<param-name>dispatcher-prefix</param-name>
<param-value>/action/**</param-value>
</init-param>
</filter>
In the above example, I would like to skip creating the filter, the filter mapping, and the servlet mapping and instead dynamically define mappings in a ContextListener.
I'm using Tomcat.
In servlet 3.0 (tomcat 7) it is possible: ServletContext.addServlet(..)
But in your case it may be wiser not to do that. Spring's DispatcherServlet for example is mapped to /*. It forwards to JSPs internally, and handles requests in its own non-servlet components, so that it doesn't need to register them dynamically. Take a look at spring-mvc anyway, even if you don't end up using it.
To dynamically add your own servlet, you need a servletcontext Object. From the api docs, there exists a method to bind your Servlet to the servletContext.
Servletconfig.getServletContext().addServlet("YourServletname",yourServletname.class);

Categories