[wooteco] 톰캣 구현
Application Container 간이식 컨테이너 생성
package support;
import java.util.HashMap;
import java.util.Map;
/**
* static 유틸 클래스로 생성할 수 없는 객체는 이곳에 저장하여 싱글톤으로 관리합니다.
*/
public class ApplicationContainer {
private final Map<Class, Object> container = new HashMap<>();
public ApplicationContainer() {
container.put(FileUtils.class, new FileUtils());
}
public <T> T getSingletonObject(Class<T> clazz) {
return (T) container.get(clazz);
}
}
HttpResponseBuilder
final String response = HttpResponseBuilderOld.builder()
.addStatus(HttpStatus.FOUND)
.add(HttpHeader.LOCATION, "/401.html")
.build();
public class HttpResponseBuilder {
private final LinkedList<String> responseHeaders = new LinkedList<>();
private boolean hasBody = false;
private HttpResponseBuilder() {
}
public static HttpResponseBuilder builder() {
return new HttpResponseBuilder();
}
public HttpResponseBuilder add(HttpHeader header, Object value) {
final String responseHeader = header.apply(value.toString());
responseHeaders.add(responseHeader);
return this;
}
public HttpResponseBuilder add(HttpHeader header, String value) {
final String responseHeader = header.apply(value);
responseHeaders.add(responseHeader);
return this;
}
public HttpResponseBuilder add(String header, String value) {
responseHeaders.add(String.format("%s %s ", header, value));
return this;
}
public HttpResponseBuilder addStatus(HttpStatus statusCode) {
final String responseHeader = HTTP_1_1_STATUS.apply(statusCode);
responseHeaders.add(responseHeader);
return this;
}
public HttpResponseBuilder addCooke() {
final String responseHeader = HttpHeader.SET_COOKIE.apply(new HttpCookie());
responseHeaders.add(responseHeader);
return this;
}
public HttpResponseBuilder addCooke(final Session session) {
final String responseHeader = HttpHeader.SET_COOKIE.apply(session);
responseHeaders.add(responseHeader);
return this;
}
/**
* 아래와 같은 Body를 포함한 문자열이 추가됩니다. <p />
* "Content-Length: 123<p />
* <p />
* This is Body"
*/
public HttpResponseBuilderOld body(String body) {
add(HttpHeader.CONTENT_LENGTH, body.getBytes().length);
responseHeaders.add("");
responseHeaders.add(body);
hasBody = true;
return this;
}
public String build() {
if (!hasBody) {
responseHeaders.add("");
}
return String.join("\r\n", responseHeaders);
}
}
uri 정보와 메서드 정보만으로 support 메서드 동적으로 만들기 (실패)
public class LoginController extends AbstractController {
@Override
public String uri() {
return "/login";
}
public boolean support(String uri) {
return true;
}
protected void doGet(HttpRequest request, HttpResponse response) throws Exception {
}
}
위와 같은 정보 만으로 uri 와 Get, Post 정보 요청 유무에 따라 support(지원 유무)를 만드려고 하였으나… 실패하였다
private static void scanPackageInternal(final String packagePath)
throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
final Reflections reflections = new Reflections(packagePath);
classes = reflections.get(Scanners.SubTypes.of(AbstractController.class).asClass());
for (final Class<?> clazz : classes) {
final AbstractController controller = (AbstractController) clazz.getDeclaredConstructor().newInstance();
controllers.add(controller);
}
for (final AbstractController controller : controllers) {
final String uri = (String) controller.getClass().getDeclaredMethod("uri").invoke(controller);
List<String> httpMethods =
Arrays.stream(controller.getClass().getDeclaredMethods())
.map(it -> it.getName())
.filter(it -> it.startsWith("do"))
.map(it -> it.replaceAll("do", ""))
.map(it -> it.toUpperCase())
.collect(Collectors.toList());
classMap.put((u, ms) -> uri.contains(u) && httpMethods.contains(ms), controller);
}
System.out.println("classMap = " + classMap);
}
public static Controller findFromUri(final String uri, final String httpMethod) {
final AbstractController controller = classMap.get(uri, httpMethod); // 이부분이 문제... key에 FunctionanlInterface가 들어가면 곤란하다!!
if (controller == null) {
throw new IllegalArgumentException("해당 URI 가 존재하지 않습니다");
}
return controller;
}
아쉽다… 하하하
오답 노트
아래의 코드는 정상 작동되지 않는다! ㅠㅠ
final byte[] bytes = inputStream.readAllBytes();
final String httpRequestString = new String(bytes);
final String uri = httpRequestString.split(" ")[1];
File Input
final Path path = Paths
.get("tomcat", "src", "main", "resources", "static", fileName)
.toAbsolutePath();
final File file = path.toFile();
아래의 코드로 대신할 수 있다. 위는 .java가 있는 곳을 읽어가며 아래와 같은 경우 실제로 classLoader에 있는 곳을 읽어간다
URL resource = getClass()
.getClassLoader()
.getResource("static" + fileName);
String filePath = Objects.requireNonNull(resource)
.getFile();
final BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
POST 전송 QueryString 파싱
postContent = headers[headers.length - 1].replaceAll("\\s+", "");
/**
* TODO 아래의 내용을 파싱해야함
* @ %40
* ! %21
*/
private String findPostContent(final String[] httpRequestStringLines) {
return Arrays.stream(httpRequestStringLines)
.filter(it -> it.contains("JSESSIONID"))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Not found JSESSIONID "))
.split("\\s+")
[1];
}
public String findPostContent() {
return nonKeyValues.stream()
.filter(nonKeyValue -> !StringUtils.isEmpty(nonKeyValue))
.findAny()
.orElse("");
}
공백 문자열이 오는 현상
알고보니 Header와 Body를 안나누었기 때문에 나타난 현상으로 보인다!
readLine() 에서 계속 멈추는 현상이다…
while (reader.ready()) {
stringBuffer.append(reader.readLine());
}
이때는 Content-Length만큼 미리 다 읽어버리면 된다 !
// IoUtils.readCertainLength(reader, Integer.parseInt(contentLength));
public static String readCertainLength(final BufferedReader reader, final int length) {
char[] buffer = new char[length];
try {
reader.read(buffer, 0, length);
} catch (IOException e) {
throw new RuntimeException(e);
}
return new String(buffer);
}
쿠키