Skip to content

Commit

Permalink
[톰캣 구현하기 1, 2단계] 두둠(최영훈) 미션 제출합니다 (#335)
Browse files Browse the repository at this point in the history
* test: 파일 입출력 학습 테스트 작성

* feat: GET /index.html 응답하기 구현

* feat: CSS 지원하기 구현

* refactor: 전체 구조 수정

* feat: Query String 파싱 구현

* feat: 로그인 여부에 따라 다른 페이지로 이동 구현

* feat: POST 방식으로 회원가입 구현 및 로그인 Post 방식으로 변경

* feat: Cookie에 JSESSIONID 값 저장하기 구현

* feat: Session 구현

* refactor: 의존관계 리펙토링

* refactor: 패키지 수정
  • Loading branch information
younghoondoodoom authored Sep 6, 2023
1 parent 68db530 commit 3152d3d
Show file tree
Hide file tree
Showing 35 changed files with 970 additions and 133 deletions.
25 changes: 14 additions & 11 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package study;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

/**
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
* File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
Expand All @@ -18,7 +20,7 @@ class FileTest {

/**
* resource 디렉터리 경로 찾기
*
* <p>
* File 객체를 생성하려면 파일의 경로를 알아야 한다.
* 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다.
* resource 디렉터리의 경로는 어떻게 알아낼 수 있을까?
Expand All @@ -28,27 +30,28 @@ class FileTest {
final String fileName = "nextstep.txt";

// todo
final String actual = "";
final URL resource = this.getClass().getClassLoader().getResource(fileName);
final String actual = resource.getFile();

assertThat(actual).endsWith(fileName);
}

/**
* 파일 내용 읽기
*
* <p>
* 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다.
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
void 파일의_내용을_읽는다() {
void 파일의_내용을_읽는다() throws IOException {
final String fileName = "nextstep.txt";

// todo
final Path path = null;
final URL resource = this.getClass().getClassLoader().getResource(fileName);
final Path path = new File(resource.getPath()).toPath();

// todo
final List<String> actual = Collections.emptyList();

final List<String> actual = Files.readAllLines(path);
assertThat(actual).containsOnly("nextstep");
}
}
19 changes: 14 additions & 5 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class OutputStream_학습_테스트 {
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/

outputStream.write(bytes);
final String actual = outputStream.toString();

assertThat(actual).isEqualTo("nextstep");
Expand All @@ -78,6 +79,7 @@ class OutputStream_학습_테스트 {
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
outputStream.flush();

verify(outputStream, atLeastOnce()).flush();
outputStream.close();
Expand All @@ -96,6 +98,7 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
try (outputStream) {}

verify(outputStream, atLeastOnce()).close();
}
Expand Down Expand Up @@ -128,7 +131,7 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
final String actual = new String(inputStream.readAllBytes());

assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
Expand All @@ -148,6 +151,7 @@ class InputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
try (inputStream) {}

verify(inputStream, atLeastOnce()).close();
}
Expand All @@ -169,12 +173,12 @@ class FilterStream_학습_테스트 {
* 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
*/
@Test
void 필터인_BufferedInputStream_사용해보자() {
void 필터인_BufferedInputStream_사용해보자() throws IOException {
final String text = "필터에 연결해보자.";
final InputStream inputStream = new ByteArrayInputStream(text.getBytes());
final InputStream bufferedInputStream = null;
final InputStream bufferedInputStream = new BufferedInputStream(inputStream);

final byte[] actual = new byte[0];
final byte[] actual = bufferedInputStream.readAllBytes();

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
Expand All @@ -197,15 +201,20 @@ class InputStreamReader_학습_테스트 {
* 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
*/
@Test
void BufferedReader_사용하여_문자열을_읽어온다() {
void BufferedReader_사용하여_문자열을_읽어온다() throws IOException {
final String emoji = String.join("\r\n",
"😀😃😄😁😆😅😂🤣🥲☺️😊",
"😇🙂🙃😉😌😍🥰😘😗😙😚",
"😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
"");
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));

final StringBuilder actual = new StringBuilder();
while (bufferedReader.ready()) {
actual.append(bufferedReader.readLine())
.append("\r\n");
}

assertThat(actual).hasToString(emoji);
}
Expand Down
22 changes: 20 additions & 2 deletions tomcat/src/main/java/nextstep/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
package nextstep;

import nextstep.jwp.HandlerResolver;
import nextstep.jwp.JwpHttpDispatcher;
import nextstep.jwp.SessionManager;
import nextstep.jwp.handler.get.LoginGetHandler;
import nextstep.jwp.handler.get.RegisterGetHandler;
import nextstep.jwp.handler.get.RootGetHandler;
import nextstep.jwp.handler.post.LoginPostHandler;
import nextstep.jwp.handler.post.RegisterPostHandler;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.http11.Handler;
import java.util.Map;

public class Application {

public static void main(String[] args) {
final var tomcat = new Tomcat();
private static final Map<String, Handler> httpGetHandlers =
Map.of("/", new RootGetHandler(),
"/login", new LoginGetHandler(new SessionManager()),
"/register", new RegisterGetHandler());
private static final Map<String, Handler> httpPostHandlers =
Map.of("/login", new LoginPostHandler(new SessionManager()),
"/register", new RegisterPostHandler());

public static void main(final String[] args) {
final var tomcat = new Tomcat(new JwpHttpDispatcher(new HandlerResolver(httpGetHandlers, httpPostHandlers)));
tomcat.start();
}
}
26 changes: 26 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/HandlerResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nextstep.jwp;

import org.apache.coyote.http11.Handler;
import org.apache.coyote.http11.HttpMethod;
import java.util.Map;

public class HandlerResolver {

private final Map<String, Handler> httpGetHandlers;
private final Map<String, Handler> httpPostHandlers;

public HandlerResolver(final Map<String, Handler> httpGetHandlers, final Map<String, Handler> httpPostHandlers) {
this.httpGetHandlers = httpGetHandlers;
this.httpPostHandlers = httpPostHandlers;
}

public Handler resolve(final HttpMethod httpMethod, final String path) {
if (httpMethod.equals(HttpMethod.GET)) {
return httpGetHandlers.get(path);
}
if (httpMethod.equals(HttpMethod.POST)) {
return httpPostHandlers.get(path);
}
return null;
}
}
46 changes: 46 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/JwpHttpDispatcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package nextstep.jwp;

import org.apache.coyote.http11.ContentType;
import org.apache.coyote.http11.Handler;
import org.apache.coyote.http11.HttpDispatcher;
import org.apache.coyote.http11.StatusCode;
import org.apache.coyote.http11.request.Http11Request;
import org.apache.coyote.http11.response.Http11Response;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Objects;

public class JwpHttpDispatcher implements HttpDispatcher {

private static final String STATIC = "static";

private final HandlerResolver handlerResolver;

public JwpHttpDispatcher(final HandlerResolver handlerResolver) {
this.handlerResolver = handlerResolver;
}

@Override
public Http11Response handle(final Http11Request request) throws IOException {
final Handler handler = handlerResolver.resolve(request.getHttpMethod(), request.getPath());
if (handler != null) {
return handler.resolve(request);
}
final var resource = getClass().getClassLoader().getResource(STATIC + request.getPath());
if (resource == null) {
final var notFoundResource = getClass().getClassLoader().getResource(STATIC + "/404.html");
return makeHttp11Response(Objects.requireNonNull(notFoundResource), StatusCode.NOT_FOUND);
}
return makeHttp11Response(resource, StatusCode.OK);
}

private Http11Response makeHttp11Response(final URL resource, final StatusCode statusCode) throws IOException {
final var actualFilePath = new File(resource.getPath()).toPath();
final var fileBytes = Files.readAllBytes(actualFilePath);
final String responseBody = new String(fileBytes, StandardCharsets.UTF_8);
return new Http11Response(statusCode, ContentType.findByPath(resource.getPath()), responseBody);
}
}
26 changes: 26 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/SessionManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nextstep.jwp;

import org.apache.catalina.Manager;
import org.apache.coyote.http11.Session;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SessionManager implements Manager {

private static final Map<String, Session> SESSIONS = new ConcurrentHashMap<>();

@Override
public void add(final Session session) {
SESSIONS.put(session.getId(), session);
}

@Override
public Session findSession(final String id) {
return SESSIONS.get(id);
}

@Override
public void remove(final Session session) {
SESSIONS.remove(session.getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package nextstep.jwp.exception;

public class BusinessException extends RuntimeException {
public BusinessException(final String message) {
super(message);
}
}
52 changes: 52 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/handler/get/LoginGetHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package nextstep.jwp.handler.get;

import nextstep.jwp.SessionManager;
import nextstep.jwp.exception.BusinessException;
import org.apache.coyote.http11.ContentType;
import org.apache.coyote.http11.Handler;
import org.apache.coyote.http11.Session;
import org.apache.coyote.http11.StatusCode;
import org.apache.coyote.http11.request.Http11Request;
import org.apache.coyote.http11.response.Http11Response;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

public class LoginGetHandler implements Handler {

private static final String STATIC = "static";

private final SessionManager sessionManager;

public LoginGetHandler(final SessionManager sessionManager) {
this.sessionManager = sessionManager;
}

@Override
public Http11Response resolve(final Http11Request request) throws IOException {
if (request.notContainJsessionId()) {
final var resource = getClass().getClassLoader().getResource(STATIC + "/login.html");
return makeHttp11Response(resource, StatusCode.OK);
}
final String jsessionId = request.findJsessionId();
final Session session = sessionManager.findSession(jsessionId);
validateSession(session);
final var resource = getClass().getClassLoader().getResource(STATIC + "/index.html");
return makeHttp11Response(resource, StatusCode.FOUND);
}

private Http11Response makeHttp11Response(final URL resource, final StatusCode statusCode) throws IOException {
final var actualFilePath = new File(resource.getPath()).toPath();
final var fileBytes = Files.readAllBytes(actualFilePath);
final String responseBody = new String(fileBytes, StandardCharsets.UTF_8);
return new Http11Response(statusCode, ContentType.findByPath(resource.getPath()), responseBody);
}

private void validateSession(final Session session) {
if (session == null) {
throw new BusinessException("세션이 적절하지 않습니다.");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package nextstep.jwp.handler.get;

import org.apache.coyote.http11.ContentType;
import org.apache.coyote.http11.Handler;
import org.apache.coyote.http11.StatusCode;
import org.apache.coyote.http11.request.Http11Request;
import org.apache.coyote.http11.response.Http11Response;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;

public class RegisterGetHandler implements Handler {

private static final String STATIC = "static";

@Override
public Http11Response resolve(final Http11Request request) throws IOException {
final var resource = getClass().getClassLoader().getResource(STATIC + "/register.html");
return makeHttp11Response(resource, StatusCode.OK);
}

private Http11Response makeHttp11Response(final URL resource, final StatusCode statusCode) throws IOException {
final var actualFilePath = new File(resource.getPath()).toPath();
final var fileBytes = Files.readAllBytes(actualFilePath);
final String responseBody = new String(fileBytes, StandardCharsets.UTF_8);
return new Http11Response(statusCode, ContentType.findByPath(resource.getPath()), responseBody);
}
}
15 changes: 15 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/handler/get/RootGetHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package nextstep.jwp.handler.get;

import org.apache.coyote.http11.ContentType;
import org.apache.coyote.http11.Handler;
import org.apache.coyote.http11.StatusCode;
import org.apache.coyote.http11.request.Http11Request;
import org.apache.coyote.http11.response.Http11Response;

public class RootGetHandler implements Handler {

@Override
public Http11Response resolve(final Http11Request request) {
return new Http11Response(StatusCode.OK, ContentType.HTML, "Hello world!");
}
}
Loading

0 comments on commit 3152d3d

Please sign in to comment.