Writing a CORS anywhere proxy server with Yarp in an ASP.Net Core 7 app

695 Views Asked by At

I'm calling the googleapis web service from Flutter. It works from Android and iOS, However, in web, it is causing a CORS issue as googleapis is not supposed to be called from a browser.

Someone has found a workaround to add this to the start of the GET HTTP request URL:

https://cors-anywhere.herokuapp.com/

so the full URL is 'https://cors-anywhere.herokuapp.com/https://maps.googleapis.com/maps/api/place/autocomplete/json' and then add some query parameters onto the end of that.

The package creator also says we need to use a proxy server. I think the Heroku solution is heavily limited in the amount of requests it can receive.

Can I achieve this using my existing ASP.NET Core 7 app?

I am trying by using Yarp.

I've added this to appsettings.json:

  "ReverseProxy": {
    "Routes": {
      "route1": {
        "ClusterId": "cluster1",
        "Match": {
          "Path": "/googlemapswebservice"
        }
      }
    },
    "Clusters": {
      "cluster1": {
        "Destinations": {
          "destination1": {
            "Address": "https://maps.googleapis.com/maps/api/place/autocomplete/json/"
          }
        }
      }
    }
  },

Inside ConfigureServices():

services.AddReverseProxy().LoadFromConfig(Configuration.GetSection("ReverseProxy")); 

Inside Configure():

app.UseEndpoints(endpoints => 
{ 
    endpoints.MapControllers(); 
    endpoints.MapReverseProxy(); 
}); 

EDIT: I suspect we are not even supposed to create a controller endpoint when using Yarp, meaning I should delete the below code.

The web controller; now this is the main bit that I do not really know what I'm doing in the context of Yarp:

[Route("[controller]")]
[ApiController]
public class GoogleMapsWebServiceController : ControllerBase
{
    private readonly VepoContext _context;
    protected readonly ExtendedVepoContext extendedContext;

    public GoogleMapsWebServiceController(
        VepoContext context,
        ExtendedVepoContext extendedContext)
    {
        _context = context;
        this.extendedContext = extendedContext;
    }

    [HttpGet]
    [AllowAnonymous]
    public async Task<OkResult> GoogleMapsWebService()
    {
        return await Task.FromResult(Ok());
    }
}

When I make an http request to here:

http://192.xxx.x.xx:5002/googlemapswebservice

I can hit my endpoint (I don't think it gets forwarded to googleapis though, but one step at a time).

When I make an http request to here:

http://192.xxx.x.xx:5002/googlemapswebservice/https://maps.googleapis.com/maps/api/place/autocomplete/json?input=a&location=-36.8508827%2C174.7644881&radius=500&types=establishment&key=xxxxxxxxxxxxxxxxx

I seem to hit my endpoint on the OPTIONS http request as it responds with 204 No Content. Then on the same url with a GET http request it responds with a 404 Not Found, so is it really hitting my endpoint?

I'm guessing that Yarp does not work when you append the forwarding http address to the initial http request in the url, by default. Is there something I can do to make it work like that, so that I can apply the same fix as the dude in my link above on Github? Then the next step is to forward the second half of the GET http url to googleapis which I am also unsure about how to do.

I get an html response, here is the relevant part of it:

    <a href=//www.google.com/>
    <span id=logo aria-label=Google></span>
</a>
<p>
    <b>404.</b>
    <ins>That’s an error.</ins>
<p>
    The requested URL <code>/maps/api/googlemapswebservice/place/autocomplete/json?input=a &amp;location=-36.8508827%2C174.7644881 &amp;radius=500 &amp;types=establishment &amp;key=xxxxxxxxxxxxxxxxxxxxxxxxxx</code>
    was not found on this server.  <ins>That’s all we know.</ins>

It seems like it is making it to google.

If i change the cluster address to:

"Address": "https://www.facebook.com/"

It returns the html for facebook.com.

So I think my query parameters have no effect, and it sends the request to exactly what is in the cluster address. How do we get query parameters into the cluster address?

1

There are 1 best solutions below

0
On

I needed to use Direct Forwarding.

I essentially copied the code from the docs in the link and made minor adjustments.

Inside the Startup class, add a nested private class:

private class CustomTransformer : HttpTransformer
{
    public override async ValueTask TransformRequestAsync(HttpContext httpContext,
        HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken)
    {
        // Copy all request headers
        await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken);

        // Customize the query string:
        var queryContext = new QueryTransformContext(httpContext.Request);

        // Assign the custom uri. Be careful about extra slashes when concatenating here. RequestUtilities.MakeDestinationAddress is a safe default.
        proxyRequest.RequestUri = RequestUtilities.MakeDestinationAddress("https://maps.googleapis.com/maps/api", httpContext.Request.Path, queryContext.QueryString);

        // Suppress the original request header, use the one from the destination Uri.
        proxyRequest.Headers.Host = null;
    }
}

Inside ConfigureServices:

services.AddHttpForwarder();

Inside Configure:

var httpClient = new HttpMessageInvoker(new SocketsHttpHandler()
{
    UseProxy = false,
    AllowAutoRedirect = false,
    AutomaticDecompression = DecompressionMethods.None,
    UseCookies = false,
    ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current),
    ConnectTimeout = TimeSpan.FromSeconds(15),
});
var transformer = new CustomTransformer(); // or HttpTransformer.Default;
var requestConfig = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) };

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();

    RequestDelegate googleMapsApi = async httpContext =>
    {
        var error = await forwarder.SendAsync(httpContext, "https://maps.googleapis.com/maps/api/",
        httpClient, requestConfig, transformer);
        // Check if the operation was successful
        if (error != ForwarderError.None)
        {
            var errorFeature = httpContext.GetForwarderErrorFeature();
            var exception = errorFeature.Exception;
        }
    };

    endpoints.Map("/place/{**catch-all}", googleMapsApi);
    endpoints.Map("/geocode/{**catch-all}", googleMapsApi);
});