How to hide the MapServer map file location on IIS (without writing any code)

We posted some notes a few days ago on how to set up MapServer WMS/WFS server on IIS. Someone called Dennis has commented appreciatively, but asked how to hide the location of the .map file and create a cleaner URL with a cgi-wrapper, as described in the MapServer documentation. Well that documentation page does tell you how to do it on IIS, with ASP script, but here’s a much simpler way that requires no hacking.

Aside: I should say that what follows is not what we normally do in our real applications – we usually use ASP.Net .ashx handlers to relay requests through the root of our web sites, which also helps to avoid cross-domain problems with GetFeatureInfo responses and WFS, but that’s another story! This post is in reaction to Dennis’s request for help, and because I wanted to check this approach is viable.

I’ve posted before on how to use URL Rewrite to give GeoServer a friendly URL on IIS, and we’re going to use the same technique here, so first ensure you have the necessaries installed.

You should also already have a working WMS/WFS – we created this one in the previous post:

http://mapservertest.esdm.co.uk/scripts/mapserv.exe?map=D:\Websites\mapservertest\map\mapfiletest.map

which was fine, but a) the URL reveals some information about the structures on the web server, and b) the URL is a bit ugly.

So decide what address you want for your MapServer service (remembering this will be specific right down to the map file). I’m using “mapservertestwrapper.esdm.co.uk” which I have pointed at the web server in question.

Create a new web site in IIS with this URL as a host header (you’ll first need to create an empty “physical” folder for the site to point at – add a readme.config file explaining what it’s for).

Now open URL Rewrite and create a reverse proxy inbound rule, entering the real service address as the forwarding address:

Creating the reverse proxy inbound rule

OK now we should be able to see our MapServer on the new URL. However, when we visit http://mapservertestwrapper.esdm.co.uk/ we get this feedback in the browser:

msLoadMap(): Regular expression error. MS_DEFAULT_MAPFILE_PATTERN validation failed. msEvalRegex(): Regular expression error. String failed expression test.

The fix is to re-edit the inbound rule, and remove the final “/” in the “rewrite URL”, going from:

http://mapservertest.esdm.co.uk/scripts/mapserv.exe?map=D:\Websites\mapservertest\map\mapfiletest.map/{R:1}

to:

http://mapservertest.esdm.co.uk/scripts/mapserv.exe?map=D:\Websites\mapservertest\map\mapfiletest.map{R:1}

Now we should get sensible MapServer feedback in the browser (conditioned by a template specified in the map file).

Now let’s see if we can do a GetCapabilities request… turns out our URL has to be this:

http://mapservertestwrapper.esdm.co.uk/?service=wms&version=1.3.0&request=getcapabilities

Yes it works, but a) that’s a slightly odd URL with “/?”, and b) the response contains the original service addresses, again revealing information about our server.

To fix a), we go back and edit our inbound rule again, and change the “pattern” from “(.*)” to “map1(.*)” (without the quotes of course). Now our service URL becomes:

http://mapservertestwrapper.esdm.co.uk/map1?service=wms&version=1.3.0&request=getcapabilities

Ah yes that’s much nicer, and now of course we see we can manage lots of .map files within one web site simply by setting up in inbound rule for each, with an appropriate pattern and target.

To fix b) we can edit the metadata in the .map file to set this:

"wms_onlineresource"    "http://mapservertestwrapper.esdm.co.uk/map1"

This works fine, returning this within the GetCapabilities:

<OnlineResource xlink:href="http://mapservertestwrapper.esdm.co.uk/map1?" xmlns:xlink="http://www.w3.org/1999/xlink"/>

And I can confirm this creates a working map in QGIS, with map requests like this visible in Fiddler:

http://mapservertestwrapper.esdm.co.uk/map1?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&BBOX=52801.44371538766426966,3347.3560407145996578,657116.82161524775438011,428145.51874091033823788&CRS=EPSG:27700&WIDTH=1020&HEIGHT=717&LAYERS=grid&STYLES=&FORMAT=image/png&DPI=96&MAP_RESOLUTION=96&FORMAT_OPTIONS=dpi:96&TRANSPARENT=TRUE

image

And a quick test shows that info clicking (GetFeatureInfo) and legends work too:

e.g. http://mapservertestwrapper.esdm.co.uk/map1?version=1.3.0&service=WMS&request=GetLegendGraphic&sld_version=1.1.0&layer=grid&format=image/png&STYLE=default

gives this:

GetLegendGraphic image

There you go Dennis, we have working OGC services from MapServer with nice clean URLs, a simple architecture for adding new services with their own map files, and no leaking of details about our directory structures.

 

Footnote…

I’m not completely keen on the last bit, having to change the wms_onlineresource, as this means the original service is now broken (depending on whether the mapping client makes us of this, which QGIS likes to). Another way should be  to create an outbound URL Rewrite rule to rewrite any references to the original URL with the new one. However, after a bit of pfaffing with this, I’m pretty sure there’s a bug in URL Rewrite that means outbound rewriting doesn’t work for attributes containing colons, and ours is “xlink:href”. Incidentally setting up the outbound rules for custom tags is a bit of an adventure through the UI, and there is a bit of escaping to do for the backslashes and question mark in the URL (highlighted below) as URL Rewrite is doing regular expressions so you might as well get down and dirty in the web.config located in the “dummy” folder we created for the new web site we created earlier. So this is my outbound rule that doesn’t work, but should when someone fixes the bug!

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <system.webServer>
        <rewrite>
            <rules>
                <rule name="ReverseProxyInboundRule2" stopProcessing="true">
                    <match url="map1(.*)" />
                    <action type="Rewrite" url="
http://mapservertest.esdm.co.uk/scripts/mapserv.exe?map=D:\Websites\mapservertest\map\mapfiletest.map{R:1}" />
                </rule>
            </rules>
            <outboundRules>
                <rule name="ReverseProxyOutboundRule1" preCondition="ResponseIsXML" enabled="true">
                    <match filterByTags="CustomTags" customTags="OGC GetCapabilities" pattern="^http(s)?://mapservertest.esdm.co.uk/scripts/mapserv.exe\?map=D:\\Websites\\mapservertest\\map\\mapfiletest.map(.*)" />
                    <action type="Rewrite" value="http{R:1}://mapservertestwrapper.esdm.co.uk/map1{R:2}" />
                </rule>
                <preConditions>
                    <preCondition name="ResponseIsHtml1">
                        <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/html" />
                    </preCondition>
                    <preCondition name="ResponseIsXML">
                        <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/xml" />
                    </preCondition>
                </preConditions>
                <customTags>
                    <tags name="OGC GetCapabilities">
                        <tag name="OnlineResource" attribute="xlink:href" />       
                    </tags>
                </customTags>
            </outboundRules>
        </rewrite>
    </system.webServer>
</configuration>

 

The red bit is the bit affected by the bug I think, unless someone can point out how this should be escaped/expressed to make it work?

Comments

Find out more