angularspring-booturl-rewritingjhipsterembedded-tomcat

How to define rewrite rules for a jhipster application with angular frontend running productive as jar with embedded tomcat?


One needs to configure some rewrite rules for Angular SPAs to redirect requests to the index.html: https://angular.io/guide/deployment#server-configuration

We use the Jhipster generator (Spring Boot) as a starting point for our applications and usually weseparate the frontend (http server) from the backend (tomcat server). But for this project we need to run frontend and backend together in the resulting JAR and I'm not sure how to configure these rewrite rules for the embedded tomcat.

I've tried various solutions I found on stackoverflow but nothing works. For example: https://stackoverflow.com/a/64616180/5214044

This is the code I added to my jhipster generated WebConfigurer class:

@Bean
    public TomcatServletWebServerFactory tomcatFactory() {
        return new TomcatServletWebServerFactory() {
            @Override
            protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {

                final RewriteValve valve = new RewriteValve() {

                    @Override
                    protected synchronized void startInternal() throws LifecycleException {
                        super.startInternal();

                        try {
                            InputStream resource = new ClassPathResource("container/tomcat/rewrite.config").getInputStream();

                            InputStreamReader resourceReader = new InputStreamReader(resource);
                            BufferedReader buffer = new BufferedReader(resourceReader);

                            parse(buffer);

                        } catch (IOException e) {
                            log.error("Error while parsing rewrite.config", e);
                        }
                    }
                };

        valve.setEnabled(true);

        log.info("Adding rewrite valve to Tomcat: {}", valve);

                // Set up the Rewrite Valve
                tomcat.getEngine().getPipeline().addValve(valve);

                return super.getTomcatWebServer(tomcat);
            }
        };
    }

My rules:

# If an existing asset or directory is requested go to it as it is
RewriteCond %{REQUEST_URI} -f [OR]
RewriteCond %{REQUEST_URI} -d
RewriteRule ^ - [L]

# If the requested resource doesn't exist, use index.html
RewriteRule ^ /index.html

I enabled logging for the RewriteValve and it seems to work:

o.a.c.c.ContainerBase.[Tomcat].rewrite   : Add rule with pattern ^ and substitution -
o.a.c.c.ContainerBase.[Tomcat].rewrite   : Add condition -f test %{REQUEST_URI} to rule with pattern ^ and substitution - [OR]
o.a.c.c.ContainerBase.[Tomcat].rewrite   : Add condition -d test %{REQUEST_URI} to rule with pattern ^ and substitution - [OR]
o.a.c.c.ContainerBase.[Tomcat].rewrite   : Add rule with pattern ^ and substitution /index.html

When I curl e.g. for the /login URL, which should be redirected to index.html, this is the log:

o.apache.tomcat.util.threads.LimitLatch  : Counting up[http-nio-8080-Acceptor] latch=1
o.a.tomcat.util.net.SocketWrapperBase    : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@2f4ddb27:org.apache.tomcat.util.net.NioChannel@7c8d60df:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:42762]], Read from buffer: [0]
org.apache.tomcat.util.net.NioEndpoint   : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@2f4ddb27:org.apache.tomcat.util.net.NioChannel@7c8d60df:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:42762]], Read direct from socket: [83]
o.a.c.authenticator.AuthenticatorBase    : Security checking request GET /login
org.apache.catalina.realm.RealmBase      :   No applicable constraints defined
o.a.c.authenticator.AuthenticatorBase    : Not subject to any constraint
o.a.c.c.C.[Tomcat].[localhost]           : Processing ErrorPage[errorCode=0, location=/error]
o.a.c.c.C.[.[.[/].[dispatcherServlet]    :  Disabling the response for further output
o.a.tomcat.util.net.SocketWrapperBase    : Socket: [org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@2f4ddb27:org.apache.tomcat.util.net.NioChannel@7c8d60df:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:42762]], Read from buffer: [0]
o.apache.tomcat.util.threads.LimitLatch  : Counting down[http-nio-8080-exec-2] latch=1
org.apache.tomcat.util.net.NioEndpoint   : Calling [org.apache.tomcat.util.net.NioEndpoint@5958be1b].closeSocket([org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper@2f4ddb27:org.apache.tomcat.util.net.NioChannel@7c8d60df:java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:42762]])

I am not sure about this. jhipster is a huge "framework". For sure I am not the only one with this requirement. Maybe I have to configure something I don't know? Or how did you solve that?


Solution

  • Do it the JHipster way using a web filter like their SpaWebFilter for Spring Boot 2 or for Spring Boot 3.

    A web filter depends only on Spring rather than depending on Tomcat specific API (e.g. Valves) and it's really powerful as it can get access to all your Spring beans.

    Also, to make your life easier: use embedded Undertow (as provided by JHipster) rather than embedding Tomcat yourself.