Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 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
4a259b5
test: FileTest 및 IOStreamTest 통과
seunghye218 Sep 4, 2024
fddfdc6
chore: lombok 의존성 추가
seunghye218 Sep 5, 2024
1d08960
feat: 1-1 'GET /index.html 응답하기' 기능 구현
seunghye218 Sep 5, 2024
90931d9
feat: 1-2 CSS 지원하기 기능 구현
seunghye218 Sep 5, 2024
7319283
refactor: getResponse 로직 메서드 분리
seunghye218 Sep 5, 2024
ba5010d
refactor: 간단한 예외처리 추가
seunghye218 Sep 5, 2024
c037870
refactor(HttpRequest): path와 parameters 추가 및 파싱
seunghye218 Sep 5, 2024
7d4f988
feat: 1-3 Query String 파싱 구현
seunghye218 Sep 5, 2024
38a85a1
feat: 2-1 로그인 여부에 따라 페이지 이동 구현
seunghye218 Sep 5, 2024
721b7e9
refactor: HttpResponse 객체 추가
seunghye218 Sep 6, 2024
6f53481
refactor: 메서드 인자 및 이름 변경
seunghye218 Sep 6, 2024
db007a2
fix: 템플릿 엔진 미사용
seunghye218 Sep 6, 2024
36c22be
test: 휴리스틱 캐싱 제거
seunghye218 Sep 6, 2024
add93cd
test: HTTP Compression 설정
seunghye218 Sep 6, 2024
7f37783
fix: 알맞은 resource 이름 반환
seunghye218 Sep 6, 2024
7497b66
test: ETag/If-None-Match 적용
seunghye218 Sep 6, 2024
a633a27
test: 캐시 무효화
seunghye218 Sep 6, 2024
34d44a6
feat: POST 방식으로 회원가입
seunghye218 Sep 6, 2024
837933e
fix: 파라미터 없는 /login 접속 불가능 수정
seunghye218 Sep 6, 2024
1b6cb98
fix: 요청 본문 URLDecode
seunghye218 Sep 6, 2024
0125570
fix: Cookie에 JSESSIONID 값 저장
seunghye218 Sep 6, 2024
4109760
feat: Session 구현
seunghye218 Sep 6, 2024
b3de68a
refactor: 요청 URL 디코딩
seunghye218 Sep 9, 2024
ac157e2
refactor: application/x-www-form-urlencoded MIME 타입 요청 본문 값을 파라미터로 처리
seunghye218 Sep 9, 2024
c3c3791
refactor(HttpResponse): 생성자 추가
seunghye218 Sep 9, 2024
1ceb56d
refactor(HttpResponse): 상태 코드 및 메시지 열거형 분리
seunghye218 Sep 9, 2024
47f234f
refactor: Http11Processor 에서 컨트롤러 로직 분리
seunghye218 Sep 10, 2024
4d930c8
refactor: 정적 자원 캐시 정책 추가
seunghye218 Sep 10, 2024
8162290
refactor: 요청, 응답 헤더 객체화
seunghye218 Sep 11, 2024
351db10
refactor: 로그인 요청 메서드 POST로 변경
seunghye218 Sep 12, 2024
4185398
test: 컨트롤러 및 도매인 테스트 추가
seunghye218 Sep 12, 2024
f733f4a
fix: 키밸류 값 문제 시 패스
seunghye218 Sep 12, 2024
9dd3ef8
chore: 세션 패키지 변경
seunghye218 Sep 12, 2024
759226d
Merge branch 'seunghye218' into seunghye218
seunghye218 Sep 12, 2024
0b20278
refactor: svg 파일 처리 추가
seunghye218 Sep 12, 2024
82f6c1d
refactor: 변수명 변경
seunghye218 Sep 13, 2024
b28e846
chore: 패키지 수정
seunghye218 Sep 13, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.techcourse.controller;

import org.apache.catalina.connector.HttpRequest;
import org.apache.catalina.connector.HttpResponse;
import com.techcourse.http.MimeType;
import java.io.IOException;
import org.apache.catalina.StaticResourceProvider;
import jakarta.servlet.http.AbstractController;

public class DashBoardController extends AbstractController {

@Override
protected void doGet(HttpRequest request, HttpResponse response) throws IOException {
response.setBody(StaticResourceProvider.getStaticResource("/index.html"))
.setContentType(MimeType.HTML.getMimeType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.techcourse.controller;

import org.apache.catalina.session.Session;
import org.apache.catalina.session.SessionManager;
import com.techcourse.db.InMemoryUserRepository;
import org.apache.catalina.connector.HttpRequest;
import org.apache.catalina.connector.HttpResponse;
import com.techcourse.http.MimeType;
import com.techcourse.model.User;
import java.io.IOException;
import java.util.UUID;
import org.apache.catalina.StaticResourceProvider;
import jakarta.servlet.http.AbstractController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoginController extends AbstractController {

private static final Logger log = LoggerFactory.getLogger(LoginController.class);
private static final String JSESSIONID = "JSESSIONID";
private static final String ACCOUNT = "account";
private static final String PASSWORD = "password";
private static final String INDEX_HTML_PATH = "/index.html";
private static final String LOGIN_HTML_PATH = "/login.html";
private static final String ERROR_401_HTML_PATH = "/401.html";

private final InMemoryUserRepository inMemoryUserRepository = InMemoryUserRepository.getInstance();
private final SessionManager sessionManager = SessionManager.getInstance();

@Override
public void doGet(HttpRequest request, HttpResponse response) throws IOException {
if (request.getCookie(JSESSIONID) != null) {
response.found(INDEX_HTML_PATH);
return;
}

response.setBody(StaticResourceProvider.getStaticResource(LOGIN_HTML_PATH))
.setContentType(MimeType.HTML.getMimeType());
}

@Override
protected void doPost(HttpRequest request, HttpResponse response) {
if (!request.hasParameter(ACCOUNT) || !request.hasParameter(PASSWORD)) {
response.found(ERROR_401_HTML_PATH);
return;
}

String account = request.getParameter(ACCOUNT);
String password = request.getParameter(PASSWORD);
inMemoryUserRepository.findByAccount(account).ifPresentOrElse(
user -> login(user, request, response, password),
() -> response.found(ERROR_401_HTML_PATH)
);
}

private void login(User user, HttpRequest request, HttpResponse response, String password) {
if (!user.checkPassword(password)) {
response.found(ERROR_401_HTML_PATH);
return;
}

log.info("user : {}", user);

if (request.getCookie(JSESSIONID) == null) {
Session session = new Session(UUID.randomUUID().toString());
session.setAttribute("user", user);
sessionManager.add(session);
response.setCookie(JSESSIONID, session.getId())
.setHttpOnly(true);
}
response.found(INDEX_HTML_PATH);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.techcourse.controller;

import com.techcourse.db.InMemoryUserRepository;
import org.apache.catalina.connector.HttpRequest;
import org.apache.catalina.connector.HttpResponse;
import com.techcourse.http.MimeType;
import com.techcourse.model.User;
import java.io.IOException;
import org.apache.catalina.StaticResourceProvider;
import jakarta.servlet.http.AbstractController;

public class RegisterController extends AbstractController {

private static final String ACCOUNT = "account";
private static final String PASSWORD = "password";
private static final String EMAIL = "email";

private final InMemoryUserRepository inMemoryUserRepository = InMemoryUserRepository.getInstance();

@Override
protected void doGet(HttpRequest request, HttpResponse response) throws IOException {
String registerHtml = StaticResourceProvider.getStaticResource("/register.html");
response.setBody(registerHtml)
.setContentType(MimeType.HTML.getMimeType());
}

@Override
protected void doPost(HttpRequest request, HttpResponse response) {
if (!request.hasParameter(ACCOUNT) || !request.hasParameter(PASSWORD) || !request.hasParameter(EMAIL)) {
Copy link

Choose a reason for hiding this comment

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

controller에서도 검증 👍

response.badRequest();
return;
}

inMemoryUserRepository.save(new User(
request.getParameter(ACCOUNT),
request.getParameter(PASSWORD),
request.getParameter(EMAIL)
));

response.found("/index.html");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.techcourse.controller;

import org.apache.catalina.connector.HttpRequest;
import org.apache.catalina.connector.HttpResponse;
import org.apache.catalina.StaticResourceProvider;
import jakarta.servlet.http.AbstractController;

public class StaticResourceController extends AbstractController {
Copy link

Choose a reason for hiding this comment

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

정적 리소스(html, css, js)는 AbstractController의 모든 행위(doPost, doPatch, doDelete 등)들이 필요하지 않은 것 같은데요 ! AbstractController를 상속하는 것보다 더 좋은 방법이 있을까요 ??

제가 구현하면서 고민한 부분이라 마크의 의견이 궁금합니다!

Copy link
Author

@seunghye218 seunghye218 Sep 12, 2024

Choose a reason for hiding this comment

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

먼저 생각된 부분은 ResourceHandler를 구현하여 특정 URL 패턴에는 '/static' 등을 매칭에서 미리 반환하는 방법도 있을 것 같아요. 두번째는 RequestMapping에서 컨트롤러 맵핑이 안되었을 때, 컨트롤러 로직을 스킵하고 정적 리소스를 응답하고, 세번쨰 방법은 템플릿 엔진처럼 Controller.service 메서드가 View에 관한 값을 리턴하도록 하면 '/template'에 있는 자원을 매칭해 줄 수 있을 것 같아요.

웹 서버는 컨트롤러 없이 정적 자원을 응답할 수 있으니까 2번으로 구현하는 것이 간단하고 톰캣 구현하기 미션에 맞을 거라고 생각이 드네요...! 미아는 어떤 결론을 지으셨는지 궁금하네요!

+) 카탈리나에서 DefaultServlet 에서 다음과 같은 주석이 있어요.

대부분 웹 애플리케이션의 기본 리소스 제공 서블릿으로, HTML 페이지와 이미지와 같은 정적 리소스를 제공하는 데 사용됩니다.
DefaultServlet.java

정적 리소스는 doGet 등의 행위가 필요하다고 볼 수 있을 것 같아요.

Copy link

Choose a reason for hiding this comment

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

오호 톰캣 레퍼런스까지 👍 저는 static resource는 http request로 수정할 일이 없다고 생각했는데 DefaultServletdoPut을 보면 또 그렇게 구현되어 있지는 않네요 🤯

미아는 어떤 결론을 지으셨는지 궁금하네요!

세 가지 방법이나 !! 좋은 의견 감사합니다 🙇🏻‍♀️ 다 일리있는 의견 같아요. 저는 ResourceHandler라는 이름의 클래스가 Controller를 구현해서 service 메소드에서 static resource들을 찾아 HttpResponse에 담아주었습니다. 위에서 언급했듯 정적 리소스들은 doPut, doPost, doDelete 등이 필요하지 않다고 생각해서 AbstractController를 상속 받지 않았어요.

하지만 톰캣은 웹 서버가 아니라 서블릿 컨테이너의 기능을 하니까 마크의 구현 방식도 적절하고 확장하기 쉽다고 생각합니다! 굿

Copy link
Author

Choose a reason for hiding this comment

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

그런 방법도 있겠네요! 꼭 AbstractController를 상속할 필요는 없었는데 말이죠. 좋은 방법 알려주셔서 고마워요😄


@Override
protected void doGet(HttpRequest request, HttpResponse response) throws Exception {
String path = request.getPath();
response.setBody(StaticResourceProvider.getStaticResource(path))
.setContentType(StaticResourceProvider.probeContentType(path));
}
}
24 changes: 18 additions & 6 deletions tomcat/src/main/java/com/techcourse/db/InMemoryUserRepository.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
package com.techcourse.db;

import com.techcourse.model.User;

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

public class InMemoryUserRepository {

private static final InMemoryUserRepository INSTANCE = new InMemoryUserRepository();
private static final Map<String, User> database = new ConcurrentHashMap<>();

private final AtomicLong id = new AtomicLong();

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

public static InMemoryUserRepository getInstance() {
return INSTANCE;
}

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

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

private InMemoryUserRepository() {}
private InMemoryUserRepository() {
}

public void clear() {
database.clear();
}
}
34 changes: 0 additions & 34 deletions tomcat/src/main/java/com/techcourse/http/HttpCookie.java

This file was deleted.

84 changes: 84 additions & 0 deletions tomcat/src/main/java/com/techcourse/http/HttpHeaders.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package com.techcourse.http;

import jakarta.servlet.http.HttpCookie;
import java.util.HashMap;
import java.util.Map;
import lombok.AllArgsConstructor;

@AllArgsConstructor
public class HttpHeaders {

private static final String HEADER_SEPARATOR = ": ";
private static final String CRLF = "\r\n";
public static final String CONTENT_TYPE = "Content-Type";
public static final String CONTENT_LENGTH = "Content-Length";
public static final String SET_COOKIE_HEADER = "Set-Cookie";
public static final String COOKIE = "Cookie";
public static final String LOCATION = "Location";
public static final String APPLICATION_X_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";

private final Map<String, String> headers;
private final HttpCookie cookie;

public HttpHeaders() {
this.headers = new HashMap<>();
this.cookie = new HttpCookie();
}

public void clear() {
headers.clear();
cookie.clear();
}

public String getHeadersString() {
if (cookie.isExist()) {
headers.put(SET_COOKIE_HEADER, cookie.toMessage());
}
StringBuilder headersString = new StringBuilder();
for (Map.Entry<String, String> entry : headers.entrySet()) {
headersString.append(entry.getKey())
.append(HEADER_SEPARATOR)
.append(entry.getValue())
.append(" ")
.append(CRLF);
}
headersString.append(cookie.toMessage());
return headersString.toString();
}

public String get(String key) {
return headers.get(key);
}

public void set(String key, String value) {
headers.put(key, value);
}

public void setContentType(String contentType) {
set(CONTENT_TYPE, contentType);
}

public void setContentLength(int contentLength) {
set(CONTENT_LENGTH, String.valueOf(contentLength));
}

public void setCookie(String cookieKey, String cookieValue) {
cookie.setCookie(cookieKey, cookieValue);
}

public void setLocation(String location) {
set(LOCATION, location);
}

public String getCookie(String key) {
return cookie.getCookie(key);
}

public void setHttpOnly(boolean httpOnly) {
cookie.setHttpOnly(httpOnly);
}

public void setMaxAge(int maxAge) {
cookie.setMaxAge(maxAge);
}
}
45 changes: 0 additions & 45 deletions tomcat/src/main/java/com/techcourse/http/HttpRequest.java

This file was deleted.

Loading