1. введение
В этой статье мы покажем основы отправки различных типов HTTP-запросов, получения и интерпретации HTTP-ответов , и как настроить клиент с помощью OkHttp .
Кроме того, мы рассмотрим более продвинутые варианты использования настройки клиента с пользовательскими заголовками, тайм-аутами, кэшированием ответов и т. Д.
2. Обзор OHTTP
OkHttp-это эффективный клиент HTTP & HTTP/2 для приложений Android и Java.
Он поставляется с расширенными функциями, такими как пул соединений (если HTTP/2 недоступен), прозрачное сжатие GZIP и кэширование ответов, чтобы полностью избежать сети для повторных запросов.
Он также способен восстанавливаться после распространенных проблем с подключением, и при сбое соединения, если служба имеет несколько IP-адресов, он может повторить запрос на альтернативные адреса.
На высоком уровне клиент предназначен как для блокировки синхронных, так и для неблокирующих асинхронных вызовов.
Ok Http поддерживает Android 2.3 и выше. Для Java минимальное требование составляет 1.7.
После этого краткого обзора давайте рассмотрим некоторые примеры использования .
3. Зависимость Maven
Давайте сначала добавим библиотеку в качестве зависимости в pom.xml :
com.squareup.okhttp3 okhttp 4.9.1
Чтобы ознакомиться с последней зависимостью этой библиотеки, перейдите на страницу Maven Central .
4. Синхронное ПОЛУЧЕНИЕ С ОХТТП
Чтобы отправить синхронный запрос GET, нам нужно построить объект Request на основе URL и сделать Вызов . После его выполнения мы получаем обратно экземпляр Response :
@Test public void whenGetRequest_thenCorrect() throws IOException { Request request = new Request.Builder() .url(BASE_URL + "/date") .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(200)); }
5. Асинхронная РАБОТА С OHTTP
Теперь, чтобы сделать асинхронный GET, нам нужно поставить в очередь Вызов ./| Обратный вызов позволяет нам читать ответ, когда он читаем. Это происходит после того, как заголовки ответов готовы.
Чтение тела ответа все еще может быть заблокировано. В настоящее время OkHttp не предлагает никаких асинхронных API для получения тела ответа по частям:
@Test public void whenAsynchronousGetRequest_thenCorrect() { Request request = new Request.Builder() .url(BASE_URL + "/date") .build(); Call call = client.newCall(request); call.enqueue(new Callback() { public void onResponse(Call call, Response response) throws IOException { // ... } public void onFailure(Call call, IOException e) { fail(); } }); }
6. ПОЛУЧИТЬ С Параметрами Запроса
Наконец, чтобы добавить параметры запроса в наш запрос GET, мы можем воспользоваться Http Url.Builder .
После построения URL-адреса мы можем передать его нашему объекту Request :
@Test public void whenGetRequestWithQueryParameter_thenCorrect() throws IOException { HttpUrl.Builder urlBuilder = HttpUrl.parse(BASE_URL + "/ex/bars").newBuilder(); urlBuilder.addQueryParameter("id", "1"); String url = urlBuilder.build().toString(); Request request = new Request.Builder() .url(url) .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(200)); }
7. Запрос на публикацию
Давайте рассмотрим простой запрос POST, в котором мы создаем RequestBody для отправки параметров “имя пользователя” и “пароль” :
@Test public void whenSendPostRequest_thenCorrect() throws IOException { RequestBody formBody = new FormBody.Builder() .add("username", "test") .add("password", "test") .build(); Request request = new Request.Builder() .url(BASE_URL + "/users") .post(formBody) .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(200)); }
В нашей статье Краткое руководство по отправке запросов с помощью OkHttp содержит больше примеров запросов на отправку с помощью OkHttp.
8. Загрузка файлов
8.1. Загрузить файл
В этом примере мы увидим, как загрузить файл /. Мы загрузим файл “ test.text” , используя Составное тело.Строитель :
@Test public void whenUploadFile_thenCorrect() throws IOException { RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", "file.txt", RequestBody.create(MediaType.parse("application/octet-stream"), new File("src/test/resources/test.txt"))) .build(); Request request = new Request.Builder() .url(BASE_URL + "/users/upload") .post(requestBody) .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(200)); }
8.2. Получить Ход Загрузки Файла
Наконец, давайте посмотрим, как получить прогресс загрузки файла . Мы расширим Тело запроса , чтобы получить представление о процессе загрузки.
Во-первых, вот метод загрузки:
@Test public void whenGetUploadFileProgress_thenCorrect() throws IOException { RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", "file.txt", RequestBody.create(MediaType.parse("application/octet-stream"), new File("src/test/resources/test.txt"))) .build(); ProgressRequestWrapper.ProgressListener listener = (bytesWritten, contentLength) -> { float percentage = 100f * bytesWritten / contentLength; assertFalse(Float.compare(percentage, 100) > 0); }; ProgressRequestWrapper countingBody = new ProgressRequestWrapper(requestBody, listener); Request request = new Request.Builder() .url(BASE_URL + "/users/upload") .post(countingBody) .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(200)); }
Вот интерфейс ProgressListener , который позволяет нам наблюдать за ходом загрузки:
public interface ProgressListener { void onRequestProgress(long bytesWritten, long contentLength); }
Вот Progress RequestWrapper , который является расширенной версией Тела запроса :
public class ProgressRequestWrapper extends RequestBody { @Override public void writeTo(BufferedSink sink) throws IOException { BufferedSink bufferedSink; countingSink = new CountingSink(sink); bufferedSink = Okio.buffer(countingSink); delegate.writeTo(bufferedSink); bufferedSink.flush(); } }
Наконец, вот раковина подсчета , которая является расширенной версией пересылки Раковины :
protected class CountingSink extends ForwardingSink { private long bytesWritten = 0; public CountingSink(Sink delegate) { super(delegate); } @Override public void write(Buffer source, long byteCount) throws IOException { super.write(source, byteCount); bytesWritten += byteCount; listener.onRequestProgress(bytesWritten, contentLength()); } }
Обратите внимание, что:
- При расширении Forwarding Sink до “CountingSink”, мы переопределяем метод write() для подсчета записанных (переданных) байтов
- При расширении RequestBody до ” Progress RequestWrapper “мы переопределяем метод writeTo (), чтобы использовать наш ” ForwardingSink”
9. Настройка пользовательского заголовка
9.1. Установка заголовка запроса
Чтобы задать любой пользовательский заголовок для Запроса , мы можем использовать простой AddHeader вызов:
@Test public void whenSetHeader_thenCorrect() throws IOException { Request request = new Request.Builder() .url(SAMPLE_URL) .addHeader("Content-Type", "application/json") .build(); Call call = client.newCall(request); Response response = call.execute(); response.close(); }
9.2. Установка заголовка по умолчанию
В этом примере мы увидим, как настроить заголовок по умолчанию на самом клиенте, а не устанавливать его для каждого запроса.
Например, если мы хотим установить тип контента “application/json” для каждого запроса, нам нужно установить перехватчик для нашего клиента. Вот метод:
@Test public void whenSetDefaultHeader_thenCorrect() throws IOException { OkHttpClient client = new OkHttpClient.Builder() .addInterceptor( new DefaultContentTypeInterceptor("application/json")) .build(); Request request = new Request.Builder() .url(SAMPLE_URL) .build(); Call call = client.newCall(request); Response response = call.execute(); response.close(); }
А вот ContentTypeInterceptor по умолчанию , который является расширенной версией Перехватчика :
public class DefaultContentTypeInterceptor implements Interceptor { public Response intercept(Interceptor.Chain chain) throws IOException { Request originalRequest = chain.request(); Request requestWithUserAgent = originalRequest .newBuilder() .header("Content-Type", contentType) .build(); return chain.proceed(requestWithUserAgent); } }
Обратите внимание, что перехватчик добавляет заголовок к исходному запросу.
10. Не Следуйте Перенаправлениям
В этом примере мы увидим, как настроить OkHttpClient для остановки следующих перенаправлений.
По умолчанию, если на запрос GET отвечает HTTP 301, перемещенный навсегда , перенаправление выполняется автоматически. В некоторых случаях это может быть совершенно нормально, но, безусловно, есть случаи, когда это нежелательно.
Чтобы добиться такого поведения, когда мы создаем наш клиент, нам нужно установить followRedirects в false .
Обратите внимание, что в ответе будет возвращен код состояния HTTP 301 :
@Test public void whenSetFollowRedirects_thenNotRedirected() throws IOException { OkHttpClient client = new OkHttpClient().newBuilder() .followRedirects(false) .build(); Request request = new Request.Builder() .url("http://t.co/I5YYd9tddw") .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(301)); }
Если мы включим перенаправление с параметром true (или полностью удалим его), клиент будет следовать перенаправлению, и тест завершится неудачей, так как код возврата будет HTTP 200.
11. Тайм-ауты
Используйте тайм-ауты для сбоя вызова, когда его одноранговый узел недоступен. Сетевые сбои могут быть вызваны проблемами с подключением клиентов, проблемами доступности серверов или чем-либо между ними. OkHttp поддерживает тайм-ауты подключения, чтения и записи.
В этом примере мы построили наш клиент с таймаутом чтения в 1 секунду, в то время как URL-адрес обслуживается с задержкой в 2 секунды:
@Test public void whenSetRequestTimeout_thenFail() throws IOException { OkHttpClient client = new OkHttpClient.Builder() .readTimeout(1, TimeUnit.SECONDS) .build(); Request request = new Request.Builder() .url(BASE_URL + "/delay/2") .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(200)); }
Обратите внимание, что тест завершится неудачно, так как время ожидания клиента меньше времени отклика ресурса.
12. Отмена вызова
Используйте Call.cancel () , чтобы немедленно остановить текущий вызов. Если поток в данный момент пишет запрос или читает ответ, будет выдано исключение IOException .
Используйте это для сохранения сети, когда вызов больше не нужен; например, когда пользователь переходит из приложения:
@Test(expected = IOException.class) public void whenCancelRequest_thenCorrect() throws IOException { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); Request request = new Request.Builder() .url(BASE_URL + "/delay/2") .build(); int seconds = 1; long startNanos = System.nanoTime(); Call call = client.newCall(request); executor.schedule(() -> { logger.debug("Canceling call: " + (System.nanoTime() - startNanos) / 1e9f); call.cancel(); logger.debug("Canceled call: " + (System.nanoTime() - startNanos) / 1e9f); }, seconds, TimeUnit.SECONDS); logger.debug("Executing call: " + (System.nanoTime() - startNanos) / 1e9f); Response response = call.execute(); logger.debug(Call was expected to fail, but completed: " + (System.nanoTime() - startNanos) / 1e9f, response); }
13. Кэширование ответов
Чтобы создать Кэш , нам понадобится каталог кэша, в который мы можем читать и записывать, а также ограничение на размер кэша.
Клиент будет использовать его для кэширования ответа:
@Test public void whenSetResponseCache_thenCorrect() throws IOException { int cacheSize = 10 * 1024 * 1024; File cacheDirectory = new File("src/test/resources/cache"); Cache cache = new Cache(cacheDirectory, cacheSize); OkHttpClient client = new OkHttpClient.Builder() .cache(cache) .build(); Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt") .build(); Response response1 = client.newCall(request).execute(); logResponse(response1); Response response2 = client.newCall(request).execute(); logResponse(response2); }
После запуска теста ответ от первого вызова не будет кэширован. Вызов метода cache Response вернет null , в то время как вызов метода network Response вернет ответ из сети.
Кроме того, папка кэша будет заполнена файлами кэша.
Выполнение второго вызова приведет к противоположному эффекту, так как ответ уже будет кэширован. Это означает, что вызов network Response вернет null , в то время как вызов cache Response вернет ответ из кэша.
Чтобы предотвратить использование кэша для ответа, используйте Cache Control.FORCE_NETWORK . Чтобы предотвратить его использование в сети, используйте Cache Control.FORCE_CACHE .
Имейте в виду: если вы используете FORCE_CACHE и для ответа требуется сеть, OkHttp вернет 504 неудовлетворительных ответа на запрос.
14. Заключение
В этой статье мы рассмотрели несколько примеров того, как использовать OkHttp в качестве клиента HTTP & HTTP/2.
Как всегда, пример кода можно найти в проекте GitHub .