Skip to content

[1 -2단계 - Tomcat 구현하기] 리비(이근희) 미션 제출합니다. #522

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

Merged
merged 43 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
d5601be
test: 파일 테스트 학습
Libienz Sep 3, 2024
3be2f0c
feat: UncheckedServletExceptiona 생성 로직 추가
Libienz Sep 3, 2024
1b91e01
feat: HttpRequest 구현
Libienz Sep 3, 2024
271e20a
feat: HttpRequest 속성 getter 작성
Libienz Sep 3, 2024
2254cc4
feat: HttpRequestHandler 명세 작성
Libienz Sep 3, 2024
3225cea
feat: index.html 정적 리소스 서빙 구현
Libienz Sep 4, 2024
5c5c3b7
refactor: 싱글톤 Dispatcher로 요청을 처리하도록 개선
Libienz Sep 4, 2024
8517c2d
feat: 파일 확장자 얻어오는 util 기능 구현
Libienz Sep 4, 2024
d6d8297
refactor: 정적 리소스 ContentType 지정 개선 및 Handler가 Response 타입을 바로 반환하도록 개선
Libienz Sep 4, 2024
319482d
refactor: 정적 파일을 한 줄씩 읽지 않고 한번에 읽도록 개선
Libienz Sep 4, 2024
b4233cb
feat: RequestLine 클래스 작성
Libienz Sep 4, 2024
fad4dcd
feat: RequestLine 클래스 getter 추가
Libienz Sep 4, 2024
62ae5af
refactor: HttpRequest 속성 RequestLine으로 응집 개선
Libienz Sep 4, 2024
4ec6d1e
feat: HttpProtocol Enum 클래스 작성
Libienz Sep 4, 2024
67764a5
refactor: HttpProtocol 원시값 포장 개선
Libienz Sep 4, 2024
6bd193e
fix: StaticResourceHandler 지원 조건 RequestLine에 맞게 수정
Libienz Sep 4, 2024
19d993e
feat: GreetingHandler 구현
Libienz Sep 4, 2024
d1becc7
refactor: Uri 클래스 구현을 통한 RequestLine 속성들의 추상화 준위 통일
Libienz Sep 4, 2024
6c5d8fc
feat: LoginPageHandler 구현
Libienz Sep 4, 2024
ee1564a
feat: QueryString 추출 기능 구현
Libienz Sep 4, 2024
3f2b8e2
feat: LoginHandler 구현
Libienz Sep 4, 2024
6df3de4
refactor: 패키지 구조 개선
Libienz Sep 4, 2024
24aefb4
feat: 로그인 기능 구현
Libienz Sep 5, 2024
274a4a9
feat: 회원가입 페이지 서비스 기능 구현
Libienz Sep 5, 2024
fed02f6
fix: remove implementation logback-classic on gradle (#501)
geoje Sep 5, 2024
7e91356
fix: add threads min-spare configuration on properties (#502)
geoje Sep 5, 2024
cbd88b7
feat: 회원가입 기능 구현
Libienz Sep 5, 2024
1d5fd41
refactor: HttpMessage 공통 요소 원시값 포장 개선
Libienz Sep 5, 2024
06f25d5
refactor: Request의 HttpMessage 공통 요소로 포장된 객체를 사용하도록 개선
Libienz Sep 5, 2024
b133e2e
feat: 로그인 성공시 쿠키 설정 기능 구현
Libienz Sep 5, 2024
c69646c
feat: 세션 클래스 작성
Libienz Sep 6, 2024
fdd3837
feat: main 브랜치 병합
Libienz Sep 6, 2024
8088bbd
test: 캐시 관련 학습테스트 작성
Libienz Sep 6, 2024
9b0930e
feat: 세션을 통한 로그인 상태 유지 구현
Libienz Sep 6, 2024
3fec0b0
refactor: 로그인 요청을 POST로 개선
Libienz Sep 6, 2024
e28f2bf
feat: 헤더 순서를 LinkedHashMap으로 고정
Libienz Sep 10, 2024
15c5319
test: loginPage요청 응답 테스트 및 registerPage응답 테스트 작성
Libienz Sep 10, 2024
3c490e0
test: login 포스트 요청 성공 실패 케이스 테스트 작성
Libienz Sep 10, 2024
70154f4
fix: 로그인 실패 응답 401 statusCode 이용하도록 수정
Libienz Sep 10, 2024
8498462
test: 회원가입 테스트 작성
Libienz Sep 10, 2024
0a17fd0
refactor: 핸들러 매핑 조건 support 가독성 향상 개선
Libienz Sep 10, 2024
f6854ca
refactor: 일관되지 않은 파라미터 불변성 처리 개선 및 사용하지 않는 메서드 제거 개선
Libienz Sep 10, 2024
eba8f7c
refactor: 핸들러 매핑 순서가 보장되도록 스트림 개선
Libienz Sep 10, 2024
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
2 changes: 1 addition & 1 deletion study/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ repositories {
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'ch.qos.logback:logback-classic:1.5.7'
implementation 'org.apache.commons:commons-lang3:3.14.0'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1'
implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.4.1'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.assertj:assertj-core:3.26.0'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package cache.com.example;

import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import jakarta.servlet.http.HttpServletResponse;

@Controller
public class GreetingController {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
package cache.com.example.cachecontrol;

import java.time.Duration;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.WebContentInterceptor;

@Configuration
public class CacheWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(final InterceptorRegistry registry) {
WebContentInterceptor webContentInterceptor = new WebContentInterceptor();
webContentInterceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/");
webContentInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365)).cachePublic(), "/resources/**");

registry.addInterceptor(webContentInterceptor);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
package cache.com.example.etag;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.ShallowEtagHeaderFilter;

@Configuration
public class EtagFilterConfiguration {

// @Bean
// public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
// return null;
// }
@Bean
public FilterRegistrationBean<ShallowEtagHeaderFilter> shallowEtagHeaderFilter() {
FilterRegistrationBean<ShallowEtagHeaderFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new ShallowEtagHeaderFilter());
registrationBean.addUrlPatterns("/etag");
registrationBean.addUrlPatterns("/resources/*");
return registrationBean;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package cache.com.example.version;

import jakarta.annotation.PostConstruct;
import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

Expand Down
3 changes: 3 additions & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ handlebars:
suffix: .html

server:
compression:
enabled: true
tomcat:
accept-count: 1
max-connections: 1
threads:
min-spare: 2
max: 2
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package cache.com.example;

import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES;

import cache.com.example.version.ResourceVersion;
import java.time.Duration;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -10,10 +13,6 @@
import org.springframework.http.HttpHeaders;
import org.springframework.test.web.reactive.server.WebTestClient;

import java.time.Duration;

import static cache.com.example.version.CacheBustingWebConfig.PREFIX_STATIC_RESOURCES;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class GreetingControllerTest {

Expand Down
27 changes: 12 additions & 15 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
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.IOException;
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 org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

/**
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
Expand All @@ -26,12 +26,12 @@ class FileTest {
@Test
void resource_디렉터리에_있는_파일의_경로를_찾는다() {
final String fileName = "nextstep.txt";

// todo
final String actual = "";

final String actual = getClass().getClassLoader().getResource(fileName).getFile();
assertThat(actual).endsWith(fileName);
}
/**
* 파일 이름으로 자원의 경로를 식별할 수 있다
*/

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

// todo
final Path path = null;

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

final List<String> actual = Files.readAllLines(path);
assertThat(actual).containsOnly("nextstep");
}
}
66 changes: 41 additions & 25 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
package study;

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

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.util.stream.Collectors;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.io.*;

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

/**
* 자바는 스트림(Stream)으로부터 I/O를 사용한다.
* 입출력(I/O)은 하나의 시스템에서 다른 시스템으로 데이터를 이동 시킬 때 사용한다.
Expand Down Expand Up @@ -39,7 +50,7 @@ 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바이트 이상을 한 번에 전송 할 수 있어 훨씬 효율적이다.
Expand All @@ -54,6 +65,7 @@ class OutputStream_학습_테스트 {
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/

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

assertThat(actual).isEqualTo("nextstep");
Expand All @@ -63,7 +75,7 @@ class OutputStream_학습_테스트 {
/**
* 효율적인 전송을 위해 스트림에서 버퍼링을 사용 할 수 있다.
* BufferedOutputStream 필터를 연결하면 버퍼링이 가능하다.
*
*
* 버퍼링을 사용하면 OutputStream을 사용할 때 flush를 사용하자.
* flush() 메서드는 버퍼가 아직 가득 차지 않은 상황에서 강제로 버퍼의 내용을 전송한다.
* Stream은 동기(synchronous)로 동작하기 때문에 버퍼가 찰 때까지 기다리면
Expand All @@ -78,7 +90,7 @@ class OutputStream_학습_테스트 {
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/

outputStream.flush();
verify(outputStream, atLeastOnce()).flush();
outputStream.close();
}
Expand All @@ -88,16 +100,18 @@ class OutputStream_학습_테스트 {
* 장시간 스트림을 닫지 않으면 파일, 포트 등 다양한 리소스에서 누수(leak)가 발생한다.
*/
@Test
void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
final OutputStream outputStream = mock(OutputStream.class);
void OutputStream은_사용하고_나서_close_처리를_해준다() {

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

verify(outputStream, atLeastOnce()).close();
OutputStream outputStream = mock(OutputStream.class);
try (outputStream) {
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

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

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

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

try (inputStream) {
} catch (IOException e) {
throw new RuntimeException(e);
}
verify(inputStream, atLeastOnce()).close();
}
}
Expand All @@ -169,12 +182,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];
byte[] actual = bufferedInputStream.readAllBytes();

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

final StringBuilder actual = new StringBuilder();
String actual = bufferedReader.lines()
.collect(Collectors.joining("\r\n")) + "\r\n";

assertThat(actual).hasToString(emoji);
assertThat(actual).isEqualTo(emoji);
}
}
}
2 changes: 1 addition & 1 deletion tomcat/src/main/java/com/techcourse/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
public class Application {

public static void main(String[] args) {
final var tomcat = new Tomcat();
Tomcat tomcat = new Tomcat();
tomcat.start();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.techcourse.controller;

import java.io.IOException;
import org.apache.coyote.http11.HttpProtocol;
import org.apache.coyote.http11.HttpRequestHandler;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.line.Method;
import org.apache.coyote.http11.response.HttpResponse;

public class GreetingController implements HttpRequestHandler {

private static final Method SUPPORTING_METHOD = Method.GET;
private static final HttpProtocol SUPPORTING_PROTOCOL = HttpProtocol.HTTP_11;

@Override
public boolean supports(HttpRequest request) {
return request.methodEquals(SUPPORTING_METHOD) &&
request.protocolEquals(SUPPORTING_PROTOCOL) &&
request.isUriHome();
}

@Override
public HttpResponse handle(HttpRequest request) throws IOException {
return HttpResponse.ok("Hello world!", "html");
}
}
Loading