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 웹 서버 리팩토링 미션 제출합니다. #179

Open
wants to merge 18 commits into
base: chws
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
207f21b
docs: 3단계 요구사항 작성
Oct 7, 2020
640d67f
feat: 회원가입한 사용자로 login 성공하는 기능 구현
Oct 7, 2020
5705e93
feat: UserLoginController 로그인 실패할 경우 처리
hotheadfactory Oct 7, 2020
7b20f65
test: RequestBody 사용자 인증 관련 테스트 작성
hotheadfactory Oct 7, 2020
125f7d9
feat: UserListController 작성
hotheadfactory Oct 20, 2020
79590c3
feat: /user/list로 접근시 사용자 목록을 보여주기
hotheadfactory Oct 20, 2020
0d9abe6
feat: 로그인한 상태일 경우 사용자 목록을 출력하는 기능 구현
Oct 20, 2020
c074b42
feat: 로그인하지 않은 상태일 경우 로그인 페이지로 이동하도록 하는 기능 구현
Oct 20, 2020
c6a6548
feat: HttpSession 클래스 생성
Oct 20, 2020
27c5c08
refactor: session, request, response 패키지 분리
Oct 26, 2020
df96a20
feat: Cookie, HttpSessionStorage 추가
Oct 26, 2020
22759b5
feat: session을 서버에 저장하고 cookie로 sessionId를 리턴하는 기능 추가
Oct 26, 2020
6866721
fix: 로그인 중 httpSession이 null이 되는 버그 해결
hotheadfactory Oct 26, 2020
2a6a8bb
feat: 로그아웃 기능 추가
hotheadfactory Oct 27, 2020
c127113
feat: 프론트엔드에 로그아웃 기능 연결
hotheadfactory Oct 27, 2020
76acaaa
test: HttpSession에 관한 테스트 추가
hotheadfactory Oct 27, 2020
87c5856
test: Cookie에 관한 테스트 추가
hotheadfactory Oct 27, 2020
6b138b7
feat: 로그아웃 버튼 링크 모든 페이지에 추가
hotheadfactory Oct 27, 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
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,15 @@
- [x] 클라이언트 요청 데이터를 처리하는 로직을 별도의 클래스로 분리한다.(HttpRequest)
- [x] 클라이언트 응답 데이터를 처리하는 로직을 별도의 클래스로 분리한다.(HttpResponse)
- [x] 다형성을 활용해 클라이언트 요청 URL에 대한 분기 처리를 제거한다.


## 3단계 - 로그인 및 세션 구현
1. - [x] 회원가입한 사용자로 로그인을 할 수 있다.
- [x] 로그인 성공 시 쿠키를 활용해 로그인 상태를 유지한다.
2. - [x] 로그인 상태일 경우 사용자 목록을 출력한다.
- [x] 로그인하지 않은 상태일 경우 로그인 페이지로 이동한다.
3. HttpSession API의 일부를 작성한다
- [ ] getId()
- [ ] setAttribute(String name, Object value)
- [ ] getAttribute(String name)
- [ ] removeAttribute(String name)
- [ ] invalidate()
19 changes: 19 additions & 0 deletions src/main/java/http/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package http.controller;

import http.request.HttpRequest;
import http.session.HttpSession;
import http.session.HttpSessionStorage;

public abstract class AuthController extends Controller {
protected HttpSession retrieveHttpSession(HttpRequest httpRequest) {
if (httpRequest.hasCookie("SESSIONID")) {
HttpSession httpSession = HttpSessionStorage.getSession(httpRequest.getSessionId());

Choose a reason for hiding this comment

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

getSession을 했을 때 존재하지 않으면 내부에서 만들어주면 어떨까요?

HttpSessionStorage를 controller까지 오픈할 필요는 없을 것 같아요.

if (httpSession == null) {
httpSession = HttpSessionStorage.create();
}
return httpSession;
} else {
return HttpSessionStorage.create();
}
}
}
4 changes: 2 additions & 2 deletions src/main/java/http/controller/Controller.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package http.controller;

import http.HttpRequest;
import http.HttpResponse;
import http.request.HttpRequest;
import http.response.HttpResponse;
import utils.HttpResponseHeaderParser;

public abstract class Controller {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/http/controller/ControllersFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ public ControllersFactory() {
Map<String, Controller> controllers = new HashMap<>();
controllers.put("/user/create", new UserCreateController());
controllers.put("/", new IndexController());
controllers.put("/user/login", new UserLoginController());
controllers.put("/user/logout", new UserLogoutController());
controllers.put("/user/list", new UserListController());
this.controllers = new Controllers(controllers);
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/http/controller/IndexController.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package http.controller;

import http.HttpRequest;
import http.HttpResponse;
import http.request.HttpRequest;
import http.response.HttpResponse;
import utils.HttpResponseHeaderParser;

public class IndexController extends Controller {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/http/controller/RawFileController.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package http.controller;

import http.HttpRequest;
import http.HttpResponse;
import http.request.HttpRequest;
import http.response.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import utils.FileIoUtils;
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/http/controller/UserCreateController.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package http.controller;

import http.HttpRequest;
import http.HttpResponse;
import http.request.HttpRequest;
import http.response.HttpResponse;
import service.UserService;
import utils.HttpResponseHeaderParser;

Expand Down
32 changes: 32 additions & 0 deletions src/main/java/http/controller/UserListController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package http.controller;

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 http.request.HttpRequest;
import http.response.HttpResponse;
import utils.HttpResponseHeaderParser;

import java.io.IOException;

public class UserListController extends Controller {
@Override
public HttpResponse get(HttpRequest httpRequest) {
try {
TemplateLoader loader = new ClassPathTemplateLoader();

Choose a reason for hiding this comment

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

template 부분과 비즈니스로직을 분리해보면 어떨까요?

loader.setPrefix("/templates");
loader.setSuffix(".html");
Handlebars handlebars = new Handlebars(loader);

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

byte[] userListPage = template.apply(DataBase.findAll()).getBytes();
String header = HttpResponseHeaderParser.ok("text/html", userListPage.length);
return new HttpResponse(header, userListPage);
} catch (IOException e) {
return new HttpResponse(HttpResponseHeaderParser.internalServerError(), null);
}
}
}
34 changes: 34 additions & 0 deletions src/main/java/http/controller/UserLoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package http.controller;

import db.DataBase;
import http.request.Cookie;
import http.request.HttpRequest;
import http.response.HttpResponse;
import http.session.HttpSession;
import model.User;
import service.UserService;
import utils.HttpResponseHeaderParser;

public class UserLoginController extends AuthController {

@Override
public HttpResponse post(HttpRequest httpRequest) {
UserService userService = UserService.getInstance();
boolean auth = userService.authenticateUser(httpRequest);
String header;
HttpSession httpSession;
Cookie cookie = new Cookie();

Choose a reason for hiding this comment

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

cookie 객체 생성을 내부로 감추면 어떨까요?

if (auth) {
User user = DataBase.findUserById(httpRequest.getBodyValue("userId"));
httpSession = retrieveHttpSession(httpRequest);
httpSession.setAttribute("email", user.getEmail());
cookie.setCookie("logined", "true");
cookie.setCookie("SESSIONID", httpSession.getId());
header = HttpResponseHeaderParser.found("/", cookie);
} else {
cookie.setCookie("logined", "false");
header = HttpResponseHeaderParser.found("/user/login_failed.html", cookie);
}
return new HttpResponse(header);
}
}
19 changes: 19 additions & 0 deletions src/main/java/http/controller/UserLogoutController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package http.controller;

import http.request.Cookie;
import http.request.HttpRequest;
import http.response.HttpResponse;
import http.session.HttpSessionStorage;
import utils.HttpResponseHeaderParser;

public class UserLogoutController extends AuthController {
@Override
public HttpResponse get(HttpRequest httpRequest) {
String header;

Choose a reason for hiding this comment

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

선언과 대입을 분리한 이유가 있나요?

Cookie cookie = new Cookie();
cookie.setCookie("logined", "false");
HttpSessionStorage.deleteSession(httpRequest.getSessionId());
header = HttpResponseHeaderParser.found("/", cookie);
return new HttpResponse(header);
}
}
40 changes: 40 additions & 0 deletions src/main/java/http/request/Cookie.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package http.request;

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

public class Cookie {
private Map<String, String> cookies = new HashMap<>();

public Cookie() {
}

public Cookie(String rawCookie) {
if (rawCookie.length() != 0) {
String[] tokens = rawCookie.trim().split("; ");
for (String token : tokens) {
String[] tmp = token
.replace(";", "")
.split("=");
this.cookies.put(tmp[0], tmp[1]);
}
}
}

public boolean hasCookie(String key) {
return this.cookies.containsKey(key);
}

public String getCookie(String key) {
return this.cookies.get(key);
}

public Set<Map.Entry<String, String>> getAllCookie() {
return this.cookies.entrySet();
}

public void setCookie(String key, String value) {
this.cookies.put(key, value);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package http;
package http.request;

public enum HeaderParam {
HOST("host"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package http;
package http.request;

import java.io.BufferedReader;
import java.io.IOException;
Expand All @@ -19,7 +19,7 @@ public String getPath() {
}

public String getBodyValue(String key) {
return this.requestBody.getValue(key);
return this.requestBody.getValue(key.toLowerCase());
}

public boolean headerContainsValueOf(HeaderParam key, String value) {
Expand All @@ -29,4 +29,12 @@ public boolean headerContainsValueOf(HeaderParam key, String value) {
public RequestMethod getRequestMethod() {
return this.requestLine.getMethod();
}

public boolean hasCookie(String key) {
return this.requestHeader.hasCookie(key);
}

public String getSessionId() {
return this.requestHeader.getCookie("SESSIONID");
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package http;
package http.request;

import utils.IOUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -24,14 +26,14 @@ public RequestBody(BufferedReader br, int contentLength, String contentType) thr
}
}

private void parseWWWForm(String body) {
private void parseWWWForm(String body) throws UnsupportedEncodingException {
String[] tokens = body.split("&");
for (String token : tokens) {
String[] keyValue = token.split("=");
if (keyValue.length != 2) {
throw new IllegalArgumentException("No value for the key: " + keyValue[0]);
}
params.put(keyValue[0].toLowerCase(), keyValue[1]);
params.put(keyValue[0].toLowerCase(), URLDecoder.decode(keyValue[1].trim(), "UTF-8"));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package http;
package http.request;

import java.io.BufferedReader;
import java.io.IOException;
Expand All @@ -7,6 +7,7 @@

public class RequestHeader {
private final Map<String, String> params;
private final Cookie cookie;

public RequestHeader(BufferedReader br) throws IOException {
params = new HashMap<>();
Expand All @@ -16,12 +17,17 @@ public RequestHeader(BufferedReader br) throws IOException {
}
while ((line != null) && !line.isEmpty()) {
String[] token = line.split(": ");
if(token.length != 2) {
if (token.length != 2) {
throw new IllegalArgumentException("No value for the key: " + token[0]);
}
params.put(token[0].toLowerCase(), token[1]);
line = br.readLine();
}
if (params.containsKey("cookie")) {
this.cookie = new Cookie(params.get("cookie"));
} else {
this.cookie = new Cookie("");
}
}

public boolean containsValueOf(HeaderParam key, String value) {
Expand All @@ -43,4 +49,12 @@ public String getContentType() {
private String getHeaderParamValue(HeaderParam headerParam) {
return this.params.get(headerParam.getParamName());
}

public boolean hasCookie(String key) {
return this.cookie.hasCookie(key);
}

public String getCookie(String key) {
return this.cookie.getCookie(key);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package http;
package http.request;

import java.io.BufferedReader;
import java.io.IOException;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package http;
package http.request;

import http.controller.Controller;
import http.response.HttpResponse;

import java.util.Arrays;
import java.util.function.BiFunction;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package http;
package http.response;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/http/session/HttpSession.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package http.session;

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

public class HttpSession {

private final String sessionId;
private final Map<String, Object> attributes;

public HttpSession() {
this.sessionId = UUID.randomUUID().toString();
this.attributes = new HashMap<>();
}

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

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