Skip to content

Commit 841e70a

Browse files
[톰캣 구현하기 1 & 2단계] 달리(김동연) 미션 제출합니다 (#351)
* feat: ioStream 학습 * feat: fileTest 학습 * docs: 기능 구현 목록 작성 * feat: get method 응답 기능 구현 * feat: queryString 파싱 기능 구현 * feat: 로그인 redirect 구현 * feat: 로그인 redirect 구현 * feat: Session 구현 * refactor : 구조 변경 * fix : 로그인 페이지 오류 수정 * style : 코드 컨벤션 준수 및 사용하지 않는 메서드 제거 * style : cookie header로 이동 * refactor: 중복된 file 코드 통합 * feat : auto-increment id 추가 * test : 테스트 추가 * refactor: equals 재정의
1 parent 68db530 commit 841e70a

File tree

27 files changed

+968
-58
lines changed

27 files changed

+968
-58
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,16 @@
11
# 톰캣 구현하기
2+
3+
## 요구사항
4+
5+
- [x] GET /index.html 응답
6+
- [x] CSS 지원
7+
- [x] Query String 파싱
8+
9+
- [x] HTTP Status Code 302
10+
- [x] POST 방식으로 회원가입
11+
- [x] Cookie에 JSESSIONID 값 저장하기
12+
- [x] Session 구현하기
13+
14+
## 기능 목록
15+
- [x] header에서 입력 url을 추출한다
16+
- [x] resource에 해당 정적 html 파일을 가져온다

study/src/test/java/study/FileTest.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
package study;
22

3+
import java.io.FileInputStream;
4+
import java.io.IOException;
5+
import java.net.URISyntaxException;
6+
import java.net.URL;
7+
import java.nio.file.Files;
38
import org.junit.jupiter.api.DisplayName;
49
import org.junit.jupiter.api.Test;
510

611
import java.nio.file.Path;
712
import java.util.Collections;
813
import java.util.List;
14+
import org.springframework.core.io.ClassPathResource;
15+
import org.springframework.core.io.Resource;
16+
import org.springframework.core.io.ResourceLoader;
917

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

@@ -28,7 +36,9 @@ class FileTest {
2836
final String fileName = "nextstep.txt";
2937

3038
// todo
31-
final String actual = "";
39+
URL url = getClass().getClassLoader().getResource(fileName);
40+
final String actual = url.getFile();
41+
3242

3343
assertThat(actual).endsWith(fileName);
3444
}
@@ -40,14 +50,15 @@ class FileTest {
4050
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
4151
*/
4252
@Test
43-
void 파일의_내용을_읽는다() {
53+
void 파일의_내용을_읽는다() throws IOException, URISyntaxException {
4454
final String fileName = "nextstep.txt";
4555

4656
// todo
47-
final Path path = null;
57+
URL url = getClass().getClassLoader().getResource(fileName);
58+
final Path path = Path.of(url.toURI());
4859

4960
// todo
50-
final List<String> actual = Collections.emptyList();
61+
final List<String> actual = Files.readAllLines(path);
5162

5263
assertThat(actual).containsOnly("nextstep");
5364
}

study/src/test/java/study/IOStreamTest.java

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,9 @@ class OutputStream_학습_테스트 {
4949
final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112};
5050
final OutputStream outputStream = new ByteArrayOutputStream(bytes.length);
5151

52-
/**
53-
* todo
54-
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
55-
*/
52+
for(byte b : bytes){
53+
outputStream.write(b);
54+
}
5655

5756
final String actual = outputStream.toString();
5857

@@ -78,6 +77,7 @@ class OutputStream_학습_테스트 {
7877
* flush를 사용해서 테스트를 통과시킨다.
7978
* ByteArrayOutputStream과 어떤 차이가 있을까?
8079
*/
80+
outputStream.flush();
8181

8282
verify(outputStream, atLeastOnce()).flush();
8383
outputStream.close();
@@ -96,6 +96,8 @@ class OutputStream_학습_테스트 {
9696
* try-with-resources를 사용한다.
9797
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
9898
*/
99+
try(outputStream){
100+
}
99101

100102
verify(outputStream, atLeastOnce()).close();
101103
}
@@ -128,7 +130,9 @@ class InputStream_학습_테스트 {
128130
* todo
129131
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
130132
*/
131-
final String actual = "";
133+
134+
final String actual = new String(inputStream.readAllBytes());
135+
132136

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

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

177-
final byte[] actual = new byte[0];
182+
final byte[] actual = bufferedInputStream.readAllBytes();
178183

179184
assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
180185
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
@@ -197,15 +202,21 @@ class InputStreamReader_학습_테스트 {
197202
* 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
198203
*/
199204
@Test
200-
void BufferedReader를_사용하여_문자열을_읽어온다() {
205+
void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException {
201206
final String emoji = String.join("\r\n",
202207
"😀😃😄😁😆😅😂🤣🥲☺️😊",
203208
"😇🙂🙃😉😌😍🥰😘😗😙😚",
204209
"😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
205210
"");
206211
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
207-
212+
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
213+
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
208214
final StringBuilder actual = new StringBuilder();
215+
int size = emoji.split("\r\n").length;
216+
for(int i = 0 ; i < size ; i ++){
217+
actual.append(bufferedReader.readLine());
218+
actual.append("\r\n");
219+
}
209220

210221
assertThat(actual).hasToString(emoji);
211222
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package nextstep.jwp.controller;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.nio.file.Files;
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
import nextstep.jwp.db.InMemorySession;
9+
import nextstep.jwp.db.InMemoryUserRepository;
10+
import nextstep.jwp.exception.UnauthorizedException;
11+
import nextstep.jwp.model.AuthUser;
12+
import nextstep.jwp.model.User;
13+
import org.apache.coyote.http11.request.Request;
14+
import org.apache.coyote.http11.response.HttpStatus;
15+
import org.apache.coyote.http11.response.Response;
16+
import org.apache.coyote.http11.servlet.Servlet;
17+
18+
public class LoginController {
19+
20+
public static Response login(Request request){
21+
Map<String, String> body = request.getBody();
22+
AuthUser authUser = AuthUser.from(body);
23+
User user = InMemoryUserRepository.findByAccount(authUser.getAccount()).orElseThrow(()->new UnauthorizedException("해당 유저가 없습니다."));
24+
if(!user.checkPassword(authUser.getPassword())){
25+
throw new UnauthorizedException("아이디 및 패스워드가 틀렸습니다.");
26+
}
27+
String jSessionId = InMemorySession.login(user);
28+
Map<String,String> cookie = new HashMap<>();
29+
if(!request.getCookie().containsKey("JSESSIONID")){
30+
cookie.put("JSESSIONID",jSessionId);
31+
}
32+
return Response.builder()
33+
.status(HttpStatus.FOUND)
34+
.contentType("html")
35+
.cookie(cookie)
36+
.location("index.html")
37+
.responseBody(getFile("index.html"))
38+
.build();
39+
}
40+
41+
public static Response signUp(Request request){
42+
Map<String, String> requestBody = request.getBody();
43+
final String account = requestBody.get("account");
44+
final String password = requestBody.get("password");
45+
final String email = requestBody.get("email");
46+
User user = new User(account,password,email);
47+
InMemoryUserRepository.save(user);
48+
return Response.builder()
49+
.status(HttpStatus.FOUND)
50+
.contentType("html")
51+
.location("index.html")
52+
.responseBody(getFile("index.html"))
53+
.build();
54+
}
55+
56+
57+
private static String getFile(String fileName){
58+
try {
59+
final var fileUrl = Servlet.class.getClassLoader().getResource("static/" + fileName);
60+
final var fileBytes = Files.readAllBytes(new File(fileUrl.getFile()).toPath());
61+
return new String(fileBytes);
62+
} catch (IOException e) {
63+
throw new RuntimeException(e);
64+
} catch (NullPointerException e){
65+
return "";
66+
}
67+
}
68+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package nextstep.jwp.controller;
2+
3+
import java.io.File;
4+
import java.io.IOException;
5+
import java.nio.file.Files;
6+
import org.apache.coyote.http11.request.Request;
7+
import org.apache.coyote.http11.response.HttpStatus;
8+
import org.apache.coyote.http11.response.Response;
9+
import org.apache.coyote.http11.servlet.Servlet;
10+
11+
public class ViewController {
12+
public static Response getLogin(Request request){
13+
return Response.builder()
14+
.status(HttpStatus.OK)
15+
.contentType("html")
16+
.responseBody(getFile("login.html"))
17+
.build();
18+
}
19+
20+
public static Response getRegister(Request request){
21+
return Response.builder()
22+
.status(HttpStatus.OK)
23+
.contentType("html")
24+
.responseBody(getFile("register.html"))
25+
.build();
26+
}
27+
28+
public static Response getVoid(Request request){
29+
return Response.builder()
30+
.status(HttpStatus.OK)
31+
.responseBody("Hello world!")
32+
.contentType("html")
33+
.build();
34+
}
35+
36+
37+
private static String getFile(String fileName){
38+
try {
39+
final var fileUrl = Servlet.class.getClassLoader().getResource("static/" + fileName);
40+
final var fileBytes = Files.readAllBytes(new File(fileUrl.getFile()).toPath());
41+
return new String(fileBytes);
42+
} catch (IOException e) {
43+
throw new RuntimeException(e);
44+
} catch (NullPointerException e){
45+
return "";
46+
}
47+
}
48+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package nextstep.jwp.db;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.UUID;
6+
import nextstep.jwp.exception.UnauthorizedException;
7+
import nextstep.jwp.model.User;
8+
9+
public class InMemorySession {
10+
private static final Map<User, UUID> session = new HashMap<>();
11+
12+
public static String login(User user){
13+
UUID uuid = UUID.randomUUID();
14+
session.put(user,uuid);
15+
return uuid.toString();
16+
}
17+
public static boolean isLogin(String id){
18+
for(UUID uuid: session.values()){
19+
if(uuid.toString().equals(id)){
20+
return true;
21+
}
22+
}
23+
return false;
24+
}
25+
}

tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package nextstep.jwp.db;
22

3+
import java.util.concurrent.atomic.AtomicLong;
34
import nextstep.jwp.model.User;
45

56
import java.util.Map;
@@ -9,14 +10,14 @@
910
public class InMemoryUserRepository {
1011

1112
private static final Map<String, User> database = new ConcurrentHashMap<>();
12-
13+
private static final AtomicLong autoIncrementId = new AtomicLong(1L);
1314
static {
14-
final User user = new User(1L, "gugu", "password", "[email protected]");
15+
final User user = new User(autoIncrementId.get(), "gugu", "password", "[email protected]");
1516
database.put(user.getAccount(), user);
1617
}
1718

1819
public static void save(User user) {
19-
database.put(user.getAccount(), user);
20+
database.put(user.getAccount(), user.putId(autoIncrementId.addAndGet(1L)));
2021
}
2122

2223
public static Optional<User> findByAccount(String account) {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package nextstep.jwp.exception;
2+
3+
public class UnauthorizedException extends RuntimeException{
4+
public UnauthorizedException(String message) {
5+
super(message);
6+
}
7+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package nextstep.jwp.model;
2+
3+
import java.util.Map;
4+
5+
public class AuthUser {
6+
private final String account;
7+
private final String password;
8+
9+
public AuthUser(String account, String password) {
10+
this.account = account;
11+
this.password = password;
12+
}
13+
14+
public static AuthUser from(Map<String, String> query){
15+
return new AuthUser(query.get("account"),query.get("password"));
16+
}
17+
18+
public String getAccount() {
19+
return account;
20+
}
21+
22+
public String getPassword() {
23+
return password;
24+
}
25+
}

0 commit comments

Comments
 (0)