2015년 9월 22일 화요일

OkHttp 이해하기 - 3

Request를 만들 때 body를 넣어주고 싶으면 RequestBody를 먼저 만들고 이것을 Request에 넣어주어야 합니다.
기억을 더듬어 보면 method 관련 함수인 post와 delete와 put과 patch를 설정해 줄 때 RequestBody를 같이 설정해 줄 수 있게 되어 있는 것이 기억나실 겁니다. 이 이외의 방법은 없죠.
그런데 RequestBody는 abstract로 다음과 같이 선언되어 있습니다.
public abstract class RequestBody {
  public abstract MediaType contentType();
  public long contentLength() throws IOException {
    return -1;
  }
  public abstract void writeTo(BufferedSink sink) throws IOException;
}

이런 우리가 직접 RequestBody를 생성할 수 없게 되어 있네요. 그럼 어떻게 해야 할까요? 그래서 RequestBody를 생성해주는 static함수들이 정의되어 있습니다. 정의를 보면 이 함수들은 content type과 content를 파라메타로 받아서 이것을 가지고 RequestBody를 생성하게 되어 있습니다. 대부분 비슷하므로 대표로 하나만 살펴보겠습니다.
public static RequestBody create(MediaType contentType, String content) {
  Charset charset = Util.UTF_8;
  if (contentType != null) {
    // content type이 설정되어 있는데 charset이 없으면 UTF-8로 설정해 줍니다.
    charset = contentType.charset();
    if (charset == null) {
      charset = Util.UTF_8;
      contentType = MediaType.parse(contentType + "; charset=utf-8");
    }
  }
  byte[] bytes = content.getBytes(charset);
  return create(contentType, bytes);
}

public static RequestBody create(final MediaType contentType, final byte[] content) {
  return create(contentType, content, 0, content.length);
}

content가 null이면 NullPointerException을 발생시키고요, abstract인 RequestBody를 바로 간단하게 생성해 줍니다.
public static RequestBody create(final MediaType contentType, final byte[] content,
    final int offset, final int byteCount) {
  if (content == null) throw new NullPointerException("content == null");
  // content의 내용중 offset에서부터 byteCount까지를 body에 넣어주니까 offset에서부터 byteCount까지가 content의 length안에 있어야 합니다. 아니면 ArrayIndexOutOfBoundsException을 발생시키는 함수입니다.
  Util.checkOffsetAndCount(content.length, offset, byteCount);
  return new RequestBody() {
    @Override public MediaType contentType() {
      return contentType;
    }

    @Override public long contentLength() {
      return byteCount;
    }

    @Override public void writeTo(BufferedSink sink) throws IOException {
      sink.write(content, offset, byteCount);
    }
  };
}

MediaType은 RFC 2045의 구현입니다. 간단히 설명하면, media type은 type과 subtype과 0개 이상의 옵션 파라메터로 이루어져 있습니다. 예를 들면 text/html; charset=UTF-8 의 경우 type은 text이고 subtype은 html이고 charset=UTF-8은 character encoding을 알려주는 옵션 파라메터입니다. MediaType은 우리가 직접 MediaType을 생성하지 않고 쉽게 생성할 수 있는 static 함수를 제공합니다. 주어진 string을 Pattern을 사용해서 분리해 냅니다.
public static MediaType parse(String string) {
  // string으로부터 type과 subtype을 찾습니다.
  Matcher typeSubtype = TYPE_SUBTYPE.matcher(string);
  // 패턴이 맞지 않으면 null을 리턴합니다.
  if (!typeSubtype.lookingAt()) return null;
  // group은 Pattern에 regular expression을 넘겨줄 때 ()에 의해 둘러싸이는 부분을 의미합니다. TYPE_SUBTYPE의 경우는 앞의 TOKEN이 group(1)이고 뒤의 TOKEN의 group(2)가 되겠네요.
  String type = typeSubtype.group(1).toLowerCase(Locale.US);
  String subtype = typeSubtype.group(2).toLowerCase(Locale.US);

  // 옵션 파라메터에서 charset를 찾습니다.
  String charset = null;
  // group에서 (?는 제외됩니다. 따라서 TOKEN, TOKEN, QUOTED의 순으로 group이 지정되게 됩니다.
  Matcher parameter = PARAMETER.matcher(string);
  for (int s = typeSubtype.end(); s < string.length(); s = parameter.end()) {
    parameter.region(s, string.length());
    if (!parameter.lookingAt()) return null; // This is not a well-formed media type.

    String name = parameter.group(1);
    if (name == null || !name.equalsIgnoreCase("charset")) continue;
    String charsetParameter = parameter.group(2) != null
        ? parameter.group(2)  // Value is a token.
        : parameter.group(3); // Value is a quoted string.
    // charset은 하나만 설정할 수 있습니다. 여러개인 경우 IllegalArgumentException을 발생시킵니다.
    if (charset != null && !charsetParameter.equalsIgnoreCase(charset)) {
      throw new IllegalArgumentException("Multiple different charsets: " + string);
    }
    charset = charsetParameter;
  }

  return new MediaType(string, type, subtype, charset);
}

댓글 없음:

댓글 쓰기

Building asynchronous views in SwiftUI 정리

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