Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 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
44fc288
Merge branch 'unifolio0' of https://github.com/unifolio0/java-http in…
unifolio0 Sep 8, 2024
7ed4d23
refactor: favicon.ico 파일 추가
unifolio0 Sep 8, 2024
078af63
refactor: 변수명 수정
unifolio0 Sep 8, 2024
25e67eb
refactor: 불필요한 로직 제거
unifolio0 Sep 8, 2024
8e570a7
fix: 잘못된 url 입력에 대한 null 처리
unifolio0 Sep 9, 2024
3b1799d
fix: input값이 덜 들어왔을 시 에러 처리
unifolio0 Sep 9, 2024
f986e25
refactor: HttpResponse 클래스에 builder 패턴 적용
unifolio0 Sep 9, 2024
dda9c17
refactor: 불필요한 파일 삭제
unifolio0 Sep 9, 2024
c53b87a
refactor: 없는 url에 대한 상태코드 변경
unifolio0 Sep 9, 2024
b24f95f
refactor: Cookie 클래스로 추출
unifolio0 Sep 9, 2024
8672b31
refactor: depth 감소
unifolio0 Sep 9, 2024
61ff56c
refactor: 쓰레드 안전성 보장
unifolio0 Sep 9, 2024
74fce63
refactor: 쓰레드풀 생성
unifolio0 Sep 9, 2024
75f9655
refactor: 존재하지 않는 url에 대한 처리 책임 이동
unifolio0 Sep 9, 2024
4ae9b63
refactor: 패키지 이동
unifolio0 Sep 9, 2024
9bff59a
refactor: 사용하지 않는 import 삭제
unifolio0 Sep 9, 2024
b3ae80d
test: 학습 테스트
unifolio0 Sep 10, 2024
a522403
refactor: depth 감소
unifolio0 Sep 10, 2024
3273872
refactor: 특정 페이지에 대한 권한 처리 적용
unifolio0 Sep 10, 2024
a4cc800
refactor: 권한 처리 시 redirect는 제외
unifolio0 Sep 10, 2024
5f3957e
refactor: 4단계 제거
unifolio0 Sep 10, 2024
7277aa2
refactor: 4단계 제거
unifolio0 Sep 10, 2024
79da605
refactor: 상수화
unifolio0 Sep 10, 2024
2740606
refactor: 예외 클래스 변경
unifolio0 Sep 10, 2024
5de4b86
refactor: HttpResponse의 Builder 패턴 간소화
unifolio0 Sep 10, 2024
76debc6
refactor: IO작업 이동
unifolio0 Sep 10, 2024
365c1bf
refactor: HttpHeaderName을 사용하도록 수정
unifolio0 Sep 10, 2024
7c78ae2
refactor: HttpMethod를 사용하도록 수정
unifolio0 Sep 10, 2024
7c54713
refactor: 클래스명 수정
unifolio0 Sep 11, 2024
c9082d1
fix: 로그인이 안되는 오류 수정
unifolio0 Sep 11, 2024
2320bd1
refactor: 컨트롤러에서 Body를 직접 사용하는 로직 이동
unifolio0 Sep 11, 2024
0bd1a98
refactor: body 파싱 시 필터 로직 추가
unifolio0 Sep 11, 2024
027ff0c
refactor: 상수화
unifolio0 Sep 11, 2024
f758221
refactor: 메소드 추출
unifolio0 Sep 11, 2024
581fdcf
refactor: 입력값 검증 로직
unifolio0 Sep 11, 2024
e854bc8
refactor: session을 다루는 로직 이동
unifolio0 Sep 11, 2024
5e51288
refactor: 메소드 분리
unifolio0 Sep 11, 2024
f933ab4
refactor: 메소드 분리
unifolio0 Sep 11, 2024
a7205ea
refactor: 메소드 명 수정
unifolio0 Sep 11, 2024
3604089
refactor: 패키지 이동
unifolio0 Sep 11, 2024
a47cab2
refactor: 예외 처리
unifolio0 Sep 11, 2024
d64f3b6
refactor: HttpMethodTest 작성
unifolio0 Sep 11, 2024
2baf5a6
refactor: 메소드 분리
unifolio0 Sep 11, 2024
e1fbb9f
refactor: 코드 간소화
unifolio0 Sep 11, 2024
f3255e9
refactor: 예외 상황 추가
unifolio0 Sep 11, 2024
5b53b77
test: RequestMappingTest, RegisterControllerTest 추가
unifolio0 Sep 12, 2024
ff86414
test: HttpRequestConvertorTest 작성
unifolio0 Sep 12, 2024
5a28c68
test: HttpCookieConvertorTest 작성
unifolio0 Sep 12, 2024
bd236dc
test: LoginControllerTest 작성
unifolio0 Sep 12, 2024
0039a00
test: HttpResponseTest 작성
unifolio0 Sep 12, 2024
3e6b67d
test: HttpRequestMaker 적용
unifolio0 Sep 12, 2024
cd9c986
refactor: 디버깅용 출력문 삭제
unifolio0 Sep 12, 2024
dbf14ba
test: 오류가 나는 테스트 수정
unifolio0 Sep 12, 2024
afa9586
test: DisplayName 추가
unifolio0 Sep 12, 2024
7b0131d
refactor: Controller의 service 형식 변경
unifolio0 Sep 12, 2024
ef947a1
refactor: 상수화
unifolio0 Sep 12, 2024
98f9e5b
refactor: 로직 이동
unifolio0 Sep 12, 2024
222b4ad
refactor: 역할 이동
unifolio0 Sep 12, 2024
c4a0070
refactor: 상수 이동
unifolio0 Sep 12, 2024
4ec21d2
fix: 세션이 삽입이 안 되는 오류 수정
unifolio0 Sep 12, 2024
4fc70b6
fix: SessionManager 싱글톤화
unifolio0 Sep 12, 2024
1b628be
refactor: 메소드명 수정
unifolio0 Sep 12, 2024
f5a4916
refactor: 코드 간소화
unifolio0 Sep 12, 2024
84a3721
refactor: depth 간소화
unifolio0 Sep 12, 2024
245384b
refactor: 사용하지 않는 메소드 및 변수 삭제
unifolio0 Sep 12, 2024
99a54b9
refactor: 메소드 간소화
unifolio0 Sep 13, 2024
05d3356
refactor: 메소드명 수정
unifolio0 Sep 13, 2024
dae1388
refactor: 클래스 삭제
unifolio0 Sep 13, 2024
99c5f8e
refactor: Concurrent Collections로 수정
unifolio0 Sep 13, 2024
459958d
refactor: 불필요한 테스트 삭제
unifolio0 Sep 13, 2024
3037360
test: 불필요한 변수 삭제
unifolio0 Sep 13, 2024
9e8ea4d
test: RequestBody 생성 테스트 작성
unifolio0 Sep 13, 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
7 changes: 3 additions & 4 deletions study/src/test/java/thread/stage0/SynchronizationTest.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package thread.stage0;

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

import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

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

/**
* 다중 스레드 환경에서 두 개 이상의 스레드가 변경 가능한(mutable) 공유 데이터를 동시에 업데이트하면 경쟁 조건(race condition)이 발생한다.
Expand Down Expand Up @@ -41,7 +40,7 @@ private static final class SynchronizedMethods {

private int sum = 0;

public void calculate() {
public synchronized void calculate() {
Copy link
Contributor

Choose a reason for hiding this comment

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

왜 synchronized를 붙이라고 했을까요?

Copy link
Author

Choose a reason for hiding this comment

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

만약 synchronized가 없으면 테스트 코드에서 실행되고 있는 1000개의 스레드 중 일부가 sum이 정확히 변경되어 저장하기 전에 접근하게 되면서 정확하게 1000번 증가한 값이 나오지 않게 됩니다.
그래서 synchronized를 사용해 한번에 하나의 스레드만 접근 가능하게 하고 다른 스레드는 그동안 대기하게 만들어 스레드 안전성을 보장해줍니다.

setSum(getSum() + 1);
}

Expand Down
15 changes: 7 additions & 8 deletions study/src/test/java/thread/stage0/ThreadPoolsTest.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package thread.stage0;

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.assertj.core.api.Assertions.assertThat;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* 스레드 풀은 무엇이고 어떻게 동작할까?
Expand All @@ -31,8 +30,8 @@ void testNewFixedThreadPool() {
executor.submit(logWithSleep("hello fixed thread pools"));

// 올바른 값으로 바꿔서 테스트를 통과시키자.
final int expectedPoolSize = 0;
final int expectedQueueSize = 0;
final int expectedPoolSize = 2;
Copy link
Contributor

Choose a reason for hiding this comment

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

Executors.newFixedThreadPool(...)와 Executors.newCachedThreadPool()는 어떤 차이가 있나요?

Copy link
Author

Choose a reason for hiding this comment

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

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)

newFixedThreadPool은 매개변수로 주어진 수만큼 스레드를 생성하고 해당 개수만큼의 스레드만 사용합니다. 그래서 해당 테스트에서 3개의 스레드를 실행시켰지만 2개의 스레드만 실행이 되고 남은 1개는 큐에서 대기하게 됩니다.
newCachedThreadPool은 처음에는 스레드를 생성하지 않고 요청이 들어올때마다(코드상으론 Integer.MAX_VALUE만큼) 스레드를 생성하게 됩니다. 따라서 테스트에서 3번의 요청이 들어와서 3개의 스레드가 실행되고 있고 큐에서 대기하고 있는 스레드는 없습니다.

final int expectedQueueSize = 1;

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
assertThat(expectedQueueSize).isEqualTo(executor.getQueue().size());
Expand All @@ -46,7 +45,7 @@ void testNewCachedThreadPool() {
executor.submit(logWithSleep("hello cached thread pools"));

// 올바른 값으로 바꿔서 테스트를 통과시키자.
final int expectedPoolSize = 0;
final int expectedPoolSize = 3;
final int expectedQueueSize = 0;

assertThat(expectedPoolSize).isEqualTo(executor.getPoolSize());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.techcourse.controller;

import org.apache.coyote.http11.HttpMethod;
import org.apache.coyote.http11.httprequest.HttpRequest;
import org.apache.coyote.http11.httpresponse.HttpResponse;

public abstract class AbstractController implements Controller {

@Override
public void service(HttpRequest httpRequest, HttpResponse httpResponse) {
if (httpRequest.isMethod(HttpMethod.GET)) {
doGet(httpRequest, httpResponse);
}
if (httpRequest.isMethod(HttpMethod.POST)) {
doPost(httpRequest, httpResponse);
}

throw new IllegalArgumentException("유효하지 않은 메소드입니다.");
}

abstract protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse);

abstract protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package org.apache.coyote.http11.controller;
package com.techcourse.controller;

Copy link
Contributor

Choose a reason for hiding this comment

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

public interface Controller {
    void service(HttpRequest request, HttpResponse response) throws Exception;
}

Copy link
Author

Choose a reason for hiding this comment

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

해당 형식으로 수정했습니다!

import org.apache.coyote.http11.httprequest.HttpRequest;
import org.apache.coyote.http11.httpresponse.HttpResponse;

public interface Controller {

HttpResponse service(HttpRequest httpRequest);
void service(HttpRequest request, HttpResponse response) throws Exception;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.techcourse.controller;

import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.model.User;
import org.apache.coyote.http11.exception.UnauthorizedException;
import org.apache.coyote.http11.httprequest.HttpRequest;
import org.apache.coyote.http11.httpresponse.HttpResponse;
import org.apache.coyote.http11.session.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoginController extends AbstractController {

private static final Logger log = LoggerFactory.getLogger(LoginController.class);
private static final String LOGIN_PATH = "/login";
private static final String ACCOUNT = "account";
private static final String PASSWORD = "password";
private static final String INDEX_PATH = "/index.html";

@Override
protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
if (validateUserInput(httpRequest)) {
log.error("입력하지 않은 항목이 있습니다.");
redirectLoginPage(httpRequest, httpResponse);
return;
}
acceptLogin(httpRequest, httpResponse);
}

private void redirectLoginPage(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.location(httpRequest, LOGIN_PATH);
}

private boolean validateUserInput(HttpRequest httpRequest) {
return !httpRequest.containsBody(ACCOUNT) || !httpRequest.containsBody(PASSWORD);
}

private void acceptLogin(HttpRequest httpRequest, HttpResponse httpResponse) {
String account = httpRequest.getBodyValue(ACCOUNT);
String password = httpRequest.getBodyValue(PASSWORD);

InMemoryUserRepository.findByAccount(account)
.filter(user -> user.checkPassword(password))
.ifPresentOrElse(
user -> redirectWithCookie(httpRequest, httpResponse, user),
() -> {
log.error("존재하지 않는 계정이거나 비밀번호 불일치");
throw new UnauthorizedException("존재하지 않는 계정입니다");
}
);
}

private void redirectWithCookie(HttpRequest httpRequest, HttpResponse httpResponse, User user) {
Session session = httpRequest.getSession();
session.setUser(user);
log.info(user.toString());
httpResponse.location(httpRequest, INDEX_PATH);
httpResponse.setSession(session);
}

@Override
protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
Session session = httpRequest.getSession();
if (!session.hasUser()) {
httpResponse.ok(httpRequest);
httpResponse.staticResource(LOGIN_PATH);
return;
}
User user = (User) session.getUser();
log.info(user.toString());
httpResponse.location(httpRequest, INDEX_PATH);
httpResponse.setSession(session);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.techcourse.controller;

import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.model.User;
import org.apache.coyote.http11.httprequest.HttpRequest;
import org.apache.coyote.http11.httpresponse.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RegisterController extends AbstractController {

private static final Logger log = LoggerFactory.getLogger(RegisterController.class);
private static final String REGISTER_PATH = "/register";
private static final String ACCOUNT = "account";
private static final String PASSWORD = "password";
private static final String EMAIL = "email";
private static final String INDEX_PATH = "/index.html";

@Override
protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
if (validateUserInput(httpRequest)) {
log.error("입력하지 않은 항목이 있습니다.");
redirectPage(httpRequest, httpResponse, REGISTER_PATH);
return;
}
acceptRegister(httpRequest, httpResponse);
}

private void acceptRegister(HttpRequest httpRequest, HttpResponse httpResponse) {
String account = httpRequest.getBodyValue(ACCOUNT);
String password = httpRequest.getBodyValue(PASSWORD);
String email = httpRequest.getBodyValue(EMAIL);
if (InMemoryUserRepository.containsByAccount(account)) {
log.error("이미 존재하는 account입니다");
redirectPage(httpRequest, httpResponse, REGISTER_PATH);
return;
}
InMemoryUserRepository.save(new User(account, password, email));
redirectPage(httpRequest, httpResponse, INDEX_PATH);
}

private boolean validateUserInput(HttpRequest httpRequest) {
return !httpRequest.containsBody(ACCOUNT)
|| !httpRequest.containsBody(PASSWORD)
|| !httpRequest.containsBody(EMAIL);
}

@Override
protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.ok(httpRequest);
httpResponse.staticResource(httpRequest.getPath());
}

private void redirectPage(HttpRequest httpRequest, HttpResponse httpResponse, String path) {
httpResponse.location(httpRequest, path);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package org.apache.coyote.http11;
package com.techcourse.controller;

import java.util.HashMap;
import java.util.Map;
import org.apache.coyote.http11.controller.Controller;
import org.apache.coyote.http11.controller.LoginController;
import org.apache.coyote.http11.controller.PageController;
import org.apache.coyote.http11.controller.RegisterController;
import org.apache.coyote.http11.exception.NotFoundException;
import org.apache.coyote.http11.httprequest.HttpRequest;

public class RequestMapping {

Expand All @@ -14,14 +12,14 @@ public class RequestMapping {
public RequestMapping() {
controllers.put("/login", new LoginController());
controllers.put("/register", new RegisterController());
controllers.put("page", new PageController());
}

public Controller getController(String path) {
public Controller getController(HttpRequest httpRequest) {
String path = httpRequest.getPath();
if (controllers.containsKey(path)) {
return controllers.get(path);
}

return new PageController();
throw new NotFoundException("존재하지 않는 경로입니다.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,9 @@ public static Optional<User> findByAccount(String account) {
return Optional.ofNullable(database.get(account));
}

public static boolean containsByAccount(String account) {
return database.containsKey(account);
}

private InMemoryUserRepository() {}
}
9 changes: 4 additions & 5 deletions tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.apache.catalina;

import jakarta.servlet.http.HttpSession;

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

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
Expand All @@ -29,7 +28,7 @@ public interface Manager {
*
* @param session Session to be added
*/
void add(HttpSession session);
void add(Session session);

/**
* Return the active Session, associated with this Manager, with the
Expand All @@ -45,12 +44,12 @@ public interface Manager {
* @return the request session or {@code null} if a session with the
* requested ID could not be found
*/
HttpSession findSession(String id) throws IOException;
Session findSession(String id) throws IOException;

/**
* Remove this Session from the active Sessions for this Manager.
*
* @param session Session to be removed
*/
void remove(HttpSession session);
void remove(Session session);
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package org.apache.catalina.connector;

import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Connector implements Runnable {

Expand Down
17 changes: 17 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/http11/CharSet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.apache.coyote.http11;

public enum CharSet {

UTF_8(";charset=utf-8")
;

private final String charset;

CharSet(String charset) {
this.charset = charset;
}

public String getCharset() {
return charset;
}
}
32 changes: 0 additions & 32 deletions tomcat/src/main/java/org/apache/coyote/http11/ContentType.java

This file was deleted.

Loading