Skip to content

Commit

Permalink
[1-2단계 Tomcat 구현하기] 산초(나영서) 미션 제출합니다. (#573)
Browse files Browse the repository at this point in the history
* fix: remove implementation logback-classic on gradle (#501)

* fix: add threads min-spare configuration on properties (#502)

* test: FileTest 작성

* test: IOStreamTest 작성

* feat: index.html 응답하기

* refactor: import 문 정렬

- 우테코 컨벤션에 맞게

* refactor: inputStreamReader 를 사용하도록 변경

* feat: 정상 응답, 예외 응답 생성 함수 추가

* test: / 경로에 대한 테스트, 잘못된 경로에 대한 테스트 추가

* style: 코드 재정렬

* feat: 파일확장자->contentType 변환 클래스 생성

* refactor: 응답 생성 객체 분리

* refactor: 분리한 객체의 함수 호출하도록 수정

* refactor: 경로와 쿼리스트링 분리

* refactor: 정적 파일 반환 로직을 클래스로 분리

* refactor: NotFound 응답 반환로직을 함수로 생성

* feat: HTTP Request 클래스 생성

* feat: 정적 파일 요청인지 구분하는 클래스 생성

* refactor: 함수 분리 및 가독성 개선

* refactor: 함수 분리

* refactor: 패키지 변경

* refactor: 함수 이름 변경

* feat: 컨트롤러 추상 클래스 생성

* feat: 요청에 매칭되는 컨트롤러 반환 로직 구현

* feat: ModelAndView 객체 생성

* feat: LogInController 생성

* refactor: 컨틀롤러 매핑을 사용하도록 수정

* style: 개행

* fix: 오류 수정

* refactor: 클래스 이름 변경

* refactor: Http Request, Body 분리

* refactor: 패키지 이동

* refactor: RequestHeader 가 사용되던 곳을 Request 로 대체

* refactor: 가독성 개선

- 개행, 함수 이름 변경, 필드 선언

* refactor: 가독성 개선 및 레코드로 변경

* feat: 객체에 toMap 함수 추가

* feat: 로그인 실패시 401 응답 반환 기능 구현

* feat: 로그인 method 를 post 로 변경

* refactor: 오타 수정

* feat: HTTP Response 객체 생성

* style: 개행 및 안쓰는 함수 제거

* feat: RequestBody 내용을 String 이 아니라 Map 으로 변경

* feat: HttpResponse 변경 내용 적용

* feat: 비밀번호 틀린 경우 리다이렉트

* refactor: 구구 대체

* refactor: 학습 테스트 정적 파일 위치 변경

* feat: 모든 응답에 no-cache 붙이는 인터셉터 추가

* feat: 회원가입 컨트롤러 생성

* fix: 인코딩 문제 해결

* feat: 세션, 세션 메니저 객체 생성

* feat: 로그인, 회원가입 시 세션 저장

* feat: 로그인, 회원가입 시 SetCookie 헤더 설정

* feat: 로그인한 유저는 리다이렉트하는 기능 추가

* feat: 응답 압축 설정

* feat: 특정 경로에 대해 etag를 응답하도록 설정

* feat: 정적 파일 요청시 etag 응답

* refactor: 루트 경로에 대해 no cache 하도록 수정

* feat: 정적 파일에 대해 캐시 만료 시간 설정

* refactor: 오타 수정

* refactor: 시간을 etag 버전으로 설정

* refactor: 파일 확장자 추가

* chore: 사용하지 않는 클래스 삭제

---------

Co-authored-by: Gyeongho Yang <[email protected]>
  • Loading branch information
nayonsoso and geoje authored Sep 7, 2024
1 parent 0b698a2 commit 58e154c
Show file tree
Hide file tree
Showing 32 changed files with 730 additions and 83 deletions.
1 change: 0 additions & 1 deletion study/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ 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'
Expand Down
8 changes: 4 additions & 4 deletions study/src/main/java/cache/com/example/GreetingController.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class GreetingController {

@GetMapping("/")
public String index() {
return "index";
return "index.html";
}

/**
Expand All @@ -25,16 +25,16 @@ public String cacheControl(final HttpServletResponse response) {
.cachePrivate()
.getHeaderValue();
response.addHeader(HttpHeaders.CACHE_CONTROL, cacheControl);
return "index";
return "index.html";
}

@GetMapping("/etag")
public String etag() {
return "index";
return "index.html";
}

@GetMapping("/resource-versioning")
public String resourceVersioning() {
return "resource-versioning";
return "resource-versioning.html";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public class CacheWebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(new NoCacheInterceptor())
.addPathPatterns("/");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cache.com.example.cachecontrol;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class NoCacheInterceptor implements HandlerInterceptor {

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
response.setHeader("Cache-Control", "no-cache, private");
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
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> filterRegistrationBean
= new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());
filterRegistrationBean.addUrlPatterns("/etag/*");
return filterRegistrationBean;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package cache.com.example.version;

import java.time.Duration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.CacheControl;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

Expand All @@ -20,6 +22,8 @@ public CacheBustingWebConfig(ResourceVersion version) {
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler(PREFIX_STATIC_RESOURCES + "/" + version.getVersion() + "/**")
.addResourceLocations("classpath:/static/");
.addResourceLocations("classpath:/static/")
.setEtagGenerator(resource -> version.getVersion())
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)).cachePublic());
}
}
4 changes: 4 additions & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ server:
accept-count: 1
max-connections: 1
threads:
min-spare: 2
max: 2
compression:
enabled: true
min-response-size: 10
File renamed without changes.
29 changes: 15 additions & 14 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
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.net.URISyntaxException;
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;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

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

// todo
final String actual = "";
URL resource = getClass().getResource("/" + fileName);
Path path = Path.of(resource.toURI());
final String actual = path.toString();

assertThat(actual).endsWith(fileName);
}
Expand All @@ -40,14 +43,12 @@ class FileTest {
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
void 파일의_내용을_읽는다() {
void 파일의_내용을_읽는다() throws IOException, URISyntaxException {
final String fileName = "nextstep.txt";
URL resource = getClass().getResource("/" + fileName);
Path path = Path.of(resource.toURI());

// todo
final Path path = null;

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

assertThat(actual).containsOnly("nextstep");
}
Expand Down
73 changes: 38 additions & 35 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
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 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 @@ -49,13 +59,9 @@ 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");
outputStream.close();
}
Expand All @@ -73,11 +79,7 @@ class OutputStream_학습_테스트 {
void BufferedOutputStream을_사용하면_버퍼링이_가능하다() throws IOException {
final OutputStream outputStream = mock(BufferedOutputStream.class);

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

verify(outputStream, atLeastOnce()).flush();
outputStream.close();
Expand All @@ -90,12 +92,10 @@ class OutputStream_학습_테스트 {
@Test
void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
final OutputStream outputStream = mock(OutputStream.class);

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

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

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

final String actual = new String(read);
assertThat(actual).isEqualTo("🤩");
assertThat(inputStream.read()).isEqualTo(-1);
inputStream.close();
Expand All @@ -143,11 +140,8 @@ class InputStream_학습_테스트 {
void InputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
final InputStream inputStream = mock(InputStream.class);

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

verify(inputStream, atLeastOnce()).close();
}
Expand All @@ -169,12 +163,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 +191,24 @@ 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 BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

final StringBuilder actual = new StringBuilder();
while (true) {
String txt = bufferedReader.readLine();
if (txt == null) {
break;
}
actual.append(txt).append("\r\n");
}

assertThat(actual).hasToString(emoji);
}
Expand Down
18 changes: 13 additions & 5 deletions tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.techcourse.db;

import com.techcourse.model.User;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
Expand All @@ -11,17 +10,26 @@ public class InMemoryUserRepository {
private static final Map<String, User> database = new ConcurrentHashMap<>();

static {
final User user = new User(1L, "gugu", "password", "hkkang@woowahan.com");
final User user = new User(1L, "sancho", "1234", "sancho@woowa.com");
database.put(user.getAccount(), user);
}

public static void save(User user) {
database.put(user.getAccount(), user);
public static User save(User user) {
return database.put(user.getAccount(), user);
}

public static Optional<User> findByAccount(String account) {
return Optional.ofNullable(database.get(account));
}

private InMemoryUserRepository() {}
public static Optional<User> findByAccountAndPassword(String account, String password) {
User user = database.get(account);
if (user != null && user.checkPassword(password)) {
return Optional.of(user);
}
return Optional.empty();
}

private InMemoryUserRepository() {
}
}
9 changes: 9 additions & 0 deletions tomcat/src/main/java/com/techcourse/model/User.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.techcourse.model;

import java.util.Map;

public class User {

private final Long id;
Expand All @@ -26,6 +28,13 @@ public String getAccount() {
return account;
}

public Map<String, Object> toMap() {
return Map.of(
"id", id,
"account", account,
"email", email);
}

@Override
public String toString() {
return "User{" +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.apache.coyote.controller;

import org.apache.coyote.http11.HttpRequest;
import org.apache.coyote.http11.HttpResponse;

public abstract class Controller {

public abstract HttpResponse process(HttpRequest request);
}
Loading

0 comments on commit 58e154c

Please sign in to comment.