Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[톰캣 구현하기 - 1, 2단계] 말랑(신동훈) 미션 제출합니다. #310

Merged
merged 63 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from 59 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
bf7b8a3
IOStreamTest 학습 테스트
shin-mallang Sep 2, 2023
27b7c04
File 학습 테스트
shin-mallang Sep 2, 2023
932f969
Http 요청의 StartLine 클래스 생성
shin-mallang Sep 2, 2023
50a20df
Http 요청의 Header 클래스 생성
shin-mallang Sep 2, 2023
f66c7a5
Http 요청의 Body 클래스 생성
shin-mallang Sep 2, 2023
42326d2
Http 요청 클래스 생성
shin-mallang Sep 2, 2023
9624e20
Http 요청 파서 구현
shin-mallang Sep 2, 2023
95e2039
정적 리소스 응답 기능 구현
shin-mallang Sep 2, 2023
de573ee
css 리소스 응답 기능 구현
shin-mallang Sep 2, 2023
ba8f91f
Query String 파싱 기능 추가
shin-mallang Sep 2, 2023
94c03c9
login으로 요청이 들어온 경우 처리
shin-mallang Sep 2, 2023
b9542c7
HttpRequestParser 에서 null request를 반환하지 않도록 함
shin-mallang Sep 3, 2023
ec636bc
여러 헤더들을 관리할 일급 컬렉션인 Headers 생성
shin-mallang Sep 3, 2023
a621fab
Header 클래스 제거하고 Headers에서만 관리
shin-mallang Sep 3, 2023
fc4aa76
요청 처리를 담당할 인터페이스인 RequestHandler 작성
shin-mallang Sep 3, 2023
f3de804
응답을 위한 StatusLine 생성
shin-mallang Sep 3, 2023
180b1e8
응답을 위한 ResponseHeaders 생성
shin-mallang Sep 3, 2023
c3bf8a1
요청 Headers 객체의 이름을 RequestHeaders로 변경
shin-mallang Sep 3, 2023
9873ec6
요청 메세지를 처리하는 HttpResponse 클래스 생성
shin-mallang Sep 3, 2023
e4fe278
기존 코드에서 HttpResponse를 사용하도록 리팩터링
shin-mallang Sep 3, 2023
aea114e
ResponseHeaders 테스트코드 패키지 위치 수정
shin-mallang Sep 3, 2023
300d736
정적 파일을 처리하는 RequestHandler 구현
shin-mallang Sep 3, 2023
4ad344b
Http11Processor에서 RequestHandler 사용하도록 수정
shin-mallang Sep 3, 2023
e0b9401
루트 접근을 처리하는 RootPageRequestHandler 구현
shin-mallang Sep 3, 2023
9e4dc50
Http11Processor에서 RootPageRequestHandler 사용하도록 수정
shin-mallang Sep 3, 2023
167442f
로그인 요청을 처리하는 LoginRequestHandler 구현
shin-mallang Sep 3, 2023
19f80a8
Http11Processor에서 LoginRequestHandler 사용하도록 수정
shin-mallang Sep 3, 2023
1425277
Resource 폴더에서 파일을 읽는 기능 Util로 추출하여 중복 제거
shin-mallang Sep 3, 2023
f606a6b
RequestHandler에서 직접 요청 처리가 가능한지 판단하도록 구현
shin-mallang Sep 3, 2023
24668ca
Http11Processor 에서 Handler들을 관리하지 않고 외부에서 주입받을 수 있도록 변경
shin-mallang Sep 3, 2023
497be0d
StaticResourceRequestHandler 에서 파일의 확장자가 없는 경우 기본적으로 html 파일 찾도록 구현
shin-mallang Sep 3, 2023
cfcf2cb
로그인 기능 구현
shin-mallang Sep 3, 2023
5837a05
로그인 기능을 담당하는 AuthService 구현
shin-mallang Sep 3, 2023
cbcb49c
LoginRequestHandler에서 AuthService 사용
shin-mallang Sep 3, 2023
ec0fa45
로그인 시 post 메서드 사용하도록 변경
shin-mallang Sep 3, 2023
6857fcd
AuthService에 회원가입 기능 추가
shin-mallang Sep 3, 2023
9d1835d
RequestParam 추출하는 기능 Util 클래스로 분리
shin-mallang Sep 3, 2023
388c3df
회원가입 처리하는 Handler 구현
shin-mallang Sep 3, 2023
9458968
쿠키 클래스 추가
shin-mallang Sep 3, 2023
fca8524
요청과 응답 객체에 Cookie 관련 기능 추가
shin-mallang Sep 3, 2023
bd4c3cf
로그인 성공 시 JSESSIONID 쿠키 발급
shin-mallang Sep 3, 2023
db585a6
세션 관련 클래스 작성
shin-mallang Sep 3, 2023
6d28786
get으로 Login 페이지 접속 시 인증되었다면 index로 redirect
shin-mallang Sep 3, 2023
10fee3d
회원가입 시 비밀번호와 이메일이 바뀌는 오류 해결
shin-mallang Sep 3, 2023
23491e2
로그인 시 SessionManager에 세션 정보 저장
shin-mallang Sep 3, 2023
62c9efe
favicon 추가
shin-mallang Sep 3, 2023
6f3340a
request, response 관련 클래스 apache.catalina.servlet 패키지로 이동
shin-mallang Sep 3, 2023
4ed47a9
DispatcherServlet 도입
shin-mallang Sep 3, 2023
094dcb8
응답 메세지 작성을 Http11Processor가 아닌 Servlet에서 진행하도록 책임 위임
shin-mallang Sep 3, 2023
9c999c8
RequestHandler 패키지를 애플리케이션으로 이동
shin-mallang Sep 3, 2023
3237397
RequestHandler 메서드 시그니처 변경
shin-mallang Sep 3, 2023
06cdaa7
import 순서 변경
shin-mallang Sep 3, 2023
9ef8053
Polish ResponseEntity
shin-mallang Sep 3, 2023
b00ff58
ResponseBody 작성의 책임을 다시 Http11Processor로 이동
shin-mallang Sep 3, 2023
8e8edfc
HttpResponse를 통해 응답 메세지 만드는 책임을 HttpResponseMessageMaker로 위임
shin-mallang Sep 3, 2023
6d435c2
HttpRequestParser static하게 변경
shin-mallang Sep 3, 2023
bcb482b
자동 정렬된 Index.html 수정
shin-mallang Sep 3, 2023
b35be00
html수정 2번째
shin-mallang Sep 3, 2023
df869a8
컴파일 에러 수정
shin-mallang Sep 3, 2023
a3e20b3
StartLine에서 RequestLine으로 변경
shin-mallang Sep 6, 2023
78616b7
HttpHeader 추출
shin-mallang Sep 6, 2023
14f7ae3
정적 파일 content type 추가
shin-mallang Sep 6, 2023
9d3f4d4
버그 수정 & Polishing
shin-mallang Sep 6, 2023
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
53 changes: 24 additions & 29 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,54 +1,49 @@
package study;

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

import java.nio.file.Path;
import java.util.Collections;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import java.util.stream.Collectors;
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 = "";

String fileName = "nextstep.txt";
URL resource = getClass().getClassLoader().getResource(fileName);
String actual = resource.getFile();
assertThat(actual).endsWith(fileName);
}

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

// todo
final Path path = null;

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

assertThat(actual).containsOnly("nextstep");
String fileName = "nextstep.txt";
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fileName);
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
List<String> actual = bufferedReader.lines().collect(Collectors.toList());
assertThat(actual).containsOnly("nextstep");
} catch (Exception e) {
}
}
}
202 changes: 104 additions & 98 deletions study/src/test/java/study/IOStreamTest.java

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion tomcat/src/main/java/nextstep/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
package nextstep;

import java.util.List;
import nextstep.jwp.handler.DispatcherServlet;
import nextstep.jwp.handler.LoginPageHandler;
import nextstep.jwp.handler.LoginRequestHandler;
import nextstep.jwp.handler.RootPageRequestHandler;
import nextstep.jwp.handler.SignUpRequestHandler;
import nextstep.jwp.handler.StaticResourceRequestHandler;
import nextstep.jwp.handler.mappaing.HandlerMapping;
import org.apache.catalina.startup.Tomcat;

public class Application {

public static void main(String[] args) {
final var tomcat = new Tomcat();
HandlerMapping mapping = new HandlerMapping(List.of(
new RootPageRequestHandler(),
new LoginPageHandler(),
new LoginRequestHandler(),
new SignUpRequestHandler(),
new StaticResourceRequestHandler()
));
DispatcherServlet dispatcherServlet = new DispatcherServlet(mapping);
final var tomcat = new Tomcat(dispatcherServlet);
tomcat.start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nextstep.jwp.exception;

public class UnAuthenticatedException extends RuntimeException {

public UnAuthenticatedException() {
super("인증에 실패하였습니다.");
}
}
22 changes: 22 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/handler/DispatcherServlet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package nextstep.jwp.handler;

import nextstep.jwp.handler.mappaing.HandlerMapping;
import org.apache.catalina.servlet.Servlet;
import org.apache.catalina.servlet.request.HttpRequest;
import org.apache.catalina.servlet.response.HttpResponse;

public class DispatcherServlet implements Servlet {

private final HandlerMapping mapping;

public DispatcherServlet(HandlerMapping mapping) {
this.mapping = mapping;
}

@Override
public void service(HttpRequest request, HttpResponse response) {
RequestHandler handler = mapping.getHandler(request);
// TODO null 처리
handler.handle(request, response);
}
}
38 changes: 38 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/handler/LoginPageHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package nextstep.jwp.handler;

import static org.apache.catalina.servlet.response.HttpStatus.FOUND;
import static org.apache.catalina.servlet.response.HttpStatus.OK;
import static org.apache.catalina.servlet.session.SessionConstant.JSESSIONID;

import nextstep.jwp.util.ResourceFileUtil;
import org.apache.catalina.servlet.request.HttpRequest;
import org.apache.catalina.servlet.response.HttpResponse;
import org.apache.catalina.servlet.response.StatusLine;
import org.apache.catalina.servlet.session.SessionManager;

public class LoginPageHandler implements RequestHandler {

@Override
public boolean canHandle(HttpRequest request) {
return request.startLine().uri().path().equals("/login")
&& request.startLine().method().equals("GET");
}

@Override
public void handle(HttpRequest request, HttpResponse response) {
if (isAuthenticated(request)) {
response.setStatusLine(new StatusLine(FOUND));
response.addHeader("Location", "/index.html");
} else {
response.setStatusLine(new StatusLine(OK));
response.addHeader("Content-Type", "text/html;charset=utf-8");
String resource = ResourceFileUtil.readAll("static" + "/login.html");
response.setMessageBody(resource);
}
}

private boolean isAuthenticated(HttpRequest request) {
String id = request.cookies().get(JSESSIONID);
return SessionManager.findSession(id) != null;
}
}
47 changes: 47 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/handler/LoginRequestHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package nextstep.jwp.handler;

import static org.apache.catalina.servlet.response.HttpStatus.FOUND;
import static org.apache.catalina.servlet.session.SessionConstant.JSESSIONID;

import java.util.Map;
import java.util.UUID;
import nextstep.jwp.exception.UnAuthenticatedException;
import nextstep.jwp.model.User;
import nextstep.jwp.service.AuthService;
import org.apache.catalina.servlet.request.HttpRequest;
import org.apache.catalina.servlet.response.HttpResponse;
import org.apache.catalina.servlet.response.StatusLine;
import org.apache.catalina.servlet.session.Session;
import org.apache.catalina.servlet.session.SessionManager;
import org.apache.catalina.servlet.util.RequestParamUtil;
import org.apache.coyote.http11.Http11Processor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoginRequestHandler implements RequestHandler {

private static final Logger log = LoggerFactory.getLogger(Http11Processor.class);
private final AuthService authService = new AuthService();

@Override
public boolean canHandle(HttpRequest request) {
return request.startLine().uri().path().equals("/login")
&& request.startLine().method().equals("POST");
}

@Override
public void handle(HttpRequest request, HttpResponse response) {
Map<String, String> formData = RequestParamUtil.parse(request.body());
response.setStatusLine(new StatusLine(FOUND));
try {
User user = authService.login(formData.get("account"), formData.get("password"));
log.info("User={}", user);
response.addHeader("Location", "/index.html");
Session session = new Session(UUID.randomUUID().toString());
SessionManager.add(session);
response.addCookie(JSESSIONID, session.id());
} catch (UnAuthenticatedException e) {
response.addHeader("Location", "/401.html");
}
}
}
11 changes: 11 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/handler/RequestHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.jwp.handler;

import org.apache.catalina.servlet.request.HttpRequest;
import org.apache.catalina.servlet.response.HttpResponse;

public interface RequestHandler {

boolean canHandle(HttpRequest request);
shin-mallang marked this conversation as resolved.
Show resolved Hide resolved

void handle(HttpRequest request, HttpResponse response);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package nextstep.jwp.handler;

import static org.apache.catalina.servlet.response.HttpStatus.OK;

import org.apache.catalina.servlet.request.HttpRequest;
import org.apache.catalina.servlet.response.HttpResponse;
import org.apache.catalina.servlet.response.StatusLine;

public class RootPageRequestHandler implements RequestHandler {

@Override
public boolean canHandle(HttpRequest request) {
return request.startLine().uri().path().equals("/");
}

@Override
public void handle(HttpRequest request, HttpResponse response) {
String responseBody = "Hello world!";
response.setStatusLine(new StatusLine(OK));
response.addHeader("Content-Type", "text/html;charset=utf-8");
response.setMessageBody(responseBody);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package nextstep.jwp.handler;

import static org.apache.catalina.servlet.response.HttpStatus.FOUND;

import java.util.Map;
import nextstep.jwp.service.AuthService;
import org.apache.catalina.servlet.request.HttpRequest;
import org.apache.catalina.servlet.response.HttpResponse;
import org.apache.catalina.servlet.response.StatusLine;
import org.apache.catalina.servlet.util.RequestParamUtil;

public class SignUpRequestHandler implements RequestHandler {

private final AuthService authService = new AuthService();

@Override
public boolean canHandle(HttpRequest request) {
return request.startLine().uri().path().equals("/register")
&& request.startLine().method().equals("POST");
}

@Override
public void handle(HttpRequest request, HttpResponse response) {
Map<String, String> formData = RequestParamUtil.parse(request.body());
authService.signUp(formData.get("account"), formData.get("email"), formData.get("password"));
response.setStatusLine(new StatusLine(FOUND));
response.addHeader("Location", "/index.html");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package nextstep.jwp.handler;

import static org.apache.catalina.servlet.response.HttpStatus.OK;

import nextstep.jwp.util.ResourceFileUtil;
import org.apache.catalina.servlet.request.HttpRequest;
import org.apache.catalina.servlet.request.URI;
import org.apache.catalina.servlet.response.HttpResponse;
import org.apache.catalina.servlet.response.StatusLine;

public class StaticResourceRequestHandler implements RequestHandler {

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

@Override
public void handle(HttpRequest request, HttpResponse response) {
URI uri = request.startLine().uri();
String responseBody = ResourceFileUtil.readAll("static" + path(uri));
response.setStatusLine(new StatusLine(OK));
response.addHeader("Content-Type", contentType(uri) + "charset=utf-8");
response.setMessageBody(responseBody);
}

private String path(URI uri) {
if (uri.path().contains(".")) {
return uri.path();
} else {
return uri.path() + ".html";
}
}

private String contentType(URI uri) {
if (uri.path().endsWith(".css")) {
return "text/css;";
}
return "text/html;";
}
Ohjintaek marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package nextstep.jwp.handler.mappaing;

import java.util.List;
import javax.annotation.Nullable;
import nextstep.jwp.handler.RequestHandler;
import org.apache.catalina.servlet.request.HttpRequest;

public class HandlerMapping {

private final List<RequestHandler> handler;

public HandlerMapping(List<RequestHandler> handler) {
this.handler = handler;
}

public @Nullable RequestHandler getHandler(HttpRequest request) {
for (RequestHandler requestHandler : handler) {
if (requestHandler.canHandle(request)) {
return requestHandler;
}
}
return null;
}
}
31 changes: 31 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package nextstep.jwp.service;

import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.exception.UnAuthenticatedException;
import nextstep.jwp.model.User;

public class AuthService {

public User login(String account, String password) {
validateNonNull(account, password);
User user = InMemoryUserRepository.findByAccount(account)
.orElseThrow(UnAuthenticatedException::new);
if (!user.checkPassword(password)) {
throw new UnAuthenticatedException();
}
return user;
}

private void validateNonNull(Object... objects) {
for (Object object : objects) {
if (object == null) {
throw new UnAuthenticatedException();
}
}
}

public void signUp(String account, String email, String password) {
validateNonNull(account, email, password);
InMemoryUserRepository.save(new User(account, password, email));
Ohjintaek marked this conversation as resolved.
Show resolved Hide resolved
}
}
Loading