Skip to content

Instantly share code, notes, and snippets.

@hossambarakat
Created April 19, 2020 12:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hossambarakat/4c6525a4dda8e7eec9e9cd2a5e3ab000 to your computer and use it in GitHub Desktop.
Save hossambarakat/4c6525a4dda8e7eec9e9cd2a5e3ab000 to your computer and use it in GitHub Desktop.
Simple-Proxy
using System;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Primitives;
using Moq;
using WebApplicationProxy.Controllers;
using Xunit;
namespace WebApplicationProxy.Tests
{
public class CallDownstreamService
{
[Fact]
public async Task Should_CreateClientFromServices()
{
var controller = new WeatherForecastController(new NullLogger<WeatherForecastController>())
{
ControllerContext = new ControllerContext()
};
var controllerContextHttpContext = new DefaultHttpContext();
controllerContextHttpContext.Request.Method = "Get";
controller.ControllerContext.HttpContext = controllerContextHttpContext;
var httpClientFactory= new Mock<IHttpClientFactory>();
var fakeHandler = new FakeHttpMessageHandler(new HttpResponseMessage(HttpStatusCode.Accepted));
HttpClient httpClient = new HttpClient(fakeHandler) {BaseAddress = new Uri("https://www.example.org")};
httpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>()))
.Returns(httpClient);
var httpContextRequestServices = new Mock<IServiceProvider>();
httpContextRequestServices.Setup(x => x.GetService(It.Is<Type>(t => t == typeof(IHttpClientFactory))))
.Returns(httpClientFactory.Object);
controllerContextHttpContext.RequestServices = httpContextRequestServices.Object;
await ProxyHelper.CallDownstreamService(controller, "sample", "abc");
controllerContextHttpContext.Response.StatusCode.Should().Be((int) HttpStatusCode.Accepted);
}
[Fact]
public async Task Should_CreateClientFromServicesBuAwesome()
{
var controller = new ControllerBuilder()
.WithHttpMethod("Get")
.WithHeader("User-Agent", "Mozilla")
.WithResponse(new HttpResponseMessage(HttpStatusCode.Accepted))
.Build();
await controller.CallDownstreamService("sample", "abc");
controller.Response.StatusCode.Should().Be((int) HttpStatusCode.Accepted);
}
}
public class ControllerBuilder
{
private string _httpMethod;
private HttpResponseMessage _responseMessage;
public ControllerBuilder()
{
_httpMethod = "Get";
_responseMessage = new HttpResponseMessage(HttpStatusCode.Accepted);
}
public ControllerBuilder WithHttpMethod(string method)
{
_httpMethod = method;
return this;
}
public ControllerBuilder WithResponse(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage;
return this;
}
public ControllerBuilder WithHeader(string key, StringValues values)
{
return this;
}
public FakeController Build()
{
var controller = new FakeController()
{
ControllerContext = new ControllerContext()
};
var controllerContextHttpContext = new DefaultHttpContext();
controllerContextHttpContext.Request.Method = _httpMethod;
controller.ControllerContext.HttpContext = controllerContextHttpContext;
var httpClientFactory= new Mock<IHttpClientFactory>();
var fakeHandler = new FakeHttpMessageHandler(_responseMessage);
HttpClient httpClient = new HttpClient(fakeHandler) {BaseAddress = new Uri("https://www.example.org")};
httpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>()))
.Returns(httpClient);
var httpContextRequestServices = new Mock<IServiceProvider>();
httpContextRequestServices.Setup(x => x.GetService(It.Is<Type>(t => t == typeof(IHttpClientFactory))))
.Returns(httpClientFactory.Object);
controllerContextHttpContext.RequestServices = httpContextRequestServices.Object;
return controller;
}
}
public class FakeController : ControllerBase
{
}
public class FakeHttpMessageHandler: DelegatingHandler
{
private readonly HttpResponseMessage _responseMessage;
public FakeHttpMessageHandler(HttpResponseMessage responseMessage)
{
_responseMessage = responseMessage;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
return Task.FromResult(_responseMessage);
}
}
}
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Xunit;
namespace WebApplicationProxy.Tests
{
public class CreateProxiedHttpRequest
{
[Fact]
public void Should_CopyHeaders()
{
HttpContext context = new DefaultHttpContext();
context.Request.Headers.Add("User-Agent", "Mozilla");
context.Request.Method = HttpMethods.Get;
var request = ProxyHelper.CreateProxiedHttpRequest(context, "http://www.example.org/anyuri");
request.Headers.Count().Should().Be(2);
request.Headers.UserAgent.ToString().Should().Be("Mozilla");
}
[Fact]
public async Task Should_CopyBody()
{
HttpContext context = new DefaultHttpContext();
context.Request.Headers.Add("User-Agent", "Mozilla");
context.Request.Method = HttpMethods.Post;
context.Request.Body = new MemoryStream(Encoding.UTF8.GetBytes("sample"));
var request = ProxyHelper.CreateProxiedHttpRequest(context, "http://www.example.org/anyuri");
var content = await request.Content.ReadAsStringAsync();
content.Should().Be("sample");
}
[Fact]
public async Task Should_CopyMethod()
{
HttpContext context = new DefaultHttpContext();
context.Request.Headers.Add("User-Agent", "Mozilla");
context.Request.Method = HttpMethods.Post;
var request = ProxyHelper.CreateProxiedHttpRequest(context, "http://www.example.org/anyuri");
request.Method.Should().Be("Post");
}
}
}
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
namespace WebApplicationProxy
{
public static class ProxyHelper
{
public static async Task CallDownstreamService(this ControllerBase controller, string serviceName, string path)
{
var httpContext = controller.HttpContext;
var clientFactory = controller.HttpContext.RequestServices.GetService<IHttpClientFactory>();
var httpClient = clientFactory.CreateClient(serviceName);
var targetUri = new Uri(httpClient.BaseAddress, path);
var requestMessage = CreateProxiedHttpRequest(httpContext, targetUri.ToString());
var response = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead,
httpContext.RequestAborted);
await WriteProxiedHttpResponseAsync(httpContext, response);
}
public static HttpRequestMessage CreateProxiedHttpRequest(HttpContext context, string uriString)
{
var uri = new Uri(uriString);
var request = context.Request;
var requestMessage = new HttpRequestMessage();
var requestMethod = request.Method;
// Write to request content, when necessary.
if (!HttpMethods.IsGet(requestMethod) &&
!HttpMethods.IsHead(requestMethod) &&
!HttpMethods.IsDelete(requestMethod) &&
!HttpMethods.IsTrace(requestMethod))
{
var streamContent = new StreamContent(request.Body);
requestMessage.Content = streamContent;
}
// Copy the request headers.
foreach (var header in context.Request.Headers)
{
if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()))
requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray());
}
// Set destination and method.
requestMessage.Headers.Host = uri.Authority;
requestMessage.RequestUri = uri;
requestMessage.Method = new HttpMethod(request.Method);
return requestMessage;
}
public static Task WriteProxiedHttpResponseAsync(HttpContext context, HttpResponseMessage responseMessage)
{
var response = context.Response;
response.StatusCode = (int) responseMessage.StatusCode;
foreach (var header in responseMessage.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
response.Headers.Remove("transfer-encoding");
if (responseMessage.Content != null)
{
foreach (var header in responseMessage.Content.Headers)
{
response.Headers[header.Key] = header.Value.ToArray();
}
return responseMessage.Content.CopyToAsync(response.Body);
}
else
{
return Task.CompletedTask;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace WebApplicationProxy
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddHttpClient("downstream", (sp, client) =>
{
client.BaseAddress = new Uri("https://localhost:6001");
});
services.AddHttpContextAccessor();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
namespace WebApplicationProxy.Controllers
{
[ApiController]
[Route("[controller]")]
public class ValuesController : ControllerBase
{
[HttpGet]
public Task ListAllValues()
{
return this.CallDownstreamService("downstream", "WeatherForecast");
}
[HttpGet("{id}")]
public Task ListValue([FromRoute]int id)
{
return this.CallDownstreamService("downstream", $"WeatherForecast/{id}");
}
[HttpPost]
public Task Post()
{
return this.CallDownstreamService("downstream", $"WeatherForecast");
}
}
}
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.AspNetCore.Http;
using Xunit;
namespace WebApplicationProxy.Tests
{
public class WriteProxiedHttpResponseAsync
{
[Fact]
public async Task Should_WriteCopyHeaders()
{
HttpContext context = new DefaultHttpContext();
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Accepted);
response.Headers.Add("cool-header", "cool-value");
await ProxyHelper.WriteProxiedHttpResponseAsync(context, response);
context.Response.Headers.Count.Should().Be(response.Headers.Count());
context.Response.Headers.Keys.Should().Contain("cool-header");
context.Response.Headers["cool-header"].FirstOrDefault().Should().Be("cool-value");
}
[Fact]
public async Task Should_WriteCopyBody()
{
HttpContext context = new DefaultHttpContext();
context.Response.Body = new MemoryStream();
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Accepted)
{
Content = new StringContent("yaaay")
};
await ProxyHelper.WriteProxiedHttpResponseAsync(context, response);
var content = await GetResponseContent(context);
content.Should().Be("yaaay");
}
private static async Task<string> GetResponseContent(HttpContext context)
{
context.Response.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(context.Response.Body);
var content = await reader.ReadToEndAsync();
return content;
}
[Fact]
public async Task Should_WriteCopyStatusCode()
{
HttpContext context = new DefaultHttpContext();
HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Accepted);
await ProxyHelper.WriteProxiedHttpResponseAsync(context, response);
context.Response.StatusCode.Should().Be((int)HttpStatusCode.Accepted);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment