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 웹 서버 구현 미션 제출합니다. #211

Open
wants to merge 48 commits into
base: include42
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
1b171c1
feat: RequestUtils 구현
include42 Sep 9, 2020
38cfed3
feat: index.html로 연결되도록 구현
include42 Sep 9, 2020
4b53bba
feat: 잘못된 파일 경로 전달 시 예외 발생 구현
include42 Sep 10, 2020
222d346
feat: RequestUtils의 로직 수정 및 css 로딩 이슈 해결
include42 Sep 10, 2020
5981f28
feat: HttpRequest 클래스 구현, 기존 로직 일부 대체
include42 Sep 10, 2020
39ceaca
test: HttpRequest에 대한 테스트 코드 보강
include42 Sep 10, 2020
b482e0c
docs: README.md 작성
include42 Sep 14, 2020
32123e9
feat: HttpRequest의 요청 타겟을 별도 클래스로 분리
include42 Sep 14, 2020
5e41cd5
feat: 회원가입 로직 구현
include42 Sep 14, 2020
5008551
feat: RequestUtils 삭제, RequestPath에 대한 테스트 구현
include42 Sep 15, 2020
89a17cd
feat: Parameters에 대한 연산을 ParameterMapper에 담고, 이를 상속하여 RequestPath를 구현…
include42 Sep 15, 2020
0248195
feat: ParameterMapper에 대한 테스트 코드 구현, RequestBody 구현
include42 Sep 15, 2020
7e27832
feat: URI 처리를 위한 URIUtils 구현, TEST 작성
include42 Sep 15, 2020
c09d181
feat: 회원가입을 POST로 변경, body가 없는 경우 대응
include42 Sep 15, 2020
3d60b03
feat: 회원가입 완료시 302를 response로 반환, index로 리다이렉트 구현
include42 Sep 16, 2020
5388102
refactor: HttpRequest의 헤더 생성 로직 수정
include42 Sep 16, 2020
6711ba4
docs: README.md 작성, 현재 진행해야 할 사항 todo로 정리
include42 Sep 17, 2020
9ed7db3
feat: 파일의 경로를 정리해서 구하기 위해 FileType Enum 클래스 구현
include42 Sep 17, 2020
bca224c
style: 코드 전체의 인덴트와 import 정리, 불필요한 log 코드 삭제
include42 Sep 17, 2020
39a4824
feat: HTTP 요청의 Method를 Enum으로 개선
include42 Sep 28, 2020
486364f
feat: HTTP 요청의 최상단을 RequestLine으로 클래스 분리
include42 Oct 3, 2020
a58dea1
feat: lombok을 통해 User에 Builder 패턴 추가, RequestHandler의 코드 리팩토링
include42 Oct 3, 2020
4459da9
Merge branch 'include42' into level2
include42 Oct 3, 2020
223877b
test: RequestLine에 대한 테스트 코드 추가
include42 Oct 3, 2020
9f602de
refactor: 테스트 패키지 변경 및 필드 이름 변경
include42 Oct 3, 2020
e230c1f
feat: HttpRequest의 header 부분을 RequestHeader 클래스로 분리
include42 Oct 11, 2020
acc5580
feat: RequestHeader를 HttpHeader로 변경하여, 추후 Response에서도 사용하도록 구현
include42 Oct 12, 2020
f4aef8b
refactor: HttpRequest가 생성 시 parameter로 InputStream을 받도록 구조 변경
include42 Oct 12, 2020
f27ed0a
feat: HttpResponse 및 세부 클래스 구현
include42 Oct 12, 2020
cc6f7ad
feat: Controller 및 AbstractController 구현
include42 Oct 12, 2020
a543d3c
feat: CreateUserController / ResourceController 구현
include42 Oct 12, 2020
971c6e6
refactor: HttpResponse에서 주소 처리를 하도록 로직 수정
include42 Oct 12, 2020
c698f91
feat: ControllerMapper 구현 및 RequestHandler 리팩토링
include42 Oct 12, 2020
15c7a9a
refactor: Controller 패키지 위치 변경
include42 Oct 13, 2020
b789efa
feat: 쓰레드풀 적용
include42 Oct 13, 2020
37ed78e
feat: Request에 대한 테스트 코드 개편, HttpRequest 리팩토링
include42 Oct 13, 2020
1aba1fb
feat: HttpResponse에 대한 테스트 코드 작성, 미사용 메서드 삭제
include42 Oct 13, 2020
202a0b1
test: Controller 상속 클래스 및 ControllerMapper에 대한 테스트 코드 구현
include42 Oct 13, 2020
fe63d91
refactor: 피드백에 따른 코드 리팩토링
include42 Oct 20, 2020
1d61547
refactor: HttpResponse의 write 로직 구성 변경
include42 Oct 20, 2020
a334a8e
Merge remote-tracking branch 'base/include42' into level3
include42 Oct 28, 2020
501f032
docs: README에 누락된 2단계 요구사항 및 3단계 요구사항 추가
include42 Nov 20, 2020
74ff35c
feat: 로그인 기능 구현 완료
include42 Nov 20, 2020
9b80920
feat: HandleBars Template을 통해 리스트를 출력할 수 있도록 구현
include42 Nov 20, 2020
9792296
feat: 로그인 상태에서 '/user/list' GET 요청 시 사용자 정보 조회하는 기능 구현
include42 Nov 20, 2020
6d521ae
feat: 세션 관리를 위한 인터페이스와 구현 클래스, 저장소 추가
include42 Nov 23, 2020
cf11c79
feat: 세션을 통해 로그인 처리를 수행하도록 로직 및 테스트 코드 추가
include42 Nov 23, 2020
632b851
docs: 3단계 수행내역 README에 반영
include42 Nov 23, 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
Prev Previous commit
Next Next commit
feat: 로그인 상태에서 '/user/list' GET 요청 시 사용자 정보 조회하는 기능 구현
- UserListController가 '/user/list' 요청 시 GET 요청을 받는다. 이후 DB에서 값을 꺼내 템플릿화하여 반환한다.
- templateUtils를 통해 Handlebars를 만든다. list와 profile html 파일 역시 수정해 두었다.
- templateUtils와 UserListController에 대한 테스트 코드를 작성하였다.
include42 committed Nov 20, 2020
commit 97922969636d95935e5e8b5665f2dd3f5ac79e7a
1 change: 1 addition & 0 deletions src/main/java/controller/ControllerMapper.java
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@ public class ControllerMapper {

static {
controllers.put("/user/create", new CreateUserController());
controllers.put("/user/list", new UserListController());
controllers.put("/user/login", new LoginController());
controllers.put("/", new IndexController());
}
39 changes: 39 additions & 0 deletions src/main/java/controller/UserListController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package controller;

import com.github.jknack.handlebars.Template;
import db.DataBase;
import exception.RequestParameterNotFoundException;
import model.User;
import utils.TemplateUtils;
import web.request.HttpRequest;
import web.response.HttpResponse;

import java.io.IOException;
import java.util.Collection;
import java.util.Objects;

public class UserListController extends AbstractController {
public static final String LOGIN_HTML_PATH = "/user/login.html";

@Override
protected void doGet(HttpRequest request, HttpResponse response) {
try {
validate(request);
String path = request.getTarget();
Template template = TemplateUtils.buildTemplate(path);
Collection<User> users = DataBase.findAll();
String result = template.apply(users);
response.ok(result);
} catch (IllegalAccessException | RequestParameterNotFoundException | IOException e) {
logger.error(e.getMessage());
response.found(LOGIN_HTML_PATH);
}
}

private void validate(HttpRequest request) throws IllegalAccessException {
String cookie = request.getCookieByKey("logined");
if (Objects.isNull(cookie) || cookie.isEmpty() || !Boolean.parseBoolean(cookie)) {
throw new IllegalAccessException("로그인하지 않은 사용자입니다.");
}
}
}
1 change: 1 addition & 0 deletions src/main/java/web/HttpHeader.java
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ public class HttpHeader {
public static final String CONTENT_TYPE = "Content-Type";
public static final String LOCATION = "Location";
public static final String SET_COOKIE = "Set-Cookie";
public static final String COOKIE = "Cookie";
public static final String HEADER_DELIMITER = ": ";

private final Map<String, String> headers;
32 changes: 32 additions & 0 deletions src/main/java/web/request/Cookies.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package web.request;

import exception.RequestParameterNotFoundException;

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

public class Cookies {
private static final String COOKIES_DELIMITER = "; ";
private static final String COOKIE_DELIMITER = "=";

private final Map<String, String> cookieMatcher = new HashMap<>();

public Cookies(String cookies) {
if (Objects.isNull(cookies) || cookies.isEmpty()) {
return;
}
String[] tokens = cookies.split(COOKIES_DELIMITER);
for (String token : tokens) {
String[] value = token.split(COOKIE_DELIMITER);
this.cookieMatcher.put(value[0], value[1]);
}
}

public String getCookieByKey(String key) {
if (cookieMatcher.containsKey(key)) {
return cookieMatcher.get(key);
}
throw new RequestParameterNotFoundException("[COOKIE]" + key);
}
}
6 changes: 6 additions & 0 deletions src/main/java/web/request/HttpRequest.java
Original file line number Diff line number Diff line change
@@ -21,13 +21,15 @@ public class HttpRequest {
private final RequestLine requestLine;
private final HttpHeader httpHeader;
private final RequestBody requestBody;
private final Cookies cookies;

public HttpRequest(InputStream inputStream) {
try {
BufferedReader request = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
requestLine = new RequestLine(request.readLine());
httpHeader = new HttpHeader(mappingHeaders(request));
requestBody = mappingBodies(request);
cookies = new Cookies(httpHeader.getHeaderByKey(HttpHeader.COOKIE));
} catch (IndexOutOfBoundsException | NullPointerException | IOException e) {
throw new InvalidHttpRequestException();
}
@@ -91,4 +93,8 @@ public String getRequestHeaderByKey(String key) {
public String getRequestBodyByKey(String key) {
return requestBody.getParameterByKey(key);
}

public String getCookieByKey(String key) {
return cookies.getCookieByKey(key);
}
}
7 changes: 7 additions & 0 deletions src/main/java/web/response/HttpResponse.java
Original file line number Diff line number Diff line change
@@ -39,6 +39,13 @@ public void ok(String path, String contentType) throws IOException, URISyntaxExc
write();
}

public void ok(String path) {
responseLine = new ResponseLine(ResponseStatus.OK, HTTP_VERSION);
addHeader(HttpHeader.CONTENT_TYPE, "text/html;charset=UTF-8");
responseBody = new ResponseBody(path.getBytes());
write();
}

public void found(String location) {
responseLine = new ResponseLine(ResponseStatus.FOUND, HTTP_VERSION);
addHeader(HttpHeader.LOCATION, location);
32 changes: 19 additions & 13 deletions src/main/resources/templates/user/profile.html
Original file line number Diff line number Diff line change
@@ -27,7 +27,8 @@
<div class="input-group" style="max-width:470px;">
<input type="text" class="form-control" placeholder="Search" name="srch-term" id="srch-term">
<div class="input-group-btn">
<button class="btn btn-default btn-primary" type="submit"><i class="glyphicon glyphicon-search"></i></button>
<button class="btn btn-default btn-primary" type="submit"><i
class="glyphicon glyphicon-search"></i></button>
</div>
</div>
</form>
@@ -47,23 +48,27 @@
<div class="navbar navbar-default" id="subnav">
<div class="col-md-12">
<div class="navbar-header">
<a href="#" style="margin-left:15px;" class="navbar-btn btn btn-default btn-plus dropdown-toggle" data-toggle="dropdown"><i class="glyphicon glyphicon-home" style="color:#dd1111;"></i> Home <small><i class="glyphicon glyphicon-chevron-down"></i></small></a>
<a class="navbar-btn btn btn-default btn-plus dropdown-toggle" data-toggle="dropdown" href="#"
style="margin-left:15px;"><i class="glyphicon glyphicon-home" style="color:#dd1111;"></i> Home <small><i
class="glyphicon glyphicon-chevron-down"></i></small></a>
<ul class="nav dropdown-menu">
<li><a href="../user/profile.html"><i class="glyphicon glyphicon-user" style="color:#1111dd;"></i> Profile</a></li>
<li><a href="../user/profile.html"><i class="glyphicon glyphicon-user" style="color:#1111dd;"></i>
Profile</a></li>
<li class="nav-divider"></li>
<li><a href="#"><i class="glyphicon glyphicon-cog" style="color:#dd1111;"></i> Settings</a></li>
</ul>

<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse2">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="collapse navbar-collapse" id="navbar-collapse2">
<ul class="nav navbar-nav navbar-right">
<li class="active"><a href="../index.html">Posts</a></li>>
<li class="active"><a href="../index.html">Posts</a></li>
>
<li><a href="../user/login.html" role="button">로그인</a></li>
<li><a href="../user/form.html" role="button">회원가입</a></li>
<li><a href="#" role="button">로그아웃</a></li>
@@ -84,9 +89,10 @@
<img class="media-object" src="../images/80-text.png">
</a>
<div class="media-body">
<h4 class="media-heading">자바지기</h4>
<h4 class="media-heading">{{name}}</h4>
<p>
<a href="#" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-envelope"></span>&nbsp;javajigi@slipp.net</a>
<a class="btn btn-xs btn-default" href="#"><span
class="glyphicon glyphicon-envelope"></span>&nbsp;{{email}}</a>
</p>
</div>
</div>
@@ -100,5 +106,5 @@ <h4 class="media-heading">자바지기</h4>
<script src="../js/jquery-2.2.0.min.js"></script>
<script src="../js/bootstrap.min.js"></script>
<script src="../js/scripts.js"></script>
</body>
</body>
</html>
77 changes: 77 additions & 0 deletions src/test/java/controller/UserListControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package controller;

import db.DataBase;
import model.User;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.request.HttpRequest;
import web.response.HttpResponse;

import java.io.*;

import static controller.AbstractControllerTest.TEST_DIRECTORY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

public class UserListControllerTest {
private static final Logger logger = LoggerFactory.getLogger(UserListControllerTest.class);

@DisplayName("GET으로 '/user/list'에 접근할 시, 로그인이 되어있다면 목록을 표시한다.")
@Test
void doGetWithLoginTest() {
User kafka = new User("kafka123", "password", "kafka", "kafka@naver.com");
User pobi = new User("javajigi", "password", "자바지기", "javajigi@gmail.com");
DataBase.addUser(kafka);
DataBase.addUser(pobi);
Controller controller = new UserListController();
try {
InputStream inputStream = new FileInputStream(new File(TEST_DIRECTORY + "HTTP_USER_LIST_LOGIN.txt"));
HttpRequest request = new HttpRequest(inputStream);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HttpResponse response = new HttpResponse(outputStream);

controller.service(request, response);

String result = outputStream.toString();
assertAll(
() -> assertThat(result).contains("HTTP/1.1 200 OK"),
() -> assertThat(result).contains(kafka.getName()),
() -> assertThat(result).contains(kafka.getEmail()),
() -> assertThat(result).contains(kafka.getUserId()),
() -> assertThat(result).contains(pobi.getName()),
() -> assertThat(result).contains(pobi.getEmail()),
() -> assertThat(result).contains(pobi.getUserId())
);
} catch (IOException e) {
logger.error(e.getMessage());
throw new AssertionError();
}
}

@DisplayName("GET으로 '/user/list'에 접근할 시, 로그인이 되어있지 않다면 로그인 페이지로 이동한다.")
@Test
void doGetWithLogoutTest() {
Controller controller = new UserListController();
try {
InputStream inputStream = new FileInputStream(new File(TEST_DIRECTORY + "HTTP_USER_LIST_LOGOUT.txt"));
HttpRequest request = new HttpRequest(inputStream);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HttpResponse response = new HttpResponse(outputStream);

controller.service(request, response);

String result = outputStream.toString();
assertAll(
() -> assertThat(result).contains("HTTP/1.1 302 FOUND"),
() -> assertThat(result).contains("Location: /user/login.html ")
);
} catch (IOException e) {
logger.error(e.getMessage());
throw new AssertionError();
}
}
}
30 changes: 15 additions & 15 deletions src/test/java/utils/HandlebarsTest.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
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 db.DataBase;
import model.User;
import org.junit.jupiter.api.Test;
@@ -12,36 +9,39 @@

import java.util.Collection;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;

public class HandlebarsTest {
private static final Logger logger = LoggerFactory.getLogger(HandlebarsTest.class);

@Test
void profileTest() throws Exception {
TemplateLoader loader = new ClassPathTemplateLoader();
loader.setPrefix("/templates");
loader.setSuffix(".html");
Handlebars handlebars = new Handlebars(loader);

Template template = handlebars.compile("user/profile");
Template template = TemplateUtils.buildTemplate("user/profile");

User user = new User("javajigi", "password", "자바지기", "javajigi@gmail.com");
String profilePage = template.apply(user);
logger.debug("ProfilePage : {}", profilePage);
assertAll(
() -> assertThat(profilePage).contains(user.getName()),
() -> assertThat(profilePage).contains(user.getEmail()),
() -> assertThat(profilePage).contains(user.getUserId())
);
}

@Test
void listTest() throws Exception {
TemplateLoader loader = new ClassPathTemplateLoader();
loader.setPrefix("/templates");
loader.setSuffix(".html");
Handlebars handlebars = new Handlebars(loader);

Template template = handlebars.compile("user/list");
Template template = TemplateUtils.buildTemplate("user/list");

User user = new User("javajigi", "password", "자바지기", "javajigi@gmail.com");
DataBase.addUser(user);
Collection<User> users = DataBase.findAll();
String listPage = template.apply(users);
logger.debug("ProfilePage : {}", listPage);
assertAll(
() -> assertThat(listPage).contains(user.getName()),
() -> assertThat(listPage).contains(user.getEmail()),
() -> assertThat(listPage).contains(user.getUserId())
);
}
}
5 changes: 5 additions & 0 deletions src/test/resources/controller/HTTP_USER_LIST_LOGIN.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
GET /user/list HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: */*
Cookie: logined=true
5 changes: 5 additions & 0 deletions src/test/resources/controller/HTTP_USER_LIST_LOGOUT.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
GET /user/list HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Accept: */*
Cookie: logined=false