Skip to content

Commit

Permalink
[톰캣 구현하기 - 3단계] 안나(김민겸) 미션 제출합니다. (#652)
Browse files Browse the repository at this point in the history
* fix: remove implementation logback-classic on gradle (#501)

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

* [톰캣 구현하기 1, 2단계] 안나 (김민겸) 미션 제출합니다. (#571)

* 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]>

* refactor: Method, Body, Path 를 VO로 분리

* refactor: HttpResponse와 하위 요소들을 VO로 분리

* refactor: HttpResponse를 활용해 응답을 보내는 코드 리팩토링

* refactor: Content-Type을 객체로 생성하여 생성 로직 위임

* refactor: 응답 본문을 반환하는 부분을 객체로 대체

* refactor: 생성자와 요청 HTTP 생성 용 정적 팩토리 메서드 분리

* refactor: initialLine을 requestLine으로 변수명 변경

* refactor: 서블릿 컨테이너를 사용해서 요청을 처리하도록 수정

* refactor: 동적 요청에 대해 분기하는 HandlerMapping 구현

* refactor: Servlet이 적절한 컨트롤러를 꺼내어 처리하도록 구현

* refactor: HttpResponse 빈 객체 생성 후 값을 대입할 수 있도록 수정

* refactor: 응답을 출력하는 역할을 하는 클래스 분리

* feat: 로그인을 담당하는 컨트롤러 구현

* feat: 회원 저장을 담당하는 Controller 구현

* feat: 정적 파일 처리를 담당하는 Controller 구현

* refactor: 분기 처리를 구현 컨트롤러에게 위임하여 로직 개선

* refactor: Body에서 속성을 파싱할 수 있도록 Properties 클래스 분리

* fix: 로그인 페이지가 출력되지 않는 버그 해결

* fix: 세션 정보가 조회되지 않는 현상 해결

* fix: 실패하는 테스트 케이스 수정

* feat: Executors로 Thread Pool 적용

* refactor: 기존의 Controller로 명명한 클래스 이름을 Servlet으로 수정

* test: 로그인 및 회원가입 서블릿 테스트 구현

* refactor: 필요한 설정 값을 상수화하도록 수정

* style: 클래스 상단 개행 컨벤션 맞춤

* refactor: 정적 파일 처리 시 . 을 붙여 확장자 판별

* refactor: 모든 헤더 키를 상수화

* refactor: 정적 메서드의 이름을 직관적으로 변경

* refactor: HTTP 파싱을 위한 Delimter 를 전부 상수화

* refactor: 세션 값을 추출하는 메서드를 분리

* refactor: RequestLine과 ResponseLine으로 객체를 추상화

* refactor: 헤더 키를 상수화하여 생성하도록 수정

* fix: 인덱스 페이지가 연결되지 않는 현상 해결

* refactor: GET, POST 이외의 메소드 요청이 온다면 405를 반환

* refactor: 프로퍼티를 HttpRequest에서 바로 꺼내오도록 설정

* refactor: 로그인이 실패한 경우 401 페이지를 응답

* refactor: RequestLine에서 한번에 처리하도록 수정

* refactor: 이미 존재하는 유저가 아니라면 저장하도록 수정

* fix: 불일치 메서드명 수정

* fix: 요청과 응답이 동작하지 않는 문제 해결

* refactor: ContentLength가 0 이하인 경우 예외처리

* refactor: 정적 메서드의 이름을 수정

* fix: 패스워드가 조회되지 않는 문제 해결

* fix: 루트경로 입력시 Hello, World가 보여지도록 수정

* test: 실패하는 테스트 케이스 수정

* fix: 잘못 올라간 파일 수정

---------

Co-authored-by: Gyeongho Yang <[email protected]>
  • Loading branch information
Mingyum-Kim and geoje authored Sep 13, 2024
1 parent 37b1313 commit 147fc92
Show file tree
Hide file tree
Showing 36 changed files with 936 additions and 345 deletions.
22 changes: 15 additions & 7 deletions tomcat/src/main/java/com/techcourse/model/User.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.techcourse.model;

import java.util.Objects;

public class User {

private final Long id;
Expand Down Expand Up @@ -27,12 +29,18 @@ public String getAccount() {
}

@Override
public String toString() {
return "User{" +
"id=" + id +
", account='" + account + '\'' +
", email='" + email + '\'' +
", password='" + password + '\'' +
'}';
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
User user = (User)o;
return Objects.equals(account, user.account) && Objects.equals(password, user.password)
&& Objects.equals(email, user.email);
}

@Override
public int hashCode() {
return Objects.hash(account, password, email);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,29 @@
import java.io.UncheckedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Connector implements Runnable {

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

private static final int DEFAULT_PORT = 8080;
private static final int DEFAULT_ACCEPT_COUNT = 100;
private static final int DEFAULT_ACCEPT_COUNT = 100;
private static final int DEFAULT_THREAD_COUNT = 250;

private final ServerSocket serverSocket;
private boolean stopped;
private final ExecutorService executorService;

public Connector() {
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT);
this(DEFAULT_PORT, DEFAULT_ACCEPT_COUNT, DEFAULT_THREAD_COUNT);
}

public Connector(final int port, final int acceptCount) {
public Connector(final int port, final int acceptCount, final int maxThreads) {
this.serverSocket = createServerSocket(port, acceptCount);
this.stopped = false;
this.executorService = Executors.newFixedThreadPool(maxThreads);
}

private ServerSocket createServerSocket(final int port, final int acceptCount) {
Expand Down Expand Up @@ -67,7 +72,7 @@ private void process(final Socket connection) {
return;
}
var processor = new Http11Processor(connection);
new Thread(processor).start();
this.executorService.execute(processor);
}

public void stop() {
Expand Down
75 changes: 30 additions & 45 deletions tomcat/src/main/java/org/apache/coyote/http11/Http11Processor.java
Original file line number Diff line number Diff line change
@@ -1,62 +1,47 @@
package org.apache.coyote.http11;


import com.techcourse.db.InMemoryUserRepository;
import com.techcourse.exception.UncheckedServletException;
import com.techcourse.model.User;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

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.apache.coyote.http11.response.HttpResponse;
import org.apache.coyote.http11.response.ResponsePrinter;
import org.apache.coyote.http11.servlet.ServletContainer;
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 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";
private final Socket connection;

public Http11Processor(final Socket connection) {
this.connection = connection;
}
public Http11Processor(final Socket connection) {
this.connection = connection;
}

@Override
public void run() {
log.info("connect host: {}, port: {}", connection.getInetAddress(), connection.getPort());
process(connection);
}
@Override
public void run() {
log.info("connect host: {}, port: {}", connection.getInetAddress(), connection.getPort());
process(connection);
}

@Override
public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
final var outputStream = connection.getOutputStream()) {
@Override
public void process(final Socket connection) {
try (final var inputStream = connection.getInputStream();
final var outputStream = connection.getOutputStream()) {

BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
HttpRequest httpRequest = new HttpRequest(reader);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
HttpRequest request = new HttpRequest(reader);
HttpResponse response = new HttpResponse();

RequestHandler requestHandler = new RequestHandler(httpRequest, outputStream);
requestHandler.handleRequest();
new ServletContainer().invoke(request, response);

} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
new ResponsePrinter(outputStream).print(response);
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
42 changes: 42 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/http11/common/Body.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.apache.coyote.http11.common;

import static org.apache.coyote.http11.common.HttpDelimiter.*;
import static org.apache.coyote.http11.common.HeaderKey.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;

public record Body(String value) {
public static Body parseRequestBody(Headers headers, BufferedReader reader) throws IOException {
return new Body(parseBody(headers, reader));
}

private static String parseBody(Headers headers, BufferedReader reader) throws IOException {
String contentLength = headers.getValue(CONTENT_LENGTH);
if (contentLength == null) {
return null;
}

int length = Integer.parseInt(contentLength);
if (length > 0) {
char[] body = new char[length];
reader.read(body, 0, length);
return new String(body);
} else if (length < 0) {
throw new IOException("Invalid Content-Length: " + length);
}
return null;
}

public int getContentLength() {
return this.value.getBytes().length;
}

public Properties parseProperty() {
Properties properties = new Properties();
Arrays.asList(value.split(BODY_PROPERTY_DELIMITER.getValue()))
.forEach(properties::add);
return properties;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.apache.coyote.http11.common;

public enum ContentType {
APPLICATION_JSON("application/json"),
TEXT_CSS("text/css"),
TEXT_HTML("text/html"),
TEXT_JAVASCRIPT("text/javascript"),
;

private final String value;

ContentType(String value) {
this.value = value;
}

public static ContentType fromPath(String path) {
if (path.endsWith(".css")) {
return ContentType.TEXT_CSS;
}
if (path.endsWith(".js")) {
return ContentType.APPLICATION_JSON;
}
if (path.endsWith(".html")) {
return ContentType.TEXT_HTML;
}
return ContentType.APPLICATION_JSON;
}

public String getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.apache.coyote.http11.common;

public enum HeaderKey {
CONTENT_LENGTH("Content-Length"),
CONTENT_TYPE("Content-Type"),
COOKIE("Cookie"),
SET_COOKIE("Set-Cookie"),
LOCATION("Location")
;

private final String value;

HeaderKey(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
40 changes: 40 additions & 0 deletions tomcat/src/main/java/org/apache/coyote/http11/common/Headers.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.apache.coyote.http11.common;

import static org.apache.coyote.http11.common.HttpDelimiter.*;

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

public record Headers(Map<String, String> headers) {
public Headers() {
this(new HashMap<>());
}

public static Headers parseRequestHeader(BufferedReader reader) throws IOException {
HashMap<String, String> headers = new HashMap<>();
String line;
while ((line = reader.readLine()) != null && !line.isEmpty()) {
String[] header = line.split(HEADER_KEY_VALUE_DELIMITER.getValue());
headers.put(header[0].trim(), header[1].trim());
}
return new Headers(headers);
}

public void add(HeaderKey key, String value) {
headers.put(key.getValue(), value);
}

public String getValue(HeaderKey headerKey) {
return headers.get(headerKey.getValue());
}

public String generatePlainText() {
StringBuilder sb = new StringBuilder();
headers().forEach((key, value) -> {
sb.append(String.format("%s: %s \r\n", key, value));
});
return sb.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.apache.coyote.http11.common;

public enum HttpDelimiter {
REQUEST_LINE_DELIMITER(" "),
BODY_PROPERTY_DELIMITER("&"),
BODY_KEY_VALUE_DELIMITER("="),
SESSION_DELIMITER("; "),
HEADER_KEY_VALUE_DELIMITER(":")
;

private final String value;

HttpDelimiter(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.apache.coyote.http11.common;

import static org.apache.coyote.http11.common.HttpDelimiter.*;

import java.util.HashMap;
import java.util.Map;

public class Properties {
private static final int KEY_INDEX_OF_PROPERTY = 0;
private static final int VALUE_INDEX_OF_PROPERTY = 1;

private final Map<String, String> properties;

public Properties() {
this.properties = new HashMap<>();
}

public void add(String rawProperty) {
String key = rawProperty.split(BODY_KEY_VALUE_DELIMITER.getValue())[KEY_INDEX_OF_PROPERTY];
String value = rawProperty.split(BODY_KEY_VALUE_DELIMITER.getValue())[VALUE_INDEX_OF_PROPERTY];
this.properties.put(key, value);
}

public String get(String key) {
return properties.get(key);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.apache.coyote.http11.common;

import static org.apache.coyote.http11.common.HttpDelimiter.*;

import java.util.Objects;

public record VersionOfProtocol(String value) {
private static final int INDEX_OF_VERSION_OF_PROTOCOL = 2;

public static VersionOfProtocol parseReqeustToVersionOfProtocol(String requestLine) {
return new VersionOfProtocol(requestLine.split(REQUEST_LINE_DELIMITER.getValue())[INDEX_OF_VERSION_OF_PROTOCOL]);
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
VersionOfProtocol that = (VersionOfProtocol)o;
return Objects.equals(value, that.value);
}

@Override
public int hashCode() {
return Objects.hashCode(value);
}
}
Loading

0 comments on commit 147fc92

Please sign in to comment.