Skip to content
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

[둔덩] 3단계 - HTTP 웹 서버 리팩터링 미션 제출합니다. #220

Open
wants to merge 35 commits into
base: dundung
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
b515bcc
docs: README.md 작성
DunDung Sep 10, 2020
2d88ed9
feat: Request Line에서 path 분리하기 기능 구현 및 테스트
DunDung Sep 10, 2020
e2f1565
feat: queryString에서 값 분리해서 User 객체 생성하는 기능 구현 및 테스트
DunDung Sep 11, 2020
aa79305
feat: 회원 가입 기능 구현
DunDung Sep 11, 2020
4a36bfc
feat: 회원 가입 POST로 구현
DunDung Sep 14, 2020
d0cf265
feat: redirect 구현, stylesheet 파일 지원
DunDung Sep 15, 2020
1cc5839
docs: README.md 파일 수정
DunDung Sep 15, 2020
e2499c9
refactor: 패키지 구조 변경
DunDung Sep 16, 2020
b6047b4
style: 메서드명 수정
DunDung Sep 16, 2020
0544cd6
refactor: 리뷰 반영
DunDung Sep 20, 2020
f27d485
refactor: UserMapper 추가
DunDung Sep 20, 2020
05979d5
style: 코드 포맷팅
DunDung Sep 20, 2020
f1ef412
refactor: 리뷰 반영
DunDung Sep 24, 2020
452f004
refactor: RequestUtils 제거, 문자열 중복 제거
DunDung Sep 28, 2020
b6ecde7
feat: WAS thread pool 적용
DunDung Sep 28, 2020
e073c14
refactor: HttpRequest가 headers와 body data를 갖고있게 수정
DunDung Sep 28, 2020
65c7f1f
docs: READEME.md 2단계 요구사항 추가
DunDung Oct 3, 2020
a35b7fc
feat: WAS에 Thread Pool 적용
DunDung Oct 5, 2020
9d07320
refactor: HttpRequest, HttpResponse 리팩토링
DunDung Oct 5, 2020
2340881
feat: 컨트롤러 추상화
DunDung Oct 5, 2020
ea684d1
feat: 에러로직 추가 및 컨트롤러 하나의 객체만 띄우도록 수정
DunDung Oct 5, 2020
bb26dd2
style: UTF-8 상수로 변경
DunDung Oct 5, 2020
52d57b6
refactor: newFixedThreadPool을 newCachedThreadPool로 변경
DunDung Oct 20, 2020
66667a5
refactor: headers, parameters 일급컬렉션을 사용하도록 수정
DunDung Oct 20, 2020
df74d02
feat: js, 폰트, 아이콘 리소스를 처리하는 기능 추가
DunDung Oct 20, 2020
9459b89
refactor: newCachedThreadPool -> newWorkStealingPool로 수정
DunDung Oct 20, 2020
8c15ed5
refactor: 2단계 리뷰 반영
DunDung Nov 20, 2020
6c766a7
feat: 요구사항 1 구현
DunDung Nov 24, 2020
e5cbabc
Merge branch 'temp2' into step3
DunDung Nov 24, 2020
6e4e53d
docs: README.md 3단계 요구사항 추가
DunDung Nov 24, 2020
b46d54d
feat: 요구사항 2, 3 구현
DunDung Nov 25, 2020
ee51707
refactor: UserService.findById Optional을 retrun하도록 수정
DunDung Nov 25, 2020
6ec14be
test: Test 코드 추가
DunDung Nov 25, 2020
7b413f6
test: UserServiceTest 수정
DunDung Nov 25, 2020
9a8e799
docs: README.md 수정
DunDung Nov 25, 2020
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
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,14 @@
5. Controller 분리
- [x] Controller 인터페이스
- [x] AbstractController로 중복 제거
- [x] Http Methode에 따라 메서드 분리
- [x] Http Methode에 따라 메서드 분리

## 3단계
1. 로그인 할 수 있다.
- [x] 로그인 메뉴 클릭 시 http://localhost:8080/user/login.html 으로 이동해 로그인할 수 있다.
- [x] 로그인이 성공하면 index.html로 이동, header의 Cookie header 값이 logined=true
- [x] 로그인이 실패하면 /user/login_failed.html로 이동, header의 Cookie header 값이 logined=false
2. /user/list로 접근할 수 있다.
- [x] 로그인 상태일 시 사용자 목록 출력
- [x] 로그인 하지 않았으면 로그인 페이지로 이동
3. 서블릿에서 지원하는 HttpSession API의 일부를 지원
4 changes: 2 additions & 2 deletions src/main/java/controller/AbstractController.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ public void service(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.sendError(Status.METHOD_NOT_ALLOWED);
}

void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
public void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.sendError(Status.METHOD_NOT_ALLOWED);
}

void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
public void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.sendError(Status.METHOD_NOT_ALLOWED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
import webserver.request.HttpRequest;
import webserver.response.HttpResponse;

public enum Controllers {
public enum ControllerType {
TEMPLATES_CONTROLLER(new TemplatesController()),
STATIC_RESOURCE_CONTROLLER(new StaticResourceController()),
USER_CONTROLLER(new UserController());
USER_CREATE_CONTROLLER(new UserCreateController()),
USER_LOGIN_CONTROLLER(new UserLoginController()),
USER_LIST_CONTROLLER(new UserListController());

private final Controller controller;

Controllers(Controller controller) {
ControllerType(Controller controller) {
this.controller = controller;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
package controller;

import domain.user.service.UserService;
import webserver.ContentType;
import webserver.EntityHeader;
import webserver.request.HttpRequest;
import webserver.response.HttpResponse;

public class UserController extends AbstractController {
public class UserCreateController extends AbstractController {
private static final String PAGE_PREFIX = "./templates";
private static final String INDEX_HTML = "/index.html";

@Override
public void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
UserService.createUser(httpRequest);
httpResponse.addHeader(EntityHeader.CONTENT_TYPE, "text/html; charset=utf-8");
httpResponse.forward("./templates/index.html");
httpResponse.addHeader(EntityHeader.CONTENT_TYPE, ContentType.HTML.get());
httpResponse.forward(PAGE_PREFIX + INDEX_HTML);
}

@Override
public void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
UserService.createUser(httpRequest);
httpResponse.sendRedirect("/index.html");
httpResponse.sendRedirect(INDEX_HTML);
}
}
26 changes: 26 additions & 0 deletions src/main/java/controller/UserListController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package controller;

import domain.user.dto.UserListResponse;
import domain.user.service.UserService;
import utils.TemplateUtils;
import webserver.request.HttpRequest;
import webserver.response.HttpResponse;
import webserver.response.ResponseHeader;

public class UserListController extends AbstractController {
private static final String USER_LIST = "user/list";
private static final String USER_LOGIN_HTML = "/user/login.html";
private static final String LOGIN_FALSE = "logined=false";

@Override
public void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
String cookieValue = httpRequest.getHeader(ResponseHeader.SET_COOKIE.get());
if (cookieValue.contains(LOGIN_FALSE)) {
httpResponse.forward(USER_LOGIN_HTML);
return;
}
UserListResponse userListResponse = new UserListResponse(UserService.findAll());
String profilePage = TemplateUtils.create(USER_LIST, userListResponse);
httpResponse.forward(profilePage.getBytes());
}
}
33 changes: 33 additions & 0 deletions src/main/java/controller/UserLoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package controller;

import domain.user.User;
import domain.user.service.UserService;
import utils.UserMapper;
import webserver.request.HttpRequest;
import webserver.response.HttpResponse;
import webserver.response.ResponseHeader;

public class UserLoginController extends AbstractController {
private static final String LOGIN_FALSE = "logined=false;";
private static final String LOGIN_TRUE = "logined=true;";
private static final String PATH = " Path=/";
private static final String USER_LOGIN_FAILED_HTML = "/user/login_failed.html";
private static final String INDEX_HTML = "/index.html";

@Override
public void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
User requestUser = UserMapper.createUser(httpRequest);
User foundUser = UserService.findById(requestUser.getUserId())
.orElse(null);
if (foundUser == null || foundUser.notMatchPassword(requestUser)) {
setCookieAndRedirect(httpResponse, LOGIN_FALSE + PATH, USER_LOGIN_FAILED_HTML);
return;
}
setCookieAndRedirect(httpResponse, LOGIN_TRUE + PATH, INDEX_HTML);
}

private void setCookieAndRedirect(HttpResponse httpResponse, String cookieValue, String path) {
httpResponse.addHeader(ResponseHeader.SET_COOKIE, cookieValue);
httpResponse.sendRedirect(path);
}
}
4 changes: 4 additions & 0 deletions src/main/java/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public User(String userId, String password, String name, String email) {
this.email = email;
}

public boolean notMatchPassword(User requestUser) {
return !this.password.equals(requestUser.password);
}

public String getUserId() {
return userId;
}
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/domain/user/dto/UserListResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package domain.user.dto;

import domain.user.User;

import java.util.Collection;

public class UserListResponse {
private Collection<User> users;

public UserListResponse(Collection<User> users) {
this.users = users;
}

public Collection<User> getUsers() {
return users;
}
}
15 changes: 15 additions & 0 deletions src/main/java/domain/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,25 @@
import utils.UserMapper;
import webserver.request.HttpRequest;

import java.util.Collection;
import java.util.Optional;

public class UserService {

public static void createUser(HttpRequest httpRequest) {
User user = UserMapper.createUser(httpRequest);
DataBase.addUser(user);
}

public static Optional<User> findById(String id) {
User user = DataBase.findUserById(id);
if (user == null) {
return Optional.empty();
}
return Optional.of(user);
}

public static Collection<User> findAll() {
return DataBase.findAll();
}
}
30 changes: 30 additions & 0 deletions src/main/java/utils/TemplateUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package utils;

import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.io.ClassPathTemplateLoader;
import com.github.jknack.handlebars.io.TemplateLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class TemplateUtils {
private static final Logger log = LoggerFactory.getLogger(TemplateUtils.class);
private static final String TEMPLATES_PATH = "/templates";
private static final String HTML = ".html";

public static String create(String path, Object object) {
TemplateLoader loader = new ClassPathTemplateLoader();
loader.setPrefix(TEMPLATES_PATH);
loader.setSuffix(HTML);
Handlebars handlebars = new Handlebars(loader);
try {
Template template = handlebars.compile(path);
return template.apply(object);
} catch (IOException e) {
log.error(e.getMessage(), e);
throw new RuntimeException("IOException 발생");
}
}
}
22 changes: 14 additions & 8 deletions src/main/java/webserver/ContentType.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import java.util.function.Predicate;

public enum ContentType {
HTML("text/html; charset=utf-8", path -> path.contains("html")),
ICON("image/x-icon; charset=utf-8", path -> path.contains("ico")),
CSS("text/css; charset=utf-8", path -> path.contains("css")),
JS("text/javascript; charset=utf-8", path -> path.contains("js")),
IMAGE("image/png; charset=utf-8", path -> path.contains("png")),
FONT_WOFF("application/x-font-woff; charset=utf-8", path -> path.contains("woff")),
FONT_TTF("application/x-font-ttf; charset=utf-8", path -> path.contains("ttf"));
HTML("text/html", containsPath("html")),
ICON("image/x-icon", containsPath("ico")),
CSS("text/css", containsPath("css")),
JS("text/javascript", containsPath("js")),
IMAGE("image/png", containsPath("png")),
FONT_WOFF("application/x-font-woff", containsPath("woff")),
FONT_TTF("application/x-font-ttf", containsPath("ttf"));

private static final String CHARSET = "charset=utf-8";

private final String contentType;
private final Predicate<String> predicate;
Expand All @@ -27,7 +29,11 @@ public static ContentType form(String path) {
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 ContentType" + path));
}

private static Predicate<String> containsPath(String type) {
return path -> path.contains(type);
}

public String get() {
return contentType;
return String.format("%s; %s", contentType, CHARSET);
}
}
2 changes: 1 addition & 1 deletion src/main/java/webserver/WebServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static void main(String args[]) throws Exception {
logger.info("Web Application Server started {} port.", port);
// 클라이언트가 연결될때까지 대기한다.
Socket connection;
ExecutorService executor = Executors.newWorkStealingPool();
ExecutorService executor = Executors.newCachedThreadPool();
while ((connection = listenSocket.accept()) != null) {
executor.submit(new RequestHandler(connection));
}
Expand Down
18 changes: 10 additions & 8 deletions src/main/java/webserver/request/HandlerMapping.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
package webserver.request;

import controller.Controllers;
import controller.ControllerType;
import webserver.response.HttpResponse;

import java.util.Arrays;
import java.util.List;

public enum HandlerMapping {
TEMPLATES(Arrays.asList(".html", ".ico"), Controllers.TEMPLATES_CONTROLLER),
STATIC(Arrays.asList("/css", "/js", "/fonts"), Controllers.STATIC_RESOURCE_CONTROLLER),
USER_CREATE(Arrays.asList("/user/create"), Controllers.USER_CONTROLLER);
TEMPLATES(Arrays.asList(".html", ".ico"), ControllerType.TEMPLATES_CONTROLLER),
STATIC(Arrays.asList("/css", "/js", "/fonts"), ControllerType.STATIC_RESOURCE_CONTROLLER),
USER_CREATE(Arrays.asList("/user/create"), ControllerType.USER_CREATE_CONTROLLER),
USER_LOGIN(Arrays.asList("/user/setCookieAndRedirect"), ControllerType.USER_LOGIN_CONTROLLER),
USER_LIST(Arrays.asList("/user/list"), ControllerType.USER_LIST_CONTROLLER);

private final List<String> paths;
private final Controllers controllers;
private final ControllerType controllerType;

HandlerMapping(List<String> paths, Controllers controllers) {
HandlerMapping(List<String> paths, ControllerType controllerType) {
this.paths = paths;
this.controllers = controllers;
this.controllerType = controllerType;
}

public static HandlerMapping from(HttpRequest httpRequest) {
Expand All @@ -27,6 +29,6 @@ public static HandlerMapping from(HttpRequest httpRequest) {
}

public void service(HttpRequest httpRequest, HttpResponse httpResponse) {
this.controllers.service(httpRequest, httpResponse);
this.controllerType.service(httpRequest, httpResponse);
}
}
12 changes: 12 additions & 0 deletions src/main/java/webserver/response/HttpResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ public void sendError(Status status) {
public void sendRedirect(String location) {
try {
processStatusLine(Status.FOUND);
if (headers.containsKey(ResponseHeader.SET_COOKIE)) {
processHeader(ResponseHeader.SET_COOKIE, headers.get(ResponseHeader.SET_COOKIE));
}
processHeader(ResponseHeader.LOCATION, location);
dos.flush();
} catch (IOException e) {
Expand All @@ -57,6 +60,15 @@ public void forward(String path) {
}
}

public void forward(byte[] body) {
try {
response200Header(body);
responseBody(body);
} catch (IOException e) {
logger.error(e.getMessage());
}
}

private void response200Header(byte[] body) throws IOException {
processStatusLine(Status.OK);
processHeader(EntityHeader.CONTENT_TYPE, headers.get(EntityHeader.CONTENT_TYPE));
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/webserver/response/ResponseHeader.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import webserver.HttpField;

public enum ResponseHeader implements HttpField {
LOCATION("Location");
LOCATION("Location"),
SET_COOKIE("Set-Cookie");

private final String header;

Expand Down
34 changes: 34 additions & 0 deletions src/main/java/webserver/session/HttpSession.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package webserver.session;

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

public class HttpSession {
private final String id;
private final Map<String, Object> attributes;

public HttpSession(String id) {
this.id = id;
this.attributes = new HashMap<>();
}

public String getId() {
return this.id;
}

public void setAttribute(String name, Object value) {
this.attributes.put(name, value);
}

public Object getAttribute(String name) {
return this.attributes.get(name);
}

public void removeAttribute(String name) {
this.attributes.remove(name);
}

public void invalidate() {
this.attributes.clear();
}
}
Loading