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단계] 애쉬 미션 제출합니다! #556

Merged
merged 12 commits into from
Sep 10, 2024
Merged
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,50 @@

## 톰캣 구현하기

### 구현하면서 궁금했던 점

1. Http11Processor의 이 부분을

```
String line = bufferedReader.readLine();

line = line.split(" ")[1];

final URL resource = getClass().getResource("/static" + line);
```

```
String line = bufferedReader.readLine();

line = line.split(" ")[1];

final URL resource = getClass().getResource("/static/index.html");
```

이렇게 적으면 `localhost:8080/index.html`을 호출했을 때 디스플레이 되는 페이지 모습이 달랐습니다.
(전자: 그래프 노출되지 않음)
(후자: 그래프 노출됨)

정확한 이유가 뭘까요? 🤔
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 문제 해결되었나요? 현재 애쉬가 적어준 코드가 Git에 어느 부분인지를 확실히 모르겠어서 다시 재현하기 어렵네요. 제 생각에는 두 개가 같을 것이라고 생각되는데 🤔 getResource의 인자로 같은 값이 들어갈 것이라고 생각되어요.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1-1단계를 구현하면서 생겼던 이슈라 코드의 구조가 많이 변화한 지금에서는 쉽게 재현되지가 않네요.

저도 두 개가 같을 거라고 생각하고 진행했는데 실제 디스플레이 되는 화면이 다르게 출력되어 궁금했던 부분이에요. 🤔

지금도 완벽하게 방법을 알고 에러를 핸들링했다기보다는 코드의 구조가 변화되며 재현이 안되는 얼렁뚱땅 해결(?)에 가까워서
조금 더 명확한 해결법이 뭐였을지, 더 정확히는 문제점이 뭐였을지 궁금한 상태인데요.

일단 이 부분은 일단 차차 구현하면서 같은 문제가 재현되는 걸 발견하게 되면 다시 한 번 질문 드리거나,
저만의 대답을 내어놓도록 하겠습니다.

살펴봐주셔서 감사해요~


<br><br><br><br><br>

### 학습목표

- 웹 서버 구현을 통해 HTTP 이해도를 높인다.
- HTTP의 이해도를 높혀 성능 개선할 부분을 찾고 적용할 역량을 쌓는다.
- 서블릿에 대한 이해도를 높인다.
- 스레드, 스레드풀을 적용해보고 동시성 처리를 경험한다.

### 시작 가이드

1. 미션을 시작하기 전에 파일, 입출력 스트림 학습 테스트를 먼저 진행합니다.
- [File, I/O Stream](study/src/test/java/study)
- 나머지 학습 테스트는 다음 강의 시간에 풀어봅시다.
2. 학습 테스트를 완료하면 LMS의 1단계 미션부터 진행합니다.

## 학습 테스트

1. [File, I/O Stream](study/src/test/java/study)
2. [HTTP Cache](study/src/test/java/cache)
3. [Thread](study/src/test/java/thread)
9 changes: 4 additions & 5 deletions study/src/main/java/cache/com/example/GreetingController.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
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 {

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

/**
Expand All @@ -25,12 +24,12 @@ 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")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package cache.com.example.cachecontrol;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인터셉터는 최초 한 번만 생성하면 되므로, 다음과 같은 두 가지 방법으로 등록할 수 있어요.

  • 애쉬가 진행해준 대로, @Component를 붙이고 설정 파일에서 의존성 주입
  • 어노테이션을 붙이지 않고, new 연산자를 통해 객체를 직접 생성

애쉬는 왜 전자를 택했나요? (그냥 궁금증이예요~)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

애쉬는 왜 전자를 택했나요?

진짜진짜 솔직하게 말하면 이유는 별로 생각 안 해봤읍니다.
그냥 그 동안 계속 그렇게 해왔고, 우테코에서 그렇게 배웠고, 그런 방식이 제게 가장 익숙하기 때문이 아니었을까요?

그래서 이번 기회에 두 개가 어떻게 다른지 생각을 조금 해봤어요!


  1. @Component 사용

이 방식은 스프링의 IoC 컨테이너가 해당 객체를 관리해요.

장점:

  • 의존성 주입: 인터셉터에서 서비스나 레포지토리 같은 다른 Bean을 주입받아 사용 가능 (객체 간 결합도가 낮고 재사용성이 높음)
  • 유지 보수 용이: 스프링이 객체 생명 주기를 관리
  • 테스트 용이성: 의존성 주입을 통해 Mock 객체나 테스트 전용 Bean을 쉽게 주입 가능

단점:

  • 스프링 컨텍스트 필요: 스프링 애플리케이션 컨텍스트가 실행되어야만 객체가 생성되고 관리됨
  1. new 연산자로 직접 생성

이 방식은 개발자가 해당 객체를 직접 관리해요.

장점과 단점은 앞서 말한 @Component를 사용했을 때 생기는 장점과 단점을 각각 뒤집어 놓은 결과겠고요.


이 중에서 당장 와닿는 장점은 역시 스프링이 해당 객체를 자동으로 관리해준다는 점인 것 같습니다.
저희가 @Controller, @Service를 자연스럽게 사용하는 것처럼요. 🤔


우선은 이처럼 정리해볼 수 있을 것 같은데 아루는 어떻게 생각하시나요?
아루는 어떤 방식을 선호하고 또 그 이유가 뭐지요? ㅇ.ㅇ

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저라면 @Bean 등록할 때 new로 생성할 것 같습니다. 그 이유로는:

  • IoC 컨테이너가 지속적으로 의존성을 추적하면서 관리할 필요가 없습니다.
  • 한 곳에서밖에 사용하지 않을 것이 명확합니다.
    테스트가 조금 어려워질 수도 있겠지만, 다른 방식으로도 충분히 테스트할 수 있으리라 생각되네요 ㅎㅎ

public class CacheInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
String cacheControl = CacheControl
.noCache()
.cachePrivate()
.getHeaderValue();

response.setHeader(HttpHeaders.CACHE_CONTROL, cacheControl);

return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package cache.com.example.cachecontrol;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CacheWebConfig implements WebMvcConfigurer {

@Autowired
private CacheInterceptor cacheInterceptor;

@Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(cacheInterceptor).addPathPatterns("/");
}
}
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> filter = new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());

filter.addUrlPatterns("/etag");

return filter;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

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;

import java.time.Duration;

@Configuration
public class CacheBustingWebConfig implements WebMvcConfigurer {

Expand All @@ -20,6 +23,9 @@ 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());
//.resourceChain(true);
}
}
11 changes: 11 additions & 0 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,14 @@ server:
threads:
min-spare: 2
max: 2
compression:
enabled: true
min-response-size: 128

spring:
resources:
cache:
cache-control:
max-age: 365d
public: true

37 changes: 26 additions & 11 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.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.nio.file.Paths;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -18,36 +21,48 @@ class FileTest {

/**
* resource 디렉터리 경로 찾기
*
* <p>
* File 객체를 생성하려면 파일의 경로를 알아야 한다.
* 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다.
* resource 디렉터리의 경로는 어떻게 알아낼 수 있을까?
*/
@Test
void resource_디렉터리에_있는_파일의_경로를_찾는다() {
void resource_디렉터리에_있는_파일의_경로를_찾는다() throws URISyntaxException {
final String fileName = "nextstep.txt";
/*

// todo
final String actual = "";
1. URL에서 경로 별도 분리
// 리소스 파일의 URL을 얻어옴
final URL resource = getClass().getClassLoader().getResource(fileName);

// URL에서 경로를 추출함
final String actual = resource.getPath();*/

// 2. 정적 경로 고정
//Path path = Paths.get("src/test/resources", fileName);

// 3. 동적 경로 할당
final Path path = Paths.get(getClass().getClassLoader().getResource(fileName).toURI());
System.out.println(path.toAbsolutePath());

final String actual = path.toString();

assertThat(actual).endsWith(fileName);
}

/**
* 파일 내용 읽기
*
* <p>
* 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다.
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자.
*/
@Test
void 파일의_내용을_읽는다() {
void 파일의_내용을_읽는다() throws IOException, URISyntaxException {
final String fileName = "nextstep.txt";

// todo
final Path path = null;
final Path path = Paths.get(getClass().getClassLoader().getResource(fileName).toURI());

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

assertThat(actual).containsOnly("nextstep");
}
Expand Down
Loading