Перехоплювач (шаблон проєктування)
Перехоплювач (англ. Interceptor) — шаблон проєктування, який використовують коли система хоче надати альтернативний спосіб виконання, або доповнити чинний цикл виконання додатковими операціями.
Важливо розуміти, що зміни є прозорими та використовується автоматично. По суті, решта системи не повинна знати, що щось додано або змінено, і може продовжувати працювати як раніше. Аби реалізувати цю логіку необхідно мати інтерфейс перехоплювача, який можна реалізувати та вбудувати у систему. Реєстрація перехоплювачів може відбуватись, під час компіляції або виконання програми або ж за допомогою конфігурацій. Важливо також, щоб перехоплювач отримував стан систему через вхідні параметри, та при потребі міг їх міняти.
Нехай дана система, яка реалізує клієнт-серверну архітектуру та автентифікацію за допомогою json web токенів. Тоді необхідно у кожний HTTP-запит додати токен. Щоб запобігти змінам у багатьох компонентах системи, напишемо перехоплювач HTTP-запитів, який буде додавати токен в заголовки запиту.
export class AddAuthHeaderInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
public intercept<T>(req: HttpRequest<T>, next: HttpHandler): Observable<HttpEvent<T>> {
return this.authService.getCurrentUserToken().pipe(
mergeMap(token => {
const clonedRequest = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
// передаємо копію запиту, а не оригінал, наступному обробнику
return next.handle(clonedRequest);
})
);
}
}
// реєстрація перехоплювача
@NgModule({
providers: [
AuthService,
{
provide: HTTP_INTERCEPTORS,
useClass: AddAuthHeaderInterceptor,
multi: true,
},
],
})
export class AppModule {}
Подібного можна досягнути при взаємодії сервера з іншим сервером.
public class HttpClientAuthorizationDelegatingHandler : DelegatingHandler
{
private readonly IHttpContextAccessor _httpContextAccesor;
public HttpClientAuthorizationDelegatingHandler(IHttpContextAccessor httpContextAccesor)
{
_httpContextAccesor = httpContextAccesor;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string authorizationHeader = _httpContextAccesor.HttpContext.Request.Headers["Authorization"];
if (!string.IsNullOrEmpty(authorizationHeader))
{
request.Headers.Add("Authorization", new List<string>() { authorizationHeader });
}
string token = await GetToken();
if (token != null)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
}
return await base.SendAsync(request, cancellationToken);
}
private Task<string> GetToken()
{
const string ACCESS_TOKEN = "access_token";
return _httpContextAccesor.HttpContext.GetTokenAsync(ACCESS_TOKEN);
}
}
// та реєстрація
services
.AddHttpClient<IServerApiClient, ServerHttpClient>()
.AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>();
Якщо нам необхідно додати логіку трасування про збереження об'єктів у базу даних при цьому не змінюючи чинний функціонал, ми також можемо написати перехоплювач метода.
public class MySaveChangesInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(DbContextEventData eventData, InterceptionResult<int> result)
{
Console.WriteLine($"Зберігаємо зміни для {eventData.Context.Database.GetConnectionString()}");
return result;
}
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = new CancellationToken())
{
Console.WriteLine($"Зберігаємо зміни асинхронно для {eventData.Context.Database.GetConnectionString()}");
return new ValueTask<InterceptionResult<int>>(result);
}
}
// та реєстрація
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.AddInterceptors(new MySaveChangesInterceptor());
}
Даний шаблон також може бути корисний у системах орієнтованих на обробку повідомлень.