Skip to content

Commit

Permalink
[제이] 3단계 - HTTP 웹 서버 구현 미션 제출합니다. (#214)
Browse files Browse the repository at this point in the history
* [또링] 1단계 - HTTP 웹 서버 구현 미션 제출합니다. (#105)

* docs : 요구사항1의 구현 기능 목록 작성

* feat : 모든 Request Header 출력하기

* feat : Request에서 path 분리하기

* feat : path에 해당하는 파일 읽어 응답하기

* docs : 요구사항2의 구현 기능 목록 작성

* feat : Request Parameter 추출 및 User 객체 생성

* docs : 요구사항3의 구현 기능 목록 작성

* feat : form.html 파일의 form 태그 method를 get에서 post로 수정

* refactor : HttpRequest를 RequestHeader와 RequestBody로 분리

* refactor : HttpRequestParser 유틸 클래스 생성

* docs : 요구사항4의 구현 기능 목록 작성

* feat : 요청에 따라 다른 HttpResponse를 내려준다.

* docs : 요구사항5의 구현 기능 목록 작성

* refactor : RequestHeader 값의 자료형을 List에서 Map으로 변경

* refactor : HeaderProperty 생성

* feat : 응답에 따라 Content-Type을 변경하여 Stylesheet 파일을 지원하도록 구현

* feat : status code를 302로 변경한 후, Location 값에 리다이렉션 할 페이지를 넣어 응답

* refactor : 변수 정리 및 파일 끝 개행 추가

* docs: 3단계 요구사항1 구현 기능목록 작성

* feat: 로그인 기능 구현

* feat: default root 설정

* docs: 요구사항 2번째 기능 목록 작성

* test: handlebar 사용을 위한 학습테스트 작성

* feat: 로그인하지 않은 상태면 로그인 페이지로 이동

* feat: 유저 목록 출력하기

* feat: 로그인 성공 여부에 따라 페이지 redirect

* refactor: http package 구조 변경

* test: /user GET method에 대한 테스트 작성

* [또링] 2단계 - HTTP 웹 서버 리팩터링 미션 제출합니다. (#202)

* docs: README.md에 요구사항 1 추가

* feat: Request Handler에서 Header 추출 기능 구현

* feat: request의 path로 파일을 읽어서 응답하는 기능 추가

* feat: Http Request 생성

	- Http Method를 위한 클래스 생성
	- Http Status를 위한 클래스 생성
	- Http Header를 위한 클래스 생성

* feat: 요구사항1 완료

* docs: 두번째 요구사항 작성

* refactoring: http spec 정의

* refactoring: http request spec 추가 및 테스트 추가

* refactor: http request spec 변경

* feat: 요구사항2 완료

* docs: 요구사항3을 위한 README.md 작성

* feat: 회원 가입 기능이 post 요청에서 정상적으로 동작하도록 구현

* docs: 요구사항4 작성

* feat: 회원가입 완료 후 index.html로 이동하도록 변경

* docs: 요구사항 5 README.md에 작성

* feat: 요청 uri이 static resource인지를 확인하는 기능 생성

* feat: stylesheet 등 다양한 형식의 파일을 지원하도록 변경

* feat: Http Response 분리

* test: SimpleHttRequest에 대한 Test 작성

* test: HttpResponse에 대한 Test 작성

* refactor: HttpServlet 및 UserController 생성

* feat: DispatcherServlet 생성

* feat: ThreadPool 사용

* docs: 구현 내용 정리

* refactor: 사용하지 않는 파일 삭제

* [제이] 1단계 - HTTP 웹 서버 리팩터링 미션 제출합니다. (#198)

* docs: README.md에 요구사항 1 추가

* feat: Request Handler에서 Header 추출 기능 구현

* feat: request의 path로 파일을 읽어서 응답하는 기능 추가

* feat: Http Request 생성

	- Http Method를 위한 클래스 생성
	- Http Status를 위한 클래스 생성
	- Http Header를 위한 클래스 생성

* feat: 요구사항1 완료

* docs: 두번째 요구사항 작성

* refactoring: http spec 정의

* refactoring: http request spec 추가 및 테스트 추가

* refactor: http request spec 변경

* feat: 요구사항2 완료

* docs: 요구사항3을 위한 README.md 작성

* feat: 회원 가입 기능이 post 요청에서 정상적으로 동작하도록 구현

* docs: 요구사항4 작성

* feat: 회원가입 완료 후 index.html로 이동하도록 변경

* docs: 요구사항 5 README.md에 작성

* feat: 요청 uri이 static resource인지를 확인하는 기능 생성

* feat: stylesheet 등 다양한 형식의 파일을 지원하도록 변경

* feat: Http Response 분리

* test: SimpleHttRequest에 대한 Test 작성

* test: HttpResponse에 대한 Test 작성

* refactor: HttpServlet 및 UserController 생성

* feat: DispatcherServlet 생성

* feat: ThreadPool 사용

* docs: 구현 내용 정리

* docs: 2단계 리팩토링 목록 작성

* refactor: AbstractController 수정
- 지원하지 않는 메서드에 대해서는 MethodNotAllowed 응답 반환

* refactor: resource 처리 영역과 아닌 영역을 구분

* test: 다양한 resource postfix 형식에 대한 테스트 작성

Co-authored-by: jaeju.jang <jjj0611@gmail.com>
Co-authored-by: Jang Jaeju <44603719+jjj0611@users.noreply.github.com>

* docs: 3단계 요구사항3 구현 기능 목록 작성

* feat: HttpSession 인터페이스 작성

* feat: SimpleHttpSession 구현체 작성

* feat: httpSession을 적용하여 로그인 구현

* feat: Cookie 객체 분리

* refactor: Cookies 생성

* refactor: 지원하지 않는 메서드에 대해서 MethodNotAllowed를 응답하도록 변경

* refactor: 테스트용 request 파일명 변경

* test: UserServiceTest#findAll 테스트 작성

* refactor: HttpVersion 객체 분리

* docs : 3단계 리팩터링 목록 작성

* refactor : 인코딩 실패 시 errorStack 출력 -> InternalServerError 반환

* refactor : null 반환 제거

* refactor : doGet() 템플릿화하어 재사용 가능한 구조를 만들기

* refactor : 로그인 검증에 대한 부분을 분리하여 재사용 가능하도록 변경

Co-authored-by: 또링 <jnsorn@gmail.com>
  • Loading branch information
jjj0611 and jnsorn committed Nov 26, 2020
1 parent e74a210 commit c02a51b
Show file tree
Hide file tree
Showing 59 changed files with 959 additions and 127 deletions.
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,33 @@ Thread Pool을 적용해 일정 수의 사용자 동시에 처리가 가능하
- [x] 지원하는 메서드만 오버라이딩 해서 사용하도록 수정
- [x] resource 처리 영역과 아닌 영역을 구분하여 response를 반환
- [x] 다양한 resource postfix 형식에 대한 테스트 작성


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

#### 요구사항 1
- [x] 로그인 기능 구현
- 성공시
- [x] /index.html로 이동
- [x] Cookie Header 값이 logined=true
- 실패시
- [x] /user/login_failed.html로 이동
- [x] Cookie Header 값이 logined=false

#### 요구사항 2
- [x] 유저 목록 출력하기
- [x] 로그인 상태일 경우 목록 출력
- [x] 로그인하지 않은 상태면 로그인 페이지로 이동

#### 요구사항 3
- [x] HttpSession 생성하여 적용하기
- [x] String getId(): 현재 세션에 할당되어 있는 고유한 세션 아이디를 반환
- [x] void setAttribute(String name, Object value): 현재 세션에 value 인자로 전달되는 객체를 name 인자 이름으로 저장
- [x] Object getAttribute(String name): 현재 세션에 name 인자로 저장되어 있는 객체 값을 찾아 반환
- [x] void removeAttribute(String name): 현재 세션에 name 인자로 저장되어 있는 객체 값을 삭제
- [x] void invalidate(): 현재 세션에 저장되어 있는 모든 값을 삭제
- [x] cookie 구조화

#### 리뷰 반영
- [x] 인코딩 실패 시 errorStack 출력 -> InternalServerError 반환
- [x] null 반환 제거
- [x] doGet() 템플릿화하어 재사용 가능한 구조를 만들기
29 changes: 20 additions & 9 deletions src/main/java/controller/AbstractController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@

import java.util.Objects;

import http.HttpRequest;
import http.HttpResponse;
import exception.UnAuthenticationException;
import http.HttpSession;
import http.HttpStatus;
import http.HttpVersion;
import http.request.HttpRequest;
import http.response.HttpResponse;
import servlet.HttpServlet;

public abstract class AbstractController implements HttpServlet {
@Override
public void service(HttpRequest httpRequest, HttpResponse httpResponse) {
String version = httpRequest.getVersion();
if (Objects.isNull(version) || version.isEmpty() || version.endsWith("0.9") || version.endsWith("1.0")) {
HttpVersion httpVersion = httpRequest.getVersion();
if (!httpVersion.isValid()) {
httpResponse.setStatus(HttpStatus.BAD_REQUEST);
return;
}
Expand All @@ -29,23 +32,31 @@ public void service(HttpRequest httpRequest, HttpResponse httpResponse) {
doPut(httpRequest, httpResponse);
break;
default:
throw new IllegalArgumentException("잘못된 요청입니다.");
httpResponse.methodNotAllowed();
}
}

protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.setMethodNotAllowed();
httpResponse.methodNotAllowed();
}

protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.setMethodNotAllowed();
httpResponse.methodNotAllowed();
}

protected void doDelete(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.setMethodNotAllowed();
httpResponse.methodNotAllowed();
}

protected void doPut(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.setMethodNotAllowed();
httpResponse.methodNotAllowed();
}

protected void validateLogin(HttpRequest httpRequest) {
HttpSession session = httpRequest.getSession();
String logined = String.valueOf(session.getAttribute("logined"));
if (Objects.isNull(logined) || !"true".equals(logined)) {
throw new UnAuthenticationException("로그인이 필요한 서비스입니다");
}
}
}
46 changes: 46 additions & 0 deletions src/main/java/controller/LoginController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package controller;

import annotation.RequestMapping;
import dto.LoginRequestDto;
import exception.UnAuthenticationException;
import http.HttpBody;
import http.HttpSession;
import http.HttpStatus;
import http.request.HttpRequest;
import http.response.HttpResponse;
import service.UserService;

@RequestMapping(path = "/user/login")
public class LoginController extends AbstractController {
private final UserService userService;

public LoginController(UserService userService) {
this.userService = userService;
}

@Override
protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
HttpBody httpBody = httpRequest.getBody();
String userId = httpBody.get("userId");
String password = httpBody.get("password");
LoginRequestDto loginRequestDto = new LoginRequestDto(userId, password);
try {
userService.login(loginRequestDto);
loginSuccess(httpRequest, httpResponse);
} catch (UnAuthenticationException e) {
loginFail(httpResponse);
}
}

private void loginSuccess(HttpRequest httpRequest, HttpResponse httpResponse) {
httpResponse.setStatus(HttpStatus.MOVED_PERMANENTLY);
HttpSession session = httpRequest.getSession();
session.setAttribute("logined", "true");
httpResponse.addHeader("Location", "/");
}

private void loginFail(HttpResponse httpResponse) {
httpResponse.setStatus(HttpStatus.MOVED_PERMANENTLY);
httpResponse.addHeader("Location", "/user/login_failed.html");
}
}
39 changes: 33 additions & 6 deletions src/main/java/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,52 @@
package controller;

import java.io.IOException;
import java.util.List;

import annotation.RequestMapping;
import db.DataBase;
import dto.JoinRequestDto;
import dto.UserResponseDto;
import http.ContentType;
import http.HttpBody;
import http.HttpRequest;
import http.HttpResponse;
import http.HttpStatus;
import model.User;
import http.request.HttpRequest;
import http.response.HttpResponse;
import service.UserService;
import view.View;

@RequestMapping(path = "/user")
public class UserController extends AbstractController {
private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@Override
protected void doPost(HttpRequest httpRequest, HttpResponse httpResponse) {
HttpBody httpBody = httpRequest.getBody();
User user = new User(
JoinRequestDto joinRequestDto = new JoinRequestDto(
httpBody.get("userId"),
httpBody.get("password"),
httpBody.get("name"),
httpBody.get("email"));
DataBase.addUser(user);
userService.join(joinRequestDto);
httpResponse.setStatus(HttpStatus.FOUND);
httpResponse.addHeader("Location", "/index.html");
}

@Override
protected void doGet(HttpRequest httpRequest, HttpResponse httpResponse) {
validateLogin(httpRequest);

List<UserResponseDto> users = userService.findAll();

try {
String page = View.render("user/list", users);
httpResponse.setStatus(HttpStatus.OK);
httpResponse.setBody(page.getBytes(), ContentType.HTML);
} catch (IOException e) {
httpResponse.internalServerError();
}
}
}
13 changes: 9 additions & 4 deletions src/main/java/db/DataBase.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package db;

import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.google.common.collect.Maps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Maps;
import model.User;

public class DataBase {
private static final Logger logger = LoggerFactory.getLogger(DataBase.class);
private static Map<String, User> users = Maps.newHashMap();

public static void addUser(User user) {
users.put(user.getUserId(), user);
logger.debug("USER 회원가입 성공 : " + user.toString());
}

public static User findUserById(String userId) {
return users.get(userId);
}

public static Collection<User> findAll() {
return users.values();
public static List<User> findAll() {
return new ArrayList<>(users.values());
}
}
31 changes: 31 additions & 0 deletions src/main/java/dto/JoinRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dto;

public class JoinRequestDto {
private String userId;
private String password;
private String name;
private String email;

public JoinRequestDto(String userId, String password, String name, String email) {
this.userId = userId;
this.password = password;
this.name = name;
this.email = email;
}

public String getUserId() {
return userId;
}

public String getPassword() {
return password;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}
}
19 changes: 19 additions & 0 deletions src/main/java/dto/LoginRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dto;

public class LoginRequestDto {
private String userId;
private String password;

public LoginRequestDto(String userId, String password) {
this.userId = userId;
this.password = password;
}

public String getUserId() {
return userId;
}

public String getPassword() {
return password;
}
}
27 changes: 27 additions & 0 deletions src/main/java/dto/UserResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dto;

import model.User;

public class UserResponseDto {
private String userId;
private String name;
private String email;

public UserResponseDto(User user) {
this.userId = user.getUserId();
this.name = user.getName();
this.email = user.getEmail();
}

public String getUserId() {
return userId;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}
}
7 changes: 7 additions & 0 deletions src/main/java/exception/DisabledEncodingException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package exception;

public class DisabledEncodingException extends RuntimeException {
public DisabledEncodingException(String message, Throwable cause) {
super(message, cause);
}
}
9 changes: 9 additions & 0 deletions src/main/java/exception/UnAuthenticationException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package exception;

public class UnAuthenticationException extends IllegalArgumentException {

public UnAuthenticationException(String message) {
super(message);
}

}
44 changes: 44 additions & 0 deletions src/main/java/http/Cookie.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package http;

public class Cookie {
private static final String SESSION_ID_KEY = "JSESSIONID";
private static final String ROOT_PATH = "/";

private String name;
private String value;
private String path;

public static Cookie createSessionIdCookie(String value) {
return new Cookie(SESSION_ID_KEY, value);
}

public static Cookie of(String name, String value) {
return new Cookie(name, value);
}

private Cookie(String name, String value) {
this.name = name;
this.value = value;
this.path = ROOT_PATH;
}

public boolean isSessionId() {
return SESSION_ID_KEY.equals(name);
}

public void setPath(String path) {
this.path = path;
}

public String getName() {
return name;
}

public String getValue() {
return value;
}

public String getPath() {
return path;
}
}
Loading

0 comments on commit c02a51b

Please sign in to comment.