Skip to content

Commit

Permalink
[톰캣 구현하기 - 1, 2단계] 말랑(신동훈) 미션 제출합니다. (#310)
Browse files Browse the repository at this point in the history
* IOStreamTest 학습 테스트

* File 학습 테스트

* Http 요청의 StartLine 클래스 생성

* Http 요청의 Header 클래스 생성

* Http 요청의 Body 클래스 생성

* Http 요청 클래스 생성

* Http 요청 파서 구현

* 정적 리소스 응답 기능 구현

* css 리소스 응답 기능 구현

* Query String 파싱 기능 추가

* login으로 요청이 들어온 경우 처리

* HttpRequestParser 에서 null request를 반환하지 않도록 함

* 여러 헤더들을 관리할 일급 컬렉션인 Headers 생성

* Header 클래스 제거하고 Headers에서만 관리

* 요청 처리를 담당할 인터페이스인 RequestHandler 작성

* 응답을 위한 StatusLine 생성

* 응답을 위한 ResponseHeaders 생성

* 요청 Headers 객체의 이름을 RequestHeaders로 변경

* 요청 메세지를 처리하는 HttpResponse 클래스 생성

* 기존 코드에서 HttpResponse를 사용하도록 리팩터링

* ResponseHeaders 테스트코드 패키지 위치 수정

* 정적 파일을 처리하는 RequestHandler 구현

* Http11Processor에서 RequestHandler 사용하도록 수정

* 루트 접근을 처리하는 RootPageRequestHandler 구현

* Http11Processor에서 RootPageRequestHandler 사용하도록 수정

* 로그인 요청을 처리하는 LoginRequestHandler 구현

* Http11Processor에서 LoginRequestHandler 사용하도록 수정

* Resource 폴더에서 파일을 읽는 기능 Util로 추출하여 중복 제거

* RequestHandler에서 직접 요청 처리가 가능한지 판단하도록 구현

* Http11Processor 에서 Handler들을 관리하지 않고 외부에서 주입받을 수 있도록 변경

* StaticResourceRequestHandler 에서 파일의 확장자가 없는 경우 기본적으로 html 파일 찾도록 구현

* 로그인 기능 구현

* 로그인 기능을 담당하는 AuthService 구현

* LoginRequestHandler에서 AuthService 사용

* 로그인 시 post 메서드 사용하도록 변경

get을 사용하면 queryString으로 붙고,
post를 사용하면 body에 붙는다!

* AuthService에 회원가입 기능 추가

* RequestParam 추출하는 기능 Util 클래스로 분리

* 회원가입 처리하는 Handler 구현

* 쿠키 클래스 추가

* 요청과 응답 객체에 Cookie 관련 기능 추가

* 로그인 성공 시 JSESSIONID 쿠키 발급

* 세션 관련 클래스 작성

* get으로 Login 페이지 접속 시 인증되었다면 index로 redirect

* 회원가입 시 비밀번호와 이메일이 바뀌는 오류 해결

* 로그인 시 SessionManager에 세션 정보 저장

* favicon 추가

* request, response 관련 클래스 apache.catalina.servlet 패키지로 이동

* DispatcherServlet 도입

* 응답 메세지 작성을 Http11Processor가 아닌 Servlet에서 진행하도록 책임 위임

* RequestHandler 패키지를 애플리케이션으로 이동

- 톰캣에서 Handler를 모르도록 하기 위함

* RequestHandler 메서드 시그니처 변경

* import 순서 변경

* Polish ResponseEntity

* ResponseBody 작성의 책임을 다시 Http11Processor로 이동

* HttpResponse를 통해 응답 메세지 만드는 책임을 HttpResponseMessageMaker로 위임

* HttpRequestParser static하게 변경

* 자동 정렬된 Index.html 수정

* html수정 2번째

* 컴파일 에러 수정

* StartLine에서 RequestLine으로 변경

* HttpHeader 추출

* 정적 파일 content type 추가

* 버그 수정 & Polishing
  • Loading branch information
shin-mallang authored Sep 7, 2023
1 parent 68db530 commit 3ea87ea
Show file tree
Hide file tree
Showing 62 changed files with 2,521 additions and 229 deletions.
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.requestLine().uri().path().equals("/login")
&& request.requestLine().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.requestLine().uri().path().equals("/login")
&& request.requestLine().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);

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.requestLine().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.requestLine().uri().path().equals("/register")
&& request.requestLine().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,47 @@
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.requestLine().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;";
}
if (uri.path().endsWith(".js")) {
return "text/javascript;";
}
if (uri.path().endsWith(".svg")) {
return "image/svg+xml;";
}
return "text/html;";
}
}
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));
}
}
Loading

0 comments on commit 3ea87ea

Please sign in to comment.