reverse-proxy
reverse-proxy copied to clipboard
integrate with Dapr as Dapr's gateway
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?
- [ ] 1-2
- [ ] 3-5
- [*] 6-10
- [ ] 10+
How do you host your application?
- [*] Kubernetes
- [ ] Azure App Service
- [*] Azure VMs
- [ ] Other Cloud Provider (please include details below)
- [ ] Other Hosting model (please include details below)
What did you think of YARP?
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.
@jkotalik yes I would like to replace envoy with YARP as the reverse proxy
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
Adding @nishanil and @jamesmontemagno here. I think it would be exciting to see a YARP-inclusive version of eShop.
Triage: @samsp-msft can you please describe what is the plan here? (sample?)
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.
hi @samsp-msft , is there any progress about this feature ?
Sorry- No progress as of yet.
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.
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
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.
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.
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.
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.
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.