Creating a Proxy to Another Web API with ASP.NET Core

Creating a proxy to another web api with Asp.net core

I ended up implementing a proxy middleware inspired by a project in Asp.Net's GitHub.

It basically implements a middleware that reads the request received, creates a copy from it and sends it back to a configured service, reads the response from the service and sends it back to the caller.

Creating an API proxy in ASP.NET MVC 6

This is just off the top of my head but you could create a middleware. This would work for get requests with no headers but could be modified to do more.

app.Use( async ( context, next ) =>
{
var pathAndQuery = context.Request.GetUri().PathAndQuery;

const string apiEndpoint = "/api/someapi/";
if ( !pathAndQuery.StartsWith( apiEndpoint ) )
//continues through the rest of the pipeline
await next();
else
{
using ( var httpClient = new HttpClient() )
{
var response = await httpClient.GetAsync( "http://some-api.com/api/v2/" + pathAndQuery.Replace( apiEndpoint, "" ) );
var result = await response.Content.ReadAsStringAsync();

context.Response.StatusCode = (int)response.StatusCode;
await context.Response.WriteAsync( result );
}
}
} );

If you put this befire app.UseMvc() it will intercept any requests where the path starts with /api/someapi

C# web application - proxy all requests - return content from another web application (Reverse proxy)

Found a good answer for Web API that led me in the right direction.

https://stackoverflow.com/a/41680404/3850405

I started out by adding a new ASP.NET Web Application -> MVC -> No Authentication.

I then removed everything accept Global.asax, packages.config and Web.config.

I then edited Global.asax to use a DelegatingHandler like this:

public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(CustomHttpProxy.Register);
}
}

public static class CustomHttpProxy
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "Proxy",
routeTemplate: "{*path}",
handler: HttpClientFactory.CreatePipeline(
innerHandler: new HttpClientHandler(),
handlers: new DelegatingHandler[]
{
new ProxyHandler()
}
),
defaults: new { path = RouteParameter.Optional },
constraints: null
);
}
}

public class ProxyHandler : DelegatingHandler
{
private static HttpClient client = new HttpClient();

protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var forwardUri = new UriBuilder(request.RequestUri.AbsoluteUri);
forwardUri.Host = "localhost";
forwardUri.Port = 62904;
request.RequestUri = forwardUri.Uri;

if (request.Method == HttpMethod.Get)
{
request.Content = null;
}

request.Headers.Add("X-Forwarded-Host", request.Headers.Host);
request.Headers.Host = "localhost:62904";
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
return response;
}
}

After this I had to add the static content and then everything worked.

Sample Image

How to set up Web API Routing for a Proxy Controller?

Here's how I got this to work. First, create a controller with a method for each verb you want to support:

public class ProxyController : ApiController
{
private Uri _baseUri = new Uri("http://otherwebservice.com");

public async Task<HttpResponseMessage> Get(string url)
{
}

public async Task<HttpResponseMessage> Post(string url)
{
}

public async Task<HttpResponseMessage> Put(string url)
{
}

public async Task<HttpResponseMessage> Delete(string url)
{
}
}

The methods are async because they're going to use an HttpClient. Map your route like this:

config.Routes.MapHttpRoute(
name: "Proxy",
routeTemplate: "api/Proxy/{*url}",
defaults: new { controller = "Proxy" });

Now back to the Get method in the controller. Create an HttpClient object, create a new HttpRequestMessage object with the appropriate Url, copy everything (or almost everything) from the original request message, then call SendAsync():

public async Task<HttpResponseMessage> Get(string url)
{
using (var httpClient = new HttpClient())
{
string absoluteUrl = _baseUri.ToString() + "/" + url + Request.RequestUri.Query;
var proxyRequest = new HttpRequestMessage(Request.Method, absoluteUrl);
foreach (var header in Request.Headers)
{
proxyRequest.Headers.Add(header.Key, header.Value);
}

return await httpClient.SendAsync(proxyRequest, HttpCompletionOption.ResponseContentRead);
}
}

The URL combining could be more sophisticated, but that's the basic idea.
For the Post and Put methods, you'll also need to copy the request body

Also please note a HttpCompletionOption.ResponseContentRead parameter passed in SendAsync call, because without it, ASP.NET will spend an exremeley long time reading the content if the content is large (in my case, it changed a 500KB 100ms request into a 60s request).

transparent server side proxy for requests to ASP.NET Web API

update for 2021:

You should probably be looking at https://microsoft.github.io/reverse-proxy/ if you have found your way here

old answer:

I wrote one for a previous version of WebApi. The code should be fairly easy to update for your purposes.

The basic idea is that you create a WebApi DelegatingHandler that passes the request on to an HttpClient:

    public class ForwardProxyMessageHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Add("X-Forwarded-For", request.GetClientIp());
if (request.Method == HttpMethod.Get || request.Method == HttpMethod.Trace) request.Content = null;
request.RequestUri = new Uri(request.RequestUri.ToString().Replace(":3002", "")); //comes through with the port for the proxy, rewrite to port 80
request.Headers.AcceptEncoding.Clear();
var responseMessage = await new HttpClient().SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
responseMessage.Headers.TransferEncodingChunked = null; //throws an error on calls to WebApi results
if (request.Method == HttpMethod.Head) responseMessage.Content = null;
return responseMessage;
}

}

Reverse proxy in ASP.NET Web API 2 Controller?

It is not possible directly in the WebAPI controller, but this can easily be done adding an ASP.NET handler to the project.

Sample Image

And then creating the reverse proxy as mentioned here

Pass a request to another API

ProxyKit is a dotnet core reverse proxy that allows you to forward requests to an upstream server, you can also modify the requests and responses.

Conditional forwarding example:

public void Configure(IApplicationBuilder app)
{
// Forwards the request only when the host is set to the specified value
app.UseWhen(
context => context.Request.Host.Host.Equals("api.example.com"),
appInner => appInner.RunProxy(context => context
.ForwardTo("http://localhost:5001")
.AddXForwardedHeaders()
.Send()));
}

Modify request example:

public void Configure(IApplicationBuilder app)
{
// Inline
app.RunProxy(context =>
{
var forwardContext = context.ForwardTo("http://localhost:5001");
if (forwardContext.UpstreamRequest.Headers.Contains("X-Correlation-ID"))
{
forwardContext.UpstreamRequest.Headers.Add("X-Correlation-ID", Guid.NewGuid().ToString());
}
return forwardContext.Send();
});
}

If on the other hand you want to forward your request from a controller action, you will have to copy the request.

How to call third party REST API from ASP.NET Core Web API?

First register a HttpClient for your Controller/Service

// In Startup.cs (5.0)
services.AddHttpClient<ThirdPartyAPIController>();

// ... or in Program.cs (6.0)
builder.Services.AddHttpClient();

Then inject the HttpClient and use it in the Controller method.

namespace MyProyect.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ThirdPartyAPIController : ControllerBase
{
// Let the `IHttpClientFactory` do the `HttpClient` handling
private HttpClient _client;

public ThirdPartyAPIController(HttpClient client)
{
_client = client;
}

[HttpGet]
// use `public` instead of `static`
// only this is then visible in Swagger UI
public async Task<Product> GetProductsAsync(string path)
{
Product product = null;

HttpResponseMessage response = await _client .GetAsync(path);

if (response.IsSuccessStatusCode)
{
product = await response.Content.ReadAsAsync<Product>();
}

return product!;
}
}
}

Like written in the code comment. You have to use public (not static) for the controller method to be registered and visible in Swagger UI.



Related Topics



Leave a reply



Submit