The last few CMS implementations that I’ve done have required reverse proxies to surface external content. By the time I had completed my last CMS 11 project, I was a pro with reverse proxies. And then CMS 12 came and changed everything. CMS 12 moved to .Net Core on Linux web apps. This change removed the availability to create the rules needed for a reserve proxy in the web.config file.
It would be wonderful if Optimizely could provide the ability to set up reverse proxies within a DXC tenant (possibly through the configuration of Cloudflare) but this is not yet available. The other two alternatives were to use a product like NGINX to sit in front of Optimizely as the reverse proxy or build the reverse proxy as part of the Optimizely CMS codebase.
YARP – Yet Another Reverse Proxy
Based on our client’s needs, we went the code route and selected YARP to handle our reverse proxy requests within Optimizely. The great thing about YARP is that it is a performant and robust reverse proxy that is easy to configure.
YARP is a reverse proxy toolkit for building fast proxy servers in .NET using the infrastructure from ASP.NET and .NET. The key differentiator for YARP is that it is being designed to be easily customized and tweaked to match the specific needs of each deployment scenario. – .Net Blog
A reverse proxy usually operates on the transport layer of the ISO/OSI model (the fourth layer), and routes a client’s requests to other servers. The YARP reverse proxy, however, operates on the seventh layer (the HTTP layer). The incoming and outgoing connections are independent of each other, enabling the URLs to be mapped and content from external URLs to be surfaced.
The first step was to install the Yarp.ReverseProxy NuGet package. This can be done either through the package manager or by calling the dotnet add package command to add the package. The YARP reverse proxy is then configured in the Startup.cs file. Configuration can be accomplished through code or through a configuration file. The example below sets up our reverse proxy through code.
public void ConfigureServices(IServiceCollection services)
{
...
...
services.AddReverseProxy();
...
}
The Configure method was then updated to inject an IHttpForwarder and then configure the reverse proxy as needed. For our rule, we are simply taking any request from the URL segment /somepath/ and forwarding that request to https://newurl.com/somepath/. Here are the updates to the Configure method
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHttpForwarder forwarder)
{
...
...
var transformer = new CustomProxyTransformer();
var requestConfig = new ForwarderRequestConfig {
ActivityTimeout = TimeSpan.FromSeconds(100)
};
app.UseEndpoints(endpoints =>
{
endpoints.MapContent();
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.Map("/somepath/{**remainder}", async httpContext =>
{
//send request to the new url with proxytransformer
var error = await forwarder.SendAsync(httpContext,
"https://newurl.com/somepath/",
httpClient,
requestConfig,
transformer);
// Check if the operation was successful
if (error != ForwarderError.None)
{
var errorFeature = httpContext.GetForwarderErrorFeature();
var exception = errorFeature.Exception;
}
});
}
}
For our use case, we need to ensure that the headers were correctly set before the request was forwarded. This was done with the CustomProxyTransformer class below:
public class CustomProxyTransformer : HttpTransformer
{
public override async ValueTask TransformRequestAsync(HttpContext httpContext,
HttpRequestMessage proxyRequest,
string destinationPrefix)
{
// Copy all request headers
await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix);
var queryContext = new QueryTransformContext(httpContext.Request);
proxyRequest.Headers.Host = "newurl.customhostheader.com";
proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress(
"https:// newurl.customhostheader.com ",
httpContext.Request.Path,
queryContext.QueryString);
}
}
And that was all that was needed to get our reverse proxy up and running. Hopefully, Optimizely will create the ability to add reverse proxies through configuration into the DXC. But until that is done, YARP is a great stop-gap answer to reverse proxies in CMS 12.