[
  {
    "path": ".gitignore",
    "content": "/ReverseProxyApplication/bin\n/ReverseProxyApplication/obj\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Andrea Chiarelli\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# netcore2-reverse-proxy\nThis is a .NET Core 2 sample project showing how to implement a custom reverse proxy, as described in the article [Building a Reverse Proxy in .NET Core](https://auth0.com/blog/building-a-reverse-proxy-in-dot-net-core/).\n\n\n## Running the project ##\n\nYou can run the Web application from Visual Studio or by typing `dotnet run` in a command window.\n\nWhen the application is running, you can point your browser to `localhost:5001` and access a Google form without leaving the `localhost` domain.\n\n"
  },
  {
    "path": "ReverseProxyApplication/Program.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Logging;\n\nnamespace ReverseProxyApplication\n{\n    public class Program\n    {\n        public static void Main(string[] args)\n        {\n            CreateWebHostBuilder(args).Build().Run();\n        }\n\n        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>\n            WebHost.CreateDefaultBuilder(args)\n                .UseStartup<Startup>();\n    }\n}\n"
  },
  {
    "path": "ReverseProxyApplication/Properties/launchSettings.json",
    "content": "﻿{\n  \"iisSettings\": {\n    \"windowsAuthentication\": false, \n    \"anonymousAuthentication\": true, \n    \"iisExpress\": {\n      \"applicationUrl\": \"http://localhost:57068\",\n      \"sslPort\": 44388\n    }\n  },\n  \"profiles\": {\n    \"IIS Express\": {\n      \"commandName\": \"IISExpress\",\n      \"launchBrowser\": true,\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      }\n    },\n    \"ReverseProxyApplication\": {\n      \"commandName\": \"Project\",\n      \"launchBrowser\": true,\n      \"applicationUrl\": \"https://localhost:5001;http://localhost:5000\",\n      \"environmentVariables\": {\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\n      }\n    }\n  }\n}"
  },
  {
    "path": "ReverseProxyApplication/ReverseProxyApplication.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>netcoreapp2.1</TargetFramework>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <Folder Include=\"wwwroot\\\" />\n  </ItemGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Microsoft.AspNetCore.App\" />\n    <PackageReference Include=\"Microsoft.AspNetCore.Razor.Design\" Version=\"2.1.2\" PrivateAssets=\"All\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "ReverseProxyApplication/ReverseProxyApplication.csproj.user",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<Project ToolsVersion=\"15.0\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)'=='Debug|AnyCPU'\">\n    <DebuggerFlavor>ProjectDebugger</DebuggerFlavor>\n  </PropertyGroup>\n  <PropertyGroup>\n    <ActiveDebugProfile>ReverseProxyApplication</ActiveDebugProfile>\n  </PropertyGroup>\n</Project>"
  },
  {
    "path": "ReverseProxyApplication/ReverseProxyMiddleware.cs",
    "content": "﻿using Microsoft.AspNetCore.Http;\nusing Microsoft.AspNetCore.WebUtilities;\nusing Microsoft.Extensions.Primitives;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Net.Http;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace ReverseProxyApplication\n{\n    public class ReverseProxyMiddleware\n    {\n        private static readonly HttpClient _httpClient = new HttpClient();\n        private readonly RequestDelegate _nextMiddleware;\n\n        public ReverseProxyMiddleware(RequestDelegate nextMiddleware)\n        {\n            _nextMiddleware = nextMiddleware;\n        }\n\n        public async Task Invoke(HttpContext context)\n        {\n            var targetUri = BuildTargetUri(context.Request);\n\n            if (targetUri != null)\n            {\n                var targetRequestMessage = CreateTargetMessage(context, targetUri);\n\n                using (var responseMessage = await _httpClient.SendAsync(targetRequestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted))\n                {\n                    context.Response.StatusCode = (int)responseMessage.StatusCode;\n\n                    CopyFromTargetResponseHeaders(context, responseMessage);\n\n                    await ProcessResponseContent(context, responseMessage);\n                }\n\n                return;\n            }\n\n            await _nextMiddleware(context);\n        }\n\n        private async Task ProcessResponseContent(HttpContext context, HttpResponseMessage responseMessage)\n        {\n            var content = await responseMessage.Content.ReadAsByteArrayAsync();\n\n            if (IsContentOfType(responseMessage, \"text/html\") || IsContentOfType(responseMessage, \"text/javascript\"))\n            {\n                var stringContent = Encoding.UTF8.GetString(content);\n                var newContent = stringContent.Replace(\"https://www.google.com\", \"/google\")\n                    .Replace(\"https://www.gstatic.com\", \"/googlestatic\")\n                    .Replace(\"https://docs.google.com/forms\", \"/googleforms\");\n                await context.Response.WriteAsync(newContent, Encoding.UTF8);\n            } else\n            {\n                await context.Response.Body.WriteAsync(content);\n            }\n        }\n\n        private bool IsContentOfType(HttpResponseMessage responseMessage, string type)\n        {\n            var result = false;\n\n            if (responseMessage.Content?.Headers?.ContentType != null)\n            {\n                result = responseMessage.Content.Headers.ContentType.MediaType == type;\n            }\n\n            return result;\n        }\n\n        private HttpRequestMessage CreateTargetMessage(HttpContext context, Uri targetUri)\n        {\n            var requestMessage = new HttpRequestMessage();\n            CopyFromOriginalRequestContentAndHeaders(context, requestMessage);\n\n            targetUri = new Uri(QueryHelpers.AddQueryString(targetUri.OriginalString, new Dictionary<string, string>() { { \"entry.1884265043\", \"John Doe\" }}));\n\n            requestMessage.RequestUri = targetUri;\n            requestMessage.Headers.Host = targetUri.Host;\n            requestMessage.Method = GetMethod(context.Request.Method);\n           \n            return requestMessage;\n        }\n\n        private void CopyFromOriginalRequestContentAndHeaders(HttpContext context, HttpRequestMessage requestMessage)\n        {\n            var requestMethod = context.Request.Method;\n\n            if (!HttpMethods.IsGet(requestMethod) &&\n                !HttpMethods.IsHead(requestMethod) &&\n                !HttpMethods.IsDelete(requestMethod) &&\n                !HttpMethods.IsTrace(requestMethod))\n            {\n                var streamContent = new StreamContent(context.Request.Body);\n                requestMessage.Content = streamContent;\n            }\n\n            foreach (var header in context.Request.Headers)\n            {\n                requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());\n            }\n        }\n\n        private void CopyFromTargetResponseHeaders(HttpContext context, HttpResponseMessage responseMessage)\n        {\n            foreach (var header in responseMessage.Headers)\n            {\n                context.Response.Headers[header.Key] = header.Value.ToArray();\n            }\n\n            foreach (var header in responseMessage.Content.Headers)\n            {\n                context.Response.Headers[header.Key] = header.Value.ToArray();\n            }\n            context.Response.Headers.Remove(\"transfer-encoding\");\n        }\n        private static HttpMethod GetMethod(string method)\n        {\n            if (HttpMethods.IsDelete(method)) return HttpMethod.Delete;\n            if (HttpMethods.IsGet(method)) return HttpMethod.Get;\n            if (HttpMethods.IsHead(method)) return HttpMethod.Head;\n            if (HttpMethods.IsOptions(method)) return HttpMethod.Options;\n            if (HttpMethods.IsPost(method)) return HttpMethod.Post;\n            if (HttpMethods.IsPut(method)) return HttpMethod.Put;\n            if (HttpMethods.IsTrace(method)) return HttpMethod.Trace;\n            return new HttpMethod(method);\n        }\n\n        private Uri BuildTargetUri(HttpRequest request)\n        {\n            Uri targetUri = null;\n            PathString remainingPath;\n\n            if (request.Path.StartsWithSegments(\"/googleforms\", out remainingPath))\n            {\n                targetUri = new Uri(\"https://docs.google.com/forms\" + remainingPath);\n            }\n\n            if (request.Path.StartsWithSegments(\"/google\", out remainingPath))\n            {\n                targetUri = new Uri(\"https://www.google.com\" + remainingPath);\n            }\n\n            if (request.Path.StartsWithSegments(\"/googlestatic\", out remainingPath))\n            {\n                targetUri = new Uri(\" https://www.gstatic.com\" + remainingPath);\n            }\n\n            return targetUri;\n        }\n    }\n}\n"
  },
  {
    "path": "ReverseProxyApplication/Startup.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.AspNetCore.Builder;\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.AspNetCore.Http;\nusing Microsoft.Extensions.DependencyInjection;\n\nnamespace ReverseProxyApplication\n{\n    public class Startup\n    {\n        // This method gets called by the runtime. Use this method to add services to the container.\n        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940\n        public void ConfigureServices(IServiceCollection services)\n        {\n        }\n\n        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.\n        public void Configure(IApplicationBuilder app, IHostingEnvironment env)\n        {\n            if (env.IsDevelopment())\n            {\n                app.UseDeveloperExceptionPage();\n            }\n\n            app.UseMiddleware<ReverseProxyMiddleware>();\n\n            app.Run(async (context) =>\n            {\n                await context.Response.WriteAsync(\"<a href='/googleforms/d/e/1FAIpQLSdJwmxHIl_OCh-CI1J68G1EVSr9hKaYFLh3dHh8TLnxjxCJWw/viewform?hl=en'>Register to receive a T-shirt</a>\");\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "ReverseProxyApplication.sln",
    "content": "﻿\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio 15\nVisualStudioVersion = 15.0.28307.136\nMinimumVisualStudioVersion = 10.0.40219.1\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"ReverseProxyApplication\", \"ReverseProxyApplication\\ReverseProxyApplication.csproj\", \"{58B4515A-294E-4C42-A583-77EC95AFF5DA}\"\nEndProject\nGlobal\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n\t\tDebug|Any CPU = Debug|Any CPU\n\t\tRelease|Any CPU = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n\t\t{58B4515A-294E-4C42-A583-77EC95AFF5DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU\n\t\t{58B4515A-294E-4C42-A583-77EC95AFF5DA}.Debug|Any CPU.Build.0 = Debug|Any CPU\n\t\t{58B4515A-294E-4C42-A583-77EC95AFF5DA}.Release|Any CPU.ActiveCfg = Release|Any CPU\n\t\t{58B4515A-294E-4C42-A583-77EC95AFF5DA}.Release|Any CPU.Build.0 = Release|Any CPU\n\tEndGlobalSection\n\tGlobalSection(SolutionProperties) = preSolution\n\t\tHideSolutionNode = FALSE\n\tEndGlobalSection\n\tGlobalSection(ExtensibilityGlobals) = postSolution\n\t\tSolutionGuid = {CE1981AA-AB03-4074-B464-1A50E3CF42DE}\n\tEndGlobalSection\nEndGlobal\n"
  }
]