사실 문자열처리일 뿐이라서 크게 특별히 정리할 부분은 없지만 굳이 처리를 할 때 가장 신경을 쓴 부분은 객체를 최대한 잘게 쪼개었다는 것이다. 최대한 작게 쪼고 필요한 validation 이나 로직이 있는 경우 그 객체에 해당 책임을 부여하여 처리하도록 했다.
그래서 requestLine(start line) 도 따로 객체로 분류를 했고 이를 구성하는 객체들도 실제 HTTP 메세지 구조에 기반하여 HttpMethod, UrlPath, Protocol 으로 쪼개서 구성했다. 사실 너무 당연한 이야기다.
public class RequestLine {
private static final List<String> RESOURCE_FILE_EXTENSIONS = List.of(".css", ".js", ".ico", "ttf", "woff", "png");
private final HttpMethod httpMethod;
private final UrlPath urlPath;
private final Protocol protocol;
....
}
테스트 코드에서 확인할 수 있듯이 가장 핵심이 되는 객체는 HttpParser 이다.
public class HttpParser {
public static final String REQUEST_LINE_SEPARATOR = " ";
public static final String PATH_SEPARATOR = "\\?";
public static final String QUERY_PARAMETER_SEPARATOR = "&";
public static final String QUERY_PARAMETER_KEY_VALUE_SEPARATOR = "=";
public static RequestLine parseRequestLine(String requestLine) {
String[] requestLineData = requestLine.split(REQUEST_LINE_SEPARATOR);
HttpMethod httpMethod = HttpMethod.find(requestLineData[0]);
UrlPath path = getPath(requestLineData);
Protocol protocol = getProtocol(requestLineData);
return new RequestLine(httpMethod, path, protocol);
}
public static Map<String, String> convertStringToMap(String content) {
String[] parameterKeyAndValues = content.split(HttpParser.QUERY_PARAMETER_SEPARATOR);
Map<String, String> parameters = new HashMap<>();
Arrays.stream(parameterKeyAndValues).forEach(parameter -> {
String[] keyAndValue = parameter.split(HttpParser.QUERY_PARAMETER_KEY_VALUE_SEPARATOR);
String parameterKey = keyAndValue[0];
String parameterValue = keyAndValue[1];
parameters.put(parameterKey, parameterValue);
});
return parameters;
}
private static UrlPath getPath(String[] requestLineData) {
if (!requestLineData[1].contains("?")) {
String path = requestLineData[1];
return new UrlPath(path);
}
String path = requestLineData[1].split(PATH_SEPARATOR)[0];
String queryString = requestLineData[1].split(PATH_SEPARATOR)[1];
return new UrlPath(path, new QueryParameter(queryString));
}
private static Protocol getProtocol(String[] requestLineData) {
String protocol = requestLineData[2];
if (!StringUtils.hasText(protocol)) {
throw new IllegalArgumentException();
}
return Protocol.find(protocol);
}
}
RequestLine parseRequestLine(String requestLine) 에서 보면 RequestLine 을 구성하는 각 객체들을 파라미터로 받은 requestLine 에서 분리하여 파싱해서 각각 생성한뒤 최종적으로 RequestLine 객체를 만들고 있다.
이 미션의 핵심은 모든 객체를 논리적으로 인식할 수 있는 최대한의 작고 의미있는 단위로 쪼개고 쪼개어서 각각을 정의해주고 책임을 분산시켜줬다는 것이 핵심이다. 그리고 추후 손수 만들 프레임워크가 HTTP 메세지를 읽고 이를 파싱할 때 이러한 로직으로 할 것이라는 것을 미리 만들었다는 것에 부수적인 의미가 있다고 봤다.