Перейти к основному содержимому

Добавление перехватчиков в OkHTTP

· 8 мин. чтения

1. Обзор

Обычно при управлении циклом запросов и ответов HTTP-запросов в наших веб-приложениях нам нужен способ подключиться к этой цепочке . Обычно это делается для того, чтобы добавить какое-то пользовательское поведение до того, как мы выполним наши запросы, или просто после того, как наш код сервлета завершится .

OkHttp — это эффективный клиент HTTP и HTTP/2 для приложений Android и Java. В предыдущем уроке мы рассмотрели основы работы с OkHttp.

В этом руководстве мы узнаем все о том, как мы можем перехватывать наши объекты HTTP-запросов и ответов .

2. Перехватчики

Как следует из названия, перехватчики — это подключаемые компоненты Java, которые мы можем использовать для перехвата и обработки запросов до того, как они будут отправлены в код нашего приложения.

Точно так же они предоставляют нам мощный механизм для обработки ответа сервера до того, как контейнер отправит ответ обратно клиенту.

Это особенно полезно, когда мы хотим что-то изменить в HTTP-запросе, например, добавить новый заголовок элемента управления, изменить тело нашего запроса или просто создать журналы для помощи в отладке.

Еще одна приятная особенность использования перехватчиков заключается в том, что они позволяют нам инкапсулировать общие функции в одном месте. Давайте представим, что мы хотим применить некоторую логику глобально ко всем нашим объектам запросов и ответов, например, обработку ошибок.

Есть по крайней мере несколько преимуществ использования такой логики в перехватчике:

  • Нам нужно поддерживать этот код только в одном месте, а не во всех наших конечных точках.
  • Каждый сделанный запрос обрабатывает ошибку одинаково

Наконец, мы также можем отслеживать, переписывать и повторять вызовы от наших перехватчиков.

3. Обычное использование

Некоторые другие распространенные задачи, когда первопричина может быть очевидным выбором, включают:

  • Регистрация параметров запроса и другой полезной информации
  • Добавление заголовков аутентификации и авторизации в наши запросы
  • Форматирование тела запроса и ответа
  • Сжатие данных ответа, отправляемых клиенту
  • Изменение заголовков наших ответов путем добавления файлов cookie или дополнительной информации заголовка.

Мы увидим несколько примеров в действии в следующих разделах.

4. Зависимости

Конечно, нам нужно добавить стандартную зависимость okhttp в наш pom.xml :

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>

Нам также понадобится еще одна зависимость специально для наших тестов. Добавим артефакт mockwebserver OkHttp :

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.9.1</version>
<scope>test</scope>
</dependency>

Теперь, когда у нас настроены все необходимые зависимости, мы можем приступить к написанию нашего первого перехватчика.

5. Определение простого перехватчика регистрации

Давайте начнем с определения нашего собственного перехватчика. Для простоты наш перехватчик будет регистрировать заголовки запроса и URL-адрес запроса:

public class SimpleLoggingInterceptor implements Interceptor {

private static final Logger LOGGER = LoggerFactory.getLogger(SimpleLoggingInterceptor.class);

@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();

LOGGER.info("Intercepted headers: {} from URL: {}", request.headers(), request.url());

return chain.proceed(request);
}
}

Как мы видим, для создания нашего перехватчика все, что нам нужно, это наследоваться от интерфейса Interceptor , который имеет один обязательный метод intercept(Chain chain) . Затем мы можем пойти дальше и переопределить этот метод нашей собственной реализацией.

Во-первых, мы получаем входящий запрос с помощью функции chain.request() , прежде чем распечатать заголовки и URL-адрес запроса.

Важно отметить, что важной частью реализации каждого перехватчика является вызов chain.proceed(request) .

В этом простом на вид методе мы сигнализируем, что хотим поразить код нашего приложения, создавая ответ, удовлетворяющий запрос.

5.1. Соединяем это вместе

Чтобы действительно использовать этот перехватчик, все, что нам нужно сделать, это вызвать метод addInterceptor при создании нашего экземпляра OkHttpClient , и он должен просто работать:

OkHttpClient client = new OkHttpClient.Builder() 
.addInterceptor(new SimpleLoggingInterceptor())
.build();

Мы можем продолжать вызывать метод addInterceptor для столько перехватчиков, сколько нам нужно. Просто помните, что они будут вызываться в том порядке, в котором они были добавлены.

5.2. Тестирование перехватчика

Теперь мы определили наш первый перехватчик; давайте продолжим и напишем наш первый интеграционный тест:

@Rule
public MockWebServer server = new MockWebServer();

@Test
public void givenSimpleLogginInterceptor_whenRequestSent_thenHeadersLogged() throws IOException {
server.enqueue(new MockResponse().setBody("Hello ForEach Readers!"));

OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new SimpleLoggingInterceptor())
.build();

Request request = new Request.Builder()
.url(server.url("/greeting"))
.header("User-Agent", "A ForEach Reader")
.build();

try (Response response = client.newCall(request).execute()) {
assertEquals("Response code should be: ", 200, response.code());
assertEquals("Body should be: ", "Hello ForEach Readers!", response.body().string());
}
}

Прежде всего, мы используем правило JUnit OkHttp MockWebServer .

Это легкий веб-сервер с поддержкой сценариев для тестирования HTTP-клиентов, который мы собираемся использовать для тестирования наших перехватчиков . Используя это правило, мы создадим чистый экземпляр сервера для каждого интеграционного теста.

Имея это в виду, давайте теперь пройдемся по ключевым частям нашего теста:

  • Прежде всего, мы настраиваем фиктивный ответ, который содержит простое сообщение в теле.
  • Затем мы создаем наш OkHttpClient и настраиваем наш SimpleLoggingInterceptor.
  • Затем мы настраиваем запрос, который мы собираемся отправить, с одним заголовком User-Agent .
  • Последний шаг — отправить запрос и убедиться, что полученный код ответа и тело соответствуют ожиданиям.

5.3. Запуск теста

Наконец, когда мы запустим наш тест, мы увидим зарегистрированный заголовок HTTP User-Agent :

16:07:02.644 [main] INFO  c.b.o.i.SimpleLoggingInterceptor - Intercepted headers: User-Agent: A ForEach Reader
from URL: http://localhost:54769/greeting

5.4. Использование встроенного HttpLoggingInterceptor

Хотя наш перехватчик ведения журнала хорошо демонстрирует, как мы можем определить перехватчик, стоит упомянуть, что OkHttp имеет встроенный регистратор, которым мы можем воспользоваться.

Чтобы использовать этот регистратор, нам нужна дополнительная зависимость Maven :

<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>4.9.1</version>
</dependency>

Затем мы можем создать экземпляр нашего регистратора и определить интересующий нас уровень ведения журнала:

HttpLoggingInterceptor logger = new HttpLoggingInterceptor();
logger.setLevel(HttpLoggingInterceptor.Level.HEADERS);

В этом примере нас интересуют только заголовки.

6. Добавление пользовательского заголовка ответа

Теперь, когда мы понимаем основы создания перехватчиков. Давайте теперь посмотрим на другой типичный вариант использования, когда мы модифицируем один из заголовков ответа HTTP.

Это может быть полезно, если мы хотим добавить HTTP-заголовок нашего собственного проприетарного приложения или переписать один из заголовков, возвращаемых с нашего сервера :

public class CacheControlResponeInterceptor implements Interceptor {

@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());
return response.newBuilder()
.header("Cache-Control", "no-store")
.build();
}
}

Как и раньше, мы вызываем метод chain.proceed , но на этот раз без предварительного использования объекта запроса. Когда ответ возвращается, мы используем его для создания нового ответа и устанавливаем для заголовка Cache-Control значение no-store .

На самом деле маловероятно, что мы захотим каждый раз указывать браузеру запрашивать данные с сервера, но мы можем использовать этот подход для установки любого заголовка в нашем ответе.

7. Обработка ошибок с помощью перехватчиков

Как упоминалось ранее, мы также можем использовать перехватчики для инкапсуляции некоторой логики, которую мы хотим применить глобально ко всем нашим объектам запросов и ответов, например, для обработки ошибок.

Давайте представим, что мы хотим вернуть облегченный ответ JSON со статусом и сообщением, когда ответ не является ответом HTTP 200.

Имея это в виду, мы начнем с определения простого bean-компонента для хранения сообщения об ошибке и кода состояния:

public class ErrorMessage {

private final int status;
private final String detail;

public ErrorMessage(int status, String detail) {
this.status = status;
this.detail = detail;
}

// Getters and setters
}

Далее мы создадим наш перехватчик:

public class ErrorResponseInterceptor implements Interceptor {

public static final MediaType APPLICATION_JSON = MediaType.get("application/json; charset=utf-8");

@Override
public Response intercept(Chain chain) throws IOException {
Response response = chain.proceed(chain.request());

if (!response.isSuccessful()) {
Gson gson = new Gson();
String body = gson.toJson(
new ErrorMessage(response.code(), "The response from the server was not OK"));
ResponseBody responseBody = ResponseBody.create(body, APPLICATION_JSON);

ResponseBody originalBody = response.body();
if (originalBody != null) {
originalBody.close();
}

return response.newBuilder().body(responseBody).build();
}
return response;
}
}

Проще говоря, наш перехватчик проверяет, был ли ответ успешным, и, если это не так, создает ответ JSON, содержащий код ответа и простое сообщение. Обратите внимание, что в этом случае мы должны не забыть закрыть исходное тело ответа, чтобы освободить все ресурсы, связанные с ним.

{
"status": 500,
"detail": "The response from the server was not OK"
}

8. Сетевые перехватчики

До сих пор мы рассмотрели перехватчики, которые OkHttp называет перехватчиками приложений. Однако OkHttp также поддерживает другой тип перехватчиков, называемый сетевыми перехватчиками.

Мы можем определить наши сетевые перехватчики точно так же, как объяснялось ранее. Однако нам нужно будет вызвать метод addNetworkInterceptor при создании экземпляра нашего HTTP-клиента :

OkHttpClient client = new OkHttpClient.Builder()
.addNetworkInterceptor(new SimpleLoggingInterceptor())
.build();

Некоторые из важных различий между приложениями и сетевыми приемниками включают:

  • Перехватчики приложений всегда вызываются один раз, даже если ответ HTTP обслуживается из кеша.
  • Сетевой перехватчик подключается к сетевому уровню и является идеальным местом для реализации логики повторных попыток.
  • Точно так же мы должны рассмотреть возможность использования сетевого перехватчика, когда наша логика не полагается на фактическое содержание ответа.
  • Использование сетевого перехватчика дает нам доступ к соединению, которое передает запрос, включая такую информацию, как IP-адрес и конфигурация TLS, которая использовалась для подключения к веб-серверу.
  • Перехватчикам приложений не нужно беспокоиться о промежуточных ответах, таких как перенаправления и повторные попытки.
  • Напротив, сетевой перехватчик может быть вызван более одного раза, если у нас есть перенаправление.

Как видим, оба варианта имеют свои достоинства. Так что это действительно зависит от нашего конкретного варианта использования, для которого мы выберем.

Однако чаще всего перехватчики приложений прекрасно справляются со своей задачей.

9. Заключение

В этой статье мы узнали все о том, как создавать перехватчики с помощью OkHttp. Во-первых, мы начали с объяснения того, что такое перехватчик и как мы можем определить простой перехватчик ведения журнала для проверки заголовков наших HTTP-запросов.

Затем мы увидели, как установить заголовок, а также другое тело в наши объекты ответа. Наконец, мы кратко рассмотрели некоторые различия между приложениями и сетевыми перехватчиками.

Как всегда, полный исходный код статьи доступен на GitHub .