Try to rewrite to a different URL if it would result in 404

0

I have a local tomcat server that I'm developing for using my local IIS instance as a proxy.

I do this because deploying the server is a painful process since a lot of the content isn't (what I would describe as) self contained. Content from different projects are essentially copied over to the root of the server. I didn't want to deal with that hassle of setting that up so with the help of the rewrite module, I could rewrite URLs to virtual directories for the most part.

e.g.,

/js/* -> /someproject/js/*
/css/* -> /someproject/css/*
/**/*.pdf -> /someotherproject/pdf/*

There are however a few corner cases where this scheme doesn't work, particularly when there is overlap in the destination directories. In the deployment, some resources are placed in the same directory so there's no real way to distinguish which is which. There is no strict pattern to these files, it's all a mixed bag.

e.g.,

/someproject1/file1.txt -> /file1.txt
/someproject2/book2.doc -> /book2.doc

So given a url /file1.txt, I wouldn't know if I can rewrite to go to someproject1 or someproject2. So I'm thinking I could get this to work if there was some sort of hierarchy to what urls to try to rewrite to. So I might take a url like /file3.txt, rewrite to the first of these patterns that appears valid.

/someproject1/file3.txt     # if 404, try the next
/someproject2/file3.txt     # if 404, try the next
/someotherproject/file3.txt # if 404, try the next
/file3.txt                  # fallback

Is this something that can be expressed only using the URL rewrite module?

Jeff Mercado

Posted 2015-09-18T23:41:09.743

Reputation: 532

Have you thought about using links, so that the same file will appear in all the relevant project directories? – AFH – 2015-09-18T23:54:02.580

Maybe, though I don't know if that would work for my scenario. If there was a way to make a single directory refer to multiple directories, then I'd do that. But AFAIK, it doesn't work that way. If that was possible, I'd accept that too. – Jeff Mercado – 2015-09-19T00:04:03.923

It is ages since I used IIS, and I don't now have a test version, but I believe that, like the other web servers I have used, an alias can be defined to point to any directory, with no restriction on multiple links to the same directory. In fact I use this for capitalisations, eg http://{Site}/music and http://{Site}/Music both point to the same {Drive}:\{Path}\SharedMusic. – AFH – 2015-09-19T12:25:28.743

For my situation, I would need to go the other way around. What you describe, I can do, multiple virtual paths can point to a single physical path. But ideally, I would need multiple physical paths to a single virtual path. That as far as I know is not possible. – Jeff Mercado – 2015-09-19T16:06:31.547

The best you can do then is to create a directory of links: you can create this on the fly and set the job to run periodically. Use a command like for /r %i in (*.*) do mklink /h "{CombinedPath}\%~nxi" "%i" (doubling the % in a batch file). You will first need to clear the {CombinedPath} directory with del {CombinedPath}\*.*. You can try creating symbolic links (omit /h), but I am not sure if IIS will handle these correctly. – AFH – 2015-09-20T00:09:29.177

Answers

0

I was able to get this working.

The first hurdle was that I didn't realize that not all conditional match types are available in the global scope (which was where I was writing my rules). Only Pattern was available. I had to change the scope to the "distributed" scope (rules per site) to gain access to the IsFile and IsDirectory match types.

Then from there, I could write out my rules with some sort of hierarchy. First rewrite to match the pattern I want to try first, then if it doesn't resolve to a file, rewrite it to the next pattern and repeat.

<rule name="try in project/content" stopProcessing="false">
    <match url=".*" />
    <action type="Rewrite" url="project/content/{R:0}" />
</rule>
<rule name="verify project/content" stopProcessing="false">
    <match url="(project)/content(/.*)" />
    <conditions logicalGrouping="MatchAll">
        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
    </conditions>
    <action type="Rewrite" url="{R:1}{R:2}" />
</rule>

In my particular case, I wanted to try a particular subdirectory first, then try parent directories if they didn't exist. But I could theoretically do this for any set of paths, as long as I know in what order to try them.


So for my example in the question, I would set these rules:

<rule name="try in someproject1" stopProcessing="false">
    <match url=".*" />
    <action type="Rewrite" url="someproject1/{R:0}" />
</rule>
<rule name="try in someproject2 otherwise" stopProcessing="false">
    <match url="someproject1/(.*)" />
    <conditions logicalGrouping="MatchAll">
        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
    </conditions>
    <action type="Rewrite" url="someproject2/{R:1}" />
</rule>
<rule name="try in someotherproject otherwise" stopProcessing="false">
    <match url="someproject2/(.*)" />
    <conditions logicalGrouping="MatchAll">
        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
    </conditions>
    <action type="Rewrite" url="someotherproject/{R:1}" />
</rule>
<rule name="fallback to root otherwise" stopProcessing="false">
    <match url="someotherproject/(.*)" />
    <conditions logicalGrouping="MatchAll">
        <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
    </conditions>
    <action type="Rewrite" url="{R:1}" />
</rule>

Jeff Mercado

Posted 2015-09-18T23:41:09.743

Reputation: 532