Spring MVC and RequestMapping with filter - java

I am having a problem with url mappings and thought somebody might help me :-)
My Spring MVC application has a dispatcherServler's mapping as follows:
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
Then I have a controller servlet with a method annotated like this:
MyServlet {
....myMethod
#RequestMapping(value = "/qwert/request", method = RequestMethod.POST)
To conclude I have a DelegatingFilterProxy with a mapping:
<filter-mapping>
<filter-name>myFilter</filter-name>
<url-pattern>/qwert/request</url-pattern>
</filter-mapping>
whose target is to intercept all requests directed to the aforementioned MyServlet's method.
The application is working fine for the typical request localhost:port/MyApp/qwert/request which means that the filter is intercepting requests and doing its business.
The problems is that a request like this localhost:port/MyApp/qwert/request.do is getting directly into the Servlet (MyServlet) method without passing through the Filter. My #RequestMapping is not /qwert/request.do, how can the request end up arriving in the servlet?
Does anyone have any idea how to solve this without changing my dispatcherServlet mapping to something like *.do and making other changes accordingly.
I would like my application to serve requests under localhost:port/MyApp/qwert/request and not localhost:port/MyApp/qwert/request.whatever and I cannot change the filter mapping to /* since there are other methods that do not require the filter intervention.
Thanks
Update 1:
Yes, I tried to introduce a filter's url-pattern like /qwert/request.* but in that case the filter does not intercept any request. Neither localhost:port/MyApp/qwert/request nor localhost:port/MyApp/qwert/request.whatever (being the first one the one normal callers should be using)
Solution
At the end I found what the problem was, #Jhonathan pointed me in the right direction
I had to define a RequestMappingHandlerMapping instead of a DefaultAnnotationHandlerMapping
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = new RequestMappingHandlerMapping();
// no dot like names will be matched
mapping.setUseSuffixPatternMatch(false);
// no trailing slash will be matched
mapping.setUseTrailingSlashMatch(false);
return mapping;
}
That did the trick and I can now see internally that the pattern does not mach "wrong" requests like the ones I mentioned at the beginning.
Thank you all

First Question
My #RequestMapping is not /qwert/request.do, how can the request end up arriving in the servlet?
Spring by default take
/qwert/request.do
/qwert/request.whatever
/qwert/request.*
like
/qwert/request
therefore your #RequestMapping(value = "/qwert/request", method = RequestMethod.POST)take request. Change in your
DefaultAnnotationHandlerMapping for change this default option:
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="useDefaultSuffixPattern" value="false" />
</bean>
From Spring Source:
setUseDefaultSuffixPattern
public void setUseDefaultSuffixPattern(boolean useDefaultSuffixPattern)
Set whether to register paths using the default suffix pattern as well: i.e. whether "/users" should be registered as "/users." and "/users/" too.
Default is "true". Turn this convention off if you intend to interpret your #RequestMapping paths strictly.
Note that paths which include a ".xxx" suffix or end with "/" already will not be transformed using the default suffix pattern in any case.*

Related

How to make a servlet handle all URLs except JSPs [duplicate]

The familiar code:
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
My understanding is that /* maps to http://host:port/context/*.
How about /? It sure doesn't map to http://host:port/context root only. In fact, it will accept http://host:port/context/hello, but reject http://host:port/context/hello.jsp.
Can anyone explain how is http://host:port/context/hello mapped?
<url-pattern>/*</url-pattern>
The /* on a servlet overrides all other servlets, including all servlets provided by the servletcontainer such as the default servlet and the JSP servlet. Whatever request you fire, it will end up in that servlet. This is thus a bad URL pattern for servlets. Usually, you'd like to use /* on a Filter only. It is able to let the request continue to any of the servlets listening on a more specific URL pattern by calling FilterChain#doFilter().
<url-pattern>/</url-pattern>
The / doesn't override any other servlet. It only replaces the servletcontainer's built in default servlet for all requests which doesn't match any other registered servlet. This is normally only invoked on static resources (CSS/JS/image/etc) and directory listings. The servletcontainer's built in default servlet is also capable of dealing with HTTP cache requests, media (audio/video) streaming and file download resumes. Usually, you don't want to override the default servlet as you would otherwise have to take care of all its tasks, which is not exactly trivial (JSF utility library OmniFaces has an open source example). This is thus also a bad URL pattern for servlets. As to why JSP pages doesn't hit this servlet, it's because the servletcontainer's built in JSP servlet will be invoked, which is already by default mapped on the more specific URL pattern *.jsp.
<url-pattern></url-pattern>
Then there's also the empty string URL pattern . This will be invoked when the context root is requested. This is different from the <welcome-file> approach that it isn't invoked when any subfolder is requested. This is most likely the URL pattern you're actually looking for in case you want a "home page servlet". I only have to admit that I'd intuitively expect the empty string URL pattern and the slash URL pattern / be defined exactly the other way round, so I can understand that a lot of starters got confused on this. But it is what it is.
Front Controller
In case you actually intend to have a front controller servlet, then you'd best map it on a more specific URL pattern like *.html, *.do, /pages/*, /app/*, etc. You can hide away the front controller URL pattern and cover static resources on a common URL pattern like /resources/*, /static/*, etc with help of a servlet filter. See also How to prevent static resources from being handled by front controller servlet which is mapped on /*. Noted should be that Spring MVC has a built in static resource servlet, so that's why you could map its front controller on / if you configure a common URL pattern for static resources in Spring. See also How to handle static content in Spring MVC?
I'd like to supplement BalusC's answer with the mapping rules and an example.
Mapping rules from Servlet 2.5 specification:
Map exact URL
Map wildcard paths
Map extensions
Map to the default servlet
In our example, there're three servlets. / is the default servlet installed by us. Tomcat installs two servlets to serve jsp and jspx. So to map http://host:port/context/hello
No exact URL servlets installed, next.
No wildcard paths servlets installed, next.
Doesn't match any extensions, next.
Map to the default servlet, return.
To map http://host:port/context/hello.jsp
No exact URL servlets installed, next.
No wildcard paths servlets installed, next.
Found extension servlet, return.
Perhaps you need to know how urls are mapped too, since I suffered 404 for hours. There are two kinds of handlers handling requests. BeanNameUrlHandlerMapping and SimpleUrlHandlerMapping. When we defined a servlet-mapping, we are using SimpleUrlHandlerMapping. One thing we need to know is these two handlers share a common property called alwaysUseFullPath which defaults to false.
false here means Spring will not use the full path to mapp a url to a controller. What does it mean? It means when you define a servlet-mapping:
<servlet-mapping>
<servlet-name>viewServlet</servlet-name>
<url-pattern>/perfix/*</url-pattern>
</servlet-mapping>
the handler will actually use the * part to find the controller. For example, the following controller will face a 404 error when you request it using /perfix/api/feature/doSomething
#Controller()
#RequestMapping("/perfix/api/feature")
public class MyController {
#RequestMapping(value = "/doSomething", method = RequestMethod.GET)
#ResponseBody
public String doSomething(HttpServletRequest request) {
....
}
}
It is a perfect match, right? But why 404. As mentioned before, default value of alwaysUseFullPath is false, which means in your request, only /api/feature/doSomething is used to find a corresponding Controller, but there is no Controller cares about that path. You need to either change your url to /perfix/perfix/api/feature/doSomething or remove perfix from MyController base #RequestingMapping.
I think Candy's answer is mostly correct. There is one small part I think otherwise.
To map host:port/context/hello.jsp
No exact URL servlets installed, next.
Found wildcard paths servlets, return.
I believe that why "/*" does not match host:port/context/hello because it treats "/hello" as a path instead of a file (since it does not have an extension).
The essential difference between /* and / is that a servlet with mapping /* will be selected before any servlet with an extension mapping (like *.html), while a servlet with mapping / will be selected only after extension mappings are considered (and will be used for any request which doesn't match anything else---it is the "default servlet").
In particular, a /* mapping will always be selected before a / mapping. Having either prevents any requests from reaching the container's own default servlet.
Either will be selected only after servlet mappings which are exact matches (like /foo/bar) and those which are path mappings longer than /* (like /foo/*). Note that the empty string mapping is an exact match for the context root (http://host:port/context/).
See Chapter 12 of the Java Servlet Specification, available in version 3.1 at http://download.oracle.com/otndocs/jcp/servlet-3_1-fr-eval-spec/index.html.

Spring 4 RestController Dispatcher url-pattern

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>

How to redirect URLs with trailing slash to the corresponding ones without it?

Spring MVC (3.0) considers URLs with and without trailing slashes as the same URL.
For example:
http://www.example.org/data/something = http://www.example.org/data/something/
I need to redirect the URL with trailing slashes
http://www.example.org/data/something/
to the URL without it:
http://www.example.org/data/something
I need to do this internally the application (so not rewrite rules via Apache, etc).
A way to do it is:
#ResponseStatus(value=HttpStatus.MOVED_PERMANENTLY)
#RequestMapping(value = "/data/something/")
public String dataSomethingRedirect(...) {
return "redirect:/data/something";
}
but this has generally 2 problems:
too many controllers
problem with parameters: like wrong encoding
Question
Is there a way to intercept all the URLs and in case they have a trailing slash, redirect them to the relative one without slash?
You could list all the rewrite rules you need in your web configuration
If there aren't many of those, you can configure redirect views like this
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/my/path/", "/my/path")
.setKeepQueryParams(true)
.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
}
Or you could create a custom HandlerInterceptor
But interceptors occur before requests are mapped to a specific Controller.action and you've got no way of knowing Controllers and actions in that context.
All you've got is HTTPServlet API and request+response; so you can:
response.sendRedirect("http://example.org/whitout-trailing-slash");
The answer you don't want to read
This behavior (URL with trailing slash = URL without it) is perfectly "valid" when considering HTTP. At least this is the default behavior with Spring, that you can disable with useTrailingSlashMatch (see javadoc).
So using rewrite/redirect rules on the front-end server could a solution; but again, I don't know your constraints (maybe you could elaborate on this and we could figure out other solutions?).
I think you best option would be to do this before entering in Spring web's servlet, using UrlRewriteFilter. This will ensure that your redirect rules would not impact your controllers.
Please note that you write the rules in your .war project, not in an apache with mod_rewrite.
Go here for the library's project on googlecode.
in the urlrewrite.xml write :
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.1//EN" "http://www.tuckey.org/res/dtds/urlrewrite3.1.dtd">
<urlrewrite>
<rule match-type="regex">
<note>Remove trailing slash</note>
<from>^(.*)/$</from>
<to type="redirect">$1</to>
</rule>
</urlrewrite>
In the web.xml of your application, add :
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<init-param>
<param-name>confPath</param-name>
<param-value>/WEB-INF/urlrewrite.xml</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
Beware, the declaration order of the filters in the web.xml is important, so try to declare this one before anything from spring.
Of course, this is but a fraction of what UrlRewriteFilter can do.
Regards.
This usually works for me with URLRewriteFilter in Spring.
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.2//EN"
"http://www.tuckey.org/res/dtds/urlrewrite3.2.dtd">
<urlrewrite>
<rule>
<note>Remove trailing slash for SEO purposes</note>
<from>/**/</from>
<to type="permanent-redirect">%{context-path}/$1</to>
</rule>
</urlrewrite>
Not sure if spring 3.0 had this, but spring 3.1 RequestMappingHandlerMapping allows you to set a "useTrailingSlashMatch" property. By default it is true.
I think switching it to false would solve your issue, however it would affect ALL mappings handled by RequestMappingHandlerMapping across your application... so you may have a fair amount of regression to do.
I agree with #Brian Clozel: I don't think is a good idea to do what you want. So, why you need it?
Anyway, I think the simplest solution is to write a custom javax.servlet.Filter. So, no Spring dependency. If the request URL ends with slash you just have to redirect to the same url without it. But caution:
All parameters (GET and POST) must be added as GET parameters. Are you sure that your application is method agnostic?
You can have some problems with encoding. In the filter you can encode POST parameters to the required encoding. But the default encoding for GET parameters is not configured in your application. Is configured in server.xml (if Tomcat) and default value is ISO-8859-1.
Good luck!
Based on SEO, I think it is important to make a distinction.
If the URL that finished in the trailing slash exist, is indexed in the search engines and there are links on Internet, a permanent redirection (301) is required as Uddhav Kambli says. The standard redirection (302) will be better than having a duplicated URL, but is not good enough.
However, if the URL never existed, it is not indexed on Internet and there are no external links, the URL does not exist. Therefore a 404, page not found, is a better fit.
WEB-INF/urlrewrite.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE urlrewrite PUBLIC "-//tuckey.org//DTD UrlRewrite 3.1//EN" "http://www.tuckey.org/res/dtds/urlrewrite3.1.dtd">
<urlrewrite>
<rule match-type="regex">
<note>Remove trailing slash</note>
<from>^(.+)/$</from>
<set type="status">404</set>
<to>null</to>
</rule>
</urlrewrite>
And in order to complete the configuration ...
add to WEB-INF/web.xml
<filter>
<filter-name>UrlRewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<init-param>
<param-name>confPath</param-name>
<param-value>/WEB-INF/urlrewrite.xml</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>UrlRewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
Maven
<dependency>
<groupId>org.tuckey</groupId>
<artifactId>urlrewritefilter</artifactId>
<version>4.0.3</version>
</dependency>
I have found that this can also be handled much more simply by using an ErrorViewResolver bean somewhere in your #Configuration:
#Autowired
private DefaultErrorViewResolver defaultErrorViewResolver;
#Bean
ErrorViewResolver errorViewResolver() {
return new ErrorViewResolver() {
#Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
if(model.containsKey("path") && !model.get("path").toString().endsWith("/")) {
return new ModelAndView("redirect:"+model.get("path") + "/");
}
return defaultErrorViewResolver.resolveErrorView(request, status, model);
}
};
}
I'm not sure if this is good or bad practice, but it is effective in my circumstance, and does exactly what I need it to do for an arbitrary path 'foo': respond with a 302 redirect to /foo/ when you request /foo and respond as it should when you request /foo.

urlrewrite using Servlet filter

I'm trying to write a simple urlrewriter using Servlet's filter (javax.servlet.Filter). The filter inspects all requests and reroutes to servlet (or JSP) depending on the URL.
Example: http://server/app/person/Roscoe would be translated to http://server/app/person.jsp?name=Roscoe
My Filter's doFilter inspects the request, and if the pattern matches, creates a new HttpServletRequest and passes it to chain.doFilter. The new HttpServletRequest extends javax.servlet.http.HttpServletRequestWrapper and overrides the parameters, URI, URL, query string, and servlet path to look like the new JSP (/person.jsp?name=Roscoe). I thought that by passing the new request to chain.doFilter it would redirect to the JSP. This works, sort of, except that the contents of person.jsp are returned to the browser. person.jsp never executes the contents are returned as plain text (Content-Type: text/plain).
My web.xml has the filter and filter-mapping:
<filter-mapping>
<filter-name>MyFilter</filter-name>
<url-pattern>*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Is this the correct was to use a Servlet filter to rewrite the request?
I am aware of existing urlrewriters (such as Tuckey) but would still like to write my own, mainly to learn and for better control.
Follow-up: I've also tried redirecting instead of chain.doFilter by doing (where req is the wrapped request):
config.getServletContext().getRequestDispatcher("/person.jsp").forward(req, resp);
This works better, but my CSS file (styles.css) is still relative to original URL http://server/app/person/styles.css, whereas it should be http://server/app/styles.css
Follow-up 2: The path issue is covered by this question.
Just wrapping the request and changing its state isn't sufficient. You'll need to forward/redirect to the appropriate resource. Filters and servlets are mapped to a URL pattern. The chain you're executing in was built based on the pattern of the incoming request's URL. Changing the URL in the request and passing it to the next item in the chain isn't going to rebuild the chain to account for the new URL. That's what a forward/redirect is for.

Using SiteMesh with RequestDispatcher's forward()

I'm attempting to integrate SiteMesh into a legacy application using Tomcat 5 as my a container. I have a main.jsp that I'm decorating with a simple decorator.
In decorators.xml, I've just got one decorator defined:
<decorators defaultdir="/decorators">
<decorator name="layout-main" page="layout-main.jsp">
<pattern>/jsp/main.jsp</pattern>
</decorator>
</decorators>
This decorator works if I manually go to http://example.com/my-webapp/jsp/main.jsp. However, there are a few places where a servlet, instead of doing a redirect to a jsp, does a forward:
getServletContext().getRequestDispatcher("/jsp/main.jsp").forward(request, response);
This means that the URL remains at something like http://example.com/my-webapp/servlet/MyServlet instead of the jsp file and is therefore not being decorated, I presume since it doesn't match the pattern in decorators.xml.
I can't do a <pattern>/*</pattern> because there are other jsps that do not need to be decorated by layout-main.jsp. I can't do a <pattern>/servlet/MyServlet*</pattern> because MyServlet may forward to main.jsp sometimes and perhaps error.jsp at other times.
Is there a way to work around this without expansive changes to how the servlets work? Since it's a legacy app I don't have as much freedom to change things, so I'm hoping for something configuration-wise that will fix this.
SiteMesh's documentation really isn't that great. I've been working mostly off the example application that comes with the distribution. I really like SiteMesh, and am hoping I can get it to work in this case.
My understanding is that SiteMesh is integrated into the application as a Servlet filter. By default, servlet filters are only invoked against the original incoming request (in your case, the request to the servlet). Subsequent forward or include requests are not passed throuh the filter, and therefore will not be passed through sitemesh.
You can, however, instruct the filter to be invoked on forwards, using something like this:
<filter-mapping>
<filter-name>sitemesh</filter-name>
<servlet-name>MyServlet</servlet-name>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
Which instructs the container to only operate on FORWARD requests. The other options are INCLUDE and REQUEST, you can have several elements.
So your options are to either change your filter config to specify FORWARD, or to change your filter-mapping to match the servlet path, rather than the JSP path. Either one should work.

Categories