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단계] 이레(이다형) 미션 제출합니다. #329

Merged
merged 34 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1b1838e
test: study 테스트
zillionme Sep 3, 2023
36cf049
feat: RequestLine, RequestHeader 객체 추가
zillionme Sep 3, 2023
e8ef036
feat: 요청 정보를 가지고 응답을 생성하는 Response 객체 추가
zillionme Sep 3, 2023
47ff91a
feat: 정적 파일 GET 요청 시, 응답 기능 구현
zillionme Sep 3, 2023
e1db9a4
feat: tomcat 실행 어플리케이션 추가
zillionme Sep 3, 2023
3fde10b
feat: 존재하지않는 url 요청시 404.html로 리다이렉트 기능 추가
zillionme Sep 3, 2023
81f7024
feat: 요청 헤더 값이 존재하지 않는경우, Null로 반환하는 기능 추가
zillionme Sep 3, 2023
9e22ea9
feat: login.html 로그인 시, post 요청으로 수정
zillionme Sep 3, 2023
74d6465
feat: RequestBody 파싱 기능 추가
zillionme Sep 3, 2023
9d1ac7d
fix: RequestBody Stream으로 읽기 기능 수정
zillionme Sep 3, 2023
41b8c06
feat: 회원가입 기능 추가
zillionme Sep 3, 2023
e9b2fd6
feat: 로그인 시 쿠키 추가해 응답 기능 구현
zillionme Sep 3, 2023
caf5f32
feat: 쿠키 객체 추가
zillionme Sep 3, 2023
4fff93f
feat: 세션, 세션 관리자 객체 추가
zillionme Sep 3, 2023
5d37c15
feat: 로그인 세션 유지 기능 추가
zillionme Sep 3, 2023
8ebd399
refactor: HttpResponse 응답 객체 수정 및 빌더 패턴 추가
zillionme Sep 4, 2023
1174e38
refactor: 기본 페이지 핸들러 추가
zillionme Sep 4, 2023
5fab481
refactor: 세션 로그인 기능 구현 - 로그인 페이지 핸들러 추가
zillionme Sep 4, 2023
d9e2c53
refactor: 정적 파일 핸들러 추가
zillionme Sep 4, 2023
1011207
refactor: 회원가입 핸들러 추가
zillionme Sep 4, 2023
a29d4d6
refactor: 톰캣 http 프로세서 리팩터링
zillionme Sep 4, 2023
fae9599
fix: http응답 수정
zillionme Sep 4, 2023
144b475
fix: StaticFileHandler의 인풋 스트림 close 수정
zillionme Sep 4, 2023
e4acb95
chore: 안쓰는 패키지 삭제
zillionme Sep 6, 2023
6346231
fix: 302코드 내용 변경 및 싱글톤 클래스 private 생성자 추가
zillionme Sep 6, 2023
81181b3
refactor: StaticFileHandler 클래스 리팩터링
zillionme Sep 6, 2023
5d54908
refactor: LoginHandler 클래스 리팩터링 및 커스텀 예외 추가
zillionme Sep 6, 2023
a4776fa
refactor: RequestLine의 httpMethod enum으로 변경 및 커스텀 예외 추가
zillionme Sep 6, 2023
78bf46e
fix: 파일 읽기 기능 수정
zillionme Sep 6, 2023
1c4c63d
fix: RequestMethod enum 추가에 따른 변경 수정
zillionme Sep 6, 2023
3a98438
refactor: RequestBody 생성자 리팩터링
zillionme Sep 6, 2023
2bf84d1
refactor: codesmell 제거
zillionme Sep 6, 2023
6dfa5f5
refactor: HttpRequest 객체 추가
zillionme Sep 6, 2023
71efff4
chore: 안쓰는 import문 제거
zillionme 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
25 changes: 18 additions & 7 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.InstanceOfAssertFactories.spliterator;

/**
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
Expand All @@ -18,7 +23,7 @@ class FileTest {

/**
* resource 디렉터리 경로 찾기
*
* <p>
* File 객체를 생성하려면 파일의 경로를 알아야 한다.
* 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다.
* resource 디렉터리의 경로는 어떻게 알아낼 수 있을까?
Expand All @@ -28,26 +33,32 @@ class FileTest {
final String fileName = "nextstep.txt";

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

assertThat(actual).endsWith(fileName);
}

/**
* 파일 내용 읽기
*
* <p>
* 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다.
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
void 파일의_내용을_읽는다() {
void 파일의_내용을_읽는다() throws IOException {
// final String fileDir = "/Users/leedo/Desktop/LEVEL4/jwp-dashboard-http/study/src/test/resources/";
// final String fileName = "nextstep.txt";
// final Path path = Paths.get(fileDir,fileName).toAbsolutePath();

final String fileName = "nextstep.txt";

// todo
final Path path = null;
URL resourceUrl = getClass().getClassLoader().getResource(fileName);
final String resourceUrlStr = resourceUrl.getPath();
Path path = Paths.get(resourceUrlStr);
final List<String> actual = Files.readAllLines(path);

// todo
final List<String> actual = Collections.emptyList();

assertThat(actual).containsOnly("nextstep");
}
Expand Down
85 changes: 59 additions & 26 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@
import java.io.*;

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

/**
* 자바는 스트림(Stream)으로부터 I/O를 사용한다.
* 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다.
*
* <p>
* InputStream은 데이터를 읽고, OutputStream은 데이터를 쓴다.
* FilterStream은 InputStream이나 OutputStream에 연결될 수 있다.
* FilterStream은 읽거나 쓰는 데이터를 수정할 때 사용한다. (e.g. 암호화, 압축, 포맷 변환)
*
* <p>
* Stream은 데이터를 바이트로 읽고 쓴다.
* 바이트가 아닌 텍스트(문자)를 읽고 쓰려면 Reader와 Writer 클래스를 연결한다.
* Reader, Writer는 다양한 문자 인코딩(e.g. UTF-8)을 처리할 수 있다.
Expand All @@ -26,20 +28,41 @@ class IOStreamTest {

/**
* OutputStream 학습하기
*
* <p>
* 자바의 기본 출력 클래스는 java.io.OutputStream이다.
* OutputStream의 write(int b) 메서드는 기반 메서드이다.
* <code>public abstract void write(int b) throws IOException;</code>
*/
@Nested
class OutputStream_학습_테스트 {

@Test
void InputStreamReader테스트() throws IOException {
// InputStreamReader inputStream = new InputStreamReader(new ByteArrayInputStream("가나다라마바".getBytes("UTF-8")), "UTF-8");
//// InputStream inputStream = new ByteArrayInputStream("가나다라마바".getBytes("UTF-8"));
//// System.out.println("날다라마바사".getBytes("UTF-8") + "++++++++++++++++");
//// int read;
//// while ((read = inputStream.read()) > 0) {
//// System.out.println(read + "숫자");
//// System.out.println((char)read + "문자열");
//// }
// char[] arr = new char[100];
// inputStream.close();
// OutputStreamWriter outputStreamWriter = new OutputStreamWriter(new FileOutputStream("src//nan"));

// BufferedInputStream bis = new BufferedInputStream(new ByteArrayInputStream("가나다라마바사아".getBytes()));
// System.out.println(new String(bis.readAllBytes()));
// ByteArrayInputStream bi = new ByteArrayInputStream("가나다라마바사아".getBytes());
// System.out.println(new String(bi.readAllBytes()));
}


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

/**
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/

outputStream.write(bytes);
final String actual = outputStream.toString();

assertThat(actual).isEqualTo("nextstep");
Expand All @@ -63,21 +82,22 @@ class OutputStream_학습_테스트 {
/**
* 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.
* BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
*
* <p>
* 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자.
* flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.
* Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면
* 데드락(deadlock) 상태가 되기 때문에 flush로 해제해야 한다.
*/
/**
* todo
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
@Test
void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException {
final OutputStream outputStream = mock(BufferedOutputStream.class);

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

verify(outputStream, atLeastOnce()).flush();
outputStream.close();
Expand All @@ -96,19 +116,24 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
try(outputStream) {
outputStream.flush();
}catch (Exception e) {

}

verify(outputStream, atLeastOnce()).close();
}
}

/**
* InputStream 학습하기
*
* <p>
* 자바의 기본 입력 클래스는 java.io.InputStream이다.
* InputStream은 다른 매체로부터 바이트로 데이터를 읽을 때 사용한다.
* InputStream의 read() 메서드는 기반 메서드이다.
* <code>public abstract int read() throws IOException;</code>
*
* <p>
* InputStream의 서브 클래스(subclass)는 특정 매체에 데이터를 읽기 위해 read() 메서드를 사용한다.
*/
@Nested
Expand All @@ -128,7 +153,9 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
// BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// final String actual = bufferedReader.readLine();
final String actual = new String(inputStream.readAllBytes());

assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
Expand All @@ -148,14 +175,17 @@ class InputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
try(inputStream) {

}

verify(inputStream, atLeastOnce()).close();
}
}

/**
* FilterStream 학습하기
*
* <p>
* 필터는 필터 스트림, reader, writer로 나뉜다.
* 필터는 바이트를 다른 데이터 형식으로 변환 할 때 사용한다.
* reader, writer는 UTF-8, ISO 8859-1 같은 형식으로 인코딩된 텍스트를 처리하는 데 사용된다.
Expand All @@ -169,14 +199,14 @@ 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 bi = new BufferedInputStream(inputStream);

final byte[] actual = new byte[0];
final byte[] actual = bi.readAllBytes();

assertThat(bufferedInputStream).isInstanceOf(FilterInputStream.class);
assertThat(bi).isInstanceOf(FilterInputStream.class);
assertThat(actual).isEqualTo("필터에 연결해보자.".getBytes());
}
}
Expand All @@ -197,16 +227,19 @@ 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 BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
final StringBuilder actual = new StringBuilder();

String str;
while((str= br.readLine())!=null) {
actual.append(str+"\r\n");
}
assertThat(actual).hasToString(emoji);
}
}
Expand Down
42 changes: 28 additions & 14 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

import nextstep.jwp.exception.UncheckedServletException;
import org.apache.coyote.Processor;
import org.apache.coyote.http11.handler.BaseHandler;
import org.apache.coyote.http11.handler.LoginHandler;
import org.apache.coyote.http11.handler.RegisterHandler;
import org.apache.coyote.http11.handler.StaticFileHandler;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.response.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class Http11Processor implements Runnable, Processor {
Expand All @@ -26,22 +32,30 @@ public void run() {

@Override
public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
final var outputStream = connection.getOutputStream()) {
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(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();
HttpRequest httpRequest = RequestGenerator.generate(reader);
HttpResponse response = handleHttpRequest(httpRequest);
writer.write(response.toString());
writer.flush();
} catch (IOException | UncheckedServletException e) {
log.error(e.getMessage(), e);
}
}


private HttpResponse handleHttpRequest(final HttpRequest httpRequest) {
String requestURI = httpRequest.getRequestLine().getRequestURI();
if (requestURI.equals("/")) {
return BaseHandler.handle();
}
if (requestURI.startsWith("/login")) {
return LoginHandler.handle(httpRequest);
}
if (requestURI.startsWith("/register")) {
return RegisterHandler.handle(httpRequest);
}
return StaticFileHandler.handle(requestURI, httpRequest);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package org.apache.coyote.http11;

import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.RequestBody;
import org.apache.coyote.http11.request.RequestHeader;
import org.apache.coyote.http11.request.RequestLine;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class RequestGenerator {

private RequestGenerator() {
}

public static HttpRequest generate(final BufferedReader reader) throws IOException {
RequestLine requestLine = readRequestLine(reader);
RequestHeader requestHeader = readHeader(reader);
RequestBody requestBody = readRequestBody(reader, requestHeader);
return new HttpRequest(requestLine, requestHeader, requestBody);
}

private static RequestLine readRequestLine(final BufferedReader reader) throws IOException {
String line = reader.readLine();
return RequestLine.from(line);
}

private static RequestHeader readHeader(final BufferedReader reader) throws IOException {
List<String> lines = new ArrayList<>();
String line;
while ((line = reader.readLine()) != null) {
if (line.equals("")) {
break;
}
lines.add(line);
}
return RequestHeader.from(lines);
}

private static RequestBody readRequestBody(final BufferedReader reader, final RequestHeader requestHeader) throws IOException {
if (requestHeader.getHeaderValue("Content-Type") == null) {
return null;
}
int contentLength = Integer.parseInt(requestHeader.getHeaderValue("Content-Length"));
char[] buffer = new char[contentLength];
reader.read(buffer, 0, contentLength);
String requestBody = new String(buffer);
return RequestBody.from(requestBody);
}
}
Loading