Skip to content

[1단계 - Tomcat 구현하기] 낙낙(이낙헌) 미션 제출합니다. #561

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 18 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
a8707e9
feat: Http 요청/응답 객체 구현
nak-honest Sep 6, 2024
5063922
feat: Http 요청을 read하고 응답을 wirte 하는 기능 구현
nak-honest Sep 6, 2024
dcb726e
feat: Http 요청으로부터 응답을 내는 기능 구현
nak-honest Sep 6, 2024
cb8d8e4
feat: 로그인을 하면 유저 정보를 찾는 기능 구현
nak-honest Sep 6, 2024
9275770
feat: 클라이언트로부터 HTTP 요청을 받아 응답을 하는 기능 구현
nak-honest Sep 6, 2024
1877ccd
refactor: 각 클라이언트의 요청을 처리하는 스레드를 데몬 스레드로 변경
nak-honest Sep 6, 2024
94a6f54
feat: 학습 테스트 1 구현
nak-honest Sep 6, 2024
c9dd251
feat: 학습 테스트 2 구현
nak-honest Sep 6, 2024
b9a6d5a
merge 충돌 해결
nak-honest Sep 6, 2024
013d6a6
refactor: query parameter 들을 일급 컬렉션으로 래핑
nak-honest Sep 6, 2024
4cfcf08
refactor: static class 의 생성자 접근제어자를 private 으로 변경
nak-honest Sep 6, 2024
064a916
style: 코드 스타일 통일성이 맞도록 수정
nak-honest Sep 6, 2024
18211e1
refactor: 변수명 변경
nak-honest Sep 6, 2024
160869d
refactor: 지역 변수로 받지 않고 바로 return하도록 변경
nak-honest Sep 6, 2024
cdec028
fix: 학습 테스트 2 4번에서 ETag 가 포함되지 않는 문제 해결
nak-honest Sep 6, 2024
e37693b
fix: 브라우저에서 versoning이 적용되도록 수정
nak-honest Sep 6, 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
3 changes: 1 addition & 2 deletions study/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ 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 'com.github.jknack:handlebars-springmvc:4.4.0'

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,34 @@
package cache.com.example.cachecontrol;

import cache.com.example.version.ResourceVersion;
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.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.WebContentInterceptor;

@Configuration
public class CacheWebConfig implements WebMvcConfigurer {

private final ResourceVersion version;

@Autowired
public CacheWebConfig(ResourceVersion version) {
this.version = version;
}

@Override
public void addInterceptors(final InterceptorRegistry registry) {
WebContentInterceptor noCacheInterceptor = new WebContentInterceptor();

Choose a reason for hiding this comment

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

미리 구현된 인터셉터를 활용하셨군요👍🏿

noCacheInterceptor.addCacheMapping(CacheControl.noCache().cachePrivate(), "/**");
registry.addInterceptor(noCacheInterceptor);

WebContentInterceptor cacheInterceptor = new WebContentInterceptor();
cacheInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)), "/etag");
cacheInterceptor.addCacheMapping(CacheControl.maxAge(Duration.ofDays(365L)).cachePublic(),
"/resources/" + version.getVersion() + "/**");
registry.addInterceptor(cacheInterceptor);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
package cache.com.example.etag;

import cache.com.example.version.ResourceVersion;
import org.springframework.beans.factory.annotation.Autowired;
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;
// }
private final ResourceVersion version;

@Autowired
public EtagFilterConfiguration(ResourceVersion version) {
this.version = version;
}


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

import com.github.jknack.handlebars.springmvc.HandlebarsViewResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;

@Configuration
public class HandlebarsConfig {

private final VersionHandlebarsHelper versionHandlebarsHelper;

public HandlebarsConfig(VersionHandlebarsHelper versionHandlebarsHelper) {
this.versionHandlebarsHelper = versionHandlebarsHelper;
}

@Bean
public ViewResolver handlebarsViewResolver() {
HandlebarsViewResolver viewResolver = new HandlebarsViewResolver();
viewResolver.registerHelper("staticUrls", versionHandlebarsHelper);
viewResolver.setPrefix("classpath:/templates/");
viewResolver.setSuffix(".html");

return viewResolver;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package cache.com.example.version;

import org.springframework.stereotype.Component;

import jakarta.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import org.springframework.stereotype.Component;

@Component
public class ResourceVersion {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package cache.com.example.version;

import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.Options;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import pl.allegro.tech.boot.autoconfigure.handlebars.HandlebarsHelper;
import org.springframework.stereotype.Component;

@HandlebarsHelper
public class VersionHandlebarsHelper {
@Component
public class VersionHandlebarsHelper implements Helper<Object> {

Choose a reason for hiding this comment

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

기존 Handlebars가 작동하지 않아, 어떻게 해결해야하는지 궁금했는데 배워갑니다ㅎㅎ

Copy link
Member Author

Choose a reason for hiding this comment

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

해당 부분은 #520 에서 참고했습니다!!
출처는 페드로에욥 ㅎㅎ


private static final Logger log = LoggerFactory.getLogger(VersionHandlebarsHelper.class);

Expand All @@ -22,4 +23,9 @@ public String staticUrls(String path, Options options) {
log.debug("static url : {}", path);
return String.format("/resources/%s%s", version.getVersion(), path);
}

@Override
public Object apply(Object context, Options options) {
return staticUrls(context.toString(), options);
}
}
7 changes: 4 additions & 3 deletions study/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
handlebars:
suffix: .html

server:
tomcat:
accept-count: 1
max-connections: 1
threads:
min-spare: 2
max: 2
compression:
enabled: true
min-response-size: 10
18 changes: 10 additions & 8 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package study;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

Expand All @@ -26,9 +31,7 @@ class FileTest {
@Test
void resource_디렉터리에_있는_파일의_경로를_찾는다() {
final String fileName = "nextstep.txt";

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

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

// todo
final Path path = null;
URL resource = getClass().getClassLoader().getResource(fileName);
File file = new File(resource.getPath());

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

assertThat(actual).containsOnly("nextstep");
}
Expand Down
43 changes: 22 additions & 21 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package study;

import java.util.StringJoiner;
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.assertj.core.api.Assertions.in;
import static org.mockito.Mockito.*;

/**
Expand Down Expand Up @@ -49,10 +52,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();

Expand All @@ -73,11 +73,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,14 +86,17 @@ class OutputStream_학습_테스트 {
@Test
void OutputStream은_사용하고_나서_close_처리를_해준다() throws IOException {
final OutputStream outputStream = mock(OutputStream.class);
try (outputStream) {
} catch (IOException e) {
}

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

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

Expand Down Expand Up @@ -128,7 +127,7 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
final String actual = new String(inputStream.readAllBytes());

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

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

verify(inputStream, atLeastOnce()).close();
}
Expand All @@ -169,12 +166,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,17 +194,21 @@ 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 Reader reader = new InputStreamReader(inputStream);
final BufferedReader bufferedReader = new BufferedReader(reader);

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

assertThat(actual).hasToString(emoji);
System.out.println(System.lineSeparator());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.techcourse.controller;

import com.techcourse.model.User;
import com.techcourse.service.UserService;
import java.io.IOException;
import org.apache.coyote.http11.handler.HttpHandler;
import org.apache.coyote.http11.handler.ViewHttpHandler;
import org.apache.coyote.http11.message.request.HttpRequest;
import org.apache.coyote.http11.message.request.QueryParameters;
import org.apache.coyote.http11.message.response.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoginController implements HttpHandler {

private static final Logger log = LoggerFactory.getLogger(LoginController.class);

private final UserService service;
private final ViewHttpHandler viewHttpHandler;

public LoginController(UserService service) {
this.service = service;
this.viewHttpHandler = new ViewHttpHandler("login");
}

@Override
public HttpResponse handle(HttpRequest request) throws IOException {
QueryParameters queryParameters = request.getQueryParameters();

String account = queryParameters.getSingleValueByKey("account");
String password = queryParameters.getSingleValueByKey("password");

User user = service.findUserByAccountAndPassword(account, password);
log.info("user: {}", user);

return viewHttpHandler.handle(request);
}
}
Loading