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단계] 이오(이지우) 미션 제출합니다. #343

Merged
merged 24 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
00797bc
test(IOStreamTest): IOStream 학습
LJW25 Sep 3, 2023
5d15f31
feat(Http11Request): HttpRequest 객체 생성
LJW25 Sep 3, 2023
722eaad
feat(Http11Response): HttpResponse 객체 생성
LJW25 Sep 3, 2023
8986f2d
feat(Http11Processor): 인덱스 페이지 접근 구현 (미션1-1)
LJW25 Sep 3, 2023
c647bc9
refactor(Http11Processor): NPE 방지 추가
LJW25 Sep 3, 2023
e038285
feat(Http11*): css 설정 구현
LJW25 Sep 3, 2023
fe1459f
feat(Http11*): QueryString 파싱 구현
LJW25 Sep 3, 2023
a83ca3e
feat(Http11Response): Response 객체 상태코드 추가
LJW25 Sep 3, 2023
9f0af06
feat(Http11Processor): 로그인 여부에 따른 페이지 이동 구현
LJW25 Sep 3, 2023
d95d07c
chore(login.html): 로그인 요청 방식 변경
LJW25 Sep 3, 2023
a4f5414
feat(Http11*): POST 구현
LJW25 Sep 3, 2023
54dbe20
feat(Http11*): Cookie 구현
LJW25 Sep 3, 2023
63ce5fe
feat(Http11*): Session 구현
LJW25 Sep 3, 2023
c840b3e
refactor(all): 구조 변경 및 컨벤션 적용
LJW25 Sep 4, 2023
6fdde09
test(Http11Processor): 테스트코드 추가
LJW25 Sep 4, 2023
40c75a8
refactor(Http11*): 컨벤션 리팩토링
LJW25 Sep 5, 2023
e3c9ece
refactor(UserService): Optional 사용 방식 변경
LJW25 Sep 5, 2023
a35bb6f
refactor(UserService): 메소드 네이밍 변경
LJW25 Sep 5, 2023
5e1f9dd
refactor(Http*): 클래스 네이밍 변경
LJW25 Sep 5, 2023
de99202
refactor(Http11Processor): 예외 처리 방식 변경
LJW25 Sep 5, 2023
702c0b7
refactor(Constant): 상수처리
LJW25 Sep 5, 2023
a0b819c
feat(*Header): Header Enum 구현
LJW25 Sep 6, 2023
d62ae11
refactor(Status): status 메소드 위치 변경
LJW25 Sep 6, 2023
90b384e
refactor(all): 코드 리팩토링
LJW25 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
38 changes: 35 additions & 3 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.io.*;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.in;
import static org.mockito.Mockito.*;

/**
Expand Down Expand Up @@ -53,6 +54,7 @@ class OutputStream_학습_테스트 {
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/
outputStream.write(bytes);

final String actual = outputStream.toString();

Expand All @@ -78,6 +80,7 @@ class OutputStream_학습_테스트 {
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
outputStream.flush();

verify(outputStream, atLeastOnce()).flush();
outputStream.close();
Expand All @@ -96,6 +99,12 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
try (final var output = outputStream) {
output.write("test".getBytes());
output.flush();
} catch (IOException e) {
System.out.println(e.getMessage());
}

verify(outputStream, atLeastOnce()).close();
}
Expand Down Expand Up @@ -128,7 +137,10 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
final OutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(inputStream.readAllBytes());

final String actual = outputStream.toString();

assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
Expand All @@ -148,6 +160,11 @@ class InputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
try (final var input = inputStream) {
input.read();
} catch (IOException e) {
System.out.println(e.getMessage());
}

verify(inputStream, atLeastOnce()).close();
}
Expand All @@ -172,9 +189,14 @@ class FilterStream_학습_테스트 {
void 필터인_BufferedInputStream를_사용해보자() {
final String text = "필터에 연결해보자.";
final InputStream inputStream = new ByteArrayInputStream(text.getBytes());
final InputStream bufferedInputStream = null;
final InputStream bufferedInputStream = new BufferedInputStream(inputStream);

final byte[] actual = new byte[0];
final byte[] actual;
try (bufferedInputStream){
actual = bufferedInputStream.readAllBytes();
} catch (IOException e) {
throw new RuntimeException();
}

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
Expand Down Expand Up @@ -204,8 +226,18 @@ class InputStreamReader_학습_테스트 {
"😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
"");
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

final StringBuilder actual = new StringBuilder();
try (bufferedReader){
for(String s = bufferedReader.readLine(); s != null; s = bufferedReader.readLine()){
actual.append(s);
actual.append("\r\n");
}
} catch (IOException e) {
System.out.println(e.getMessage());
}

assertThat(actual).hasToString(emoji);
}
Expand Down
55 changes: 55 additions & 0 deletions tomcat/src/main/java/nextstep/jwp/service/UserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package nextstep.jwp.service;

import java.util.Map;
import java.util.UUID;
import nextstep.jwp.db.InMemoryUserRepository;
import nextstep.jwp.model.User;
import org.apache.coyote.http11.Session;
import org.apache.coyote.http11.SessionManager;
import org.apache.coyote.http11.exception.UnauthorizedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class UserService {
Copy link

Choose a reason for hiding this comment

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

UserService 👍

전 이 생각을 못하고 그냥 InMemoryUserRepository를 가져다 썼는데 좋은 접근 방법인 것 같습니다

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

public String logIn(final Map<String, String> requestBody) {
final String account = requestBody.get("account");
final String password = requestBody.get("password");

final User user = InMemoryUserRepository.findByAccount(account)
.orElseThrow(() -> new UnauthorizedException(account));

if (!user.checkPassword(password)) {
throw new UnauthorizedException(account);
}
log.info("{}", user);

return setSession(user);
}

private String setSession(final User user) {
final String id = UUID.randomUUID().toString();
final Session session = new Session(id);
session.setAttribute("user", user);
SessionManager.add(session);
return id;
}

public String register(final Map<String, String> requestBody) {
final String account = requestBody.get("account");
final String password = requestBody.get("password");
final String email = requestBody.get("email");

InMemoryUserRepository.findByAccount(account)
.ifPresent(ignored -> {
throw new UnauthorizedException(account);
});

final User user = new User(account, password, email);
InMemoryUserRepository.save(user);

return setSession(user);
}

}
192 changes: 178 additions & 14 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,48 @@
package org.apache.coyote.http11;

import static org.apache.coyote.http11.request.Method.GET;
import static org.apache.coyote.http11.request.Method.POST;
import static org.apache.coyote.http11.request.RequestHeader.ACCEPT;
import static org.apache.coyote.http11.response.ResponseHeader.LOCATION;
import static org.apache.coyote.http11.response.ResponseHeader.SET_COOKIE;
import static org.apache.coyote.http11.response.Status.FOUND;
import static org.apache.coyote.http11.response.Status.INTERNAL_SERVER_ERROR;
import static org.apache.coyote.http11.response.Status.NOT_FOUND;
import static org.apache.coyote.http11.response.Status.OK;
import static org.apache.coyote.http11.response.Status.UNAUTHORIZED;
import static org.apache.coyote.http11.utils.Constant.BASE_PATH;
import static org.apache.coyote.http11.utils.Constant.COOKIE_DELIMITER;
import static org.apache.coyote.http11.utils.Constant.EMPTY;
import static org.apache.coyote.http11.utils.Constant.LINE_SEPARATOR;
import static org.apache.coyote.http11.utils.Parser.parseFormData;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URL;
import java.util.Map;
import nextstep.jwp.exception.UncheckedServletException;
import nextstep.jwp.service.UserService;
import org.apache.coyote.Processor;
import org.apache.coyote.http11.exception.PageNotFoundException;
import org.apache.coyote.http11.exception.UnauthorizedException;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.Method;
import org.apache.coyote.http11.request.RequestHeader;
import org.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.ResponseHeader;
import org.apache.coyote.http11.response.Status;
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 String SESSION_COOKIE_NAME = "JSESSIONID";
private static final Logger log = LoggerFactory.getLogger(Http11Processor.class);

private final Socket connection;
private final UserService userService = new UserService();

public Http11Processor(final Socket connection) {
this.connection = connection;
Expand All @@ -29,19 +59,153 @@ public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
final var outputStream = connection.getOutputStream()) {

final var responseBody = "Hello world!";
final HttpRequest request = readRequest(inputStream);
final HttpResponse response = handleRequest(request);

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);

outputStream.write(response.getBytes());
outputStream.write(response.getResponse().getBytes());
outputStream.flush();
} catch (IOException | UncheckedServletException e) {

} catch (final IOException | UncheckedServletException e) {
log.error(e.getMessage(), e);
}
}

private HttpRequest readRequest(final InputStream inputStream) {
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

final HttpRequest request = readRequestHeader(bufferedReader);
final String contentLength = request.getHeader(RequestHeader.CONTENT_LENGTH);
if (contentLength != null) {
final String requestBody = readRequestBody(bufferedReader, Integer.parseInt(contentLength));
request.setBody(requestBody);
}
return request;
}

private HttpRequest readRequestHeader(final BufferedReader bufferedReader) {
final StringBuilder input = new StringBuilder();
try {
for (String s = bufferedReader.readLine();
!EMPTY.equals(s);
s = bufferedReader.readLine()) {
input.append(s)
.append(LINE_SEPARATOR);
}
} catch (final IOException e) {
log.error(e.getMessage(), e);
}

return HttpRequest.from(input.toString());
}

private String readRequestBody(final BufferedReader bufferedReader, final int contentLength) {
final char[] buffer = new char[contentLength];
try {
bufferedReader.read(buffer, 0, contentLength);
} catch (final IOException e) {
log.error(e.getMessage(), e);
}
return new String(buffer);
}

private HttpResponse handleRequest(final HttpRequest request) {
try {
return handleRequestWithMethod(request);
} catch (final PageNotFoundException e) {
log.error(e.getMessage());
return handleResponseWithException(NOT_FOUND);
} catch (final UnauthorizedException e) {
log.error(e.getMessage());
return handleResponseWithException(UNAUTHORIZED);
} catch (final Exception e) {
log.error(e.getMessage());
return handleResponseWithException(INTERNAL_SERVER_ERROR);
}
}

private HttpResponse handleRequestWithMethod(final HttpRequest request) {
final Method method = request.getMethod();
if (method == GET) {
return handleGetRequest(request);
}
if (method == POST) {
return handlePostRequest(request);
}
throw new PageNotFoundException(request.getPath());
}

private HttpResponse handleGetRequest(final HttpRequest request) {
final String path = request.getPath();
if (path.equals(BASE_PATH)) {
return new HttpResponse(OK, "Hello world!");
}
if (path.equals("/favicon.ico")) {
return new HttpResponse(OK, "Icon Not Exist!");
}
if (path.equals("/login") && request.isCookieExist(SESSION_COOKIE_NAME)) {
return handleAuthResponse(request, request.getCookie(SESSION_COOKIE_NAME));
}

try {
final URL resource = convertPathToUrl(path);
final HttpResponse response = new HttpResponse(OK, resource);
checkContentType(request, response);
return response;
} catch (final Exception e) {
throw new PageNotFoundException(path);
}
}

private HttpResponse handleAuthResponse(final HttpRequest request, final String uuid) {
final HttpResponse response = new HttpResponse(FOUND);

response.addHeader(LOCATION, "/index.html");
if (!request.isCookieExist(SESSION_COOKIE_NAME)) {
response.addHeader(SET_COOKIE, SESSION_COOKIE_NAME + COOKIE_DELIMITER + uuid);
}
return response;
}

private URL convertPathToUrl(String path) {
if (!path.contains(".")) {
path += ".html";
}
return getClass().getClassLoader().getResource("static" + path);
}

private void checkContentType(final HttpRequest request, final HttpResponse response) {
final String accept = request.getHeader(ACCEPT);
if (accept != null && accept.contains("css")) {
response.addHeader(ResponseHeader.CONTENT_LENGTH, "text/css;charset=utf-8 ");
}
}

private HttpResponse handlePostRequest(final HttpRequest request) throws UnauthorizedException {
final String path = request.getPath();
if (path.equals("/login")) {
return logIn(request);
}
if (path.equals("/register")) {
return register(request);
}
throw new PageNotFoundException(request.getPath());
}

private HttpResponse logIn(final HttpRequest request) throws UnauthorizedException {
final Map<String, String> bodyFields = parseFormData(request.getBody());
final String uuid = userService.logIn(bodyFields);
return handleAuthResponse(request, uuid);
}

private HttpResponse register(final HttpRequest request) {
final Map<String, String> bodyFields = parseFormData(request.getBody());
final String uuid = userService.register(bodyFields);
return handleAuthResponse(request, uuid);
}

private HttpResponse handleResponseWithException(final Status status) {
final URL resource = convertPathToUrl(BASE_PATH + status.getCode());
return new HttpResponse(status, resource);
}
}
Loading