Um Guia para o OkHttp
1. Introdução
Neste artigo, mostraremos os fundamentos do envio de diferentes tipos de solicitações HTTP, recebimento e interpretação de respostas HTTP, e como configurar um clientewith OkHttp.
Além disso, entraremos em casos de uso mais avançados de configuração de um cliente com cabeçalhos personalizados, tempos limite, cache de resposta, etc.
2. Visão geral do OkHttp
OkHttp é um eficiente cliente HTTP & HTTP / 2 para aplicativos Android e Java.
Ele vem com recursos avançados, como pool de conexão (se HTTP / 2 não estiver disponível), compactação GZIP transparente e cache de resposta para evitar a rede completamente para solicitações repetidas.
Ele também pode se recuperar de problemas comuns de conexão e, em uma falha de conexão, se um serviço tiver vários endereços IP, ele pode tentar novamente a solicitação para endereços alternativos
Em um nível alto, o cliente foi projetado para bloquear chamadas síncronas e não-bloqueadas.
O OkHttp suporta o Android 2.3 e superior. Para Java, o requisito mínimo é 1.7.
After this brief overview, let’s see some usage examples.
3. Dependência do Maven
Vamos primeiro adicionar a biblioteca como uma dependência empom.xml:
com.squareup.okhttp3
okhttp
3.4.2
Para ver a dependência mais recente desta biblioteca, verifique a página emMaven Central.
4. GET síncrono com OkHttp
Para enviar uma solicitação GET síncrona, precisamos construir um objetoRequest baseado emURLe fazer umCall. Após sua execução, obtemos de volta uma instância deResponse:
@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 assíncrono com OkHttp
Agora, para fazer um GET assíncrono, precisamos enfileirar aCall. UmCallback nos permite ler a resposta quando ela for legível. Isso acontece depois que os cabeçalhos de resposta estão prontos.
A leitura do corpo da resposta ainda pode bloquear. OkHttp não oferece atualmente nenhuma API assíncrona para receber um corpo de resposta em 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. GET com parâmetros de consulta
Finalmente, para adicionar parâmetros de consulta à nossa solicitação GET, podemos aproveitar oHttpUrl.Builder.
Depois que a URL é construída, podemos passá-la para nosso objetoRequest:
@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. Exemplo de pós-solicitação
7.1. POST básico
Neste exemplo simples, construímos umRequestBody para enviar dois parâmetros - “nome de usuário” e “senha” - com a solicitação POST:
@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));
}
7.2. POST com autorização
Neste exemplo, veremos como fazer um POST com credenciais de autenticação básica, além de enviar umString como o corpo da solicitação:
@Test
public void whenSendPostRequestWithAuthorization_thenCorrect()
throws IOException {
String postBody = "test post";
Request request = new Request.Builder()
.url(URL_SECURED_BY_BASIC_AUTHENTICATION)
.addHeader("Authorization", Credentials.basic("test", "test"))
.post(RequestBody.create(
MediaType.parse("text/x-markdown; charset=utf-8"), postBody))
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}
7.3. POST com JSON
Neste exemplo, enviaremos uma solicitação POST comJSON comoRequestBody:
@Test
public void whenPostJson_then:Correct() throws IOException {
String json = "{\"id\":1,\"name\":\"John\"}";
RequestBody body = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"), json);
Request request = new Request.Builder()
.url(BASE_URL + "/users/detail")
.post(body)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}
7.4. Solicitação POST multiparte
Neste exemplo, enviaremos uma solicitação POST multipart. Precisamos construir nossoRequestBody comoMultipartBody para postar umFile, um nome de usuário e uma senha:
@Test
public void whenSendMultipartRequest_thenCorrect()
throws IOException {
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("username", "test")
.addFormDataPart("password", "test")
.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/multipart")
.post(requestBody)
.build();
Call call = client.newCall(request);
Response response = call.execute();
assertThat(response.code(), equalTo(200));
}
8. Upload de arquivo
8.1. Enviar um arquivo
Neste exemplo, veremos como fazer upload de umFile. Faremos o upload do arquivo “test.ext” usandoMultipartBody.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. Obtenha o andamento do upload do arquivo
Finalmente, vamos ver como obter o progresso de um upload deFile. EstenderemosRequestBody para obter visibilidade no processo de upload.
Primeiro, aqui está o método de upload:
@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));
}
Aqui está a interfaceProgressListener que nos permite observar o andamento do upload:
public interface ProgressListener {
void onRequestProgress(long bytesWritten, long contentLength);
}
Aqui está oProgressRequestWrapper, que é a versão estendida deRequestBody:
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, aqui está oCountingSink, que é a versão estendida do EncaminhamentoSink:
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());
}
}
Observe que:
-
Ao estenderForwardingSink para“CountingSink”,, estamos substituindo o método write () para contar os bytes gravados (transferidos)
-
Ao estenderRequestBody para “ProgressRequestWrapper“, estamos substituindo o método writeTo () para usar nosso“ForwardingSink”
9. Configurando um cabeçalho personalizado
9.1. Definir um cabeçalho em uma solicitação
Para definir qualquer cabeçalho personalizado em umRequest, podemos usar uma chamada simplesaddHeader:
@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. Definindo um cabeçalho padrão
Neste exemplo, veremos como configurar um cabeçalho padrão no próprio cliente, em vez de configurá-lo em cada solicitação.
Por exemplo, se quisermos definir um tipo de conteúdo“application/json” para cada solicitação, precisamos definir um interceptor para nosso cliente. Aqui está o método:
@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();
}
E aqui está oDefaultContentTypeInterceptor, que é a versão estendida deInterceptor:
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);
}
}
Observe que o interceptador adiciona o cabeçalho à solicitação original.
10. Não siga redirecionamentos
Neste exemplo, veremos como configurarOkHttpClient para parar de seguir redirecionamentos.
Por padrão, se uma solicitação GET for respondida comHTTP 301 Moved Permanently, o redirecionamento será seguido automaticamente. Em alguns casos de uso, isso pode ser perfeitamente aceitável, mas certamente há casos de uso em que isso não é desejado.
Para atingir esse comportamento, quando construímos nosso cliente, precisamos definirfollowRedirects parafalse.
Observe que a resposta retornará um código de statusHTTP 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));
}
Se ativarmos o redirecionamento com um parâmetrotrue (ou removê-lo completamente), o cliente seguirá o redirecionamento e o teste falhará, pois o código de retorno será um HTTP 200.
11. Timeouts
Use tempos limite para falhar uma chamada quando seu par estiver inacessível. As falhas de rede podem ocorrer devido a problemas de conectividade do cliente, problemas de disponibilidade do servidor ou qualquer outra coisa. O OkHttp suporta tempos limite de conexão, leitura e gravação.
Neste exemplo, construímos nosso cliente com umreadTimeout de 1 segundo, enquanto o URL é servido com 2 segundos de atraso:
@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));
}
Observe que o teste falhará, pois o tempo limite do cliente é menor que o tempo de resposta do recurso.
12. Cancelando uma chamada
UseCall.cancel() para interromper uma chamada em andamento imediatamente. Se um thread estiver escrevendo uma solicitação ou lendo uma resposta, umIOException será lançado.
Use isso para conservar a rede quando uma chamada não for mais necessária; por exemplo, quando seu usuário navega para fora de um aplicativo:
@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. Cache de resposta
Para criar umCache, precisaremos de um diretório de cache no qual possamos ler e gravar e um limite para o tamanho do cache.
O cliente irá usá-lo para armazenar em cache a resposta:
@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);
}
Após o início do teste, a resposta da primeira chamada não será armazenada em cache. Uma chamada para o métodocacheResponse retornaránull, enquanto uma chamada para o métodonetworkResponse retornará a resposta da rede.
Além disso, a pasta de cache será preenchida com os arquivos de cache.
A execução da segunda chamada produzirá o efeito oposto, pois a resposta já terá sido armazenada em cache. Isso significa que uma chamada paranetworkResponse retornaránull, enquanto uma chamada paracacheResponse retornará a resposta do cache.
Para evitar que uma resposta use o cache, useCacheControl.FORCE_NETWORK. Para evitar que ele use a rede, useCacheControl.FORCE_CACHE.
Esteja avisado: se você usarFORCE_CACHE e a resposta exigir a rede,OkHttp retornará uma resposta 504 Solicitação insatisfatória.
14. Conclusão
Neste artigo, vimos vários exemplos de como usar o OkHttp como um cliente HTTP & HTTP / 2.
Como sempre, o código de exemplo pode ser encontrado emGitHub project.