Skip to content

Commit

Permalink
[1 -2단계 - Tomcat 구현하기] 아톰(이하늘) 미션 제출합니다. (#531)
Browse files Browse the repository at this point in the history
* fix: remove implementation logback-classic on gradle (#501)

* fix: add threads min-spare configuration on properties (#502)

* feat: index.html 응답 구현

* refactor: 응답값과 contentType 자료구조로 포장

* feat: css, javascript 파일 지원

* feat: login.html 응답 구현

* docs: todo list 작성

* refactor: query parameter 책임 분리

* refactor: process 메서드 가독성 개선

* fix: javascript contentType 변경

* test: 누락된 쿼리 파라미터 테스트 추가

* refactor: header 책임 분리

* refactor: 패키지 이동

* fix: header pair 구분자 변경

* refactor: 정적 파일 핸들링 책임 분리

* refactor: login 책임 분리

* refactor: handler 추상화

* refactor: 추상화된 핸들러 사용하도록 변경

* fix: */* 타입 처리하도록 변경

* fix: hello handler hello.html 반환하도록 변경

* refactor: 불필요한 로그 제거

* chore: import 최적화

* refactor: response 내부로 메시지 생성 책임 위임

* feat: 로그인 redirection 처리

* refactor: 메서드 이름 변경

* refactor: /login get post 분리

* feat: register 페이지 조회 기능 구현

* feat: not found 핸들러 구현

* refactor: content-type 분리

* refactor: forward 결과 포장

* feat: 회원가입 기능 구현

* refactor: http header 상수화

* feat: header 추가 메서드 구현

* fix: null 검증 제거

* chore: import문 최적화

* refactor: forward 내부에서 status, header 다룰 수 있도록 변경

* feat: cookie 클래스 구현

* refactor: 패키지 구조 변경

* feat: session 구현

* feat: 로그인 세션 적용

* style: enum 개행 제거

* refactor: 불필요한 throws 제거

* chore: 학습 테스트 흔적 제거

* chore: p 태그 제거

* refactor: content-type 내부로 content-type 결정 책임 이동

* refactor: header.empty 정적 팩터리 메서드로 변경

* refactor: http version 상수화

* refactor: http request body를 char[]로 변경

* fix: from 메서드에서 case 무시하지 않도록 변경

---------

Co-authored-by: Gyeongho Yang <[email protected]>
  • Loading branch information
le2sky and geoje authored Sep 8, 2024
1 parent 0b698a2 commit 20e36b8
Show file tree
Hide file tree
Showing 44 changed files with 1,375 additions and 30 deletions.
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
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
max: 2
18 changes: 13 additions & 5 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.io.*;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

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

/**
* 자바는 스트림(Stream)으로부터 I/O를 사용한다.
Expand Down Expand Up @@ -39,7 +47,7 @@ class OutputStream_학습_테스트 {
* OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다.
* 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때,
* 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다.
*
*
* write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다.
* <code>write(byte[] data)</code>와 <code>write(byte b[], int off, int len)</code> 메서드는
* 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다.
Expand All @@ -63,7 +71,7 @@ class OutputStream_학습_테스트 {
/**
* 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.
* BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
*
*
* 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자.
* flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.
* Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면
Expand Down Expand Up @@ -108,7 +116,7 @@ class OutputStream_학습_테스트 {
* InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.
* InputStream의 read() 메서드는 기반 메서드이다.
* <code>public abstract int read() throws IOException;</code>
*
*
* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다.
*/
@Nested
Expand Down
1 change: 1 addition & 0 deletions tomcat/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
testImplementation 'org.mockito:mockito-core:5.12.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.2'
}

test {
Expand Down
25 changes: 25 additions & 0 deletions tomcat/src/main/java/com/techcourse/handler/GetLoginHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.techcourse.handler;

import org.apache.catalina.Manager;
import org.apache.coyote.http11.AbstractHandler;
import org.apache.coyote.http11.ForwardResult;
import org.apache.coyote.http11.HttpRequest;
import org.apache.coyote.http11.HttpStatus;

import java.net.URI;

public class GetLoginHandler extends AbstractHandler {

@Override
public boolean canHandle(HttpRequest httpRequest) {
URI uri = httpRequest.getUri();
String path = uri.getPath();

return "/login".equals(path) && httpRequest.getMethod().isGet();
}

@Override
protected ForwardResult forward(HttpRequest httpRequest, Manager sessionManager) {
return new ForwardResult("login.html", HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.techcourse.handler;

import org.apache.catalina.Manager;
import org.apache.coyote.http11.AbstractHandler;
import org.apache.coyote.http11.ForwardResult;
import org.apache.coyote.http11.HttpRequest;
import org.apache.coyote.http11.HttpStatus;

import java.net.URI;

public class GetRegisterHandler extends AbstractHandler {

@Override
public boolean canHandle(HttpRequest httpRequest) {
URI uri = httpRequest.getUri();
String path = uri.getPath();

return "/register".equals(path) && httpRequest.getMethod().isGet();
}

@Override
protected ForwardResult forward(HttpRequest httpRequest, Manager sessionManager) {
return new ForwardResult("register.html", HttpStatus.OK);
}
}
25 changes: 25 additions & 0 deletions tomcat/src/main/java/com/techcourse/handler/HelloHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.techcourse.handler;

import org.apache.catalina.Manager;
import org.apache.coyote.http11.AbstractHandler;
import org.apache.coyote.http11.ForwardResult;
import org.apache.coyote.http11.HttpRequest;
import org.apache.coyote.http11.HttpStatus;

import java.net.URI;

public class HelloHandler extends AbstractHandler {

@Override
public boolean canHandle(HttpRequest httpRequest) {
URI uri = httpRequest.getUri();
String path = uri.getPath();

return "/".equals(path);
}

@Override
protected ForwardResult forward(HttpRequest httpRequest, Manager sessionManager) {
return new ForwardResult("hello.html", HttpStatus.OK);
}
}
20 changes: 20 additions & 0 deletions tomcat/src/main/java/com/techcourse/handler/NotFoundHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.techcourse.handler;

import org.apache.catalina.Manager;
import org.apache.coyote.http11.AbstractHandler;
import org.apache.coyote.http11.ForwardResult;
import org.apache.coyote.http11.HttpRequest;
import org.apache.coyote.http11.HttpStatus;

public class NotFoundHandler extends AbstractHandler {

@Override
public boolean canHandle(HttpRequest httpRequest) {
return true;
}

@Override
protected ForwardResult forward(HttpRequest httpRequest, Manager sessionManager) {
return new ForwardResult("404.html", HttpStatus.NOT_FOUND);
}
}
61 changes: 61 additions & 0 deletions tomcat/src/main/java/com/techcourse/handler/PostLoginHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.techcourse.handler;

import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.model.User;
import jakarta.servlet.http.HttpSession;
import org.apache.catalina.Manager;
import org.apache.coyote.http11.AbstractHandler;
import org.apache.coyote.http11.ForwardResult;
import org.apache.coyote.http11.Header;
import org.apache.coyote.http11.HttpHeaderKey;
import org.apache.coyote.http11.HttpRequest;
import org.apache.coyote.http11.HttpStatus;
import org.apache.coyote.http11.QueryParameter;

import java.net.URI;

public class PostLoginHandler extends AbstractHandler {

@Override
public boolean canHandle(HttpRequest httpRequest) {
URI uri = httpRequest.getUri();
String path = uri.getPath();

return "/login".equals(path) && httpRequest.getMethod().isPost();
}

@Override
protected ForwardResult forward(HttpRequest httpRequest, Manager sessionManager) {
if (httpRequest.hasNotApplicationXW3FormUrlEncodedBody()) {
throw new RuntimeException();
}

QueryParameter queryParameter = new QueryParameter(httpRequest.body());
Header header = Header.empty();
String redirectionPath = "401.html";

if (isLoggedIn(queryParameter)) {
HttpSession session = findSessionOrCreate(sessionManager, createCookie(httpRequest));
session.setAttribute("user", getUser(queryParameter));
header.append(HttpHeaderKey.SET_COOKIE, getSessionKey() + "=" + session.getId());
redirectionPath = "index.html";
}

header.append(HttpHeaderKey.LOCATION, redirectionPath);
return new ForwardResult(HttpStatus.FOUND, header);
}

private boolean isLoggedIn(QueryParameter queryParameter) {
String password = queryParameter.get("password").orElse("");

return queryParameter.get("account")
.flatMap(InMemoryUserRepository::findByAccount)
.map(it -> it.checkPassword(password))
.orElse(false);
}

private User getUser(QueryParameter queryParameter) {
String account = queryParameter.get("account").orElseThrow();
return InMemoryUserRepository.findByAccount(account).orElseThrow();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.techcourse.handler;

import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.model.User;
import org.apache.catalina.Manager;
import org.apache.coyote.http11.AbstractHandler;
import org.apache.coyote.http11.ForwardResult;
import org.apache.coyote.http11.HttpRequest;
import org.apache.coyote.http11.HttpStatus;
import org.apache.coyote.http11.QueryParameter;

import java.net.URI;

public class PostRegisterHandler extends AbstractHandler {

@Override
public boolean canHandle(HttpRequest httpRequest) {
URI uri = httpRequest.getUri();
String path = uri.getPath();

return "/register".equals(path) && httpRequest.getMethod().isPost();
}

@Override
protected ForwardResult forward(HttpRequest httpRequest, Manager sessionManager) {
if (httpRequest.hasNotApplicationXW3FormUrlEncodedBody()) {
throw new RuntimeException();
}

registerNewUser(httpRequest);

return new ForwardResult("index.html", HttpStatus.OK);
}

private void registerNewUser(HttpRequest httpRequest) {
QueryParameter body = new QueryParameter(httpRequest.body());
String account = body.get("account").orElseThrow();
String password = body.get("password").orElseThrow();
String email = body.get("email").orElseThrow();

InMemoryUserRepository.save(new User(account, password, email));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.apache.catalina.connector;

import org.apache.catalina.Manager;
import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -17,15 +18,18 @@ public class Connector implements Runnable {
private static final int DEFAULT_ACCEPT_COUNT = 100;

private final ServerSocket serverSocket;
private final Manager sessionManager;

private boolean stopped;

public Connector() {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT);
public Connector(Manager sessionManager) {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, sessionManager);
}

public Connector(final int port, final int acceptCount) {
public Connector(final int port, final int acceptCount, final Manager sessionManager) {
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
this.sessionManager = sessionManager;
}

private ServerSocket createServerSocket(final int port, final int acceptCount) {
Expand Down Expand Up @@ -66,7 +70,7 @@ private void process(final Socket connection) {
if (connection == null) {
return;
}
var processor = new Http11Processor(connection);
var processor = new Http11Processor(connection, sessionManager);
new Thread(processor).start();
}

Expand Down
Loading

0 comments on commit 20e36b8

Please sign in to comment.