How to redirect URLs with trailing slash to the co

2019-01-17 11:16发布

问题:

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:

  1. too many controllers
  2. 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?

回答1:

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?).



回答2:

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.



回答3:

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>


回答4:

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.



回答5:

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!



回答6:

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>


回答7:

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.