Created
April 19, 2020 12:18
-
-
Save hossambarakat/4c6525a4dda8e7eec9e9cd2a5e3ab000 to your computer and use it in GitHub Desktop.
Simple-Proxy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
}); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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