Yeoman Backbone apps meet Tuckey’s UrlRewriteFilter!

A couple months ago, I started using Yeoman to rapidly scaffold out webapps with AngularJS.  I even wrote a quick blog post about Yeoman, as well as how to use the Yeoman Maven plugin when writing webapps in Spring and setting up the Jetty Maven plugin to use the scaffolded app during development.  Since then, I’ve begun using Backbone and Underscore and I’ve honed how I use Yeoman on a daily basis.

Today I don’t really use the Yeoman Maven plugin anymore during development.  Indeed it is a great plugin.  The main issue I had during development time was the time it took to complete a build because the Yeoman Maven plugin is doing a few long lasting IO tasks over the internet.  Combine with that the fact that I didn’t need to actually build my entire WAR every time I wanted to compile changes I made to my JavaScript source.  In fact, for the most part, the servlet part of my webapp didn’t require much changes at all since I was mainly exposing REST endpoints producing JSON data.  Now during development, I simply build my WAR once (assuming there are indeed no changes to make on the server side) and test my app using the Jetty Maven plugin as usual.

A few things have changed in my application structure in the past couple months.  When I started using Yeoman, I was creating a directory called “yo” outside of my usual src/main directory structure used by Maven.  At the time, it seemed like the right thing to do from what I was reading in blog posts and StackOverflow.  Over the course of development, this posed a few challenges that I spent some time trying to find solutions to, but nothing seemed all that elegant or straightforward.  So the first big change to my structure is now I run my Yeoman commands within my src/main/webapp directory which Maven does recognize by default.

Create a Backbone app with Yeoman

The first thing I do is install the Backbone Yeoman generator.

npm install generator-backbone

Then I scaffold out a Backbone application using the generator I just installed.

yo backbone

Currently this generator prompts you to select a couple options. If you want Twitter Bootstrap for SASS, and if you want to include RequireJS. I'm no expert on SASS or LESS, so I've been opting out of that option, but I do keep RequireJS as I've grown to like how it allows me to keep my app modular.

Now that the basic app is scaffolded, simply run the grunt command from within src/main/webapp to do the initial build.

grunt

The grunt command will build your Backbone app, minifying CSS and JavaScript into directory src/main/webapp/dist.  At this point, you could start fleshing out your app with your own JavaScript modules that do all kinds of fancy stuff like fetching data from your servlet, drawing maps and graphs, etc...  For the sake of this post, I'll just stick with the app that was generated which just has a single page.

Create a Spring MVC ViewResolver to look for view files in src/main/webapp/dist

This part is pretty straightforward if you're already an experienced Spring MVC developer.  I'm using Spring's @Bean config to setup my app and have virtually no need for XML config for most things.  I still use XML heavily though when writing apps that leverage Spring Integration.  The important @Bean is the ViewResolver where I set the prefix to "/dist/" and the suffix to ".html".  This will tell my @Controllers to look for views within the src/main/webapp/dist folder generated during my grunt build earlier.  For the sake of the scaffolded app, it will copy the index.html from src/main/webapp/app to src/main/webapp/dist.  For the uninitiated, the src/main/webapp/app folder is where the non-compiled JavaScript source and bower components reside during development when using a Yeoman generator.  At least that's what it is for the Backbone generator.  This is my full MVC config class.

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.example"})
public class WebConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("index");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/", "classpath:/META-INF/web-resources/");
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/dist/");
        viewResolver.setSuffix(".html");
        viewResolver.setViewClass(InternalResourceView.class);
        return viewResolver;
    }
}

Update: As pointed out by Josh Long, changing the resource handler can negate the need for UrlRewriteFilter altogether. Se my Gist here for the modification.

Test your app with the Jetty Maven plugin

Now that you've built your client side app and setup your Spring MVC app to locate views in the generated src/main/webapp/dist directory, we can tell the Maven War plugin how to put the two together.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-war-plugin</artifactId>
  <version>2.3</version>
  <configuration>
    <warSourceDirectory>src/main/webapp</warSourceDirectory>
    <warName>${project.artifactId}</warName>
    <failOnMissingWebXml>false</failOnMissingWebXml>
    <warSourceIncludes>dist/**</warSourceIncludes>
  </configuration>
</plugin>

Nothing too revolutionary here.  The configuration tells it to look in src/main/webapp, name the war using the ${project.artifactId} property, and not fail on missing web.xml since I'm using Spring and implementing their WebApplicationInitializer interface for Servlet 3.0 which doesn't require a web.xml anymore.  The one part you should focus your attention on is the <warSourceIncludes> element with a value of dist/**.  Basically, this tells the Maven War plugin to include the whole "dist" directory in src/main/webapp and ignore anything else.  This ensures that the final WAR won't include any of the uncompiled sources, or bower components installed during development that aren't needed in the final build.

At this point, you could run "mvn clean install", get a successful build, then run "mvn jetty:run" within your app's root directory and Jetty would fire up.  Load up your favourite browser (mine is Chrome) and browse to http://localhost:8080/myapp/ and see it bomb on you.  If you inspect what's going on with the Chrome dev tools, you'll notice that it can't find any of your scripts, styles, or bower components.  This is where I faced the most challenges back when I was using Yeoman outside of src/main/webapp, and in a "yo" directory off the root of my application.  How could I tell it to look for all those resources in the yo/dist folder being generated at the time.

To make a long story short, I just couldn't make it happen.  I tried everything from using the Maven Dependency and/or Resource plugin to copy some files around and ignore others.  I tried telling the Maven War plugin to also look in yo/dist for resources, but that didn't solve my problem either.  Sometimes I thought I had it working on the WAR side, but testing with Jetty would then fail to find the resources.  The main reason for all of this is really the path hierarchy of the application.  The root of your app on the web could be something like http://localhost:8080/myapp/ but the view being rendered could be in WEB-INF/views/index.html.  Getting the generated index.html out of yo/dist into WEB-INF/views at build time was possible, but the difficulty or virtual impossibility was ensuring all the links to scripts, styles and bower components worked.  Everything always seemed to be off by one directory level and overall, it all just seemed like a bunch of ugly build hacks, rather than an elegant solution.

Enter UrlRewriteFilter to save the day!

I first saw Tuckey's UrlRewriteFilter being used in my earlier Spring MVC development days.  If I remember correctly (pardon me if I'm wrong), but it was being used for serving static resources like scripts and styles to the web, while keeping them beneath WEB-INF in the app's directory structure.  At this point in my scaffolded app, I knew the way I had been doing things in my build wasn't going to work and I had a sudden flash of light.  Why not try UrlRewriteFilter to serve up files under src/main/webapp/dist so that they appear to be coming off the root of my app on the web?  After a bit of trial and error, I got the URL rewriting working correctly.

The first thing I had to do was add the UrlRewriteFilter to my servlet context.  In my WebApplicationInitializer implementation, I add the filter using this code which I found on this blog post.

FilterRegistration.Dynamic urlRewriteFilter = servletContext.addFilter("urlRewriteFilter", UrlRewriteFilter.class);
urlRewriteFilter.setInitParameter("logLevel", "slf4j");
EnumSet<DispatcherType> urlRewriteDispatcherTypes = EnumSet.of(DispatcherType.FORWARD, DispatcherType.REQUEST);
urlRewriteFilter.addMappingForUrlPatterns(urlRewriteDispatcherTypes, true, "/*");

With the filter in place, now I simply had to add a file called urlrewrite.xml to src/main/webapp/WEB-INF with the rewrite rules I wanted in my app.  It took me a few tries and reading the documentation a couple times to get the rules working correctly, but the result is actually quite simple.

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

<!--
  Configuration file for UrlRewriteFilter

http://www.tuckey.org/urlrewrite/

-->
<urlrewrite default-match-type="wildcard">
  <rule>
    <from>/bower_components/**</from>
    <to>/dist/bower_components/$1</to>
  </rule>

  <rule>
    <from>/scripts/**</from>
    <to>/dist/scripts/$1</to>
  </rule>

  <rule>
    <from>/styles/**</from>
    <to>/dist/styles/$1</to>
  </rule>
</urlrewrite>

Now all I had to do was rerun the "mvn clean install" and "mvn jetty:run" commands, fire up my browser to http://localhost:8080/myapp/ and voila! Everything worked perfectly. Spring's ViewResolver located the index view within src/main/webapp/dist, and all requests for /bower_components/**, /scripts/** and /styles/** were automatically forwarded to /dist/bower_components/**, /dist/scripts/** and /dist/styles/** by the UrlRewriteFilter.

Conclusion

After much trial and error, and some time spent RTFM, I have a solution that I feel is elegant and simple.  I hope this post helps you in some way and if I've missed something or forgot to include something, feel free to let me know ;)

About these ads

2 Comments on “Yeoman Backbone apps meet Tuckey’s UrlRewriteFilter!

  1. Seams like a pretty clean solution and it is exactly what I was looking for to get started with angularJS along with a spring backend. But what about setting up the spring mvc web app to send the needed headers for CORS, so that the backend and angularJS front end is as good as possible decoupled?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 77 other followers

%d bloggers like this: