Skip to content

Commit

Permalink
[톰캣 구현하기 - 1, 2단계] 준팍(박준현) 미션 제출합니다. (#379)
Browse files Browse the repository at this point in the history
* test: IOStreamTest 완료

* test: FileTest 완료

* test: FileTest path에서 getResource로 방법 변경

* docs : 요구사항 정리

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

* feat: css 지원 기능 구현

* refactor: 클래스 분리

* feat: QueryString 파싱 기능 구현

* refactor: FileExtension 클래스 분리

* refactor: HttpStatus 클래스 분리

* refactor: RequestHandler 클래스 분리

* refactor: RequestHandler Path 로직 수정

* feat: favicon 추가

* feat: HTTP 302 기능 구현

* refactor: 개행 문자 lineSeparator()

* refactor: 클래스 분리

* feat: Post method 기능 구현

* feat: 회원 가입 후 리다이렉트 완료

* refacotr: lineSeperator() "\r\n"로 수정

* refactor: 피드백 반영

* refactor: QueryStrings 일급 컬렉션 구현

* feat: HttpCookie 구현

* refactor: 네이밍 변경

* chore: 패키지 분리

* feat: RequestHeaders에 HttpCookie 추가

* refactor: 메서드 분리

* refactor: HandlerMapping 상수화

* feat: 쿠키 값을 스트링으로 반환하는 메서드 구현

* refactor: 메서드 이동

* refactor: 클래스별 역할과 책임 재분배

* refactor: Handler 추가

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

* feat: Session 및 SessionManager 객체 구현

* feat: Session 기능 구현 완료

* feat: User ID AutoIncrement 기능 구현
  • Loading branch information
junpakPark authored Sep 7, 2023
1 parent 68db530 commit 3453b3d
Show file tree
Hide file tree
Showing 40 changed files with 1,272 additions and 42 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
# 톰캣 구현하기

## 1단계 - HTTP 서버 구현하기
- [x] GET /index.html 응답하기
- [x] CSS 지원하기
- [x] Query String 파싱

## 2단계 - 로그인 구현하기
- [x] HTTP Status Code 302
- [x] 로그인 성공 시, 응답 헤더에 http status code를 302로 반환하고 `/index.html`로 리다이렉트
- [x] 로그인 실패 시, `401.html`로 리다이렉트
- [x] POST 방식으로 회원가입
- [x] `http://localhost:8080/register` 으로 접속하면 회원가입 페이지(`register.html`)를 보여준다.
- [x] 회원가입 페이지를 보여줄 때는 GET을 사용한다.
- [x] 회원가입을 버튼을 누르면 HTTP method를 GET이 아닌 POST를 사용한다.
- [x] 회원가입을 완료하면 `index.html`로 리다이렉트한다.
- [x] 로그인 페이지도 버튼을 눌렀을 때 GET 방식에서 POST 방식으로 전송하도록 변경하자.
- [x] Cookie에 JSESSIONID 값 저장하기
- [x] 서버에서 HTTP 응답을 전달할 때 응답 헤더에 `Set-Cookie`를 추가하고 `JSESSIONID=656cef62-e3c4-40bc-a8df-94732920ed46` 형태로 값을 전달하면
클라이언트 요청 헤더의 `Cookie` 필드에 값이 추가된다.
- [x] Cookie 클래스를 추가하고 HTTP Request Header의 Cookie에 `JSESSIONID`가 없으면 HTTP Response Header에 `Set-Cookie`를 반환해주는 기능을
구현한다.
- [x] Session 구현하기
- [x] 쿠키에서 전달 받은 JSESSIONID의 값으로 로그인 여부를 체크할 수 있어야 한다.
- [x] 로그인에 성공하면 Session 객체의 값으로 User 객체를 저장해보자.
- [x] 로그인 상태에서 `/login` 페이지에 HTTP GET method로 접근하면 `index.html` 페이지로 리다이렉트 처리한다
33 changes: 21 additions & 12 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package study;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

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

import static org.assertj.core.api.Assertions.assertThat;
import java.util.Objects;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
Expand All @@ -28,26 +33,30 @@ class FileTest {
final String fileName = "nextstep.txt";

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

File file = new File(Objects.requireNonNull(resource).getPath());

String actual = file.getName();

assertThat(actual).endsWith(fileName);
}

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

// todo
final Path path = null;
URL resource = getClass().getClassLoader().getResource(fileName);
final Path path = Paths.get(Objects.requireNonNull(resource).toURI());

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

assertThat(actual).containsOnly("nextstep");
}
Expand Down
97 changes: 89 additions & 8 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
package study;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.io.*;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;

/**
* 자바는 스트림(Stream)으로부터 I/O를 사용한다.
* 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다.
Expand Down Expand Up @@ -54,6 +66,8 @@ class OutputStream_학습_테스트 {
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/

outputStream.write(bytes);

final String actual = outputStream.toString();

assertThat(actual).isEqualTo("nextstep");
Expand All @@ -79,6 +93,8 @@ class OutputStream_학습_테스트 {
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/

outputStream.flush();

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

try (outputStream) {

}

verify(outputStream, atLeastOnce()).close();
}
}
Expand Down Expand Up @@ -127,8 +147,50 @@ class InputStream_학습_테스트 {
/**
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
* https://www.baeldung.com/convert-input-stream-to-string
*/

InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);

/*
* StringBuilder
*/
// StringBuilder stringBuilder = new StringBuilder();
//
// try (Reader reader = new BufferedReader(inputStreamReader)) {
// int c = 0;
// while ((c = reader.read()) != -1) {
// stringBuilder.append((char) c);
// }
// }
// final String actual = stringBuilder.toString();

/*
* BufferedReader
*/
final String actual = "";
// String actual = new BufferedReader(inputStreamReader)
// .lines()
// .collect(Collectors.joining("/n"));

/*
* InputStream.readAllBytes()
*/
String actual = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);

/*
* Scanner
*/
// String actual = null;
// try (Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) {
// actual = scanner.useDelimiter("\\A").next();
// }

/*
* java.nio
*/
// Path tempFile = Files.createTempDirectory("").resolve(UUID.randomUUID().toString() + ".tmp");
// Files.copy(inputStream, tempFile, StandardCopyOption.REPLACE_EXISTING);
// String actual = new String(Files.readAllBytes(tempFile));

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

try (inputStream) {
}

verify(inputStream, atLeastOnce()).close();
}
}
Expand All @@ -172,9 +237,14 @@ class FilterStream_학습_테스트 {
void 필터인_BufferedInputStream_사용해보자() {
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;
try (inputStream) {
actual = bufferedInputStream.readAllBytes();
} catch (IOException e) {
throw new RuntimeException(e);
}

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
Expand Down Expand Up @@ -207,6 +277,17 @@ class InputStreamReader_학습_테스트 {

final StringBuilder actual = new StringBuilder();

try (inputStream) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String collect = bufferedReader.lines()
.collect(Collectors.joining("\r\n"));
actual.append(collect)
.append("\r\n");
} catch (IOException e) {
throw new RuntimeException(e);
}

assertThat(actual).hasToString(emoji);
}
}
Expand Down
26 changes: 26 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nextstep.jwp.controller;

import nextstep.jwp.model.User;
import nextstep.jwp.service.UserService;
import org.apache.coyote.http11.common.HttpStatus;
import org.apache.coyote.http11.response.ResponseEntity;

public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

public User login(String account, String password) {
return userService.login(account, password);
}

public ResponseEntity signUp(String account, String password, String email) {
userService.save(account, password, email);

return ResponseEntity.of(HttpStatus.FOUND, "/index");
}

}
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
package nextstep.jwp.db;

import nextstep.jwp.model.User;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import nextstep.jwp.model.User;

public class InMemoryUserRepository {

private static final Map<String, User> database = new ConcurrentHashMap<>();
private static final AtomicLong idCounter = new AtomicLong();

static {
final User user = new User(1L, "gugu", "password", "[email protected]");
final User user = new User("gugu", "password", "[email protected]").persist(idCounter.incrementAndGet());
database.put(user.getAccount(), user);
}

public static void save(User user) {
database.put(user.getAccount(), user);
database.put(user.getAccount(), user.persist(idCounter.incrementAndGet()));
}

public static Optional<User> findByAccount(String account) {
Expand Down
6 changes: 5 additions & 1 deletion tomcat/src/main/java/nextstep/jwp/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public class User {
private final String password;
private final String email;

public User(Long id, String account, String password, String email) {
private User(Long id, String account, String password, String email) {
this.id = id;
this.account = account;
this.password = password;
Expand All @@ -18,6 +18,10 @@ public User(String account, String password, String email) {
this(null, account, password, email);
}

public User persist(Long userId) {
return new User(userId, account, password, email);
}

public boolean checkPassword(String password) {
return this.password.equals(password);
}
Expand Down
35 changes: 35 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/service/UserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package nextstep.jwp.service;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserService {

private static final Logger log = LoggerFactory.getLogger(UserService.class);

public User login(String account, String password) {
User user = InMemoryUserRepository.findByAccount(account)
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 계정입니다."));

checkPassword(password, user);

return user;
}

private void checkPassword(String password, User user) {
if (user.checkPassword(password)) {
log.info("User: {}", user);
return;
}
throw new IllegalArgumentException("잘못된 패스워드입니다.");
}

public void save(String account, String password, String email) {
User user = new User(account, password, email);
InMemoryUserRepository.save(user);

log.info("User: {}", InMemoryUserRepository.findByAccount(account));
}
}
2 changes: 1 addition & 1 deletion tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package org.apache.catalina;

import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import org.apache.coyote.http11.session.HttpSession;

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
Expand Down
Loading

0 comments on commit 3453b3d

Please sign in to comment.