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

Open
wants to merge 8 commits into
base: kimseonggyu1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,29 @@
- [x] 다수의 사용자 요청에 대해 Queue에 저장한 후 순차적으로 처리가 가능하도록 한다.
- [x] 요청에 대한 Thread를 매번 새로 생성하지 않고 TreadPool을 이용한다.
- [x] 정적 파일에 관한 요청을 분리해 처리한다
- [x] 응답 객체를 구현한다
- [x] 응답 객체를 구현한다

# 3단계 - 로그인 및 세션 구현

## 요구사항 1
- [x] 로그인 할 수 있다.
- [x] 로그인 성공 시, /index.html로 리다이렉트 한다.
- [x] 로그인 성공 시, 성공 쿠키를 전달한다.
- [x] 로그인 실패 시, /user/login_failed.html로 리다이렉트 한다.
- [x] 로그인 실패 시, 실패 쿠키를 전달한다.

## 요구사항 2
- [x] 유저 리스트를 볼 수 있다.
- [x] 로그인이 되어있다면 보여준다.
- [x] 로그인이 되어있지 않다면 로그인 페이지로 이동한다.
- [x] handlebar를 사용한다.

## 요구사항 3
- [x] HttpSession API의 일부를 구현한다.
- [x] getId()
- [x] setAttribute(String name, Object value)
- [x] getAttribute(String name)
- [x] removeAttribute(String name)
- [x] invalidate()
- [x] 세션 id는 쿠키를 활용해 공유한다.
- [x] 세션 id는 UUID를 사용한다.
2 changes: 1 addition & 1 deletion src/main/java/exception/CreateFailException.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package exception;

public class CreateFailException extends RuntimeException {
public class CreateFailException extends RuntimeException {
public CreateFailException(String message) {
super(message);
}
Expand Down
53 changes: 53 additions & 0 deletions src/main/java/http/session/HttpSession.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package http.session;

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

public class HttpSession {
private UUID id = UUID.randomUUID();
private Map<String, Object> attributes = new HashMap<>();

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

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

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

public void invalidate() {
attributes.clear();
}
Comment on lines +24 to +26
Copy link

Choose a reason for hiding this comment

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

session의 invalidate 메소드가 호출된 후에는 해당 세션은 사용하지 못하는 것으로 알고 있어요.
아래 링크 한번 읽어보시고, 한번 검색해보시면 좋을 것 같아요 :)

https://love2taeyeon.tistory.com/entry/invalidate%EC%9D%80-%EC%84%B8%EC%85%98%EC%9D%84-%EC%86%8C%EB%A9%B8%EC%8B%9C%ED%82%A4%EB%8A%94-%EA%B2%83%EC%9D%B4-%EC%95%84%EB%8B%88%EB%9D%BC-%EB%AC%B4%ED%9A%A8%ED%99%94-%EC%8B%9C%ED%82%AC%EB%BF%90%EC%9D%B4%EB%8B%A4


public UUID getId() {
return id;
}

@Override
public String toString() {
String sessionId = String.format("jsessionid=%s; ", id.toString());
String attributes = this.attributes.entrySet()
.stream()
.filter(this::exclude)
.map(this::parseAttribute)
.collect(Collectors.joining("; "));
return sessionId + attributes;
}

private boolean exclude(Map.Entry<String, Object> entry) {
return !entry.getKey().equals("userId") && !entry.getValue().equals(false);
}

private String parseAttribute(Map.Entry<String, Object> entry) {
if (entry.getValue().equals(true)) {
return entry.getKey();
}
return String.format("%s=%s", entry.getKey(), entry.getValue().toString());
}
}
29 changes: 29 additions & 0 deletions src/main/java/http/session/HttpSessionStorage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package http.session;

import java.util.*;
import java.util.stream.Stream;

public class HttpSessionStorage {
private static Map<UUID, HttpSession> sessions = new HashMap<>();
Copy link

Choose a reason for hiding this comment

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

sessions 에는 여러 Request Thread가 동시에 접근 가능하기 때문에
HashMap 보단 ConcurrentHashMap 이 Thread Safe 할 것 같아요 :)


public static HttpSession generate(String userId) {
HttpSession httpSession = new HttpSession();
httpSession.setAttribute("userId", userId);
sessions.put(httpSession.getId(), httpSession);
return httpSession;
}

public static boolean isValidSession(String cookie) {
String[] attributes = cookie.split("; ");
Optional<String> jsessionid = Stream.of(attributes)
.filter(it -> it.startsWith("jsessionid"))
.map(it -> it.substring("jsessionid=".length()))
.findAny();

try {
return sessions.containsKey(UUID.fromString(jsessionid.get()));
} catch (IllegalArgumentException | NoSuchElementException | NullPointerException e) {
return false;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package webserver.requestmapping.behavior;
package implementedbehavior;

import db.DataBase;
import http.HttpBody;
Expand All @@ -7,6 +7,7 @@
import http.response.HttpStatus;
import http.response.ResponseEntity;
import model.User;
import webserver.requestmapping.behavior.RequestBehavior;

public class UserCreateBehavior implements RequestBehavior {
@Override
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/implementedbehavior/UserListBehavior.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package implementedbehavior;

import db.DataBase;
import http.request.RequestEntity;
import http.response.HttpStatus;
import http.response.ResponseEntity;
import webserver.requestmapping.DynamicHtmlGenerator;
import webserver.requestmapping.behavior.RequestBehavior;

public class UserListBehavior implements RequestBehavior {
@Override
public void behave(RequestEntity requestEntity, ResponseEntity responseEntity) {
responseEntity.status(HttpStatus.OK)
.body(DynamicHtmlGenerator.applyHandlebar("user/list", DataBase.findAll()));
}
}
36 changes: 36 additions & 0 deletions src/main/java/implementedbehavior/UserLoginBehavior.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package implementedbehavior;

import db.DataBase;
import http.request.Params;
import http.request.RequestEntity;
import http.response.HttpStatus;
import http.response.ResponseEntity;
import http.session.HttpSession;
import http.session.HttpSessionStorage;
import model.User;
import webserver.requestmapping.behavior.RequestBehavior;

import java.util.Objects;

public class UserLoginBehavior implements RequestBehavior {
@Override
public void behave(RequestEntity requestEntity, ResponseEntity responseEntity) {
Params userInfo = Params.from(requestEntity.getHttpBody().getContent());
if (isValid(userInfo)) {
HttpSession session = HttpSessionStorage.generate(userInfo.findValueBy("userId"));
responseEntity.status(HttpStatus.FOUND)
.addHeader("Location", "/index.html")
.addHeader("Set-Cookie", session.toString());
} else {
responseEntity.status(HttpStatus.FOUND)
.addHeader("Location", "/user/login_failed.html");
}
}

private boolean isValid(Params userInfo) {
String userId = userInfo.findValueBy("userId");
String password = userInfo.findValueBy("password");
User user = DataBase.findUserById(userId);
return Objects.nonNull(user) && user.hasPasswordOf(password);
}
}
31 changes: 31 additions & 0 deletions src/main/java/implementedfilter/AuthFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package implementedfilter;

import http.request.RequestEntity;
import http.response.HttpStatus;
import http.response.ResponseEntity;
import http.session.HttpSessionStorage;
import webserver.filter.Filter;

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

public class AuthFilter implements Filter {
Copy link

Choose a reason for hiding this comment

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

filter로 처리한 점 👏👏👏👏👏

private static final List<String> PATH_PATTERN = Arrays.asList(
"/user/list"
);

@Override
public boolean doFilter(RequestEntity requestEntity, ResponseEntity responseEntity) {
String path = requestEntity.getHttpUrl().getPath();
String cookie = requestEntity.getHttpHeader().findOrEmpty("Cookie");
if (PATH_PATTERN.contains(path) && isNotAuthorized(cookie)) {
responseEntity.status(HttpStatus.FOUND).addHeader("Location", "/user/login.html");
return false;
}
return true;
}

private boolean isNotAuthorized(String cookie) {
return !HttpSessionStorage.isValidSession(cookie);
}
}
4 changes: 4 additions & 0 deletions src/main/java/model/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 hasPasswordOf(String password) {
return this.password.equals(password);
}

public String getUserId() {
return userId;
}
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/webserver/RequestHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ public RequestHandler(Socket connectionSocket) {
}

public void run() {
logger.debug("New Client Connect! Connected IP : {}, Port : {}",
connection.getInetAddress(), connection.getPort());
logger.debug(
"New Client Connect! Connected IP : {}, Port : {}",
connection.getInetAddress(), connection.getPort()
);

try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
Expand All @@ -41,8 +43,8 @@ public void run() {
httpEntityProcessing(requestEntity, responseEntity);

writeOutResponse(dos, responseEntity);
} catch (IOException e) {
logger.error(e.getMessage());
} catch (Exception e) {
logger.error("error message", e);
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/webserver/filter/FilterStorage.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@

import http.request.RequestEntity;
import http.response.ResponseEntity;
import implementedfilter.AuthFilter;

public class FilterStorage {

private static final List<Filter> inputFilters = new ArrayList<>();
private static final List<Filter> outputFilters = new ArrayList<>();

static {
inputFilters.add(new AuthFilter());
inputFilters.add(new StaticFileFilter());
outputFilters.add(new ContentLengthFilter());
}
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/webserver/requestmapping/DynamicHtmlGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package webserver.requestmapping;

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 java.io.IOException;

public class DynamicHtmlGenerator {
public static String applyHandlebar(String path, Object model) {
TemplateLoader loader = new ClassPathTemplateLoader();
loader.setPrefix("/templates");
loader.setSuffix(".html");
Handlebars handlebars = new Handlebars(loader);

try {
Template template = handlebars.compile(path);
return template.apply(model);
} catch (IOException e) {
throw new RuntimeException();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
package webserver.requestmapping;

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

import http.request.HttpMethod;
import http.request.RequestEntity;
import webserver.requestmapping.behavior.UserCreateBehavior;
import implementedbehavior.UserCreateBehavior;
import implementedbehavior.UserListBehavior;
import implementedbehavior.UserLoginBehavior;

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

public class RequestMappingStorage {

private static List<RequestMapping> REQUEST_MAPPINGS;

static {
REQUEST_MAPPINGS = Arrays.asList(
new RequestMapping(HttpMethod.POST, "/user/create", new UserCreateBehavior())
new RequestMapping(HttpMethod.POST, "/user/create", new UserCreateBehavior()),
new RequestMapping(HttpMethod.POST, "/user/login", new UserLoginBehavior()),
new RequestMapping(HttpMethod.GET, "/user/list", new UserListBehavior())
);
}

Expand Down
Loading