IIS with URL Rewrite as a reverse proxy - part 3 – rewriting the outbound response contents

This is the third part of the article series dealing with IIS using URL rewrite as a reverse proxy for real world apps. Check out part 1 and part 2 before reading on.

Configuring outbound rules for Javascript encoded content.

More and more applications send content to the browser in the form of Javascript encoded content, which the javascript running in the page that has requested the content then integrates into the DOM (Document Object Model) of the page. This content might include such things as Anchor <a> tags, or form tags which have action attributes. Below are examples of such snippets of code:

<a href=\"https://privateserver:8080/coding_rules/#rule_key=OneVal%3APPErrorDirectiveReached\" target=\"_blank\">
<form method=\"post\" class=\"rule-remediation-form-update\" action=\"https://privateserver:8080/admin_rules_remediation/update\">

Note the \ (inverted slash) before each of the values of the href and action attributes.

If we look at the 'ReverseProxyOutboundRule1' in the rules section of URL Rewrite, rule which was created in the Reverse Proxy wizard we ran in part 1 of this blog series and we check the Preconditions associated with this rule, we will see that a precondition was created during the Reverse Proxy setup wizard, the precondition is called ResponseIsHtml1.

If you click on the 'Edit' button next to the ResoponseIsHtml1 precondition, we can see the configuration of the precondition. This precondition matches any responses coming from the back end server that have the response content type set to text/html.

Since Javascript encoded content is text/application-javascript, the easiest way to work around this limitation is to change the precondition to match responses with the content type of type text/* - text followed by slash anything. To do this, click on the {Response_Content_Type} in the list and then click the 'Edit' button next to this. This will allow you to edit the regular expression that is used to inspect the content type of responses coming from the backend server.

Change the pattern of the regular expression to ' ^text/(.+) ' – meaning that the content type should start with text/ followed by any character, but at least one character. Click the 'Ok' button to save these changes.

Site Node: you could also create a second precondition called ResponseIsTextStar and set the new regular expression in this precondition as we will be creating more outbound rules. In this way you can have a rule for only HTML content and a rules for the rest.

Now we will need to create two new outbound rules to address the case of the <a> anchor tags and the action attributes of the form tags which are encoded. Because they are encoded we cannot use the built in tag scanning that URL Rewrite provides for us in outbound rules. We will have to write a regular expression to match these two tags in all content.

Let's start with the anchor <a> tags. Create a new blank outbound rule from the Rule Wizard, and then configure it to use the precondition we created / modified earlier. In the Match pane configure the rule as shown below:

Set the 'Matching Scope' to 'Response' in the dropdown, make sure that all the items within the 'Match Content Within' dropdown are deselected – this will mean URL Rewrite will scan the entire response not just specific tags. Select 'Matches the Pattern' in the 'Content' dropdown and 'Regular Expressions' in the 'Using' dropdown. Use the following pattern in the Pattern textbox: 'href=(.*?)https://privateserver:8080/(.*?)\s' - you should replace privateserver:8080 with the url of your backend server.

Moving down to the Actions pane, configure the following:

Set the 'Action' dropdown to 'Rewrite' and then use the following pattern: 'href={R:1}https://www.mypublicserver.com/{R:2} ' in the Pattern textbox. Replace the https://www.mypublicserver.com/ with the URL of your server. Finally press the 'Apply' action link on the right hand pane to create the new rule.

We will need to add a second outbound rule to deal with the form element's encoded action attributes. To do this, we will create a second blank outbound rule. The configuration of the rule is the same as above in the Match pane, except for the regular expression to be used, which changes to: 'action=(.*?)https://privateserver:8080/(.*?)\\ ' – again replace the https://privateserver:8080/ with the URL of the backend server.

The configuration is identical to the first rule in the Action pane as well. The pattern to be used here is the following: 'action={R:1}https://www.mypublicserver.com/{R:2}\ ' where you will need to replace https://www.mypublicserver.com/ with the IIS server URL accessible to your users. Once you have pressed the Apply action link on the right hand side pane, the rule is saved and the configuration is now applied.

In conclusion:

We now have an IIS web-server that uses URL Rewrite to act as a reverse proxy. The server can deal with the issue of compressed responses coming out of the backend web-application by disabling the accept-encoding header, and is able to modify content coming back from the backend web-application even if this content is javascript encoded and contains anchor tags or action attributes on form elements.

By Paul Cociuba
https://linqto.me/about/pcociuba

Post Scriptum:

Here is the excerpt of the configuration for url-rewrite in the web.config file at the end of the tutorial:

     
       <rewrite>
            <rules>
                <clear></clear>
                <rule name="ReverseProxyInboundRule1" stopprocessing="true">
                    <match url="(.*)"></match>
                    <conditions logicalgrouping="MatchAll" trackallcaptures="false"></conditions>
                    <servervariables>
                        <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCODING}"></set>
                        <set name="HTTP_ACCEPT_ENCODING" value=""></set>
                    </servervariables>
                    <action type="Rewrite" url="https://privateserver:8080/{R:1}">
                </action></rule>
            </rules>
            <outboundrules>
                <rule name="ReverseProxyOutboundRule1" precondition="ResponseIsHtml1">
                    <match filterbytags="None" pattern="href=(.*?)https://privateserver:8080/(.*?)\s">
                    <action type="Rewrite" value="href={R:1}https://www.mypublicserver.com/{R:2} ">
                </action></match></rule>
             
                <rule name="Restore-AcceptEncoding" precondition="NeedsRestoringAcceptEncoding">
                    <match servervariable="HTTP_ACCEPT_ENCODING" pattern="^(.*)"></match>
                    <action type="Rewrite" value="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}"></action>
                </rule>
                <preconditions>
                    <precondition name="ResponseIsHtml1">
                        <add input="{RESPONSE_CONTENT_TYPE}" pattern="^text/(.+)">
                    </add></precondition>
                    <precondition name="NeedsRestoringAcceptEncoding">
                        <add input="{HTTP_X_ORIGINAL_ACCEPT_ENCODING}" pattern=".+"></add>
                    </precondition>
                </preconditions>
            </outboundrules>
        </rewrite>