DelegatingHandlers not executing when routes not defined - c#

I am working on a WebAPI 2 project which currently uses attribute-based routing exclusively, to the point that there are no routes defined in registration, and everything is working as expected.
However, I am now adding a DelegatingHandler to provide a heartbeat that I can ping with a HEAD request:
public class HeartbeatMessagingHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (IsHeartbeat(request))
{
if (request.Method == HttpMethod.Head)
{
var response = new HttpResponseMessage(HttpStatusCode.NoContent) { Content = new StringContent(string.Empty) };
var task = new TaskCompletionSource<HttpResponseMessage>();
task.SetResult(response);
return task.Task;
}
}
return base.SendAsync(request, cancellationToken);
}
private static bool IsHeartbeat(HttpRequestMessage request)
{
return request.RequestUri.LocalPath.Equals("/Heartbeat", StringComparison.OrdinalIgnoreCase);
}
}
However, the handler is not being invoked if I make the expected HEAD http://localhost/heartbeat request; if I call any route that does exist then the handler is invoked; and if I add an old-school routing configuration then the handler is invoked on the expected /heartbeat endpoint.
So, using attribute routing only, how can I handle a request to a "virtual" endpoint?
Update
The message handlers are registered as follows:
config.MessageHandlers.Add(new HeartbeatMessagingHandler());
config.MessageHandlers.Add(new RequestAndResponseLoggerDelegatingHandler());
config.MessageHandlers.Add(new ApplicationInsightsMessageHandler());
so it is registered before e.g. my global logging handler, and according to my understanding, message handlers receive messages in the order that they are registered.

Most likely your handler is too late in the pipeline and a handler higher in the pipeline is short-circuiting the request before it reaches your handler.
consider inserting your handler higher up so it has a better chance of intercepting the request
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// Web API routes
config.MapHttpAttributeRoutes();
// add to the front of the pipeline
config.MessageHandlers.Insert(0, new HeartbeatMessagingHandler());
}
}

Related

How to create named client after startup?

For my application i need to make a named client for HttpRequests. I can create a named client in Startup. And to access it i inject an "IHttpClientFactory" and create a client from that. But the client needs to have an access token as an authorization header, and i cannot create the token in Startup. Therefor i need a way to create a named client outside of the Startup class. i have already tried injecting "IServiceCollection" into a controller. But this does not work.
Or is there maybe a way to edit a named client after it is already created in startup?
A similar solution to the one posted by #Ruben-J is to create a custom HttpMessageHandler which assigns an authorization header to requests made through the HttpClient at request-time.
You can create a custom HttpMessageHandler that can be assigned to a named HttpClient in Startup like so:
public class YourHttpMessageHandler : DelegatingHandler
{
private readonly IYourTokenProviderService _yourTokenProviderService;
public YourHttpMessageHandler(IYourTokenProviderService yourTokenProviderService)
: base()
{
_yourTokenProviderService = yourTokenProviderService;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = SendAsyncWithAuthToken(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
await _yourTokenProviderService.RefreshTokenAsync();
response = SendAsyncWithAuthToken(request, cancellationToken);
}
return response;
}
private async Task<HttpResponseMessage> SendWithAuthTokenAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _yourTokenProviderService.Token);
return await base.SendAsync(request, cancellationToken);
}
}
You then configure your services and named HttpClient in Startup:
public virtual void ConfigureServices(IServiceCollection services)
{
...
services.AddTransient<IYourTokenProviderService, YourTokenProviderService>();
services.AddTransient<YourHttpMessageHandler>();
services.AddHttpClient<IYourNamedHttpClient, YourNamedHttpClient>()
.AddHttpMessageHandler<YourHttpMessageHandler>();
...
}
Its worth noting that the current implementation of Polly's AddPolicyHandler is also adding its own DelegatingHandler.
For more background see the Microsoft documentation on adding DelegatingHandler's. Here is also great series of articles from Steve Gordon.
You could use Polly to add a policy handler to your client. You can then add logic if a request returns a 401 Unauthorized. So for example get your service that uses the client to refresh a bearer token and also set it for the current request. This is just a quick solution and maybe there are more elegant solutions. But this will also come in handy if your token expires. Cause then it will be refreshed automatically.
services.AddHttpClient("YourClient")
.AddPolicyHandler((provider, request) =>
{
return Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(1, async (response, retryCount, context) =>
{
var service = provider.GetRequiredService<IYourService>();
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await service.RefreshToken());
});
});

Refresh Token using Polly with Named Client

I have a policy that looks like this
var retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(resp => resp.StatusCode == HttpStatusCode.Unauthorized)
.WaitAndRetryAsync(3,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (resp, timeSpan, context) =>
{
// not sure what to put here
});
Then I have a named client that looks like this
services.AddHttpClient("MyClient", client =>
{
client.BaseAddress = new Uri("http://some-url.com");
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
client.Timeout = 30000;
})
.AddPolicyHandler(retryPolicy);
I need to refresh the bearer token on the http client in the event I receive a 401. So in a perfect world the following code would do exactly what I'm trying to accomplish
var retryPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(resp => resp.StatusCode == HttpStatusCode.Unauthorized)
.WaitAndRetryAsync(3,
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (resp, timeSpan, context) =>
{
var newToken = GetNewToken();
//httpClient doesn't exists here so I need to grab it some how
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", newToken);
});
I have read the following articles:
Re-establishing authentication using Retry
Refresh Token using Polly with Typed Client
retry-to-refresh-authorization
and a couple others as well. However, they all seem use policy.ExecuteAsync() which I don't want to use because then I would have to change all the HttpClient calls throughout my solution. I'm trying to find a way to simply add this functionality to every request by only changing code in the StartUp.cs.
TL;DR: You need to define a communication protocol between a RetryPolicy, a DelegatingHandler and a TokenService.
In case of Typed Clients you can explicitly call the ExecuteAsync and use the Context to exchange data between the to-be-decorated method and the onRetry(Async) delegate.
This trick can't be used in a named client situation. What you need to do instead:
Separate out the Token management into a dedicated service
Use a DelegatingHandler to intercept the HttpClient's communication
This sequence diagram depicts the communication between the different components
Token Service
The DTO
public class Token
{
public string Scheme { get; set; }
public string AccessToken { get; set; }
}
The interface
public interface ITokenService
{
Token GetToken();
Task RefreshToken();
}
The dummy implementation
public class TokenService : ITokenService
{
private DateTime lastRefreshed = DateTime.UtcNow;
public Token GetToken()
=> new Token { Scheme = "Bearer", AccessToken = lastRefreshed.ToString("HH:mm:ss")};
public Task RefreshToken()
{
lastRefreshed = DateTime.UtcNow;
return Task.CompletedTask;
}
}
The registration into the DI as Singleton
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITokenService, TokenService>();
...
}
Delegating Handler
The custom exception
public class OutdatedTokenException : Exception
{
}
The handler (interceptor)
public class TokenFreshnessHandler : DelegatingHandler
{
private readonly ITokenService tokenService;
public TokenFreshnessHandler(ITokenService service)
{
tokenService = service;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var token = tokenService.GetToken();
request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
var response = await base.SendAsync(request, cancellationToken);
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
throw new OutdatedTokenException();
}
return response;
}
}
It retrieves the current token from the TokenService
It sets the authorization header
It executes the base method
It checks the response's status
If 401 then it throws the custom exception
If other than 401 then it returns with the response
The registration into the DI as Transient
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITokenService, TokenService>();
services.AddTransient<TokenFreshnessHandler>();
...
}
Retry Policy
The policy definition
public IAsyncPolicy<HttpResponseMessage> GetTokenRefresher(IServiceProvider provider)
{
return Policy<HttpResponseMessage>
.Handle<OutdatedTokenException>()
.RetryAsync(async (_, __) => await provider.GetRequiredService<ITokenService>().RefreshToken());
}
It receives an IServiceProvider to be able to access the TokenService
It performs a single retry if an OutdatedTokenException was thrown
Inside the onRetryAsync delegate it calls the TokenService's RefreshToken method
Putting all things together
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ITokenService, TokenService>();
services.AddTransient<TokenFreshnessHandler>();
services.AddHttpClient("TestClient")
.AddPolicyHandler((provider, _) => GetTokenRefresher(provider))
.AddHttpMessageHandler<TokenFreshnessHandler>();
...
}
Please bear in mind that the ordering of AddPolicyHandler and AddHttpMessageHandler matters
If you would call the AddHttpMessageHandler first and then the AddPolicyHandler in that case your retry would not be triggered
This post contains an alternative version of my previously suggested solution.
I'm posting this as a separate answer (rather than editing the previous one) because both solutions are viable and the other post is already a lengthy one.
Why do we need an alternative version?
Because the TokenFreshnessHandler has too much responsibility whereas the Retry policy has too few.
If you look at the SendAsync method overridden implementation then you can see that it perform some operation on the request and on the response as well.
If we could make a separation where
the handler deals only with the request
and policy make its assessment on the response
then we would end up with a much cleaner solution (IMHO).
How can we achieve this separation?
If we could use the Polly's Context as an intermediate storage between the retry attempts then we were able to do this separation. Fortunately the Microsoft.Extensions.Http.Polly package defines two extension methods against the HttpRequestMessage:
SetPolicyExecutionContext
GetPolicyExecutionContext
These are under-documented features. On the docs.microsoft I could not even find the related pages. I have only found them under the dotnet-api-docs repo.
These can be useful if we know that the AddPolicyHandler attaches a new Context to the request only if it did not have one already. Unfortunately, this is yet again not documented, so it is an implementation detail which might change in the future. But currently we can rely on this.
How will this change the protocol?
As you see only difference here is the usage of the Context.
How should we change the handler?
public class TokenRetrievalHandler : DelegatingHandler
{
private readonly ITokenService tokenService;
private const string TokenRetrieval = nameof(TokenRetrieval);
private const string TokenKey = nameof(TokenKey);
public TokenRetrievalHandler(ITokenService service)
{
tokenService = service;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var context = request.GetPolicyExecutionContext();
if(context.Count == 0)
{
context = new Context(TokenRetrieval, new Dictionary<string, object> { { TokenKey, tokenService.GetToken() } });
request.SetPolicyExecutionContext(context);
}
var token = (Token)context[TokenKey];
request.Headers.Authorization = new AuthenticationHeaderValue(token.Scheme, token.AccessToken);
return await base.SendAsync(request, cancellationToken);
}
}
I've changed the name of the handler since its responsibilities have changed
Now, the handler's implementation only cares about the request (and does not care about the response)
As it was said previously: the PolicyHttpMessageHandler creates a new Context if there wasn't any
Because of this the GetPolicyExecutionContext does not return null (even for the very first attempt) rather than a Context with an empty context data collection (context.Count == 0)
How should we change the policy?
public IAsyncPolicy<HttpResponseMessage> GetTokenRefresher(IServiceProvider provider, HttpRequestMessage request)
{
return Policy<HttpResponseMessage>
.HandleResult(response => response.StatusCode == HttpStatusCode.Unauthorized)
.RetryAsync(async (_, __) =>
{
await provider.GetRequiredService<ITokenService>().RefreshToken();
request.SetPolicyExecutionContext(new Context());
});
}
Rather than triggering the policy for a custom exception, now it triggers in case of 401 response's status code
The onRetryAsync has been modified in the way that it clears the attached context of the request
The registration code should be adjusted as well
services.AddHttpClient("TestClient")
.AddPolicyHandler((sp, request) => GetTokenRefresher(sp, request))
.AddHttpMessageHandler<TokenRetrievalHandler>()
Now, we should pass to the GetTokenRefresher method not just the IServiceProvider but also HttpRequestMessage as well
Which solution should I use?
This solution offers nicer separation but it relies on an implementation detail
The other solution makes the handler smart whereas the policy dumb

A proxy web service to a web service - where am I missing?

I program ASP.NET Framework MVC and Web API 2
I have to access a REST service for some information. The nature of the security requirements for this service require that I ask from a limited set of known IP addresses. The nature of my client requirements is that there will be an unknown number of them with IPs that are assigned by some DHCP. I think I need to stand up a proxy that will forward requests to the service and return responses to the client that asked. This server can be assigned a single static IP, that I can register with the target service. I don't want to try to duplicate the signatures of the target service and have to maintain my proxy whenever they decide to improve interfaces.
I would have the service that is restricting IPs and accepts a GET for http://S/action as an example. I would have the proxy at http://P/action. The client would send GET http://P/action and P would, in response, send GET http://S/action, collect the response, return it back to the client.
An attempt to implement this strategy, here is a handler I built for P that doesn't work:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
DelegatingHandler handler = new DelegatingHandlerProxy<ProxyHandler>();
config.MessageHandlers.Add(handler);
}
}
DelegatingProxyHandler is a way to get my dependency injection container involved:
public sealed class DelegatingHandlerProxy<THandler> : DelegatingHandler
where THandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
IDependencyScope scope = request.GetDependencyScope();
Task<HttpResponseMessage> task;
if (scope.GetService(typeof(THandler)) is DelegatingHandler handler)
{
if (!ReferenceEquals(handler.InnerHandler, InnerHandler))
{
handler.InnerHandler = InnerHandler;
}
HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
task = invoker.SendAsync(request, cancellationToken);
}
else
{
throw new InvalidOperationException("Handler not registered with DI container");
}
return task;
}
}
The ProxyHandler that I want to do the work is:
public class ProxyHandler: DelegatingHandler
{
public ProxyHandler(
ITransformRequest preProcessor,
ITransformResponse postProcessor,
IForwardRequest forwarder)
{
PreProcessor = preProcessor;
PostProcessor = postProcessor;
Forwarder = forwarder;
}
private ITransformRequest PreProcessor { get; }
private ITransformResponse PostProcessor { get; }
private IForwardRequest Forwarder { get; }
#region Overrides of DelegatingHandler
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
if (request == null)
{
throw new ArgumentNullException(nameof(request));
}
if (PreProcessor != null)
{
request.RequestUri = PreProcessor.Transform(request.RequestUri);
}
HttpResponseMessage response = await Forwarder.Forward(request, cancellationToken);
HttpResponseMessage transformedResponse = PostProcessor.Transform(response);
return transformedResponse;
}
#endregion
}
In this case, the DI container supplies a PreProcessor that changes host, port, and prefix of the request to the target service. The Forwarder sends the request to the target using HttpClient. The PostProcessor will be a noop.
I didn't build any controllers. My thinking is that if this pipeline behaves as I expect, there won't be any controller that needs invoking. When I send ant request to this, http://P/anything returns a 404, not htto://S/anything. What am I missing?
Any particular reason you're not just writing a set of matching controllers that accept client requests and then execute the equivalent request on the 3rd arty API using a service that implements a simple web client and then returning the responses - perhaps including some authentication & caching logic to lower the impact on their API?
If your 3rd party API provider is limiting requests by IP, that is likely because they trust (or explicitly require) you to manage requests to their API in order to protect it from excessive load and/or security risks. Directly forwarding all client requests without any logic in your middleware means you're negating this limitation.
If the only purpose of your application is to provide a static IP (and you do not need to add any logic in your code) then you should consider using one of the many off the shelf API gateway products - e.g. Kong, which is an open source and very well established with plenty of community support https://konghq.com/kong-community-edition/

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

I would like to proxy every requests to a web application, pass it to another web application and then return my other web applications response to the original sender.
It should be able to handle all http methods and content-types etc. I should also be able to edit the incoming request and add additional headers and content.
The background for doing this is the security architecture for a project that has one web server in a public DMZ and then another web server in the internal network that is allowed to talk to the database server.
Found a thread for ASP.NET core but preferably it should be done with .Net Framework and not be dependent on an external library.
Creating a proxy to another web api with Asp.net core
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.

Converting HttpRequestMessage to OwinRequest and OwinResponse to HttpResponseMessage

I have a web API message handler MyHandler that I want to run in OWIN pipeline as a middleware. So configuring the handler like this.
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.UseHttpMessageHandler(new MyHandler());
HttpConfiguration config = new HttpConfiguration();
config.Routes.MapHttpRoute(
"DefaultWebApi",
"{controller}/{id}",
new { id = RouteParameter.Optional });
app.UseWebApi(config);
}
}
Handler is very simple and does nothing.
public class MyHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{ // <--- breakpoint here
var response = await base.SendAsync(request, cancellationToken);
return response;
}
}
I put a break point inside SendAsync and it does break but the following base.SendAsync bombs silently and I see A first chance exception of type 'System.InvalidOperationException' occurred in System.Net.Http.dll.
I can quite easily add MyHandler to config.MessageHandlers and it will run perfect in the Web API pipeline but that's not what I want to do. I want to run MyHandler in the OWIN pipeline. Is this possible at all? It should be. Otherwise, there is no point in having the extension method UseHttpMessageHandler, I guess. Just that I couldn't figure out a way to do what I want to do.
Yeah, this experience needs to be improved as the exception is silently ignored.
For your above scenario, you would need to derive from HttpMessageHandler instead of DelegatingHandler as the delegating handler would try to delegate the request to handlers after it.(example: The exception mentions Message=The inner handler has not been assigned)
For example, the following would work:
appBuilder.UseHttpMessageHandler(new MyNonDelegatingHandler());
public class MyNonDelegatingHandler : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = new HttpResponseMessage();
response.Content = new StringContent("Hello!");
return Task.FromResult<HttpResponseMessage>(response);
}
}
And for creating a chain of handlers, you could do the following:
appBuilder.UseHttpMessageHandler(HttpClientFactory.CreatePipeline(innerHandler: new MyNonDelegatingMessageHandler(),
handlers: new DelegatingHandler[] { new DelegatingHandlerA(), new DelegatingHandlerB() }));

Categories