Skip to content

Commit

Permalink
[톰캣 구현하기 1, 2단계] 안나 (김민겸) 미션 제출합니다. (#571)
Browse files Browse the repository at this point in the history
* test: 학습 테스트 진행

* feat: `/index.html`에 접근했을 때 static/index.html 파일이 보여지도록 구현

* feat: 자원에 따라 다른 Conent-Type으로 응답을 보내도록 수정

* fix: remove implementation logback-classic on gradle (#501)

* fix: 공백 처리 때문에 index.html이 불러와지지 않는 현상 해결

* feat: 로그인 요청 시 쿼리 파라미터를 파싱하는 기능 구현

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

* feat: 로그인에 성공하거나 실패한 경우 다른 페이지로 리다이렉트하는

* feat: POST 요청으로 회원 가입 시 요청 본문에서 정보를 가져와 사용자 저장하는 기능 구현

* feat: 로그인 시 POST 요청을 보내도록 수정

* feat: 로그인 시 세션 아이디를 쿠키에 등록하는 기능 구현

* feat: 쿠키에 JSESSIONID가 없을 때 Set-Cookie를 발급하는 기능 구현

* feat: 이미 로그인한 회원의 경우 로그인 페이지에 접근 불가하도록 구현

* refactor: 쿠키가 없을 때 일반 페이지에 접근하면 세션 아이디를 주지 않음

* feat: HTTP 활용 학습 테스트 진행

* refactor: 뭉친 코드를 클래스 분리하여 리팩토링

* fix: JSESSIONID를 추가하지 않아 로그인되지 않는 문제 해결

* fix: 세션 아이디가 달라서 로그인 시 로그인 페이지가 접속되는 현상 해결

* refactor: 기존 코드 주석 제거

* style: 주석 제거 및 컨벤션 맞추기

* refactor: 미구현된 파일 제거

* refactor: 필터를 사용하여 ETag를 지정하는 것으로 변경

* refactor: 인터셉터에서 CacheControl을 적용할 수 있도록 수정

* refactor: Content-Length 헤더 이름을 상수화

* refactor: 정적 파일을 한 번에 처리하도록 리팩토링

* fix: Json 파일을 보내는 경우를 고려하여 MediaType 수정

* refactor: 로그인 시 쿠키 세션을 꺼내오는 코드 가독성 개선

* refactor: 요청한 사용자 정보가 없는 경우 401 페이지를 보여주도록 수정

* refactor: 쿠키의 키값을 상수화하여 리팩토링

---------

Co-authored-by: Gyeongho Yang <[email protected]>
  • Loading branch information
Mingyum-Kim and geoje authored Sep 8, 2024
1 parent 0b698a2 commit 37b1313
Show file tree
Hide file tree
Showing 17 changed files with 427 additions and 34 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
12 changes: 8 additions & 4 deletions study/src/main/java/cache/com/example/GreetingController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

Expand All @@ -11,8 +12,10 @@
public class GreetingController {

@GetMapping("/")
public String index() {
return "index";
public ResponseEntity<String> index() {
return ResponseEntity.ok()
.header("Content-Encoding", "gzip")
.body("index");
}

/**
Expand All @@ -29,8 +32,9 @@ public String cacheControl(final HttpServletResponse response) {
}

@GetMapping("/etag")
public String etag() {
return "index";
public ResponseEntity<String> etag() {
return ResponseEntity.ok()
.body("index");
}

@GetMapping("/resource-versioning")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package cache.com.example.cachecontrol;

import org.springframework.web.servlet.HandlerInterceptor;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class CacheControlInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
response.addHeader("Cache-Control", "no-cache, private");
return true;
}
}
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 CacheControlInterceptor())
.addPathPatterns("/");
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
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,9 +1,13 @@
package cache.com.example.version;

import java.util.concurrent.TimeUnit;

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 org.springframework.web.servlet.resource.VersionResourceResolver;

@Configuration
public class CacheBustingWebConfig implements WebMvcConfigurer {
Expand All @@ -20,6 +24,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/")
.setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic())
.resourceChain(false)
.addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import org.springframework.stereotype.Component;

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

import jakarta.annotation.PostConstruct;

@Component
public class ResourceVersion {

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 @@ -6,4 +6,7 @@ server:
accept-count: 1
max-connections: 1
threads:
min-spare: 2
max: 2
compression:
enabled: true
24 changes: 19 additions & 5 deletions study/src/test/java/study/FileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDirFactory;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

import javax.swing.filechooser.FileSystemView;

/**
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다.
* File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다.
Expand All @@ -28,7 +38,10 @@ class FileTest {
final String fileName = "nextstep.txt";

// todo
final String actual = "";
URL url = getClass().getClassLoader().getResource(fileName);
File file = new File(url.getPath());

final String actual = file.getAbsolutePath();

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

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

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

final List<String> actual = Files.readAllLines(path);
assertThat(actual).containsOnly("nextstep");
}
}
25 changes: 19 additions & 6 deletions study/src/test/java/study/IOStreamTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class OutputStream_학습_테스트 {
* todo
* OutputStream 객체의 write 메서드를 사용해서 테스트를 통과시킨다
*/
outputStream.write(bytes);

final String actual = outputStream.toString();

Expand All @@ -78,6 +79,7 @@ class OutputStream_학습_테스트 {
* flush를 사용해서 테스트를 통과시킨다.
* ByteArrayOutputStream과 어떤 차이가 있을까?
*/
outputStream.flush();

verify(outputStream, atLeastOnce()).flush();
outputStream.close();
Expand All @@ -97,6 +99,8 @@ class OutputStream_학습_테스트 {
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/

outputStream.close();

verify(outputStream, atLeastOnce()).close();
}
}
Expand Down Expand Up @@ -128,7 +132,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 @@ -148,6 +152,7 @@ class InputStream_학습_테스트 {
* try-with-resources를 사용한다.
* java 9 이상에서는 변수를 try-with-resources로 처리할 수 있다.
*/
inputStream.close();

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

final StringBuilder actual = new StringBuilder();

try (
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
) {
String line;
while((line = bufferedReader.readLine()) != null) {
actual.append(line + "\r\n");
}
}
assertThat(actual).hasToString(emoji);
}
}
Expand Down
35 changes: 25 additions & 10 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,38 @@
package org.apache.coyote.http11;


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

import org.apache.coyote.Processor;
import org.apache.coyote.http11.request.HttpRequest;
import org.apache.coyote.http11.request.RequestHandler;
import org.apache.coyote.http11.session.SessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

public class Http11Processor implements Runnable, Processor {

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

private final Socket connection;
private static final Map<String, String> httpRequestHeader = new HashMap<>();
private static final String sessionId = "JSESSIONID=sessionId";

public Http11Processor(final Socket connection) {
this.connection = connection;
Expand All @@ -29,18 +49,13 @@ public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
final var outputStream = connection.getOutputStream()) {

final var responseBody = "Hello world!";
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
HttpRequest httpRequest = new HttpRequest(reader);

final var response = String.join("\r\n",
"HTTP/1.1 200 OK ",
"Content-Type: text/html;charset=utf-8 ",
"Content-Length: " + responseBody.getBytes().length + " ",
"",
responseBody);
RequestHandler requestHandler = new RequestHandler(httpRequest, outputStream);
requestHandler.handleRequest();

outputStream.write(response.getBytes());
outputStream.flush();
} catch (IOException | UncheckedServletException e) {
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.apache.coyote.http11.request;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class HttpRequest {

private static final String CONTENT_LENGTH = "Content-Length";

private final String method;
private final String path;
private final Map<String, String> headers = new HashMap<>();
private final String body;

public HttpRequest(BufferedReader reader) throws IOException {
String initialLine = reader.readLine();
this.method = initialLine.split(" ")[0];
this.path = initialLine.split(" ")[1];
String line;
while ((line = reader.readLine()) != null && !line.isEmpty()) {
String[] header = line.split(":");
headers.put(header[0].trim(), header[1].trim());
}
this.body = parseBody(reader);
}

private String parseBody(BufferedReader reader) throws IOException {
if(headers.get(CONTENT_LENGTH) == null) {
return null;
}
int contentLength = Integer.parseInt(headers.get(CONTENT_LENGTH));
if(contentLength > 0) {
char[] body = new char[contentLength];
reader.read(body, 0, contentLength);
return new String(body);
}
return null;
}

public String getMethod() {
return method;
}

public String getPath() {
return path;
}

public String getCookie() {
return headers.get("Cookie");
}

public String getBody() {
return body;
}
}
Loading

0 comments on commit 37b1313

Please sign in to comment.