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단계] 푸우(백승준) 미션 제출합니다. #311

Merged
merged 55 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
dca2d56
test: IOStreamTest 작성
BGuga Sep 2, 2023
e563f31
test: FileTest 작성
BGuga Sep 2, 2023
781453a
docs: 기능 목록 작성
BGuga Sep 2, 2023
6815d77
feat: index.html 파일 반환 구현
BGuga Sep 2, 2023
fe895a7
feat: 정적 파일을 반환하는 ResourceProvider 생성
BGuga Sep 2, 2023
352e3ce
feat: CSS 파일 반환 구현
BGuga Sep 2, 2023
01777dc
feat: Method 구현
BGuga Sep 3, 2023
b423dce
feat: RequestLine 객체화
BGuga Sep 3, 2023
91a21d9
fix: 디렉터리 조회시 파일을 반환하지 않도록 변경
BGuga Sep 3, 2023
c99c922
feat: Http11Processor 가 RequestLine 사용하도록 변경
BGuga Sep 3, 2023
9e766d4
fix: ? 를 contatins 과 split 사용에 따라 분리한다
BGuga Sep 3, 2023
0c0458e
style: 패키지 변경
BGuga Sep 3, 2023
f2ef5a9
feat: 컨트롤러 정의
BGuga Sep 3, 2023
33d8368
feat: 1. 정적파일을 찾은 후 2. 컨트롤러를 찾고 3. 못 찾았다면 Not Found page 반환
BGuga Sep 3, 2023
be73de7
fix: queryParam 이 없더라도 login page 를 반환하도록 변경
BGuga Sep 3, 2023
055a841
docs: 추가 기능 목록 추가
BGuga Sep 3, 2023
f5d67b7
feat: Response 객체 생성
BGuga Sep 3, 2023
6d77acc
feat: Redirect 기능 구현
BGuga Sep 3, 2023
db76d4f
feat: Request 객체 구현
BGuga Sep 3, 2023
91d0958
feat: 회원가입 구현 및 로그인 post 변경
BGuga Sep 3, 2023
b122950
feat: Cookie 객체 생성
BGuga Sep 3, 2023
e5553ba
feat: 로그인 혹은 회원가입 시 cookie 발급
BGuga Sep 3, 2023
d3cc7bc
feat: Session, SessionManager 생성
BGuga Sep 3, 2023
d76ad73
feat: LoginService 가 유저를 세션에 저장한다
BGuga Sep 3, 2023
c22ee77
feat: 로그인 되어 Session 에 저장된 유저라면 login, register 요청은 index 로 리다이렉션 한다.
BGuga Sep 3, 2023
b0ffc0c
style: 쿠키 객체 이름 변경
BGuga Sep 3, 2023
c3c3853
refactor: 파일을 Files 를 이용해 읽어온다.
BGuga Sep 3, 2023
9db4b00
refactor: 로그인 검증을 SessionManager 가 담당하도록 변경
BGuga Sep 3, 2023
14c3a37
fix: 세션 쿠키가 없을 경우 empty 를 반환한다.
BGuga Sep 3, 2023
22f500e
refactor: sonarLint 반영
BGuga Sep 3, 2023
2bc7165
refactor: sonarLint 반영
BGuga Sep 3, 2023
ffa5280
refactor: Session 객체를 상속 받지 않도록 변경
BGuga Sep 3, 2023
3131965
style: Request, Response, HttpResponse, Method-> HttpRequest, HttpRes…
BGuga Sep 4, 2023
f961503
refactor: processor 에서 null 대신 예외를 반환하도록 변경
BGuga Sep 4, 2023
76ccbe5
refactor: HttpRequest 생성을 HttpRequest 에게 위임
BGuga Sep 4, 2023
0bac4df
style: processor 메서드 순서 변경
BGuga Sep 4, 2023
0941449
feat: RequestHeaders 필드 private 화
BGuga Sep 4, 2023
7151c94
refactor: HttpRequest 의 불필요한 메서드 지역 변수 제거 및 null 대신 예외를 발생시키도록 변경
BGuga Sep 4, 2023
6221614
refactor: HttpResponse 가 기본 상태코드를 가지지 않도록 변경 및 인스턴스 변수의 final 화
BGuga Sep 4, 2023
d695d9d
refactor: Cookie 변환 로직을 Cookies 에게 위임
BGuga Sep 4, 2023
40cf300
fix: headers 재활용을 위해 메서드 지역 변수 생성
BGuga Sep 4, 2023
29d5233
refactor: RequestHeaders 가 Cookies 를 가지고 있도록 변경
BGuga Sep 4, 2023
a3c0fae
refactor: Session 생성 책임을 SessionManger 에게 할당
BGuga Sep 4, 2023
70b48fd
refactor: 정적 파일 response 반환 책임을 ResourceProvider 에게 위임, 컨트롤러 response…
BGuga Sep 4, 2023
d062222
style: HandlerMapper 오타 수정
BGuga Sep 4, 2023
e9130dd
style: handler 반환 메서드 private
BGuga Sep 4, 2023
79f36c6
fix: 잘못 수정된 html method 수정
BGuga Sep 5, 2023
d7b4556
refactor: 잘못된 Class 명명 수정 및 Body 값에 대한 Optional 처리
BGuga Sep 5, 2023
c14371f
refactor: toURI -> getFile 을 통한 exception handling 과정 삭제
BGuga Sep 5, 2023
ac5e6ac
refactor: css 반환 헤더에 utf-8 적용 및 text/plain header 추가
BGuga Sep 5, 2023
eece233
refactor: ResourceProvider 에서 존재하지 않는 파일을 읽는다면 null 대신 예외를 발생시키도록 변경
BGuga Sep 5, 2023
d258d7e
style: HandlerMapper 에서 body 없는 응답 작성 메서드 네이밍 변경
BGuga Sep 5, 2023
0c23a5e
chore: 정적 필드와 인스턴스 필드 간의 공백추가
BGuga Sep 5, 2023
2909da4
refactor: Enum 의 name method 를 통해 HttpMethod 를 반환하도록 변경
BGuga Sep 5, 2023
2aa6ba1
fix: Option.get 호출 전 isEmpty 를 호출하도록 변경
BGuga Sep 5, 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
43 changes: 24 additions & 19 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,54 +1,59 @@
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.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
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 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_디렉터리에_있는_파일의_경로를_찾는다() {
void resource_디렉터리에_있는_파일의_경로를_찾는다() throws URISyntaxException {
final String fileName = "nextstep.txt";

// todo
final String actual = "";
File file = new File(getClass().getClassLoader().getResource("nextstep.txt").toURI());

Choose a reason for hiding this comment

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

Suggested change
File file = new File(getClass().getClassLoader().getResource("nextstep.txt").toURI());
File file = new File(getClass().getClassLoader().getResource(fileName).toURI());

상수 부분 위에서 정의했던 변수 그대로 사용하는게 좀더 안정적일 것 같아요! (취향인 것 같슴니당)

Choose a reason for hiding this comment

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

참고: . toURI() 대신에 .getFile()을 사용하면 URISyntaxException 예외를 처리하지 않아도 되는 것 같습니다.

Copy link
Author

@BGuga BGuga Sep 5, 2023

Choose a reason for hiding this comment

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

상수 부분 위에서 정의했던 변수 그대로 사용하는게 좀더 안정적일 것 같아요! (취향인 것 같슴니당)

저도 원래 그걸 선호하는데... 급하게 하다보니.. 크흠 ㅋㅋ

참고: . toURI() 대신에 .getFile()을 사용하면 URISyntaxException 예외를 처리하지 않아도 되는 것 같습니다.

오 대박이네요👍 ResourceProvider 에서도 toURI 를 사용해서 try catch 문이 있었는데 제거할 수 있었네요 ㅎㅎ 감사합니다!!

final String actual = file.getName();

assertThat(actual).endsWith(fileName);
}

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

// todo
final Path path = null;
String file = getClass().getClassLoader().getResource(fileName).getFile();
Path path = Path.of(file);

// todo
final List<String> actual = Collections.emptyList();
BufferedReader bufferedReader = Files.newBufferedReader(path);
final List<String> actual = bufferedReader.lines().collect(Collectors.toList());

// then
assertThat(actual).containsOnly("nextstep");
}
}
216 changes: 150 additions & 66 deletions study/src/test/java/study/IOStreamTest.java

Large diffs are not rendered by default.

20 changes: 20 additions & 0 deletions tomcat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Mission 1 : HTTP 서버 구현하기

1. [x] Get /index.html 응답하기
2. [x] CSS 지원하기
- [x] 정적 파일을 지원하는 ResourceProvider 를 생성한다.
- [x] 해당 정적 파일을 읽어온다.
- [x] 해당 정적 파일의 타입을 반환한다.
3. [x] Query String 파싱
- [x] 먼저 정적 파일을 찾는다.
- [x] 요청을 handle 할 수 있는 핸들러를 찾는다.
- [x] 아무것도 못 찾았다면 not Found 페이지 반환.

## Mission 2 : 로그인 구현하기

1. [x] HTTP Status Code 302
2. [x] POST 방식으로 회원가입 및 로그인
- [x] Request 를 생성한다
- [x] 요청을 해결할 수 있는 Contoller 를 Mapper 로 엮어둔다.
3. [x] Cookie에 JSESSIONID 값 저장하기
4. [x] Session 구현하기
12 changes: 7 additions & 5 deletions tomcat/src/main/java/nextstep/jwp/db/InMemoryUserRepository.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
package nextstep.jwp.db;

import nextstep.jwp.model.User;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import nextstep.jwp.model.User;

public class InMemoryUserRepository {

private static final Map<String, User> database = new ConcurrentHashMap<>();
private static Long publishedId = 1L;

static {
final User user = new User(1L, "gugu", "password", "[email protected]");
database.put(user.getAccount(), user);
}

public static void save(User user) {
database.put(user.getAccount(), user);
private InMemoryUserRepository() {
}

public static Optional<User> findByAccount(String account) {
return Optional.ofNullable(database.get(account));
}

private InMemoryUserRepository() {}
public static void save(User user) {
User saveUser = new User(++publishedId, user.getAccount(), user.getPassword(), user.getEmail());
database.put(saveUser.getAccount(), saveUser);

Choose a reason for hiding this comment

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

꼼꼼하게 auto increment까지 👍

Copy link
Author

Choose a reason for hiding this comment

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

세세한 것 까지 봐주셨네요 ㅎㅎ 감사합니다!

}
}
22 changes: 17 additions & 5 deletions tomcat/src/main/java/nextstep/jwp/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,29 @@ public boolean checkPassword(String password) {
return this.password.equals(password);
}

public Long getId() {
return id;
}

public String getAccount() {
return account;
}

public String getPassword() {
return password;
}

public String getEmail() {
return email;
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", account='" + account + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
'}';
"id=" + id +
", account='" + account + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
'}';
}
}
44 changes: 29 additions & 15 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
package org.apache.coyote.http11;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import nextstep.jwp.exception.UncheckedServletException;
import org.apache.coyote.Processor;
import org.apache.coyote.http11.controller.HandlerMapper;
import org.apache.coyote.http11.request.HttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.Socket;

public class Http11Processor implements Runnable, Processor {

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

private final Socket connection;
private final ResourceProvider resourceProvider;
private final HandlerMapper handlerMapper;

public Http11Processor(final Socket connection) {
this.connection = connection;
this.resourceProvider = new ResourceProvider();
this.handlerMapper = new HandlerMapper(this.resourceProvider);
}

@Override
Expand All @@ -26,22 +33,29 @@ public void run() {

@Override
public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
final var outputStream = connection.getOutputStream()) {

final var responseBody = "Hello world!";

final var response = String.join("\r\n",
"HTTP/1.1 200 OK ",
"Content-Type: text/html;charset=utf-8 ",
"Content-Length: " + responseBody.getBytes().length + " ",
"",
responseBody);

try (final var inputReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
final var outputStream = connection.getOutputStream()) {
HttpRequest httpRequest = HttpRequest.makeRequest(inputReader);

Choose a reason for hiding this comment

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

객체 내에서 파싱하도록 아주 잘 설계한 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

감사합니다 ㅎㅎ

String response = getResponse(httpRequest);
outputStream.write(response.getBytes());
outputStream.flush();
} catch (IOException | UncheckedServletException e) {
log.error(e.getMessage(), e);
}
}

private String getResponse(HttpRequest httpRequest) {
if (resourceProvider.haveResource(httpRequest.getRequestLine().getPath())) {
return resourceProvider.staticResourceResponse(httpRequest.getRequestLine().getPath());
}
if (handlerMapper.haveAvailableHandler(httpRequest)) {
return handlerMapper.controllerResponse(httpRequest);
}
return String.join("\r\n",
"HTTP/1.1 200 OK ",
"Content-Type: text/html;charset=utf-8 ",
"Content-Length: 12 ",
"",
"Hello world!");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.apache.coyote.http11;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.util.Optional;

public class ResourceProvider {

public boolean haveResource(String resourcePath) {
return findFileURL(resourcePath).isPresent();
}

public String resourceBodyOf(String resourcePath) {
try {
Optional<URL> fileURL = findFileURL(resourcePath);
if (fileURL.isPresent()) {
URL url = fileURL.get();
return new String(Files.readAllBytes(new File(url.getFile()).toPath()));
}
throw new IllegalArgumentException("파일이 존재하지 않습니다.");
} catch (IOException e) {
throw new IllegalArgumentException("존재하지 않는 파일 입니다.");
}
}

private Optional<URL> findFileURL(String resourcePath) {
URL resourceURL = getClass().getClassLoader().getResource("static" + resourcePath);
if (resourceURL == null) {
return Optional.empty();
}
if (new File(resourceURL.getFile()).isDirectory()) {
return Optional.empty();
}
return Optional.of(resourceURL);
}

public String contentTypeOf(String resourcePath) {
File file = getFile(resourcePath);
String fileName = file.getName();
if (fileName.endsWith(".js")) {
return "Content-Type: text/javascript ";
}
if (fileName.endsWith(".css")) {
return "Content-Type: text/css;charset=utf-8";
}
if (fileName.endsWith(".html")) {
return "Content-Type: text/html;charset=utf-8 ";

Choose a reason for hiding this comment

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

charset=utf-8은 컨텐트 타입이 css인 경우에도 사용해도 괜찮지 않을까요?
공통적인 부분이어서 꼭 html로 분기한 곳에만 넣어놓을 필요가 없을 것 같아요!

Copy link
Author

@BGuga BGuga Sep 5, 2023

Choose a reason for hiding this comment

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

text/css 도 해주는게 좋을 것 같네요!! css 파일 내에서 인코딩 순서를 다음과 결정한다하네요!! 링크
이런걸 전혀 인지하고 있지 못했네요 감사합니다 ㅎㅎ
javascript 의 경우에는 이런 header 가 의미있는지 확인하기가 어렵네요 ㅠㅠ 혹시 어떤 경우에 charset 을 지정해줘야 하나요??

Choose a reason for hiding this comment

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

ㅋㅋ저도 이부분은 잘 모르겠어요..
막 알아보고 만들었다기보다는 요구사항에 있는 최소한만 만족하려고 만들었거등요 ㅎㅎㅎ
직접 할 일은 없을테니 이만큼 알아간거로도 훌륭하지 않을까?(?)

}
return "Content-Type: text/plain";
}

private File getFile(String resourcePath) {
return new File(
findFileURL(resourcePath).orElseThrow((() -> new IllegalArgumentException("파일이 존재하지 않습니다.")))
.getFile());
}

public String staticResourceResponse(String resourcePath) {
String responseBody = resourceBodyOf(resourcePath);
return String.join("\r\n",
"HTTP/1.1 200 OK ",
contentTypeOf(resourcePath),
"Content-Length: " + responseBody.getBytes().length + " ",
"",
responseBody);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.apache.coyote.http11.controller;

import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public interface Controller {

HttpResponse<? extends Object> handle(HttpRequest httpRequest);
}
Loading