Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
436e7b0
chore: .gitignore 학습 테스트를 버전 관리하지 않도록 수정
takoyakimchi Sep 4, 2024
ae9a4f4
feat: /index.html 응답하기 구현
takoyakimchi Sep 4, 2024
8fd0d5d
refactor: 200 응답 문자열 생성하는 부분을 메서드로 분리
takoyakimchi Sep 4, 2024
edab62e
refactor: index.html 이외 다른 파일명이 들어와도 리소스 반환하도록 수정
takoyakimchi Sep 4, 2024
7c28a39
refactor: 메서드명 간결화
takoyakimchi Sep 4, 2024
ba2e0fc
feat: 리소스 존재하지 않는 경우 404.html 반환
takoyakimchi Sep 4, 2024
67a27ac
feat: Query String 파싱 구현
takoyakimchi Sep 4, 2024
8a3f7ae
fix: 올바른 Content-Type 지정
takoyakimchi Sep 4, 2024
9fdc595
fix: id, pw 존재하지 않아도 예외 발생하지 않도록 수정, /login으로 로그인 페이지 접속 가능하도록 수정
takoyakimchi Sep 4, 2024
ddd688c
refactor: Response Message를 생성하는 메서드에서 상태 코드를 인자로 받도록 수정
takoyakimchi Sep 4, 2024
95d88b2
feat: 로그인 시 리다이렉트 구현
takoyakimchi Sep 4, 2024
5552f95
feat: 로그인을 POST 메서드로 변경
takoyakimchi Sep 4, 2024
67c7fff
feat: 회원가입 구현
takoyakimchi Sep 4, 2024
afdf820
fix: .js 파일의 Content-Type 지정
takoyakimchi Sep 5, 2024
344aa3d
feat: HttpCookie 클래스 생성
takoyakimchi Sep 5, 2024
69bf219
feat: Cookie에 JSESSIONID 값 저장하기 구현
takoyakimchi Sep 5, 2024
9ecd573
refactor: 상수 사용하도록 일부 수정
takoyakimchi Sep 5, 2024
a86407a
fix: jakarta의 HttpSession을 사용하지 않도록 수정
takoyakimchi Sep 5, 2024
239b42d
feat: 세션 구현
takoyakimchi Sep 5, 2024
d326ace
refactor: Request 관련 로직을 별도의 클래스로 분리
takoyakimchi Sep 5, 2024
c44ef38
refactor: 헤더 읽기, body 읽기 로직을 메서드로 분리
takoyakimchi Sep 5, 2024
1310577
refactor: 쿠키 관련 로직을 분리
takoyakimchi Sep 5, 2024
50f8f48
refactor: HTTP 응답 로직 분리
takoyakimchi Sep 5, 2024
9c16499
refactor: 메서드명 및 시그니처명 변경
takoyakimchi Sep 5, 2024
83023c1
refactor: inline variable 적용
takoyakimchi Sep 5, 2024
d931626
refactor: 핸들러 메서드 분리
takoyakimchi Sep 5, 2024
875729e
refactor: 중복 제거
takoyakimchi Sep 5, 2024
c2d23de
refactor: 메서드명 변경
takoyakimchi Sep 5, 2024
af08606
refactor: 불필요한 if문 depth 제거
takoyakimchi Sep 5, 2024
7fee36c
chore: 패키지 구조 변경
takoyakimchi Sep 5, 2024
fe6992b
refactor: set-cookie 하는 부분 메서드 분리
takoyakimchi Sep 5, 2024
ce21761
fix: Location 헤더로 올바르게 리다이렉션 하도록 수정
takoyakimchi Sep 5, 2024
851ee85
refactor: 메서드명 수정
takoyakimchi Sep 5, 2024
db1cba5
fix: request line 마지막에 공백 추가
takoyakimchi Sep 5, 2024
5735196
refactor: 단순 텍스트를 응답하는 경우에 대한 응답 메서드 작성
takoyakimchi Sep 5, 2024
8fab15d
test: 깨지는 테스트 수정
takoyakimchi Sep 5, 2024
74bdc40
refactor: 메서드명 변경
takoyakimchi Sep 5, 2024
a70c565
feat: 존재하지 않는 리소스에 접근하는 경우 404 페이지를 보여줌
takoyakimchi Sep 6, 2024
bb21902
refactor: Location 헤더를 enum으로 관리
takoyakimchi Sep 6, 2024
436b85c
chore: print문 삭제
takoyakimchi Sep 6, 2024
af4c48a
refactor: Set-Cookie 헤더를 상수가 아닌 enum으로 변경
takoyakimchi Sep 6, 2024
33af31d
test: FileTest 작성
takoyakimchi Sep 6, 2024
f69e93d
test: IOStreamTest 작성
takoyakimchi Sep 6, 2024
a321dd7
chore: 학습 테스트를 다시 버전관리 하도록 수정
takoyakimchi Sep 6, 2024
9728731
fix: remove implementation logback-classic on gradle (#501)
geoje Sep 5, 2024
6a3782a
fix: add threads min-spare configuration on properties (#502)
geoje Sep 5, 2024
03b1b72
chore: Thymeleaf 의존성 추가
takoyakimchi Sep 6, 2024
df06a96
feat: 휴리스틱 캐싱 제거
takoyakimchi Sep 6, 2024
c0b08ee
chore: HTTP 압축 설정
takoyakimchi Sep 6, 2024
2b3dcaf
feat: ETag 추가
takoyakimchi Sep 6, 2024
69ec4d5
feat: .js와 .css 파일은 1년 동안 캐시 적용
takoyakimchi Sep 6, 2024
e3e1f40
refactor: interceptor 구현체를 지우고 WebContentIntercepor 사용
takoyakimchi Sep 6, 2024
99ebc01
refactor: 헤더의 이름을 enum에서 꺼내오도록 수정
takoyakimchi Sep 10, 2024
f32bde8
refactor: 미사용 메서드 제거
takoyakimchi Sep 10, 2024
2ec1366
refactor: depth 개선
takoyakimchi Sep 10, 2024
65a1130
refactor: request에서 cookie 가져오는 로직 메서드로 분리
takoyakimchi Sep 10, 2024
f9b3f5b
refactor: HttpCookie에 세션 관련 메서드 생성
takoyakimchi Sep 10, 2024
f6848e4
refactor: Set-Cookie 관련 로직 개선
takoyakimchi Sep 10, 2024
7511686
refactor: if문 개선
takoyakimchi Sep 10, 2024
24a7655
refactor: HttpHeaders -> HttpHeaderType
takoyakimchi Sep 10, 2024
4f1a76f
refactor: 반복문을 스트림으로 개선
takoyakimchi Sep 10, 2024
45851e4
refactor: 각종 delimiters 상수화
takoyakimchi Sep 10, 2024
e5eac82
refactor: 중복되는 build() 로직 개선
takoyakimchi Sep 10, 2024
6a2027b
refactor: CRLF 상수화
takoyakimchi 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,13 +1,26 @@
package cache.com.example.cachecontrol;

import static java.util.concurrent.TimeUnit.DAYS;

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 noCacheIntercepter = new WebContentInterceptor();
noCacheIntercepter.addCacheMapping(CacheControl.noCache().cachePrivate(), "/**");
registry.addInterceptor(noCacheIntercepter)
Copy link
Member

Choose a reason for hiding this comment

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

이미지 확장자는 처리하지 않은 이유가 있을까요 😃

.excludePathPatterns("/**/*.js", "/**/*.css");

WebContentInterceptor cacheIntercepter = new WebContentInterceptor();
cacheIntercepter.addCacheMapping(CacheControl.maxAge(365, DAYS).cachePublic(), "/**");
registry.addInterceptor(cacheIntercepter)
.addPathPatterns("/**/*.js", "/**/*.css");
}
}
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> filter
= new FilterRegistrationBean<>(new ShallowEtagHeaderFilter());
filter.addUrlPatterns("/etag", "*.js", "*.css");
return filter;
}
}
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
42 changes: 20 additions & 22 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,51 @@
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.nio.file.Paths;
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 파일을 제공 할 수 있어야 한다.
* File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
*/
@DisplayName("File 클래스 학습 테스트")
class FileTest {

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

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

assertThat(actual).endsWith(fileName);
}

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

// todo
final Path path = null;

// todo
final List<String> actual = Collections.emptyList();
URL url = getClass().getClassLoader().getResource(fileName);
final Path path = Paths.get(url.toURI());
final List<String> actual = Files.readAllLines(path);

assertThat(actual).containsOnly("nextstep");
}
Expand Down
27 changes: 14 additions & 13 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,7 @@ class OutputStream_학습_테스트 {
void OutputStream은_데이터를_바이트로_처리한다() throws IOException {
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 @@ -72,13 +68,12 @@ class OutputStream_학습_테스트 {
@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,6 +91,7 @@ class OutputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
outputStream.close();

verify(outputStream, atLeastOnce()).close();
}
Expand Down Expand Up @@ -128,7 +124,8 @@ class InputStream_학습_테스트 {
* todo
* inputStream에서 바이트로 반환한 값을 문자열로 어떻게 바꿀까?
*/
final String actual = "";
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
final String actual = bufferedReader.readLine();

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

inputStream.close();
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,7 +194,7 @@ class InputStreamReader_학습_테스트 {
* 필터인 BufferedReader를 사용하면 readLine 메서드를 사용해서 문자열(String)을 한 줄 씩 읽어올 수 있다.
*/
@Test
void BufferedReader를_사용하여_문자열을_읽어온다() {
void BufferedReader를_사용하여_문자열을_읽어온다() throws IOException {
final String emoji = String.join("\r\n",
"😀😃😄😁😆😅😂🤣🥲☺️😊",
"😇🙂🙃😉😌😍🥰😘😗😙😚",
Expand All @@ -206,6 +203,10 @@ class InputStreamReader_학습_테스트 {
final InputStream inputStream = new ByteArrayInputStream(emoji.getBytes());

final StringBuilder actual = new StringBuilder();
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
while (bufferedReader.ready()) {
actual.append(bufferedReader.readLine()).append("\r\n");
}

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

import com.techcourse.exception.UncheckedServletException;
import com.techcourse.model.User;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

public class InMemoryUserRepository {
Expand All @@ -19,9 +18,22 @@ public static void save(User user) {
database.put(user.getAccount(), user);
}

public static Optional<User> findByAccount(String account) {
return Optional.ofNullable(database.get(account));
public static boolean exists(String account, String password) {
return database.containsKey(account)
&& database.get(account).checkPassword(password);
}

public static User getByAccount(String account) {
if (database.containsKey(account)) {
return database.get(account);
}
throw new UncheckedServletException("유저가 존재하지 않습니다.");
}

private InMemoryUserRepository() {}
public static boolean existsByAccount(String account) {
return database.containsKey(account);
}

private InMemoryUserRepository() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ public class UncheckedServletException extends RuntimeException {
public UncheckedServletException(Exception e) {
super(e);
}

public UncheckedServletException(String message) {
super(message);
}
}
40 changes: 17 additions & 23 deletions tomcat/src/main/java/org/apache/catalina/Manager.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package org.apache.catalina;

import jakarta.servlet.http.HttpSession;

import java.io.IOException;
import java.util.Optional;
import org.apache.catalina.session.Session;

/**
* A <b>Manager</b> manages the pool of Sessions that are associated with a
* particular Container. Different Manager implementations may support
* value-added features such as the persistent storage of session data,
* as well as migrating sessions for distributable web applications.
* A <b>Manager</b> manages the pool of Sessions that are associated with a particular Container. Different Manager
* implementations may support value-added features such as the persistent storage of session data, as well as migrating
* sessions for distributable web applications.
* <p>
* In order for a <code>Manager</code> implementation to successfully operate
* with a <code>Context</code> implementation that implements reloading, it
* must obey the following constraints:
* In order for a <code>Manager</code> implementation to successfully operate with a <code>Context</code> implementation
* that implements reloading, it must obey the following constraints:
* <ul>
* <li>Must implement <code>Lifecycle</code> so that the Context can indicate
* that a restart is required.
Expand All @@ -29,28 +27,24 @@ public interface Manager {
*
* @param session Session to be added
*/
void add(HttpSession session);
void add(String id, Session session);

/**
* Return the active Session, associated with this Manager, with the
* specified session id (if any); otherwise return <code>null</code>.
* Return the active Session, associated with this Manager, with the specified session id (if any); otherwise
* return
* <code>null</code>.
*
* @param id The session id for the session to be returned
*
* @exception IllegalStateException if a new session cannot be
* instantiated for any reason
* @exception IOException if an input/output error occurs while
* processing this request
*
* @return the request session or {@code null} if a session with the
* requested ID could not be found
* @return the request session or {@code null} if a session with the requested ID could not be found
* @throws IllegalStateException if a new session cannot be instantiated for any reason
* @throws IOException if an input/output error occurs while processing this request
*/
HttpSession findSession(String id) throws IOException;
Optional<Session> findSession(String id) throws IOException;

/**
* Remove this Session from the active Sessions for this Manager.
*
* @param session Session to be removed
* @param id Session to be removed
*/
void remove(HttpSession session);
void remove(String id);
}
21 changes: 21 additions & 0 deletions tomcat/src/main/java/org/apache/catalina/session/Session.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.apache.catalina.session;

import com.techcourse.model.User;
import java.util.HashMap;
import java.util.Map;

public class Session {

private static final String USER_SESSION_NAME = "user";

private final Map<String, Object> values = new HashMap<>();

private Session() {
}

public static Session ofUser(User user) {
Session session = new Session();
session.values.put(USER_SESSION_NAME, user);
return session;
Comment on lines +16 to +19
Copy link
Member

Choose a reason for hiding this comment

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

실제 사용하는 메서드만 남겨주셨군요 👍

}
}
Loading