-
Notifications
You must be signed in to change notification settings - Fork 90
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
[제이] 1단계 - HTTP 웹 서버 리팩터링 미션 제출합니다. #198
Merged
Merged
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
cfdedc5
docs: README.md에 요구사항 1 추가
jjj0611 7ae41d0
feat: Request Handler에서 Header 추출 기능 구현
jjj0611 48f831b
feat: request의 path로 파일을 읽어서 응답하는 기능 추가
jjj0611 0cfe18f
feat: Http Request 생성
jjj0611 1f2a23a
feat: 요구사항1 완료
jjj0611 f9efed8
docs: 두번째 요구사항 작성
jjj0611 e7f841e
refactoring: http spec 정의
jjj0611 880a9c9
refactoring: http request spec 추가 및 테스트 추가
jjj0611 7f051bf
refactor: http request spec 변경
jjj0611 1577d31
feat: 요구사항2 완료
jjj0611 790c275
docs: 요구사항3을 위한 README.md 작성
jjj0611 51ca0e6
feat: 회원 가입 기능이 post 요청에서 정상적으로 동작하도록 구현
jjj0611 804187e
docs: 요구사항4 작성
jjj0611 4e33b83
feat: 회원가입 완료 후 index.html로 이동하도록 변경
jjj0611 6d3ccd0
docs: 요구사항 5 README.md에 작성
jjj0611 bb413a8
feat: 요청 uri이 static resource인지를 확인하는 기능 생성
jjj0611 c5a52cf
feat: stylesheet 등 다양한 형식의 파일을 지원하도록 변경
jjj0611 49ec3b8
feat: Http Response 분리
jjj0611 81991f4
test: SimpleHttRequest에 대한 Test 작성
jjj0611 dce3ec9
test: HttpResponse에 대한 Test 작성
jjj0611 97b6dcc
refactor: HttpServlet 및 UserController 생성
jjj0611 66cac42
feat: DispatcherServlet 생성
jjj0611 ef60277
feat: ThreadPool 사용
jjj0611 e4ff0ee
docs: 구현 내용 정리
jjj0611 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Servlet | ||
## 1. 서블릿 동작과정 | ||
1. HTTP Request를 Servlet Container에 보낸다. | ||
2. Servlet Container는 HttpServletRequest, HttpServletResponse를 생성한다. | ||
3. 사용자가 요청한 URL을 석하여 어느 서블릿에 대한 요청인지 찾는다 | ||
4. 컨테이너는 서블릿 service() 메소드를 호출하며, POST/GET 여부에 따라 doGet() 또는 doPost()가 호출된다. | ||
5. doGet() 이나 doPost() 메소드는 동적인 페이지를 생성한 후 HttpServletResponse 객체에 응답을 보낸다. | ||
6. 응답이 완료되면 HttpServletRequest, HttpServletResponse 두 객체를 소멸시킨다. | ||
|
||
## 2. 종류 | ||
1. Servlet | ||
서블릿 프로그램을 개발할 때 반드시 구현해야하는 메서드를 선언하고 있는 인터페이스 | ||
2. GenericServlet | ||
Servlet 인터페이스를 상속하여 필요한 기능을 구현한 추상 클래스. | ||
service() 메서드를 제외한 모든 메서드를 재정의하여 적절한 기능으로 구현. | ||
GenericServlet 클래스를 상속하면 애플리케이션의 프로토콜에 따라 메서드 재정의 구문을 적용해야 함 | ||
3. HttpServlet | ||
GenericServlet을 상속받아 service를 HTTP 프로토콜 요청 메서드에 적합하게 재구현해놓음 | ||
이미 DELETE,GET,HEAD,OPTIONS,POST,PUT,TRACE를 처리하는 메서드가 모두 정의되어 있다. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
# ThreadPool | ||
> 다수의 사용자 요청에 대해 Queue에 저장한 후 순차적으로 처리가 가능하도록 해야 한다. | ||
서버가 모든 요청에 대해 Thread를 매번 생성하는 경우 성능상 문제가 발생할 수 있다. | ||
Thread Pool을 적용해 일정 수의 사용자 동시에 처리가 가능하도록 한다. | ||
|
||
## 1. Thread Pool의 정의 및 필요성 | ||
자바의 쓰레드는 CPU를 최대한 사용해서 많은 업무를 동시에 처리할 수 있도록 도와준다. | ||
그런데 이 쓰레드의 생성에는 약간의 시간과 메모리가 필요하다. | ||
JVM은 쓰레드의 생성 개수를 제약하지 않기 때문에 계속해서 쓰레드를 생성하게 된다면 결과적으로 성능저하와 메모리 고갈 문제가 생길 수 있다. | ||
이렇게 무제한적인 쓰레드의 생성을 막기 위해서 쓰레드풀이라는 쓰레드 관리 방식이 사용되고 있다. | ||
쓰레드 풀이란 쓰레드를 이용된 갯수 안에서만 사용할 수 있도록 스스로 제약하는 방식이다. | ||
|
||
쓰레드풀 방식을 사용하는 대표적인 소프트웨어 : Tomcat과 같은 웹서버들 | ||
웹서버들은 동시에 많기는 수천~수만의 요청이 들어올 수 있는데, 그 때마다 쓰레드를 생성하는 것은 JVM의 메모리를 급속하게 | ||
소비시키며 성능에도 저하가 발생하게 된다. 이럴 때 최대 쓰레드 개수를 지정해놓고, 스레드 개수 이상의 HTTP 요청에 대해서는 처리하지 않고 | ||
기다렸다가, 놀고있는 스레드가 생기면 그 때 Http 요청을 스레드를 통해 처리한다. | ||
|
||
이렇게 되면, 현재의 작업을 수행하기 위해 이전에 생성된 스레드 풀을 재사용함으로써 사이클 오버헤드 및 자원 낭비를 막을 수 있다. | ||
|
||
## 2. 적절한 Thread Pool | ||
ThreadPoolExecutor는 Executors 클래스에 들어 있는 newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool과 같은 팩토리 메소드에서 생성해주는 Executor에 대한 기본적인 내용이 구현되어 있는 클래스입니다. | ||
|
||
- newFixedThreadPool : 주어진 스레드 개수만큼 생성하고 그 수를 유지, 생성된 스레드 중 일부가 종료되었으면 스레드를 다시 생성 | ||
- newCachedThreadPool : 처리할 스레드가 많아지면 그만큼 스레드를 증가(최대 스레드 개수 : Integaer.MAX_VALUE) | ||
- newSingleThreadExecutor : 스레드를 하나만 생성(1개로 계속 유지) | ||
- newScheduledThreadPool : 특정 시간 이후, 또는 주기적 작업 스레드 사용시 활용 | ||
|
||
ThreadPool은 위와 같이 4가지 종류가 있다. 어떤 ThreadPool을 이용해서 구현하는 것이 좋을까? | ||
newCachedThreadPool : DDOS 공격이 일어나면 OOM이 일어나지 않을까? | ||
newSingleThreadExecutor : 많은 요청을 하나의 스레드로 처리한다면 성능상 문제가 발생할 것임 | ||
newScheduledThreadPool : 요청이 언제 발생할지 모르는 서비스에서는 사용하기 부적합해보임 | ||
따라서 newFixedThreadPool을 이용한다. | ||
|
||
## 3. 세부 구현 | ||
### 적절한 ThreadPool Size | ||
스레드풀의 가장 이상적인 크기는 스레드 풀에서 실행할 작업의 종류와 스레드풀을 활용할 애플리케이션에 특성에 따라 결정이 됨 | ||
- 스레드풀의 크기가 너무 큰 경우 : 스레드는 CPU나 메모리 등의 자원을 조금이라도 더 확보하기 위해 경쟁하게 될 것이다. | ||
그러다보면 CPU에는 부하가 걸리고 메모리는 모자라 금방 자원 부족에 시달리게 될 것이다 | ||
- 스레드풀의 크기가 너무 작은 경우 : 작업량은 계속해서 쌓이는데 CPU나 메모리는 남아돌면서 작업 처리 속도가 떨어질 수 있습니다. | ||
|
||
스레드 풀의 사이즈를 조정하기 위해서는 주어진 환경의 제약 사항을 확실히 이해해야 한다. | ||
> 애플리케이션을 실제로 탑재해 동작할 하드웨어에 CPU가 몇 개나 꽂혀 있는지? | ||
메모리는 얼마나 꽂혀 있는지? | ||
실행하는 작업이 CPU 연산을 많이 하는지 아니면 I/O 작업을 많이 하는지? | ||
아니면 CPU와 I/O 작업을 비슷하게 많이 사용하는지? | ||
그다지 많이 확보할 수 없는 JDBC 연결과 같은 자원을 얼마나 사용하는지? | ||
|
||
|
||
|
||
스레드가 DB의존적인 작업을 할 경우 DB의 Connection Pool Size와 적절히 맞추어 설정해야 효과를 볼 수 있을 것이다. | ||
|
||
지금 이 프로그램은 DB를 따로 연결해두지 않았으니 하지만 지금은 CPU로만 생각을 해보자 | ||
java로 CPU 개수 구하는 법 | ||
int numOfCores = Runtime.getRuntime().availableProcessors(); | ||
8이 나온다. | ||
Brian Goetz의 유명한 책인 "Java Concurrency in Practice"에서는 | ||
스레드 수 = 사용 가능한 코어 수 * (1+대기 시간/서비스 시간) | ||
대기시간 : 작업 하나가 완료되기까지 소모되는 시간 | ||
서비스 시간 : 작업이 실제로 동작 중인 시간 | ||
즉 스레드수 = 8 * (1+대기시간/서비스시간) | ||
|
||
### 작업 처리 요청 | ||
작업 처리 요청이란 ExecutorService의 작업 큐에 Runnable 또는 Callable 객체를 넣는 행위를 말합니다. | ||
- execute() : Runnable을 작업 큐에 저장, 작업 처리 결과를 받지 못함 | ||
- submit() : Runnable 또는 Callable를 작업 큐에 저장, 리턴된 Future를 통해 작업 처리 결과를 얻을 수 있음 | ||
|
||
execute()는 작업 처리 도중에 예외가 발생하면 스레드가 종료되고 해당 스레드는 스레드 풀에서 제거됨. | ||
따라서 스레드풀은 다른 작업 처리를 위해 새로운 스레드를 생성해야함 | ||
submit()은 작업 처리 도중 예외가 발생하더라도 스레드는 종료되지 않고 다음 작업을 위해 재사용됨. | ||
그렇기 때문에 가급적이면 스레드의 오버헤드를 줄이기 위해 submit()을 사용하는 것이 좋음 | ||
|
||
-> 작업 통보 완료가 필요 없어도 submit을 쓰는 게 더 성능에 좋은가? | ||
|
||
### Thread Pool 종료 | ||
스레드풀의 스레드는 기본적으로 main스레드가 종료되더라도 작업을 처리하기 위해 계속 실행 상태로 남아있습니다. | ||
|
||
- shutdown() : 현재 처리 중인 작업뿐만 아니라 작업 큐에 대기하고 있는 모든 작업을 처리한 뒤에 스레드 풀을 종료시킨다. | ||
- shutdownNow() : 현재 작업 처리 중인 스레드를 interrupt해서 작업 중지를 시도하고 스레드풀을 종료시킨다. | ||
리턴값은 작업 큐에 있는 미처리된 작업의 목록이다(List<Runnable>) | ||
- awaitTermination() : shutdown()메소드 호출 이후, 모든 작업을 timeout 시간 내에 완료하면 true를 리턴하고 | ||
완료하지 못하면 작업 처리중인 스레드를 interrupt하고 false를 리턴합니다. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package annotation; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Retention(RetentionPolicy.RUNTIME) | ||
@Target(ElementType.TYPE) | ||
public @interface RequestMapping { | ||
String path(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package controller; | ||
|
||
import http.HttpRequest; | ||
import http.HttpResponse; | ||
|
||
public abstract class AbstractController implements HttpServlet { | ||
@Override | ||
public void service(HttpRequest httpRequest, HttpResponse httpResponse) { | ||
switch (httpRequest.getMethod()) { | ||
case GET: | ||
doGet(httpRequest, httpResponse); | ||
break; | ||
case POST: | ||
doPost(httpRequest, httpResponse); | ||
break; | ||
case DELETE: | ||
doDelete(httpRequest, httpResponse); | ||
break; | ||
case PUT: | ||
doPut(httpRequest, httpResponse); | ||
break; | ||
default: | ||
throw new IllegalArgumentException("잘못된 요청입니다."); | ||
} | ||
} | ||
|
||
protected abstract void doGet(HttpRequest httpRequest, HttpResponse httpResponse); | ||
|
||
protected abstract void doPost(HttpRequest httpRequest, HttpResponse httpResponse); | ||
|
||
protected abstract void doDelete(HttpRequest httpRequest, HttpResponse httpResponse); | ||
|
||
protected abstract void doPut(HttpRequest httpRequest, HttpResponse httpResponse); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package controller; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.stream.Collectors; | ||
|
||
import annotation.RequestMapping; | ||
import http.HttpRequest; | ||
import http.HttpResponse; | ||
|
||
public class DispatcherServlet implements HttpServlet { | ||
private static final Map<String, HttpServlet> SERVLET_MATCHER; | ||
|
||
static { | ||
List<HttpServlet> servlets = Arrays.asList(new UserController()); | ||
SERVLET_MATCHER = servlets.stream() | ||
.collect(Collectors.toMap( | ||
servlet -> servlet.getClass().getAnnotation(RequestMapping.class).path(), | ||
servlet -> servlet)); | ||
} | ||
|
||
@Override | ||
public void service(HttpRequest httpRequest, HttpResponse httpResponse) { | ||
if (!SERVLET_MATCHER.containsKey(httpRequest.getURI())) { | ||
httpResponse.notFound(); | ||
return; | ||
} | ||
HttpServlet servlet = SERVLET_MATCHER.get(httpRequest.getURI()); | ||
servlet.service(httpRequest, httpResponse); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package controller; | ||
|
||
import http.HttpRequest; | ||
import http.HttpResponse; | ||
|
||
public interface HttpServlet { | ||
void service(HttpRequest httpRequest, HttpResponse httpResponse); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package controller; | ||
|
||
import static http.HttpMethod.*; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
import annotation.RequestMapping; | ||
import db.DataBase; | ||
import http.HttpBody; | ||
import http.HttpMethod; | ||
import http.HttpRequest; | ||
import http.HttpResponse; | ||
import http.HttpStatus; | ||
import model.User; | ||
|
||
@RequestMapping(path = "/user") | ||
public class UserController extends AbstractController { | ||
private static final List<HttpMethod> ALLOWED_METHODS = Arrays.asList(GET, DELETE, PUT); | ||
|
||
@Override | ||
protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) { | ||
httpResponse.setMethodNotAllowed(ALLOWED_METHODS); | ||
} | ||
|
||
@Override | ||
protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) { | ||
HttpBody httpBody = httpRequest.getBody(); | ||
User user = new User( | ||
httpBody.get("userId"), | ||
httpBody.get("password"), | ||
httpBody.get("name"), | ||
httpBody.get("email")); | ||
DataBase.addUser(user); | ||
httpResponse.setStatus(HttpStatus.FOUND); | ||
httpResponse.addHeader("Location", "/index.html"); | ||
} | ||
|
||
@Override | ||
protected void doDelete(HttpRequest httpRequest, HttpResponse httpResponse) { | ||
httpResponse.setMethodNotAllowed(ALLOWED_METHODS); | ||
} | ||
|
||
@Override | ||
protected void doPut(HttpRequest httpRequest, HttpResponse httpResponse) { | ||
httpResponse.setMethodNotAllowed(ALLOWED_METHODS); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package http; | ||
|
||
import java.util.Objects; | ||
|
||
public enum ContentType { | ||
HTML("html", "text/html;charset=utf-8"), | ||
CSS("css", "text/css;charset=utf-8"), | ||
JS("js", "text/javascript;charset=utf-8"), | ||
ICO("ico", "image/icon"), | ||
PNG("png", "image/jpeg"), | ||
TTF("ttf", "application/font-ttf"), | ||
WOFF("woff", "application/font-woff"); | ||
|
||
private String extension; | ||
private String contentType; | ||
|
||
ContentType(String extension, String contentType) { | ||
this.extension = extension; | ||
this.contentType = contentType; | ||
} | ||
|
||
public static ContentType findByURI(String uri) { | ||
if (Objects.isNull(uri) || uri.isEmpty()) { | ||
throw new IllegalArgumentException("잘못된 리소스입니다."); | ||
} | ||
String extension = uri.substring(uri.lastIndexOf(".") + 1); | ||
return findByExtension(extension); | ||
} | ||
|
||
private static ContentType findByExtension(String extension) { | ||
try { | ||
return valueOf(extension.toUpperCase()); | ||
} catch (IllegalArgumentException | NullPointerException e) { | ||
throw new IllegalArgumentException("잘못된 확장자입니다."); | ||
} | ||
} | ||
|
||
public String getContentType() { | ||
return contentType; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
지원되는 메서드만 남기고 지원되지 않는 메서드는 굳이 오버라이드 되지 않는 구조를 만들어 보는게 좋을것 같습니다~!