Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fed02f6
fix: remove implementation logback-classic on gradle (#501)
geoje Sep 5, 2024
7e91356
fix: add threads min-spare configuration on properties (#502)
geoje Sep 5, 2024
ba5c36d
test: 스터디 테스트 작성
Kimprodp Sep 4, 2024
1079350
refactor: 자동 정렬 이전 파일로 교체
Kimprodp Sep 4, 2024
8309210
refactor: import 변경
Kimprodp Sep 4, 2024
88ff134
refactor: 요청에 따라 자원을 반환하도록 변경
Kimprodp Sep 4, 2024
a3a5a38
feat: HttpRequest 추가
Kimprodp Sep 5, 2024
87faf32
feat: HttpResponse 추가
Kimprodp Sep 5, 2024
ed82d8a
refactor: Http11Processor에서 요청과 응답 분리
Kimprodp Sep 5, 2024
e80274a
refactor: 로그인 메서드 post로 변경
Kimprodp Sep 6, 2024
055439f
refactor: HttpRequest에 requestBody 추가
Kimprodp Sep 6, 2024
20b3b92
refactor: HttpResponse에 상태코드 추가
Kimprodp Sep 6, 2024
236a0f6
feat: 요청을 처리하는 컨트롤러 구현
Kimprodp Sep 6, 2024
06e8288
feat: 매핑된 컨트롤러를 찾아서 반환하는 RequestMapping 추가
Kimprodp Sep 6, 2024
5f3c7f8
feat: ViewResolver 및 View 추가
Kimprodp Sep 6, 2024
b9d83f4
refactor: Http11Processor에서 Controller를 사용하도록 변경
Kimprodp Sep 6, 2024
280caf5
refactor: HttpRequest 에서 key 값으로 찾도록 변경
Kimprodp Sep 6, 2024
2cb85c0
refactor: HttpResponse 에 쿠키를 포함하는 기능 추가
Kimprodp Sep 6, 2024
d9bdda1
feat: 쿠키 및 세션 추가
Kimprodp Sep 6, 2024
c509f08
refactor: 로그인 시, 세션 확인하는 기능 추가
Kimprodp Sep 6, 2024
455ea74
refactor: ReqeustParser를 통해 HttpRequest 를 만들도록 변경
Kimprodp Sep 9, 2024
e094dda
refactor: HttpRequest 관련 클래스 분리
Kimprodp Sep 9, 2024
ce0ca5d
refactor: RequestMapping 싱글톤으로 변경
Kimprodp Sep 9, 2024
ace0a51
refactor: RegisterController에서 request의 기능을 활용하도록 변경
Kimprodp Sep 9, 2024
de3b27b
refactor: 로깅 추가 및 수정
Kimprodp Sep 9, 2024
ea605f6
refactor: Accpet로 response의 ContentType 구성하도록 변경
Kimprodp Sep 9, 2024
6209a7a
refactor: 정의되지 않은 요청 시, 404 페이지 반환하도록 변경
Kimprodp Sep 9, 2024
6ad5478
refactor: 사용하지 않는 주석 제거
Kimprodp Sep 9, 2024
9a67e20
refactor: 패키지 구조 변경
Kimprodp Sep 9, 2024
4c7fa83
refactor: post 요청에서 body가 존재하지 않을 경우 400을 반환하도록 변경
Kimprodp Sep 9, 2024
2fe0e5a
refactor: cookie에 대한 NPE 처리
Kimprodp Sep 9, 2024
c45a246
refactor: SessionManager 를 static 으로 활용하도록 변경
Kimprodp Sep 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion study/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'ch.qos.logback:logback-classic:1.5.7'
implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package cache.com.example;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import jakarta.servlet.http.HttpServletResponse;

@Controller
public class GreetingController {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package cache.com.example.version;

import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.stereotype.Component;

@Component
public class ResourceVersion {
Expand Down
1 change: 1 addition & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ server:
accept-count: 1
max-connections: 1
threads:
min-spare: 2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

min-spare 를 설정한 이유와 2인 근거가 궁금해요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 메인 브랜치 변경사항 받아오면서 생긴 부분인 것 같아요!

max: 2
37 changes: 19 additions & 18 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,54 @@
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.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.util.List;

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

/**
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
* File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
*/
@DisplayName("File 클래스 학습 테스트")
class FileTest {

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

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

assertThat(actual).endsWith(fileName);
}

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

// todo
final Path path = null;
URL resource = getClass().getClassLoader().getResource(fileName);
final Path path = Path.of(resource.toURI());

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

assertThat(actual).containsOnly("nextstep");
}
Expand Down
32 changes: 27 additions & 5 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package study;

import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -54,6 +55,7 @@ class OutputStream_학습_테스트 {
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/

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

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

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

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

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

}

verify(inputStream, atLeastOnce()).close();
}
Expand All @@ -169,12 +177,13 @@ 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();
bufferedInputStream.close();

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
Expand Down Expand Up @@ -204,9 +213,22 @@ class InputStreamReader_학습_테스트 {
"😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
"");
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);

final StringBuilder actual = new StringBuilder();

try(BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
while (true) {
String read = bufferedReader.readLine();
if (read == null) {
break;
}
actual.append(read).append("\r\n");
}
} catch (IOException e) {
e.printStackTrace();
}

assertThat(actual).hasToString(emoji);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.techcourse.controller;

import java.io.IOException;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public abstract class AbstractController implements Controller {

@Override
public void service(HttpRequest request, HttpResponse response) throws IOException {
String method = request.getRequestLine().getMethod();
if (method.equals("POST")) {
doPost(request, response);
} else if (method.equals("GET")) {
doGet(request, response);
} else {
response.setStatus405();
}
}

protected void doPost(HttpRequest request, HttpResponse response) throws IOException {
}

protected void doGet(HttpRequest request, HttpResponse response) throws IOException {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.techcourse.controller;

import java.io.IOException;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public interface Controller {
void service(HttpRequest request, HttpResponse response) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.techcourse.controller;

import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.model.User;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import com.techcourse.session.Session;
import com.techcourse.session.SessionManager;
import com.techcourse.view.View;
import com.techcourse.view.ViewResolver;
import org.apache.coyote.http11.HttpCookie;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public class LoginController extends AbstractController {

@Override
protected void doGet(HttpRequest request, HttpResponse response) throws IOException {
Session session = extractSession(request);
if (session != null) {
User user = (User) session.getAttribute("user");
if (user != null) {
responseLoginSuccess(response, session);
return;
}
}
responseLoginPage(request, response);
}

@Override
protected void doPost(HttpRequest request, HttpResponse response) {
if (!request.hasBodyData()) {
throw new IllegalArgumentException("Query string is missing in the request");
}

Map<String, String> requestFormData = request.getFormData();
String userName = requestFormData.get("account");
String password = requestFormData.get("password");

Optional<User> account = InMemoryUserRepository.findByAccount(userName);
if (account.isEmpty()) {
responseLoginFail(response);
} else {
User user = account.get();
if (user.checkPassword(password)) {
Session session = saveSession(user);
responseLoginSuccess(response, session);
} else {
responseLoginFail(response);
}
}
}

private Session extractSession(HttpRequest request) {
String cookie = request.getCookie();
if (cookie == null) {
Map<String, String> cookies = new HashMap<>();
for (String cookieParts : cookie.split(" ")) {
String[] keyAndValue = cookieParts.split("=");
cookies.put(keyAndValue[0], keyAndValue[1]);
}

String jsessionId = cookies.get("JSESSIONID");
return SessionManager.findSession(jsessionId);
}

return null;
}
Comment on lines +56 to +70
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의견) 해당 메서드는 LoginController 보다는 SesseionManager의 책임이라고 느껴지는 것 같은데 테드의 생각이 궁금해요!🧐

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쿠키에서 JSessionId를 추출하는 책임을 쿠키가 가지고 있는 것이 맞는 것 같아서 조정했습니다


private Session saveSession(User user) {
Session session = new Session();
session.setAttribute("user", user);
SessionManager.add(session);
return session;
}

private void responseLoginPage(HttpRequest request, HttpResponse response) throws IOException {
View view = ViewResolver.getView("/login.html");
response.setStatus200();
response.setResponseBody(view.getContent());
response.setContentType(request.getContentType());
}

private void responseLoginSuccess(HttpResponse response, Session session) {
response.setStatus302();
response.setLocation("/index.html");
response.setCookie(HttpCookie.ofJSessionId(session.getId()));
}

private void responseLoginFail(HttpResponse response) {
response.setStatus302();
response.setLocation("/401.html");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.techcourse.controller;

import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.model.User;
import java.io.IOException;
import java.util.Map;
import com.techcourse.view.View;
import com.techcourse.view.ViewResolver;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RegisterController extends AbstractController {

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

@Override
protected void doPost(HttpRequest request, HttpResponse response) {
try {
if (!request.hasBodyData()) {
throw new IllegalArgumentException("RequestBody is missing in the request");
}

Map<String, String> requestFormData = request.getFormData();
String account = requestFormData.get("account");
String password = requestFormData.get("password");
String email = requestFormData.get("email");

User user = new User(account, password, email);
Comment on lines +25 to +30
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의견) reqeustForm 에 account 가 없거나 email 이 없거나 등에 대한 검증은 추가해주면 좋을 것 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영 완료 👍

InMemoryUserRepository.save(user);
responseRegisterSuccess(response);

} catch (IllegalArgumentException e) {
response.setStatus400();
response.setResponseBody(e.getMessage());
log.info("Bad Request: {}", e.getMessage());

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요한 개행이네요😁

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3단계 하면서 공백도 다시 확인했습니다 👍

}
}

@Override
protected void doGet(HttpRequest request, HttpResponse response) throws IOException {
responseRegisterPage(request, response);
}

private void responseRegisterSuccess(HttpResponse response) {
response.setStatus302();
response.setLocation("/index.html");
}

private void responseRegisterPage(HttpRequest request, HttpResponse response) throws IOException {
View view = ViewResolver.getView("/register.html");
response.setStatus200();
response.setResponseBody(view.getContent());
response.setContentType(request.getContentType());
}
}
Loading