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);
}
댓글 없음:
댓글 쓰기