OkHttp 가이드

1. 소개

이 기사에서는 다양한 유형의 HTTP 요청 전송, HTTP 응답 수신 및 해석 , OkHttp를 사용하여 클라이언트를 구성하는 방법에 대한 기본 사항을 보여줍니다 .

또한 사용자 지정 헤더, 시간 제한, 응답 캐싱 등을 사용하여 클라이언트를 구성하는 고급 사용 사례에 대해 살펴 보겠습니다.

2. OkHttp 개요

OkHttp는 Android 및 Java 애플리케이션을위한 효율적인 HTTP 및 HTTP / 2 클라이언트입니다.

연결 풀링 (HTTP / 2를 사용할 수없는 경우), 투명한 GZIP 압축 및 응답 캐싱과 같은 고급 기능이 함께 제공되어 반복되는 요청에 대해 네트워크를 완전히 피할 수 있습니다.

또한 일반적인 연결 문제로부터 복구 할 수 있으며, 연결 실패시 서비스에 여러 IP 주소가있는 경우 대체 주소로 요청을 재 시도 할 수 있습니다.

높은 수준에서 클라이언트는 동기 및 비 차단 비동기 호출을 모두 차단하도록 설계되었습니다.

OkHttp는 Android 2.3 이상을 지원합니다. Java의 경우 최소 요구 사항은 1.7입니다.

이 간단한 개요 후에 몇 가지 사용 예를 살펴 보겠습니다.

3. Maven 종속성

먼저 라이브러리를 pom.xml에 종속성으로 추가해 보겠습니다 .

 com.squareup.okhttp3 okhttp 3.4.2 

이 라이브러리의 최신 종속성을 보려면 Maven Central의 페이지를 확인하십시오.

4. OkHttp를 사용한 동기식 GET

동기식 GET 요청을 보내려면 URL을 기반으로 Request 객체를 만들고 Call을해야 합니다. 실행 후 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. OkHttp를 사용한 비동기 GET

이제 비동기 GET을 만들려면 Call 을 대기열에 넣어야합니다 . 콜백 이 읽을 때 응답을 읽을 수있게 해준다. 이것은 응답 헤더가 준비된 후에 발생합니다.

응답 본문 읽기는 여전히 차단 될 수 있습니다. 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

마지막으로 GET 요청에 쿼리 매개 변수를 추가하기 위해 HttpUrl.Builder를 활용할 수 있습니다 .

URL이 빌드 된 후 요청 객체에 전달할 수 있습니다 .

@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 요청

"username""password" 매개 변수를 전송 하는 RequestBody 를 빌드하는 간단한 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)); }

우리의 기사 OkHttp를 사용한 Post Requests에 대한 Quick Guide에는 OkHttp를 사용한 POST 요청의 더 많은 예제가 있습니다.

8. 파일 업로드

8.1. 파일을 올리다

이 예에서는 File 을 업로드하는 방법을 살펴 보겠습니다 . MultipartBody.Builder를 사용하여 “ test.ext” 파일을 업로드합니다 .

@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. 파일 업로드 진행률 가져 오기

마지막으로 파일 업로드 진행률을 확인하는 방법을 살펴 보겠습니다 . RequestBody 를 확장 하여 업로드 프로세스에 대한 가시성을 확보 할 것입니다.

먼저 업로드 방법은 다음과 같습니다.

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

RequestBody 의 확장 버전 인 ProgressRequestWrapper 는 다음과 같습니다 .

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

마지막으로 다음은 Forwarding Sink 의 확장 버전 인 CountingSink 입니다 .

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

참고 :

  • ForwardingSink"CountingSink"로 확장 할 때 write () 메서드를 재정 의하여 기록 된 (전송 된) 바이트 수를 계산합니다.
  • RequestBody 를 " ProgressRequestWrapper "로 확장 할 때 "ForwardingSink" 를 사용하도록 writeTo () 메서드를 재정의합니다.

9. 사용자 정의 헤더 설정

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.

항상 그렇듯이 예제 코드는 GitHub 프로젝트에서 찾을 수 있습니다.