microsoft / reverse-proxy

A toolkit for developing high-performance HTTP reverse proxy applications.
https://microsoft.github.io/reverse-proxy
MIT License
8.44k stars 829 forks source link

integrate with Dapr as Dapr's gateway #967

Open geffzhang opened 3 years ago

geffzhang commented 3 years ago

Both the original and updated eShop application leverage the Envoy proxy as an API gateway, In the original eShopOnContainers implementation, the Envoy API gateway forwarded incoming HTTP requests directly to aggregator or back-end services. In the new eShopOnDapr, the Envoy proxy forwards the request to a Dapr sidecar. I want to replace Enovy with Yarp forwards the request to a Dapr sidecar 。

Some details

How many backends are in your application?

How do you host your application?

What did you think of YARP?

jkotalik commented 3 years ago

I'm not familiar with any envoy integrations with dapr, but are you effectively saying that you'd like to replace envoy with YARP as the reverse proxy? Does dapr have any unique integrations with envoy to make it work with Dapr?

I see these configurations here and here. The former being the configuration for envoy and the latter for k8s.

geffzhang commented 3 years ago

@jkotalik yes I would like to replace envoy with YARP as the reverse proxy

geffzhang commented 3 years ago

I have implemented a demo TyeAndYarp and I expect yarp to have a better implementation that directly supports dapr ,this is main code: https://github.com/geffzhang/TyeAndYarp/blob/master/ReverseProxy/DaprTransformProvider.cs

bradygaster commented 2 years ago

Adding @nishanil and @jamesmontemagno here. I think it would be exciting to see a YARP-inclusive version of eShop.

karelz commented 2 years ago

Triage: @samsp-msft can you please describe what is the plan here? (sample?)

samsp-msft commented 2 years ago

Plan is a proof of concept using YARP as a gateway into a set of DAPR services. based on the results of the POC we can decide if it should be a sample etc.

v2codes commented 2 years ago

hi @samsp-msft , is there any progress about this feature ?

samsp-msft commented 2 years ago

Sorry- No progress as of yet.

nikneem commented 1 year ago

Any news on plans to implement this? I would like to have DAPR as a side-car sit in between my request and service and make sure the routing is done properly.

private RouteConfig GetUsersRoute()
{
    var route = new RouteConfig
    {
        RouteId = "users-route",
        ClusterId = "users-service",
        CorsPolicy = Constants.DefaultCorsPolicy,
        Match = new RouteMatch
        {
            Path = "/users/{**catch-all}"
        },
    };
    return route
        .WithTransformPathPrefix("/api")
        .WithTransformRequestHeader("dapr-app-id", "pollstar-users-api");  // Add DAPR Service name in the request header
}

Something like this, only adding the DAPR service name would be a really nice way of configuring YARP.

Arcalise08 commented 11 months ago

Almost a year later. Dapr has become even more dominant in the microservice space with Microsoft adopting it for Azure container apps. Easy integration (or really any type of integration) is essential for the health of a reverse proxy.

We have got this working in production with Ocelot by using our Dapr service IDs as host names in combination with Daprs own delegating handler and it works perfectly fine.

I'm just starting to look into it with YARP since Ocelot isnt being maintained anymore. But it looks like YARP doesnt directly expose anything that would make the integration easy like an .AddDelegatingHandler

nikneem commented 11 months ago

I solved this problem by addressing the (internal)container name, and not use DAPR. I really love the Azure Container Apps and use YARP as a reverse proxy to my underlying API's. This https://github.com/nikneem/tinylnk-gateway is my solution so far and to be honest, works like a charm. But still, you have to stay within a K8s context for this to work.

Arcalise08 commented 11 months ago

I solved this problem by addressing the (internal)container name, and not use DAPR. I really love the Azure Container Apps and use YARP as a reverse proxy to my underlying API's. This https://github.com/nikneem/tinylnk-gateway is my solution so far and to be honest, works like a charm. But still, you have to stay within a K8s context for this to work.

Yeah, hard coding service addresses most definitely not the way. Saying "Oh just dont use dapr" thats not a solution to the issue. Dapr is deeply integrated with azure container apps and makes service-to-service calls work the exact same in development as it does in production (Before dapr we would have to keep a list of urls to use for each environment which gets out of hand quick when you have 20+ services running)

My initial look at this seems to show it is relatively trivial to at least get a proof of concept working with yarp/dapr integration. This is what it looks like right now.

Create a ForwarderHttpFactory and use daprs invocation handler.

public class DaprForwarderHttpClientFactory : IForwarderHttpClientFactory
{
    public HttpMessageInvoker CreateClient(ForwarderHttpClientContext context)
    {
        //using yarps default socket settings
        var handler = new SocketsHttpHandler
        {
            UseProxy = false,
            AllowAutoRedirect = false,
            AutomaticDecompression = DecompressionMethods.None,
            UseCookies = false,
            ActivityHeadersPropagator = new ReverseProxyPropagator(DistributedContextPropagator.Current)
        };
        //using daprs own delegating handler here
        var daprHandler = new InvocationHandler();
        daprHandler.InnerHandler = handler;
        return new HttpMessageInvoker(daprHandler);
    }
}

Then in your service setup, just add the IForwarderHttpClientFactory

services.AddSingleton<IForwarderHttpClientFactory, DaprForwarderHttpClientFactory>();

It seems to work fine for simple service-to-service invocation calls. I cant promise it works in every situation. This works by setting your destinations as your dapr ID. So if your service dapr ID is "users" then your destination address should be "http://users". Heres an example of the setup

{
  "ReverseProxy": {
    "Routes": {
      "UserRoute": {
        "ClusterId": "UserCluster",
        "Match": {
          "Path": "/api/users"
        }
      }
    },
    "Clusters": {
      "UserCluster": {
        "Destinations": {
          "first": {
            "Address": "http://users"
          }
        }
      }
    }
  }
}

I'm looking at this now and it works fine. But i think you probably should use the direct forwarding for this instead. Dapr has its own load balancing so we dont need those features from yarp.

Again this would be quite a lot easier to accomplish if we had the ability to .AddDelegatingHandler like ocelot does on the service injection itself.

Arcalise08 commented 11 months ago

Heres the same thing as above with direct forwarding.

services.AddHttpForwarder();
services.AddSingleton<IForwarderHttpClientFactory, DaprForwarderHttpClientFactory>();
var app = builder.Build();
app.Map("/api/users", async (HttpContext context, IHttpForwarder forwarder, IForwarderHttpClientFactory factory) =>
    await forwarder.SendAsync(context, "http://users", factory.CreateClient(new ForwarderHttpClientContext()))
);

If anyone has any feedback on this I'd be happy to hear it. It's using the same clientfactory as the above method uses. I'm unsure how to go about getting the ForwarderHttpClientContext so I have to create a new one, which seems to work fine considering my client factory doesn't use the context at all.

MihaZupan commented 11 months ago

factory.CreateClient(new ForwarderHttpClientContext())

This will create a new HttpClient for every request. If you're already using custom logic there, you can drop the IForwarderHttpClientFactory indirection and create your handler once upfront.

Arcalise08 commented 11 months ago

Yeah, I was just load-testing that on a basic service project and it stopped working after a few thousand requests. So that changes it to something like so

var factory = new DaprForwarderHttpClientFactory();
var client = factory.CreateClient(new ForwarderHttpClientContext());
app.Map("/api/users", async (HttpContext context, IHttpForwarder forwarder) =>
{
    await forwarder.SendAsync(context, "https://users", client);
});

You can remove the logic from the forwarder http client factory. I just used that because it already creates the handler and http client. That seems to perform a lot better also.