O que é OkHttp?

OkHttp é um cliente HTTP de Square para aplicativos Java e Android. Ele foi projetado para carregar recursos com mais rapidez e economizar largura de banda. OkHttp é amplamente usado em projetos de código aberto e é a espinha dorsal de bibliotecas como Retrofit , Picasso e muitos outros.

Aqui estão as principais vantagens de usar OkHttp:

  • Suporte HTTP/2 (uso de soquete eficiente)
  • Pool de conexão (reduz a latência da solicitação na ausência de HTTP/2)
  • Compressão GZIP (reduz os tamanhos de download)
  • Cache de resposta (evita buscar novamente os mesmos dados)
  • Recuperação silenciosa de problemas comuns de conexão
  • Detecção de endereço IP alternativo (em ambientes IPv4 e IPv6)
  • Suporte para recursos modernos de TLS (TLS 1.3, ALPN, fixação de certificado)
  • Suporte a chamadas síncronas e assíncronas

Neste guia, cobriremos os fundamentos do OkHttp criando um aplicativo imaginário de lista de tarefas para Android.

Primeiro, vamos definir alguns requisitos funcionais para nosso aplicativo de lista de tarefas. Nossos usuários desejarão ver suas tarefas salvas no servidor de tarefas, salvar uma nova tarefa no servidor e acessar com segurança e exclusivamente suas tarefas.

Como desenvolvedores, queremos depurar facilmente a comunicação de rede de nosso aplicativo e reduzir a carga no servidor.

Pré-requisitos

O OkHttp 4.x estável funciona no Android 5.0+ (API de nível 21+) e Java 8+. Se você precisar de suporte para versões inferiores do Android e Java, ainda pode contar com o branch OkHttp 3.12.x com alguns considerações .

Ao importar o OkHttp, ele também trará duas dependências: Okio , a biblioteca de E/S de alto desempenho e a biblioteca padrão Kotlin . Você não precisa importá-los separadamente.

Para usar OkHttp em seu projeto Android, você precisa importá-lo no arquivo Gradle no nível do aplicativo:

 implementação ("com.squareup.okhttp3: okhttp: 4.9.1")

Não se esqueça de que no Android, você precisa solicitar a permissão INTERNET no arquivo AndroidManifest.xml de seu aplicativo se desejar acessar os recursos de rede:

 

Configurando OkHttp

Para que nossos usuários vejam todas as tarefas salvas no servidor, precisaremos de solicitações GET síncronas e assíncronas, bem como de parâmetros de consulta.

Solicitações GET

Para obter nossa lista de tarefas do servidor, precisamos executar uma solicitação GET HTTP. OkHttp fornece uma boa API por meio de Request.Builder para criar solicitações.

GET síncrono

Fazer uma solicitação GET é tão fácil quanto isto:

 Cliente OkHttpClient=novo OkHttpClient (); Request getRequest=new Request.Builder () .url ("https://mytodoserver.com/todolist") .construir(); tentar { Resposta de resposta=client.newCall (getRequest).execute (); System.out.println (response.body (). String ());
} catch (IOException e) { e.printStackTrace ();
}

Como você pode ver, esta é uma forma síncrona de executar a solicitação com OkHttp. (Você deve executar isso em um thread que não seja da IU, caso contrário, você terá problemas de desempenho em seu aplicativo e o Android lançará um erro .)

GET assíncrono

A versão assíncrona desta solicitação fornece um retorno de chamada quando a resposta foi obtida ou ocorreu um erro.

 Cliente OkHttpClient=novo OkHttpClient (); Request getRequest=new Request.Builder () .url ("https://mytodoserver.com/todolist") .construir(); client.newCall (getRequest).enqueue (new Callback () { @Sobrepor public void onFailure (@NotNull Call call, @NotNull IOException e) { e.printStackTrace (); } @Sobrepor public void onResponse (@NotNull Call call, @NotNull Response response) throws IOException { System.out.println (response.body (). String ()); }
});

Nota : De agora em diante, mostrarei apenas a versão síncrona das chamadas para evitar o uso de toneladas de código clichê. Também tento usar APIs Java padrão sempre que é possível tornar o código reutilizável em ambientes não Android.

Parâmetros de consulta em OkHttp

Você pode passar parâmetros de consulta para sua solicitação, como a implementação de filtragem no lado do servidor para tarefas concluídas ou incompletas.

 Cliente OkHttpClient=novo OkHttpClient (); HttpUrl.Builder queryUrlBuilder=HttpUrl.get ("https://mytodoserver.com/todolist").newBuilder ();
queryUrlBuilder.addQueryParameter ("filter","done"); Solicitar solicitação=novo Request.Builder () .url (queryUrlBuilder.build ()) .construir(); tentar { Resposta de resposta=client.newCall (solicitação).execute (); System.out.println (response.body (). String ());
} catch (IOException e) { e.printStackTrace ();
}

HttpUrl.Builder gerará o URL apropriado com o parâmetro de consulta: https://mytodoserver.com/todolist?filter=done .

Você pode perguntar com razão: “Por que não usar o próprio URL criado manualmente?” Você poderia. Mas, uma vez que sua lógica de construção de URL fica mais complicada (mais parâmetros de consulta), essa classe é útil. Os desenvolvedores da biblioteca têm motivos adicionais para usar HttpUrl .

Solicitação POST

Agora temos todas as tarefas baixadas de nosso servidor. Mas como criar novas tarefas ou marcar uma como concluída? Com uma simples solicitação POST.

Solicitação POST simples

Vamos enviar solicitações POST para nosso endpoint:

 Cliente OkHttpClient=novo OkHttpClient (); RequestBody requestBody=new FormBody.Builder () .add ("novo","Este é meu novo TODO") .construir(); Request postRequest=new Request.Builder () .url ("https://mytodoserver.com/new") .post (requestBody) .construir(); tentar { Resposta de resposta=client.newCall (postRequest).execute (); System.out.println (response.body (). String ());
} catch (IOException e) { e.printStackTrace ();
}

Como você pode ver, o corpo da solicitação POST é um par de dados de valor-chave application/x-www-form-urlencoded . Mas podemos enviar qualquer tipo que quisermos. Aqui está um exemplo com um corpo JSON:

 Cliente OkHttpClient=novo OkHttpClient (); JSONObject jsonObject=novo JSONObject ();
jsonObject.put ("todo_id", 123);
jsonObject.put ("status","concluído"); RequestBody requestJsonBody=RequestBody.create ( jsonObject.toString (), MediaType.parse ("application/json")
); Request postRequest=new Request.Builder () .url ("https://mytodoserver.com/modify") .post (requestJsonBody) .construir(); tentar { Resposta de resposta=client.newCall (postRequest).execute (); System.out.println (response.body (). String ());
} catch (IOException e) { e.printStackTrace ();
}

Upload de arquivo

Também é possível que queiramos anexar um arquivo (como uma imagem) à nossa nova tarefa:

 Cliente OkHttpClient=novo OkHttpClient (); RequestBody requestBody=new MultipartBody.Builder () .addFormDataPart ("new","This is my new TODO") .addFormDataPart ("imagem","anexo.png", RequestBody.create (new File ("path/of/attachment.png"), MediaType.parse ("image/png")) ) .setType (MultipartBody.FORM) .construir(); Request postRequest=new Request.Builder () .url ("https://mytodoserver.com/new") .post (requestBody) .construir(); tentar { Resposta de resposta=client.newCall (postRequest).execute (); System.out.println (response.body (). String ());
} catch (IOException e) { e.printStackTrace ();
}

Semelhante a antes, executamos uma solicitação HTTP multipartes onde podemos anexar o (s) arquivo (s) desejado (s).

Cancelamento de uma solicitação

É possível escolher acidentalmente o anexo errado ao salvar uma tarefa, então, em vez de esperar até que o upload seja concluído, certifique-se de que a solicitação possa ser cancelada a qualquer momento e reiniciada com o valor correto mais tarde.

//mesmo pedido de antes
Request postRequest=new Request.Builder () .url ("https://mytodoserver.com/new") .post (requestBody) .construir(); Chame cancelableCall=client.newCall (postRequest); tentar { Resposta de resposta=cancelableCall.execute (); System.out.println (response.body (). String ());
} catch (IOException e) { e.printStackTrace ();
} //... alguns segundos depois de outro tópico
cancelableCall.cancel ();

Agora temos todo o conhecimento necessário para as funcionalidades básicas de nosso aplicativo. Podemos verificar nossa lista de tarefas, podemos adicionar novas e podemos alterar seu estado.

Vejamos o lado da segurança de nosso aplicativo.

Segurança e autorização em OkHttp

Definindo um cabeçalho HTTP em uma solicitação

Nosso back-end implementou uma autenticação básica baseada em nome de usuário/senha para evitar ver e modificar as tarefas uns dos outros.

Acessar nossos dados agora requer um cabeçalho Authorization para ser definido em nossas solicitações. Sem isso, a solicitação poderia falhar com uma resposta 401 Unauthorized .

 Cliente OkHttpClient=novo OkHttpClient (); Solicitar solicitação=novo Request.Builder () .url ("https://mytodoserver.com/todolist") .addHeader ("Autorização", Credentials.basic ("nome de usuário","senha")) .construir(); tentar { Resposta de resposta=client.newCall (solicitação).execute (); System.out.println (response.body (). String ());
} catch (IOException e) { e.printStackTrace ();
}

O método addHeader () no Request.Builder nos permitirá especificar quantos cabeçalhos como desejamos.

Agora, nossos dados confidenciais só podem ser acessados ​​se alguém souber nosso nome de usuário e senha. Mas e se alguém estiver ouvindo na rede e tentar sequestrar nossas solicitações com um ataque man-in-the-middle e certificados fabricados?

O OkHttp oferece uma maneira fácil de confiar apenas em seu próprio certificado usando o fixador de certificado.

Configurando o pinner de certificado em OkHttp

 OkHttpClient.Builder clientBuilder=new OkHttpClient.Builder ();
clientBuilder.certificatePinner ( novo CertificatePinner.Builder (). add ( "mytodoserver.com","sha256/public_key_hash_of_my_certification" ).construir()
); OkHttpClient client=clientBuilder.build ();

Aqui, usamos OkHttpClient.Builder para construir um cliente OkHttp personalizado (mais sobre isso posteriormente). Então, com CertificatePinner , escolhemos quais certificados para quais domínios específicos são confiáveis.

Para obter mais informações sobre certificado fixação e segurança em geral, visite a página de documentação relevante do OkHttp .

Depurando com OkHttp

Se ocorrer um problema ao fazer uma solicitação, precisamos nos aprofundar para saber por que isso aconteceu. OkHttp tem suas próprias APIs internas para habilitar o registro de depuração , que pode ajudar. Mas também podemos aproveitar a API interceptor do OkHttp para tornar nossa vida mais fácil.

Interceptador

Os interceptores podem monitorar, reescrever e repetir chamadas. Podemos usá-los para modificar uma solicitação antes que ela seja enviada, pré-processar uma resposta antes que ela alcance nossa lógica ou simplesmente imprimir alguns detalhes sobre as solicitações.

OkHttp tem seu próprio interceptor de registro pré-fabricado que podemos apenas importar via Gradle:

 implementação ("com.squareup.okhttp3: logging-interceptor: 4.9.1")

E para usá-lo:

 HttpLoggingInterceptor loggingInterceptor=new HttpLoggingInterceptor ();
loggingInterceptor.setLevel (HttpLoggingInterceptor.Level.BASIC); Cliente OkHttpClient=new OkHttpClient.Builder () .addInterceptor (loggingInterceptor) .construir();

Ou podemos implementar nosso próprio interceptor personalizado:

 classe estática BasicLoggingInterceptor implementa Interceptor { @Não nulo @Sobrepor Interceptação de resposta pública (Interceptor.Chain chain) throws IOException { Solicitação de solicitação=chain.request (); System.out.println (String.format ("Enviando solicitação% s em% s% n% s", request.url (), chain.connection (), request.headers ())); Resposta de resposta=cadeia.processo (solicitação); System.out.println (String.format ("Resposta recebida para% s% n% s", response.request (). url (), response.headers ())); resposta de retorno; }
} //...
//uso mais tarde
Cliente OkHttpClient=new OkHttpClient.Builder () .addInterceptor (new BasicLoggingInterceptor ()) .construir();

Também podemos declarar nossos interceptores no aplicativo e no nível da rede com base em nossas necessidades. Você pode ler mais sobre isso aqui .

Proxy

Às vezes, é útil manipular as respostas de nossa API de back-end. Podemos conseguir isso manipulando o código do lado do servidor, mas é mais eficiente por meio de um servidor proxy.

Podemos usar uma configuração de proxy para todo o sistema no próprio dispositivo ou instruir nosso cliente OkHttp a usar um internamente.

 Proxy proxyServerOnLocalNetwork=novo Proxy ( Proxy.Type.HTTP, new InetSocketAddress ("192.168.1.100", 8080)//o proxy local
); Cliente OkHttpClient=new OkHttpClient.Builder () .proxy (proxyServerOnLocalNetwork) .construir();

Cache em OkHttp

Depois de depurar nosso aplicativo, você deve ter percebido que completamos várias solicitações desnecessárias que colocam uma carga extra em nosso servidor. Não há necessidade de buscar a lista de tarefas novamente se não houver alteração no back-end.

Há uma implementação de cache padrão em OkHttp onde só precisamos para especificar a localização do cache e seu tamanho, como:

 OkHttpClient client=new OkHttpClient.Builder () .cache (new Cache (new File ("/local/cacheDirectory"), 10 * 1024 * 1024))//10 MB .construir();

Mas você pode ficar louco com isso se quiser personalizar o comportamento .

Se você tem uma lógica de cache personalizada, também pode implementar sua própria maneira de fazer cache. Por exemplo, você pode executar uma solicitação HEAD primeiro para o seu servidor, em seguida, verificar os cabeçalhos de indicação de cache e, se houver uma alteração, executar uma solicitação GET para o mesmo URL para buscar o conteúdo.

Configuração OkHttp

Já cobrimos algum uso do OkHttpClient.Builder . Esta classe é útil se quisermos alterar o comportamento padrão do cliente OkHttp.

Existem alguns parâmetros que vale a pena mencionar:

 OkHttpClient client=new OkHttpClient.Builder () .cache (cache)//configure o cache, veja acima .proxy (proxy)//configure o proxy, veja acima .certificatePinner (certificatePinner)//fixação de certificado, veja acima .addInterceptor (interceptor)//interceptor de nível de aplicativo, veja acima .addNetworkInterceptor (interceptor)//interceptor de nível de rede, veja acima .authenticator (autenticador)//autenticador para solicitações (suporta casos de uso semelhantes como"Cabeçalho de autorização"anterior .callTimeout (10000)//tempo limite padrão para chamadas completas .readTimeout (10000)//tempo limite de leitura padrão para novas conexões .writeTimeout (10000)//tempo limite de gravação padrão para novas conexões .dns (dns)//Serviço DNS usado para pesquisar endereços IP para nomes de host .followRedirects (true)//seguir redirecionamentos de solicitações .followSslRedirects (true)//seguir redirecionamentos HTTP tp HTTPS .connectionPool (connectionPool)//pool de conexão usado para reciclar conexões HTTP e HTTPS .retryOnConnectionFailure (true)//tentar novamente ou não quando for encontrado um problema de conectividade .cookieJar (cookieJar)//gerenciador de cookies .dispatcher (despachante)//despachante usado para definir a política e executar solicitações assíncronas .construir();

Para obter a lista completa, visite documentação .

WebSocket

Está pensando em uma lista de tarefas colaborativa? Ou notificando os usuários assim que uma nova tarefa for adicionada? Que tal ter um bate-papo em tempo real sobre uma tarefa? OkHttp também cobre você aqui.

Se você concluiu a implementação do lado do servidor WebSocket, pode se conectar a esse endpoint e obter mensagens em tempo real ativas e em execução a partir de um cliente OkHttp.

 Cliente OkHttpClient=novo OkHttpClient (); String socketServerUrl="ws://mytodoserver.com/realtime";
Solicitação de solicitação=novo Request.Builder (). Url (socketServerUrl).build (); //conectando-se a um soquete e recebendo mensagens
client.newWebSocket (request, new WebSocketListener () { @Sobrepor public void onClosed (@NotNull WebSocket webSocket, int code, @NotNull String reason) { super.onClosed (webSocket, código, razão); //TODO: implemente seu próprio tratamento de eventos } @Sobrepor public void onClosing (@NotNull WebSocket webSocket, código int, @NotNull String reason) { super.onClosing (webSocket, código, motivo); //TODO: implemente seu próprio tratamento de eventos } @Sobrepor public void onFailure (@NotNull WebSocket webSocket, @NotNull Throwable t, @Nullable Response response) { super.onFailure (webSocket, t, resposta); //TODO: implemente seu próprio tratamento de eventos } @Sobrepor public void onMessage (@NotNull WebSocket webSocket, @NotNull String text) { super.onMessage (webSocket, texto); //TODO: implemente seu próprio tratamento de eventos para mensagens recebidas } @Sobrepor public void onMessage (@NotNull WebSocket webSocket, @NotNull ByteString bytes) { super.onMessage (webSocket, bytes); //TODO: implemente seu próprio tratamento de eventos para mensagens recebidas } @Sobrepor public void onOpen (@NotNull WebSocket webSocket, @NotNull Response response) { super.onOpen (webSocket, resposta); //TODO: implemente seu próprio tratamento de eventos }
}); //enviando mensagem
webSocket.send ("new_todo_added");

Teste

Não podemos nos esquecer dos testes. OkHttp oferece seu próprio MockWebServer para ajudar a testar a rede HTTP e HTTPS chamadas. Ele nos permite especificar qual resposta retornar para qual solicitação e verificar todas as partes dessa solicitação.

Para começar, precisamos importá-lo via Gradle:

 testImplementation ("com.squareup.okhttp3: mockwebserver: 4.9.1")

Aqui estão algumas APIs importantes:

  • MockWebServer.start () : inicia o servidor da web simulado no host local
  • MockWebServer.enqueue (mockResponse) : enfileira um MockResponse . Esta é uma fila FIFO que garante que as solicitações receberão respostas na ordem em que foram colocadas na fila
  • MockResponse : uma resposta OkHttp programável
  • RecordRequest : uma solicitação HTTP que foi recebida pelo MockWebServer
  • MockWebServer.takeRequest () : leva a próxima solicitação recebida ao MockWebServer

Assim que entendermos o básico, podemos escrever nosso primeiro teste. Agora, para uma solicitação GET básica:

 public class MockWebServerTest { servidor final MockWebServer=novo MockWebServer (); cliente OkHttpClient final=novo OkHttpClient (); @Teste public void getRequest_Test () lança Exception { final String jsonBody="{'todo_id':'1'}"; //configurar um MockResponse para a primeira solicitação server.enqueue ( novo MockResponse () .setBody (jsonBody) .addHeader ("Content-Type","application/json") ); //inicie o MockWebServer server.start (); //crie uma solicitação direcionada ao MockWebServer Solicitar solicitação=novo Request.Builder () .url (server.url ("/")) .header ("User-Agent","MockWebServerTest") .construir(); //faça a solicitação com OkHttp Call call=client.newCall (pedido); Resposta de resposta=call.execute (); //verificar a resposta assertEquals (200, response.code ()); assertTrue (response.isSuccessful ()); assertEquals ("application/json", response.header ("Content-Type")); assertEquals (jsonBody, response.body (). string ()); //verifique a solicitação de entrada no lado do servidor RecordedRequest savedRequest=server.takeRequest (); assertEquals ("GET", recordsRequest.getMethod ()); assertEquals ("MockWebServerTest", savedRequest.getHeader ("User-Agent")); assertEquals (server.url ("/"), savedRequest.getRequestUrl ()); }
}

Conclusão

Resumindo, OkHttp é uma biblioteca poderosa que oferece muitas vantagens, incluindo suporte a HTTP/2, mecanismo de recuperação de problemas de conexão, armazenamento em cache e suporte a TLS moderno.

Se você já tentou implementar essas funcionalidades do zero por meio das APIs de rede padrão do Android e Java, você sabe quanto trabalho e dor é (e quantos casos extremos que você se esqueceu de cobrir). Felizmente, implementar a rede em seu aplicativo com OkHttp torna isso mais fácil.

Para obter mais detalhes, visite a página do projeto e GitHub . Você pode encontrar algumas extensões úteis, exemplos de implementação e exemplos de teste.

A postagem Um guia completo para OkHttp apareceu primeiro em LogRocket Blog .