How to convert a subdomain to a path with Embedded Tomcat 8 and Spring boot

与世无争的帅哥 提交于 2019-12-03 03:12:00

Create you own Filter.

This Filter should:

  • check if you have to rewrite your request
  • If yes, rewrite URL and URI
  • forward request
  • it comes again through same filter, but first check will deliver false
  • don't forget and be carefull to call chain.doFilter

Forwarding will not change any URLs in browser. Just ship content of file.

Follow code could be implementation of such filter. This is not any kind of clean code. Just quick and dirty working code:

@Component
public class SubdomainToReversePathFilter implements Filter {
    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
        final HttpServletRequest req = (HttpServletRequest) request;
        final String requestURI = req.getRequestURI();

        if (!requestURI.endsWith("/")) {
            chain.doFilter(request, response);
        } else {
            final String servername = req.getServerName();
            final Domain domain = getDomain(servername);

            if (domain.hasSubdomain()) {
                final HttpServletRequestWrapper wrapped = wrapServerName(req, domain);
                wrapped.getRequestDispatcher(requestURI + domain.getSubdomainAsPath()).forward(wrapped, response);
            } else {
                chain.doFilter(request, response);
            }
        }
    }

    private Domain getDomain(final String domain) {
        final String[] domainParts = domain.split("\\.");
        String mainDomain;
        String subDomain = null;

        final int dpLength = domainParts.length;
        if (dpLength > 2) {
            mainDomain = domainParts[dpLength - 2] + "." + domainParts[dpLength - 1];

            subDomain = reverseDomain(domainParts);
        } else {
            mainDomain = domain;
        }

        return new Domain(mainDomain, subDomain);
    }
    private HttpServletRequestWrapper wrapServerName(final HttpServletRequest req, final Domain domain) {
        return new HttpServletRequestWrapper(req) {
            @Override
            public String getServerName() {
                return domain.getMaindomain();
            }
            // more changes? getRequesetURL()? ...?
        };
    }

    private String reverseDomain(final String[] domainParts) {
        final List<String> subdomainList = Arrays.stream(domainParts, 0, domainParts.length - 2)//
                .collect(Collectors.toList());

        Collections.reverse(subdomainList);

        return subdomainList.stream().collect(Collectors.joining("."));
    }

    @Override
    public void init(final FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

Here is Domain class:

public static class Domain {
    private final String maindomain;
    private final String subdomain;

    public Domain(final String maindomain, final String subdomain) {
        this.maindomain = maindomain;
        this.subdomain = subdomain;
    }

    public String getMaindomain() {
        return maindomain;
    }

    public String getSubdomain() {
        return subdomain;
    }

    public boolean hasSubdomain() {
        return subdomain != null;
    }

    public String getSubdomainAsPath() {
        return "/" + subdomain.replaceAll("\\.", "/") + "/";
    }
}

And you need a Controller that catches everything

@RestController
public class CatchAllController {

    @RequestMapping("**")
    public FileSystemResource deliver(final HttpServletRequest request) {
        final String file = ((String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));

        return new FileSystemResource(getStaticFile(file));
    }

    private File getStaticFile(final String path) {
        try {
            // TODO handle correct
            return new File(CatchAllController.class.getResource("/static/" + path + "/index.html").toURI());
        } catch (final Exception e) {
            throw new RuntimeException("not found");
        }
    }
}

I'm not sure if this is necessary to override other methods in HttpServletRequestWrapper. That's reason for comment.

Also you have to handle cases for file delivery (not existent, ...).

You can use Backreferences to use grouped parts that matched in your <condition>. Something like this -

<condition name="host" operator="equal">(*).(*).example.com</condition>
<from>^(.*)</from>
<to type="redirect">example.com/%1/%2</to>

Of course, you'll have to tweak the condition rule above to stop eager matching.

More here - http://urlrewritefilter.googlecode.com/svn/trunk/src/doc/manual/4.0/index.html#condition

Another way to solve this issue: Do it in Controller itself. This is in my opinion better way than with filter because:

  • filter catches every request. Here you can better controll which requests should be delivered with 'subdomain pattern'. As example I've choosen /subdomain2path.
  • You don't have to change/wrap URI/URLs and forward via filter chain again.
  • All logic is in this controller

Methods getSubdomain and reverseDomain are same as in answer before.

This is the impl:

@RestController
@RequestMapping("/subdomain2path")
public class Subdomain2PathController {

    @RequestMapping("/")
    public FileSystemResource deliver(final HttpServletRequest request) {
        final Domain subdomain = getSubdomain(request.getServerName());

        String file = "/";
        if (subdomain.hasSubdomain()) {
            file = subdomain.getSubdomainAsPath();
        }

        return new FileSystemResource(getStaticFile(file));
    }

    private Domain getSubdomain(final String domain) {
        final String[] domainParts = domain.split("\\.");
        String mainDomain;
        String subDomain = null;

        final int dpLength = domainParts.length;
        if (dpLength > 2) {
            mainDomain = domainParts[dpLength - 2] + "." + domainParts[dpLength - 1];

            subDomain = reverseDomain(domainParts);
        } else {
            mainDomain = domain;
        }

        return new Domain(mainDomain, subDomain);
    }

    private String reverseDomain(final String[] domainParts) {
        final List<String> subdomainList = Arrays.stream(domainParts, 0, domainParts.length - 2)//
                .collect(Collectors.toList());

        Collections.reverse(subdomainList);

        return subdomainList.stream().collect(Collectors.joining("."));
    }

    private File getStaticFile(final String path) {
        try {
            // TODO handle correct
            return new File(Subdomain2PathController.class.getResource("/static/" + path + "/index.html").toURI());
        } catch (final Exception e) {
            throw new RuntimeException("not found");
        }
    }
}

Domain class is same as in answer before:

public static class Domain {
    private final String maindomain;
    private final String subdomain;

    public Domain(final String maindomain, final String subdomain) {
        this.maindomain = maindomain;
        this.subdomain = subdomain;
    }

    public String getMaindomain() {
        return maindomain;
    }

    public String getSubdomain() {
        return subdomain;
    }

    public boolean hasSubdomain() {
        return subdomain != null;
    }

    public String getSubdomainAsPath() {
        return "/" + subdomain.replaceAll("\\.", "/") + "/";
    }
}

It worked for me. Hope it works for others too.

Please use the following dependency


         <dependency>
           <groupId>org.tuckey</groupId>
           <artifactId>urlrewritefilter</artifactId>
           <version>4.0.4</version>
         </dependency>

Created urlrewrite.xml in resource folder

 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE urlrewrite
    PUBLIC "-//tuckey.org//DTD UrlRewrite 3.0//EN"
    "http://www.tuckey.org/res/dtds/urlrewrite3.0.dtd">

<urlrewrite>
<rule>
    <name>Domain Name Check</name>
    <condition name="host" operator="notequal">www.userdomain.com</condition>
    <from>^(.*)$</from>
    <to type="redirect">http://www.userdomain.com$1</to>
</rule>


Added in main ApplicationRunner.java

@Bean
public FilterRegistrationBean tuckeyRegistrationBean() {
    final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
    registrationBean.setFilter(new CustomURLRewriter());
    return registrationBean;
}

And created a CustomURLRewriter

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.tuckey.web.filters.urlrewrite.Conf;
import org.tuckey.web.filters.urlrewrite.UrlRewriteFilter;
import org.tuckey.web.filters.urlrewrite.UrlRewriter;
import javax.servlet.*;
import java.io.InputStream;

public class CustomURLRewriter extends UrlRewriteFilter {
private UrlRewriter urlRewriter;

@Autowired
Environment env;

@Override
public void loadUrlRewriter(FilterConfig filterConfig) throws ServletException {
    try {
        ClassPathResource classPathResource = new ClassPathResource("urlrewrite.xml");
        InputStream inputStream = classPathResource.getInputStream();
        Conf conf1 = new Conf(filterConfig.getServletContext(), inputStream, "urlrewrite.xml", "");
        urlRewriter = new UrlRewriter(conf1);
    } catch (Exception e) {
        throw new ServletException(e);
    }
}

@Override
public UrlRewriter getUrlRewriter(ServletRequest request, ServletResponse response, FilterChain chain) {
    return urlRewriter;
}

@Override
public void destroyUrlRewriter() {
    if(urlRewriter != null)
        urlRewriter.destroy();
}
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!