Skip to content

Commit

Permalink
[톰캣 구현하기 - 1,2단계] 에단(김석호) 미션 제출합니다. (#372)
Browse files Browse the repository at this point in the history
* test: FileTest 작성

* test: IOEStreamTest 작성

* feat: 정적파일 파싱 기능 구현

* feat: header 파싱 기능 분리

* feat: queryString 파싱 기능 구현

* feat: login 기능 구현

* refactor: login을 post로 변경

* refactor: fronthandler를 생성해서 정적파일을 처리하도록 변경

* refactor: fronthandler가 GET을 처리하도록 변경

* refactor: LoginMapping, LoginPageMapping으로 리팩토링

* feat: 회원가입 화면 추가

* feat: 회원가입 기능 구현

* refactor: handle 메서드에 header 추가

* feat: Cookie를 파싱하는 HttpCookie 클래스 구현

* feat: Cookie 추가

* feat: Session 추가

* refactor: 필요없는 throws 제거

* refactor: HttpMethod enum 추가

* refactor: HttpHeaders 로 추가

* refactor: 필요없는 static 제거

* refactor: BufferedReader를 try with resources로 변겨

* refactor: HttpRequest로 변경

* refactor: HttpRequest로 변경

* refactor: session 및 request 패키지 변경

* refactor: startLine 을 requestLine 으로 변경

* refactor: httpResponse 로 리팩토링

* refactor: 레거시 코드 제거

* refactor: hello world가 text로 나가도록 변경

* fix: 리다이렉트가 될 때 html이 안보이는 문제 해결

* refactor: isAlreadyLogined 메서드를 한 줄로 변경

* refactor: "/" url을 homePageMapping으로 변경

* refactor: body에 빈 공백 제거

* refactor: header관련 설정 변경

* refactor: ContentLength가 잘못나오던 문제 해결
  • Loading branch information
cookienc authored Sep 7, 2023
1 parent 68db530 commit b6e02c7
Show file tree
Hide file tree
Showing 32 changed files with 1,058 additions and 56 deletions.
26 changes: 16 additions & 10 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.net.URL;
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;
Expand All @@ -25,12 +28,14 @@ class FileTest {
*/
@Test
void resource_디렉터리에_있는_파일의_경로를_찾는다() {
// given
final String fileName = "nextstep.txt";

// todo
final String actual = "";
// when
final URL resourceUrl = getClass().getClassLoader().getResource(fileName);

assertThat(actual).endsWith(fileName);
// then
assertThat(resourceUrl.getPath()).endsWith(fileName);
}

/**
Expand All @@ -40,15 +45,16 @@ class FileTest {
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
void 파일의_내용을_읽는다() {
void 파일의_내용을_읽는다() throws IOException {
// given
final String fileName = "nextstep.txt";
final URL fileUrl = getClass().getClassLoader().getResource(fileName);
final Path path = new File(fileUrl.getPath()).toPath();

// todo
final Path path = null;

// todo
final List<String> actual = Collections.emptyList();
// when
final List<String> actual = Files.readAllLines(path);

// then
assertThat(actual).containsOnly("nextstep");
}
}
87 changes: 52 additions & 35 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.io.*;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

/**
* 자바는 스트림(Stream)으로부터 I/O를 사용한다.
Expand Down Expand Up @@ -39,46 +51,46 @@ class OutputStream_학습_테스트 {
* OutputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 쓰기 위해 write(int b) 메서드를 사용한다.
* 예를 들어, FilterOutputStream은 파일로 데이터를 쓸 때,
* 또는 DataOutputStream은 자바의 primitive type data를 다른 매체로 데이터를 쓸 때 사용한다.
*
*
* write 메서드는 데이터를 바이트로 출력하기 때문에 비효율적이다.
* <code>write(byte[] data)</code>와 <code>write(byte b[], int off, int len)</code> 메서드는
* 1바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다.
*/
@Test
void OutputStream_데이터를_바이트로_처리한다() throws IOException {
// given
final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112};
final OutputStream outputStream = new ByteArrayOutputStream(bytes.length);

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

// then
final String actual = outputStream.toString();

assertThat(actual).isEqualTo("nextstep");
outputStream.close();
}

/**
* 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.
* BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
*
*
* 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자.
* flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.
* Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면
* 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다.
*/
@Test
void BufferedOutputStream_사용하면_버퍼링이_가능하다() throws IOException {
// given
final byte[] bytes = {110, 101, 120, 116, 115, 116, 101, 112};
final OutputStream outputStream = mock(BufferedOutputStream.class);

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

// then
verify(outputStream, atLeastOnce()).flush();
outputStream.close();
}
Expand All @@ -89,14 +101,15 @@ class OutputStream_학습_테스트 {
*/
@Test
void OutputStream_사용하고_나서_close_처리를_해준다() throws IOException {
final OutputStream outputStream = mock(OutputStream.class);
// given
final OutputStream outputStream = mock(BufferedOutputStream.class);

/**
* todo
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
// when
try (outputStream) {

}

// then
verify(outputStream, atLeastOnce()).close();
}
}
Expand All @@ -108,7 +121,7 @@ class OutputStream_학습_테스트 {
* InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.
* InputStream의 read() 메서드는 기반 메서드이다.
* <code>public abstract int read() throws IOException;</code>
*
*
* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다.
*/
@Nested
Expand All @@ -121,15 +134,14 @@ class InputStream_학습_테스트 {
*/
@Test
void InputStream_데이터를_바이트로_읽는다() throws IOException {
// given
byte[] bytes = {-16, -97, -92, -87};
final InputStream inputStream = new ByteArrayInputStream(bytes);

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

// then
assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
inputStream.close();
Expand All @@ -141,14 +153,14 @@ class InputStream_학습_테스트 {
*/
@Test
void InputStream_사용하고_나서_close_처리를_해준다() throws IOException {
// given
final InputStream inputStream = mock(InputStream.class);

/**
* todo
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
// when
try (inputStream) {
}

// then
verify(inputStream, atLeastOnce()).close();
}
}
Expand All @@ -169,12 +181,12 @@ class FilterStream_학습_테스트 {
* 버퍼 크기를 지정하지 않으면 버퍼의 기본 사이즈는 얼마일까?
*/
@Test
void 필터인_BufferedInputStream_사용해보자() {
void 필터인_BufferedInputStream_사용해보자() throws IOException {
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 = bufferedInputStream.readAllBytes();

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
Expand All @@ -197,15 +209,20 @@ 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 InputStreamReader inputStreamReader = new InputStreamReader(inputStream);

final StringBuilder actual = new StringBuilder();
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
while (bufferedReader.ready()) {
actual.append(bufferedReader.readLine()).append("\r\n");
}

assertThat(actual).hasToString(emoji);
}
Expand Down
39 changes: 39 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/handler/FrontHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.apache.coyote.handler;

import org.apache.coyote.handler.mapping.HandlerMapping;
import org.apache.coyote.handler.mapping.HomePageMapping;
import org.apache.coyote.handler.mapping.LoginMapping;
import org.apache.coyote.handler.mapping.LoginPageMapping;
import org.apache.coyote.handler.mapping.RegisterMapping;
import org.apache.coyote.handler.mapping.RegisterPageMapping;
import org.apache.coyote.handler.mapping.StaticFileMapping;
import org.apache.coyote.http.request.HttpRequest;
import org.apache.coyote.http.response.HttpResponse;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

public class FrontHandler {

private static final Set<HandlerMapping> handlerMapping = new HashSet<>();

static {
handlerMapping.add(new HomePageMapping());
handlerMapping.add(new StaticFileMapping());
handlerMapping.add(new LoginMapping());
handlerMapping.add(new LoginPageMapping());
handlerMapping.add(new RegisterMapping());
handlerMapping.add(new RegisterPageMapping());
}

public HttpResponse handle(final HttpRequest httpRequest) throws IOException {
for (final HandlerMapping mapping : handlerMapping) {
if (mapping.supports(httpRequest)) {
return mapping.handle(httpRequest);
}
}

return HttpResponse.redirect("/404.html");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.apache.coyote.handler.mapping;

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

import java.io.IOException;

public interface HandlerMapping {

boolean supports(final HttpRequest httpRequest);

HttpResponse handle(final HttpRequest httpRequest) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.apache.coyote.handler.mapping;

import org.apache.coyote.http.common.HttpBody;
import org.apache.coyote.http.common.HttpHeaders;
import org.apache.coyote.http.request.HttpRequest;
import org.apache.coyote.http.response.ContentType;
import org.apache.coyote.http.response.HttpResponse;
import org.apache.coyote.http.response.StatusCode;
import org.apache.coyote.http.response.StatusLine;

import java.io.IOException;
import java.util.Map;

import static org.apache.coyote.http.common.HttpHeader.CONTENT_TYPE;

public class HomePageMapping implements HandlerMapping {

@Override
public boolean supports(final HttpRequest httpRequest) {
return httpRequest.isGetRequest() && "/".equals(httpRequest.getRequestUri().getRequestUri());
}

@Override
public HttpResponse handle(final HttpRequest httpRequest) throws IOException {
return HttpResponse.builder()
.statusLine(StatusLine.from(StatusCode.OK))
.httpHeaders(new HttpHeaders(Map.of(CONTENT_TYPE, ContentType.HTML.getValue())))
.body(new HttpBody("Hello world!"))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.apache.coyote.handler.mapping;

import org.apache.coyote.http.LoginManager;
import org.apache.coyote.http.session.Session;
import org.apache.coyote.http.session.SessionManager;

import java.util.Map;

public abstract class LoginFilter {

private static final LoginManager loginManager = new SessionManager();

protected boolean isAlreadyLogined(final String jSessionId) {
return loginManager.isAlreadyLogined(jSessionId);
}

protected void setSession(final String jSessionId, final Map<String, String> sessionData) {
final Session session = new Session(jSessionId);
for (final String key : sessionData.keySet()) {
session.add(key, sessionData.get(key));
}
loginManager.add(session);
}
}
Loading

0 comments on commit b6e02c7

Please sign in to comment.