Skip to content

Commit

Permalink
[톰캣 구현하기 1,2단계] 여우(조승현) 미션 제출합니다. (#314)
Browse files Browse the repository at this point in the history
* docs: 레벨 1에서 할 일 정리

* study: IOStreamTest 완료하기

* feat: http request의 uri와 method 읽어오기

* feat: /index.html로 요청을 보내면 index.html 응답하기

* refactor: httpRequest를 uri별로 핸들링하는 작업 깔끔하게 분리하기

* feat: css와 js를 받는 핸들러 추가하기

* feat: login 페이지 접속시 쿼리 파라미터를 뽑아 회원 정보를 조회하는 핸들러 구현하기

* refactor: HttpResponse와 관련된 작업을 별도의 도메인으로 분리하기

* feat: 로그인 실패시 401 처리, 성공시 302 처리하는 핸들러 개발

* feat: 회원가입 작업 수행 후 index 페이지로 리다이렉트하는 핸들러 개발하기

* feat: 로그인 성공시 쿠키에 JSESSION을 등록하는 기능 개발하기

* refactor: 쿠키 값을 cookie라는 일급 컬렉션에 캡슐화하기

* feat: 로그인된 상태에서 로그인 페이지로 접속하려는 경우 index.html로 강제 리다이렉트하는 기능 개발하기

* test: 테스트의 개행 방식을 운영체제에 무관하게 수정

* docs: 관리 안하는 요구사항 문서 제거하기

* refactor: 프로세서에서 inputStream과 outputStream을 가져오는 작업을 실행하도록 변경

* refactor: StringBuilder 대신 String을 반환하도록 변경

* refactor: String.split 대신 StringTokenizer를 사용하도록 변경

* refactor: 로그인 요청으로 GET 대신 POST를 사용하도록 변경

* refactor: 로그인 성공시 / 로그인 상태로 로그인 페이지 접속시 200 + responseBody 대신 /index.html로 302 리다이렉트하도록 변경

* refactor: 패키지 정리 후 response 응답 메시지를 만드는 작업을 별도 utils 클래스로 분리

* refactor: register 작업 이후 index.html로 리다이렉트하도록 변경
  • Loading branch information
BackFoxx authored Sep 7, 2023
1 parent 68db530 commit 6eeb5f8
Show file tree
Hide file tree
Showing 26 changed files with 960 additions and 71 deletions.
56 changes: 32 additions & 24 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,61 @@
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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
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 URL url = this.getClass().getClassLoader().getResource("nextstep.txt");
final String fileName = "nextstep.txt";

// todo
final String actual = "";
final String actual = url.getPath();

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();
void 파일의_내용을_읽는다() throws URISyntaxException, IOException {
final URL url = this.getClass().getClassLoader().getResource("nextstep.txt");
final File file = new File(url.toURI());

final List<String> actual = new ArrayList<>();
try (
final FileInputStream fileInputStream = new FileInputStream(file);
final BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(fileInputStream, StandardCharsets.UTF_8))
) {
actual.add(bufferedReader.readLine());
}

assertThat(actual).containsOnly("nextstep");
}
Expand Down
49 changes: 28 additions & 21 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package study;

import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
Expand All @@ -24,6 +25,8 @@
@DisplayName("Java I/O Stream 클래스 학습 테스트")
class IOStreamTest {

public static final byte[] BYTES = new byte[]{110, 101, 120, 116, 115, 116, 101, 112};

/**
* OutputStream 학습하기
*
Expand All @@ -46,13 +49,9 @@ class OutputStream_학습_테스트 {
*/
@Test
void OutputStream_데이터를_바이트로_처리한다() throws IOException {
final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112};
final OutputStream outputStream = new ByteArrayOutputStream(bytes.length);
final OutputStream outputStream = new ByteArrayOutputStream(BYTES.length);

/**
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/
outputStream.write(BYTES);

final String actual = outputStream.toString();

Expand All @@ -73,11 +72,8 @@ class OutputStream_학습_테스트 {
void BufferedOutputStream_사용하면_버퍼링이_가능하다() throws IOException {
final OutputStream outputStream = mock(BufferedOutputStream.class);

/**
* todo
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
outputStream.write(BYTES);
outputStream.flush();

verify(outputStream, atLeastOnce()).flush();
outputStream.close();
Expand All @@ -96,6 +92,9 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
try(Writer writer = new OutputStreamWriter(outputStream)) {

}

verify(outputStream, atLeastOnce()).close();
}
Expand Down Expand Up @@ -124,13 +123,10 @@ class InputStream_학습_테스트 {
byte[] bytes = {-16, -97, -92, -87};
final InputStream inputStream = new ByteArrayInputStream(bytes);

/**
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

assertThat(actual).isEqualTo("🤩");
assertThat(bufferedReader.readLine()).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
inputStream.close();
}
Expand All @@ -148,6 +144,9 @@ class InputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
try (Reader reader = new BufferedReader(new InputStreamReader(inputStream))) {

}

verify(inputStream, atLeastOnce()).close();
}
Expand All @@ -169,12 +168,13 @@ class FilterStream_학습_테스트 {
* 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
*/
@Test
void 필터인_BufferedInputStream_사용해보자() {
void 필터인_BufferedInputStream_사용해보자() throws IOException {
final String text = "필터에 연결해보자.";
final InputStream inputStream = new ByteArrayInputStream(text.getBytes());
final InputStream bufferedInputStream = null;
final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream, 4);

final byte[] actual = new byte[0];
final byte[] actual = bufferedInputStream.readAllBytes();
// 기본 버퍼의 크기는 8192Byte(8KB)

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
Expand All @@ -197,15 +197,22 @@ class InputStreamReader_학습_테스트 {
* 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
*/
@Test
void BufferedReader_사용하여_문자열을_읽어온다() {
void BufferedReader_사용하여_문자열을_읽어온다() throws IOException {
final String emoji = String.join("\r\n",
"😀😃😄😁😆😅😂🤣🥲☺️😊",
"😇🙂🙃😉😌😍🥰😘😗😙😚",
"😋😛😝😜🤪🤨🧐🤓😎🥸🤩",
"");
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());


final StringBuilder actual = new StringBuilder();
try(final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
while (bufferedReader.ready()) {
actual.append(bufferedReader.readLine());
actual.append("\r\n");
}
}

assertThat(actual).hasToString(emoji);
}
Expand Down
46 changes: 29 additions & 17 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,35 @@
package org.apache.coyote.http11;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.List;
import nextstep.jwp.exception.UncheckedServletException;
import org.apache.coyote.Processor;
import org.apache.coyote.http11.handler.*;
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 List<HttpRequestHandler> requestHandlers = List.of(
new BasicURIHandler(),
new IndexPageHandler(),
new IndexCSSHandler(),
new HttpJavascriptHandler(),
new HttpAssetHandler(),
new LoginPageHandler(),
new LoginHandler(),
new RegistrationPageHandler(),
new RegistrationHandler()
);

public Http11Processor(final Socket connection) {
this.connection = connection;
}
Expand All @@ -26,20 +42,16 @@ 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);

outputStream.write(response.getBytes());
outputStream.flush();
try (
final InputStream inputStream = connection.getInputStream();
final OutputStream outputStream = connection.getOutputStream()
) {
final HttpRequest httpRequest = HttpRequest.from(inputStream);
for (HttpRequestHandler requestHandler : this.requestHandlers) {
if (requestHandler.support(httpRequest)) {
requestHandler.handle(httpRequest, outputStream);
}
}
} catch (IOException | UncheckedServletException e) {
log.error(e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.apache.coyote.http11.handler;

import java.io.IOException;
import java.io.OutputStream;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;

public class BasicURIHandler implements HttpRequestHandler {
@Override
public boolean support(final HttpRequest httpRequest) {
return httpRequest.isMethodEqualTo("GET") && httpRequest.isUriEqualTo("/");
}

@Override
public void handle(final HttpRequest httpRequest, final OutputStream outputStream) throws IOException {
final var responseBody = "Hello world!";

final HttpResponse httpResponse = new HttpResponse.Builder()
.responseBody(responseBody)
.build(outputStream);
httpResponse.flush();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.apache.coyote.http11.handler;

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

import java.io.IOException;
import java.io.OutputStream;

public class HttpAssetHandler implements HttpRequestHandler {

public static final String ASSETS_PATH_PREFIX = "static/assets/";

@Override
public boolean support(final HttpRequest httpRequest) {
return httpRequest.isAssetRequest();
}

@Override
public void handle(final HttpRequest httpRequest, final OutputStream outputStream) throws IOException {
final HttpResponse httpResponse = new HttpResponse.Builder()
.contentType("text/javascript")
.responseBody(new FileHandler().readFromResourcePath(ASSETS_PATH_PREFIX + httpRequest.getEndPoint()))
.build(outputStream);
httpResponse.flush();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.apache.coyote.http11.handler;

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

import java.io.IOException;
import java.io.OutputStream;

public class HttpJavascriptHandler implements HttpRequestHandler {

public static final String JAVASCRIPT_PATH_PREFIX = "static/js/";

@Override
public boolean support(final HttpRequest httpRequest) {
return httpRequest.isJavascriptRequest();
}

@Override
public void handle(final HttpRequest httpRequest, final OutputStream outputStream) throws IOException {
final HttpResponse httpResponse = new HttpResponse.Builder()
.contentType("text/javascript")
.responseBody(new FileHandler().readFromResourcePath(JAVASCRIPT_PATH_PREFIX + httpRequest.getEndPoint()))
.build(outputStream);
httpResponse.flush();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.apache.coyote.http11.handler;

import java.io.IOException;
import java.io.OutputStream;
import org.apache.coyote.http11.request.HttpRequest;

public interface HttpRequestHandler {
boolean support(HttpRequest httpRequest);

void handle(HttpRequest httpRequest, OutputStream outputStream) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.apache.coyote.http11.handler;

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

import java.io.IOException;
import java.io.OutputStream;

public class IndexCSSHandler implements HttpRequestHandler {

public static final String CSS_PATH_PREFIX = "static/css/";

@Override
public boolean support(final HttpRequest httpRequest) {
return httpRequest.isMethodEqualTo("GET") && httpRequest.isUriEqualTo("/css/styles.css");
}

@Override
public void handle(final HttpRequest httpRequest, final OutputStream outputStream) throws IOException {
final HttpResponse httpResponse = new HttpResponse.Builder()
.contentType("text/css")
.responseBody(new FileHandler().readFromResourcePath(CSS_PATH_PREFIX + httpRequest.getEndPoint()))
.build(outputStream);
httpResponse.flush();
}
}
Loading

0 comments on commit 6eeb5f8

Please sign in to comment.