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에 의존성으로 추가해 보겠습니다 .
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.1</version>
</dependency>
이 라이브러리의 최신 의존성을 보려면 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 을 대기열에 넣어야합니다 . 콜백 이 읽을 때 응답을 읽을 수있게 해준다. 이것은 응답 헤더가 준비된 후에 발생합니다.
Response body 읽기는 여전히 차단 될 수 있습니다. OkHttp는 현재 Response body을 부분적으로 수신하는 비동기 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. 요청에 헤더 설정
요청 에 사용자 정의 헤더를 설정하려면 간단한 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();
}
그리고 여기 에 Interceptor 의 확장 버전 인 DefaultContentTypeInterceptor 가 있습니다 .
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 초의 readTimeout 으로 클라이언트를 빌드하고 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. 응답 캐싱
Cache 를 만들려면 읽고 쓸 수있는 캐시 디렉토리와 캐시 크기 제한이 필요합니다.
클라이언트는이를 사용하여 응답을 캐시합니다.
@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);
}
테스트를 시작한 후 첫 번째 호출의 응답은 캐시되지 않습니다. 메소드에 대한 호출 의 cacheResponse는 반환 널이 메소드를 호출하면서 networkResponse는 네트워크로부터의 응답을 반환한다.
또한 캐시 폴더는 캐시 파일로 채워집니다.
두 번째 호출 실행은 응답이 이미 캐시되었으므로 반대 효과를 생성합니다. 즉, networkResponse에 대한 호출 은 null 을 반환하고 cacheResponse에 대한 호출 은 캐시에서 응답을 반환합니다.
응답이 캐시를 사용하지 않도록하려면 CacheControl.FORCE_NETWORK 를 사용하십시오 . 네트워크 사용을 방지하려면 CacheControl.FORCE_CACHE 를 사용하십시오 .
경고 : FORCE_CACHE 를 사용 하고 응답에 네트워크가 필요한 경우 OkHttp 는 504 Unsatisfiable Request 응답을 반환합니다.
14. 결론
이 기사에서는 OkHttp를 HTTP 및 HTTP / 2 클라이언트로 사용하는 방법에 대한 몇 가지 예를 살펴 보았습니다.
항상 그렇듯이 예제 코드는 GitHub 프로젝트 에서 찾을 수 있습니다 .
- https://docs.spring.io/spring-framework/docs/current/reference/html
- https://www.baeldung.com/guide-to-okhttp
'Java' 카테고리의 다른 글
Java에서 문자열에 개행 문자(줄 바꿈) 추가 (0) | 2021.03.11 |
---|---|
Java로 파일을 읽는 방법 (0) | 2021.03.11 |
Keycloak과 함께 사용자 지정 사용자 공급자 사용 (0) | 2021.03.11 |
Spring에서 캐싱 사용방법 (0) | 2021.03.10 |
Java 8의 기능 인터페이스 (0) | 2021.03.10 |