Перейти до вмісту

Перехоплювач (шаблон проєктування)

Очікує на перевірку
Матеріал з Вікіпедії — вільної енциклопедії.
Приклад перехоплювача

Перехоплювач (англ. 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());
}

Даний шаблон також може бути корисний у системах орієнтованих на обробку повідомлень.

Див. також

[ред. | ред. код]

Джерела

[ред. | ред. код]