Una guía para OkHttp

1. Introducción

En este artículo, vamos a mostrar los conceptos básicos de enviar diferentes tipos de peticiones HTTP, recibir e interpretar las respuestas HTTP , y cómo configurar un cliente con OkHttp.

Además, analizaremos casos de uso más avanzados de configuración de un cliente con encabezados personalizados, tiempos de espera, almacenamiento en caché de respuestas, etc.

2. Descripción general de OkHttp

OkHttp es un cliente HTTP y HTTP / 2 eficiente para aplicaciones de Android y Java.

Viene con funciones avanzadas como agrupación de conexiones (si HTTP / 2 no está disponible), compresión GZIP transparente y almacenamiento en caché de respuestas para evitar la red por completo para solicitudes repetidas.

También puede recuperarse de problemas de conexión comunes y, en caso de falla de conexión, si un servicio tiene varias direcciones IP, puede volver a intentar la solicitud a direcciones alternativas.

En un nivel alto, el cliente está diseñado para bloquear llamadas sincrónicas y asincrónicas sin bloqueo.

OkHttp es compatible con Android 2.3 y superior. Para Java, el requisito mínimo es 1.7.

Después de esta breve descripción general, veamos algunos ejemplos de uso.

3. Dependencia de Maven

Primero agreguemos la biblioteca como una dependencia en pom.xml :

 com.squareup.okhttp3 okhttp 3.4.2 

Para ver la última dependencia de esta biblioteca, consulte la página en Maven Central.

4. GET sincrónico con OkHttp

Para enviar una solicitud GET sincrónica, necesitamos crear un objeto Solicitud basado en una URL y realizar una llamada . Después de su ejecución, obtenemos una instancia de 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. GET asincrónico con OkHttp

Ahora, para hacer un GET asincrónico necesitamos poner en cola una llamada . Una devolución de llamada nos permite leer la respuesta cuando es legible. Esto sucede después de que los encabezados de respuesta estén listos.

Leer el cuerpo de la respuesta aún puede bloquear. OkHttp actualmente no ofrece ninguna API asincrónica para recibir un cuerpo de respuesta en partes:

@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. OBTENER con parámetros de consulta

Finalmente, para agregar parámetros de consulta a nuestra solicitud GET, podemos aprovechar HttpUrl.Builder .

Una vez creada la URL, podemos pasarla a nuestro objeto 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. Solicitud POST

Veamos una solicitud POST simple donde creamos un RequestBody para enviar los parámetros "nombre de usuario" y "contraseña" :

@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)); }

Nuestro artículo Una guía rápida para publicar solicitudes con OkHttp tiene más ejemplos de solicitudes POST con OkHttp.

8. Carga de archivos

8.1. Cargar un archivo

En este ejemplo, veremos cómo cargar un archivo . Subiremos el archivo " test.ext" usando MultipartBody.Builder :

@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. Obtener progreso de carga de archivos

Finalmente, veamos cómo obtener el progreso de la carga de un archivo . Ampliaremos RequestBody para obtener visibilidad del proceso de carga.

Primero, aquí está el método de carga:

@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)); } 

Aquí está la interfaz ProgressListener que nos permite observar el progreso de la carga:

public interface ProgressListener { void onRequestProgress(long bytesWritten, long contentLength); }

Aquí está el ProgressRequestWrapper, que es la versión extendida de RequestBody :

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(); } }

Finalmente, aquí está el CountingSink, que es la versión extendida de Forwarding Sink :

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()); } }

Tenga en cuenta que:

  • Al extender ForwardingSink a "CountingSink", anulamos el método write () para contar los bytes escritos (transferidos)
  • Al extender RequestBody a " ProgressRequestWrapper ", anulamos el método writeTo () para usar nuestro "ForwardingSink"

9. Configuración de un encabezado personalizado

9.1. Setting a Header on a Request

To set any custom header on a Request we can use a simple addHeader call:

@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. Setting a Default Header

In this example, we will see how to configure a default header on the Client itself, instead of setting it on each and every request.

For example, if we want to set a content type “application/json” for every request we need to set an interceptor for our client. Here is the method:

@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(); }

And here is the DefaultContentTypeInterceptor which is the extended version of Interceptor:

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); } }

Note that the interceptor adds the header to the original request.

10. Do Not Follow Redirects

In this example, we'll see how to configure the OkHttpClient to stop following redirects.

By default, if a GET request is answered with an HTTP 301 Moved Permanently the redirect is automatically followed. In some use cases, that may be perfectly fine, but there are certainly use cases where that’s not desired.

To achieve this behavior, when we build our client, we need to set followRedirects to false.

Note that the response will return an HTTP 301 status code:

@Test public void whenSetFollowRedirects_thenNotRedirected() throws IOException { OkHttpClient client = new OkHttpClient().newBuilder() .followRedirects(false) .build(); Request request = new Request.Builder() .url("//t.co/I5YYd9tddw") .build(); Call call = client.newCall(request); Response response = call.execute(); assertThat(response.code(), equalTo(301)); } 

If we turn on the redirect with a true parameter (or remove it completely), the client will follow the redirection and the test will fail as the return code will be an HTTP 200.

11. Timeouts

Use timeouts to fail a call when its peer is unreachable. Network failures can be due to client connectivity problems, server availability problems, or anything between. OkHttp supports connect, read, and write timeouts.

In this example, we built our client with a readTimeout of 1 seconds, while the URL is served with 2 seconds of delay:

@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)); }

Note as the test will fail as the client timeout is lower than the resource response time.

12. Canceling a Call

Use Call.cancel() to stop an ongoing call immediately. If a thread is currently writing a request or reading a response, an IOException will be thrown.

Use this to conserve the network when a call is no longer necessary; for example when your user navigates away from an application:

@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. Response Caching

To create a Cache, we'll need a cache directory that we can read and write to, and a limit on the cache's size.

The client will use it to cache the response:

@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("//publicobject.com/helloworld.txt") .build(); Response response1 = client.newCall(request).execute(); logResponse(response1); Response response2 = client.newCall(request).execute(); logResponse(response2); }

After launching the test, the response from the first call will not have been cached. A call to the method cacheResponse will return null, while a call to the method networkResponse will return the response from the network.

Also, the cache folder will be filled with the cache files.

The second call execution will produce the opposite effect, as the response will have already been cached. This means that a call to networkResponse will return null while a call to cacheResponse will return the response from the cache.

To prevent a response from using the cache, use CacheControl.FORCE_NETWORK. To prevent it from using the network, use CacheControl.FORCE_CACHE.

Be warned: if you use FORCE_CACHE and the response requires the network, OkHttp will return a 504 Unsatisfiable Request response.

14. Conclusion

In this article, we have seen several examples of how to use OkHttp as an HTTP & HTTP/2 client.

Como siempre, el código de ejemplo se puede encontrar en el proyecto GitHub.