2015년 9월 22일 화요일

OkHttp 이해하기 - 1

OkHttp를 사용하여 http 요청을 하려면 가장 먼저 해야할 일이 Request를 만드는 것입니다. 이 Request를 만들기 위해 Request.Builder를 제공합니다.

public final class Request {
  ...
  // Request를 만들기 위한 Builder 제공
  public static class Builder {
    ...
  }
}

그럼 Builder를 살펴봅시다.
Builder를 생성할 때 접속할 URL, 접속할 때 사용할 헤더들, 요청 바디 그리고 나중에 요청을 취소할 때 사용할 tag를 주어서 생성할 수 있습니다. 이것들을 하나하나 알아보겠습니다.
접속할 URL은 HttpUrl이나 String 또는 URL을 통해서 넣어줄 수 있습니다. 구현은 아래와 같습니다.

public Builder url(HttpUrl url) {
  if (url == null) throw new IllegalArgumentException("url == null");
  this.url = url;
  return this;
}
HttpUrl을 쓰는 경우에는 간단하네요. 그냥 내부 변수에 그대로 가지고 있게 됩니다.
url 스트링을 그대로 넘겨주는 경우에는 아래와 같이 HttpUrl로 변환하는 과정을 거치게 됩니다.
public Builder url(String url) {
  if (url == null) throw new IllegalArgumentException("url == null");

  // 웹소켓 url인지를 확인한 후 http url로 변경해 줍니다.
  if (url.regionMatches(true, 0, "ws:", 0, 3)) {
    url = "http:" + url.substring(3);
  } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
    url = "https:" + url.substring(4);
  }
  // string을 HttpUrl로 변환합니다. HttpUrl이 알아서 변환해줍니다.
  HttpUrl parsed = HttpUrl.parse(url);
  if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
  return url(parsed);
}
URL의 HttpUrl로의 변환도 간단하네요. HttpUrl이 알아서 해줍니다.
public Builder url(URL url) {
  if (url == null) throw new IllegalArgumentException("url == null");
  HttpUrl parsed = HttpUrl.get(url);
  if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
  return url(parsed);
}

다음으로는 헤더을 포함시킬 수 있습니다.
(헤더는 Headers라는 클래스로 구현되어 있습니다. 이에 대한 내용은 나중에 살펴봅니다.)

헤더가 중복이 되지 않게 추가하고 싶으면 아래의 함수를 사용합니다.
public Builder header(String name, String value) {
  headers.set(name, value);
  return this;
}
기존에 같은 이름의 헤더가 있어도 중복해서 추가하고 싶으면 아래의 함수를 사용합니다.
public Builder addHeader(String name, String value) {
  headers.add(name, value);
  return this;
}
특정 이름의 헤더를 지우고 싶으면 아래의 함수를 사용합니다.
public Builder removeHeader(String name) {
  headers.removeAll(name);
  return this;
}
기존의 모든 헤더를 지우고 새로운 헤더의 집합으로 바꾸고 싶으면 아래의 함수를 사용합니다.
public Builder headers(Headers headers) {
  this.headers = headers.newBuilder();
  return this;
}
헤더에 "Cache-Control"을 위한 값을 설정할 수 있는 함수는 따로 있습니다.
만약 cacheControl에 비어있는 스트링을 주면 기존의 "Cache-Control"값을 제거하고 값이 있으면 그 값을 설정하는 함수입니다.
public Builder cacheControl(CacheControl cacheControl) {
  String value = cacheControl.toString();
  if (value.isEmpty()) return removeHeader("Cache-Control");
  return header("Cache-Control", value);
}

Builder의 기본 method는 "GET"으로 되어 있습니다. 이를 바꾸고 싶으면 다음의 함수들을 사용합니다.
public Builder get() {
  return method("GET", null);
}

public Builder head() {
  return method("HEAD", null);
}

public Builder post(RequestBody body) {
  return method("POST", body);
}

public Builder delete(RequestBody body) {
  return method("DELETE", body);
}

public Builder delete() {
  return delete(RequestBody.create(null, new byte[0]));
}

public Builder put(RequestBody body) {
  return method("PUT", body);
}

public Builder patch(RequestBody body) {
  return method("PATCH", body);
}

이들이 호출해주는 method함수는 다음과 같습니다. 설정한 method 값에 따라 body가 반드시 있어야 하는지 아니면 반드시 없어야 하는지 등을 확인하고 문제가 없으면 method와 body를 설정을 해줍니다.
public Builder method(String method, RequestBody body) {
  if (method == null || method.length() == 0) {
    throw new IllegalArgumentException("method == null || method.length() == 0");
  }
  if (body != null && !HttpMethod.permitsRequestBody(method)) {
    throw new IllegalArgumentException("method " + method + " must not have a request body.");
  }
  if (body == null && HttpMethod.requiresRequestBody(method)) {
    throw new IllegalArgumentException("method " + method + " must have a request body.");
  }
  this.method = method;
  this.body = body;
  return this;
}
method와 body와의 관계는 아래와 같이 확인합니다.
POST와 PUT과 PATCH는 body가 반드시 있어야 하구요.
public static boolean requiresRequestBody(String method) {
  return method.equals("POST")
      || method.equals("PUT")
      || method.equals("PATCH");
}
POST와 PUT과 PATCH와 DELETE를 제외한 나머지는 body가 없어야 합니다. DELETE는 body가 있을 수도 있고 없을 수도 있
public static boolean permitsRequestBody(String method) {
  return requiresRequestBody(method)
      || method.equals("DELETE"); // Permitted as spec is ambiguous.
}

tag 설정은 아래와 같이 간단합니다.
public Builder tag(Object tag) {
  this.tag = tag;
  return this;
}
앞에서 tag는 요청을 취소할 때 사용하기 위한 값이라고 했는데요. 만약에 이 설정을 안해주면 Request 자체를 사용해서 요청을 취소할 수도 있습니다.

이와같이 Request에 필요한 값들을 설정한 뒤에는 build() 함수를 호출해 주면 됩니다.
public Request build() {
  if (url == null) throw new IllegalStateException("url == null");
  return new Request(this);
}
Request class는 이 Builder를 받아서 필요한 값들을 설정해하고 나중에 필요할 때 이 값들을 꺼낼 수 있는 getter를 제공합니다. 이 함수들은 간단하니 따로 살펴보지는 않겠습니다.
Request에서는 HttpUrl의 형태로 url을 저장하고 있는데요. 이것을 URL이나 URI로 내보내주는 함수를 제공하고 있습니다. 모두 처음 요청시 url로부터 값을 만들어서 저장해놓고 두번째 요청부터는 저장해 놓은 값을 사용하는 lazily initialized의 형태를 취하고 있습니다.
public URL url() {
  URL result = javaNetUrl;
  return result != null ? result : (javaNetUrl = url.url());
}

public URI uri() throws IOException {
  try {
    URI result = javaNetUri;
    return result != null ? result : (javaNetUri = url.uri());
  } catch (IllegalStateException e) {
    throw new IOException(e.getMessage());
  }
}
cache control의 경우도 마찬가지로 구현되어 있는 것을 아래의 함수로 보실 수 있습니다.
public CacheControl cacheControl() {
  CacheControl result = cacheControl;
  return result != null ? result : (cacheControl = CacheControl.parse(headers));
}
여기에 추가로 Request class는 거꾸로 Builder로 내보낼 수 있는 함수를 제공합니다. 이를 통해 Request와 Builder간에 서로 편하게 변환이 가능하게 됩니다.
public Builder newBuilder() {
  return new Builder(this);
}

마지막으로 알아야 할 것은 Builder를 만들때 잘못된 값을 넣어주는 경우에는 모두 IllegalArgumentException을 발생하게 되어 있습니다. 이에 대한 catch가 필요할 수도 있겠네요.

정리해보면 Builder를 통해 HTTP 요청에 필요한 Request를 만들어 줍니다. Builder에 설정해주는 값은 url, method, headers, body, tag의 다섯가지가 있습니다.
headers의 타입인 Headers와 body의 타입인 RequestBody는 다음에 살펴보겠습니다.

댓글 없음:

댓글 쓰기

Building asynchronous views in SwiftUI 정리

Handling loading states within SwiftUI views self loading views View model 사용하기 Combine을 사용한 AnyPublisher Making SwiftUI views refreshable r...