This post describes a solution where a SharePoint 2010 and a separate SharePoint 2013 environment share the same address. To be clear, we are talking here about the following scenario:
http://sharepoint.company.com –> resolves to 2013 instance
http://sharepoint.company.com/sites/sc1 –> resolves to 2010 instance
http://sharepoint.company.com/sites/sc2 –> 2013 instance
http://sharepoitn.company.com/SomeSite –> either 2013 or 2010
In our case we were migrating content to a new SharePoint 2013 environment but needed to retain the same url. The 2010 environment was full of outdated content so we took the opportunity to start from fresh with a new 2013 environment and move across the content into new Sites. The business insisted that the same url was used for both, hence this solution using a reverse proxy and some hackery.
This should not be taken lightly – the 2010 instance will almost certainly be unsupported by Microsoft. The solution required quite a lot of work but once shook-down we managed to use this scenario for over a year with very few issues. Of course it would be preferable to use a different url for the second instance but our requirement owners were insistent.
We managed to resolve it this using:
- reverse proxy – IIS Extention ARR installed on each 2010 Web Front End
- custom HTTP Module to catch calls on each 2010 Web Front End
Here’s how to implement…
2010 Environment
The Default Web Site in IIS needs to handle all requests so the default binding http://sharepoint.company.com is removed from the SharePoint Web Application.
An Alternative Access Mapping is then configured for sharepointOLD.company.com and a new binding for the SharePoint instance is added in IIS for this address.
Hence requests will be handled as follows:
Request for 2013 content: –> 2010 WFE –> 2013 http://sharepoint.company.com
Request for 2010 content: –> 2010 WFE –> 2010 http://sharepointOLD.company.com
A hosts file entry on each WFE sends traffic to http://sharepointOLD.company.com back to itself (remember to add this to the list of sites excluded from the local loopback check).
In this way the 2013 environment will not require any changes. In our case, once the migration was complete we could just change the corporate DNS setting for sharepoint.company.com to point to the 2013 load balancer instead of 2010.
Application Request Routing
The ARR extension is installed on each 2010 Web Front End. The 2010 traffic will be handled by the WFE itself and a corresponding 2013 WFE will handle any other traffic i.e. 2010 WFE 1 sends traffic to 2013 WFE 1, 2010 WFE2 sends traffic to 2013 WFE2 etc.
http://www.iis.net/downloads/microsoft/application-request-routing
A second hosts file entry on each WFE sends requests for http://sharepoint.company.com directly to the corresponding 2013 WFE.
All the reverse proxy rules run on Default Web Site of 2010 Web Front Ends.
The Inbound Rules for the individual Sites are configured – duplicated (the xml can be copied from the <rewrite> section of the web.config file to save recreating on each WFE) .
As an example a 2010 site in the root Site Collection called functions and all its subsites needs to be handled by the 2010 WFE . Hence the rule needs to match any address that starts with ^(functions/?.*) and map that to the 2010 instance i.e.
http://sharepoint.company.com/functions/.* –> http://sharepointOLD.company.com/functions/.*
The local host file setting will send this request back to this 2010 WFE to be handled by SharePoint
Next we need a rule that will catch all requests for that came from a /functions/.* page for a top level 2010 asset e.g.
http://portal.wiley.com/_layouts/Company/JavaScript/jquery.min.js
– this request came from a 2010 page and needs to be replied to from 2010
Finally the last rule will send everything that was not interrupted by one of the other rules to go to the 2013 environment. This means catch all requests, including the root /, and send it to the SharePoint 2013 instance http://sharepoint.company.com found in the hosts file.
Custom HTTP Module
I had hoped that the ARR proxy would be sufficient but it turned out to be a little more complex because some requests could not be traced back to the correct instance. When an Office application opens up a document in SharePoint the application makes 3 requests. These requests are unfortunately targeted to the root of the Web Application i.e. sharepoint.company.com without the subsite. This means there is no way in the ARR rule-set to identify which environment (2010 or 2013) the document resides
The unidentified calls are to these addresses
/_vti_bin/Web.asmx
/_vti_inf.html
/_vit_bin/shtml.dll/_vti_rpc
If these are not handled correctly the document can only be opened in Read Only mode and Versions and Check-Out information is not available in the Office Application.
To handle these requests we required a custom HTTP Module running in IIS :
The pseudo code for the Module is as follow
If request is /_vti_bin/Webs.asmx or /_vti_inf.html or /_vti_bin/shtml.dll/_vti_rpc
Then
Copy the incoming request to SP2013REQUEST and send it to SP 2013
If response for SP2013REQUEST is valid (correct environment)
Then
Send back SP2013REQUEST response to original request
Else
Copy the incoming request to SP2010REQUEST and send it to SP2010
Send SP2010REQUEST response to original request
Else
Do nothing
The incoming HttpRequest object is copied to a new HttpWebRequest and sent to either 2010 or 2013 environments. The response from the correct environment is then copied back to the original request. If the module responds it stops the request in IIS otherwise the module lets the request pass on to the next module.
The module needs to be at the bottom of the Modules List so that is executed before the ARR. Once implemented the trace in Fiddler looks like this:
Some Gotchas
_vit_bin/Client.svc Fix
In order that the client.svc web service is correctly handled it must be excluded from immediate handling by the Default Application Pool of IIS i.e.
http://sharepoint.company.com/_vti_bin/client.svc
Add the following to the web.config file of default site running ARR rules
OneDrive for Business Support
In the http module add a check for the OneDrive for Business request user agent and ignore it
HttpContext context = ((HttpApplication)sender).Context;
context.Request.UserAgent;
if (reqUserAgent.Contains(“SkyDrive Pro 2013”)) {
// ignore this request
}