2

I'm building a project using Spring Boot and Angular 1.5.X and I am struggling to handle full page refreshes of Angular routes - typical "404 because the path I made doesn't actually exist" problem. I've done a fair bit of research and the solution that I keep seeing is to implement a .htaccess file with the following snippet in order to redirect all unknown requests back to the index (I pulled the following from this post)

RewriteEngine On 
Options FollowSymLinks

RewriteBase /

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /#/$1 [L]

I have Tuckey's UrlRewriteFilter installed - according to this blog post since I don't have a WEB-INF folder - and it is working. It starts and it reads the urlrewrite.xml successfully. However, I don't know what to put in my urlrewrite.xml - I haven't the slightest clue of how to translate the above into something that the UrlRewriteFilter can understand. I've browsed the manual for the UrlRewriteFilter and I don't really know where / how to start.

Basically, what do I have to put in my urlrewrite.xml so that if I hit F5, my website doesn't puke back 404 errors?

Any help is appreciated.

Edit 1

I should mention that all of my API endpoints are prefaced with /api/** in order to distinguish them from links on my front end - an example would be /api/open/getUser and /api/secured/updateSettings.

Edit 2

Couple things I've discovered so far. One is that the UrlRewriteFilter can actually support .htaccess files and I did get it (as far as I can tell) to load in by moving the .htacess into my Resources folder and tweaking the code sample in the above blog post slightly, changing this

 private static final String CONFIG_LOCATION = "classpath:/urlrewrite.xml";

to

 private static final String CONFIG_LOCATION = "classpath:/.htaccess";

and

Conf conf = new Conf(filterConfig.getServletContext(), resource.getInputStream(), resource.getFilename(), "MyProject");

to

Conf conf = new Conf(filterConfig.getServletContext(), resource.getInputStream(), resource.getFilename(), "MyProject", true);

The addition of the true tells the filter to use a .htaccess file. Awesome, problem solved right? Not quite - it's hard to explain but it doesn't seem like the UrlRewriteFilter was/is reading the .htacess correctly. I was using an .htacess tester to verify that the regex's and rewrite conditions were working as I expected and they seemed to be. The tester said that they were fine. However, the UrlRewriteFilter would freak out and get stuck in some kind of loop, to the point that Java would throw a stack overflow exception (as to why, I've no idea - I can't seem to find a way to set the filter's logging level to debug via Java D:< ).

So clearly that didn't work - I am currently attempting to translate the .htaccess into urlrewrite.xml myself, and here is what I've managed to created so far.

<urlrewrite use-query-string="true">
<rule match-type="regex" enabled="true">
    <note>
        Any URI that ends with one of the following extensions will be allowed to continue on unimpeded.
        Buried in the manual was the single line that said a "-" in the "to" will allow the request to
        continue on unmodified.
    </note>
    <from>\.(html|css|jpeg|gif|png|js|ico|txt|pdf)$</from>
    <to last="true">-</to>
</rule>

<rule match-type="regex" enabled="true">
    <note>
        Any URI that is prefaced with "/api/open/**" or "/api/secured/**" will be allowed through unmodified.
    </note>
    <condition type="request-uri" operator="equal">\/api\/(open|secured)\/([a-zA-Z0-9\/]+)</condition>
    <from>^.*$</from>
    <to last="true">-</to>
</rule>

<rule match-type="regex" enabled="false">
    <note>
        This one is supposed to be a "when all else fail" rule - if the other two rules don't match,
        forward to the index and let Angular figure out the rest.
        !! This one seems to be getting stuck in a loop of sorts !!
    </note>
    <from>^.*$</from>
    <to last="true">/</to>
</rule>
</urlrewrite>

The first two seem to be working splendidly. The third rule (the one with enabled set to false for good reason) does not - it also appears to getting stuck in the same filter loop (or whatever is happening - the stack trace is so big that Intellij is like "nah man") as the .htacess method. Making progress.

Community
  • 1
  • 1
Snowie
  • 223
  • 1
  • 3
  • 8

1 Answers1

0

Huzzah, I managed to get it! It was a right pain the butt since I couldn't figure out how to turn on debugging and see what the filter was actually doing, but alas, I have succeeded!

Spent one metric crap ton of time using a regex tester, and this is what I came up with. I am by no means even remotely close to a regex master, so please try to contain your nausea should you have any.

<urlrewrite use-query-string="true">

<rule match-type="regex" enabled="true">
    <note>
        - "/post/**" and "/user/.../**" are optional - this is because when you're on, say, "/post/20" and you hit
            F5, the browser will attempt to get the static assets from "/post/**"
        - the second group is used to see if the request is for a static asset
        - take advantage of back references and forward only the part that matches the second group
        - i.e. "/post/20" as URI -> hit F5 -> "/post/scripts/mainController.js" request of server -> "/scripts/mainController.js" forwarded
        - i.e. "/user/Tester/home" -> hit F5 -> "/user/Tester/scripts/mainController.js" -> "/scripts/mainController.js" forwarded
    </note>
    <condition type="request-uri" operator="equal">\/?(post\/|user\/[a-zA-Z0-9]+\/)(.*.(html|css|jpe?g|gif|png|js|ico|txt|pdf))</condition>
    <from>^.*$</from>
    <to last="true">/%2</to>
</rule>

<rule match-type="regex" enabled="true">
    <note>
        Any URI that is prefaced with "/api/open/**" or "/api/secured/**" will be allowed through unmodified.
    </note>
    <condition type="request-uri" operator="equal">\/api\/(open|secured)\/([a-zA-Z0-9\/]+)</condition>
    <from>^.*$</from>
    <to last="true">-</to>
</rule>

<rule match-type="regex" enabled="true">
    <note>
        - Register, browse, search, and upload are all single level urls - the "\z" is to match the end of the string,
            otherwise "/register" would match "/registerController.js"
        - Inbox CAN be like "/inbox/favorites" so that's why it has a secondary regex - my Regex-Fu isn't good enough to combine
        - Settings always has a secondary level
        - User always has either home, gallery (w/ page and number), or favorites (w/ page and number)
        - A post will always have a number
    </note>
    <condition type="request-uri" operator="equal" next="or">\/(register\z|browse\z|search\z|upload\z|inbox\z|tag\z)</condition>
    <condition type="request-uri" operator="equal" next="or">\/inbox\/(favorites\z|uploads\z|comments\z)?</condition>
    <condition type="request-uri" operator="equal" next="or">\/settings\/[A-Za-z-_0-9]+</condition>
    <condition type="request-uri" operator="equal" next="or">\/user\/[A-Za-z-_0-9]+\/(home\z|gallery\/[0-9]+\/[0-9]+|favorites\/[0-9]+\/[0-9]+)</condition>
    <condition type="request-uri" operator="equal" next="or">\/post\/[0-9]+</condition>
    <from>^.*$</from>
    <to last="true">/</to>
</rule>

The rules are not as general as I'd like, but they are functional (I have a sneaking suspicion that those five conditionals daisy chained together are a bit of a performance hit). The rules are pretty much specifically tailored solely to my needs but hopefully they can at least be starting point to anybody else who was in my shoes about 4 days ago.

Another important thing to take note of is that in your Angular config (if you're using HTML5 mode - I don't believe that the following is required for hashbang mode), make sure you set requiredBase to true, like:

$locationProvider.html5Mode({
    enabled: true,
    requireBase: true
});

and include a

<base href="/">

in the <head> of your index.html file. If you don't, Angular will get confused and parts of your application might not quite load correctly - parts of my URI were being trimmed, for example.

Also, tip for anybody new to using .htaccess / UrlRewriteFilter, go get yourself Postman in order to test your rules - probably a major "well, duh" for most, but for the rest of us it'll be a life saver :)

If anybody has any tips on how to improve the efficiency / combine the regex's at all, please let me know.

Snowie
  • 223
  • 1
  • 3
  • 8