Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
11649a6
main() 아규먼트 java 스타일로 변경
siyoon210 Jun 24, 2020
b03c593
webapp 디렉토리 하위에 있는 정적파일 요청시에 해당 파일 보여주기
siyoon210 Jun 24, 2020
ebaf447
Junit 버전 5로 변경
siyoon210 Jun 24, 2020
eb2ee7a
Controller 인터페이스와 index.html 처리하는 MainController 생성
siyoon210 Jun 25, 2020
f1fc1ad
RequestHandler가 Controller에 의존하도록 변경
siyoon210 Jun 25, 2020
bcfc4e3
process() 파라미터 InputStream -> Map으로 변경
siyoon210 Jun 25, 2020
d14f295
정적파일 반환하는 DefaultController 생성
siyoon210 Jun 25, 2020
5f297e0
Path에 맞는 Controller 반환하도록 Constructor 생성, 적용
siyoon210 Jun 25, 2020
6151bc8
Controller 싱글톤 객체로 변경
siyoon210 Jun 25, 2020
4ca9752
불필요한 매핑 삭제
siyoon210 Jun 25, 2020
a75390d
Controller 접근제어자 수정
siyoon210 Jun 25, 2020
8bac4b2
InputStream을 RequestInfo로 바꾸는 로직 Uitls로 이동
siyoon210 Jun 25, 2020
d786159
LoggerFactorry class 수정
siyoon210 Jun 25, 2020
1d1272e
요구사항2 GET 방식으로 회원가입하기
siyoon210 Jun 25, 2020
fc11dd2
Controller 매핑을 요청method + path로 변경
siyoon210 Jun 25, 2020
7829db0
요청정보로 헤더 추출할때 parseHeader() 메서드 사용
siyoon210 Jun 25, 2020
be57b3f
요구사항3 POST 방식으로 회원가입하기
siyoon210 Jun 25, 2020
cc18e8d
요구사항4 302 status code 적용하고 인덱스페이지로 리다이렉트 구현하기
siyoon210 Jun 25, 2020
3b57742
요구사항5 로그인하기
siyoon210 Jun 25, 2020
52b539f
요구사항6 사용자 목록 출력
siyoon210 Jun 25, 2020
9823a73
요구사항7 css 적용
siyoon210 Jun 25, 2020
5f97448
MainController 삭제하고 Default에서 루트를 인덱스로
siyoon210 Jun 25, 2020
ed0ebf6
응답을 위한 Response 객체 구현
siyoon210 Jun 25, 2020
df48ca3
User 생성시에 body content를 인자로 받는 생성자 구현
siyoon210 Jun 25, 2020
31a15fe
requestInfo final 키워드
siyoon210 Jun 25, 2020
c8bc061
Response 모델 객체를 controller 패키지 하위로 이동
siyoon210 Jun 25, 2020
97b0bfb
요청정보를 맵에서 일급객체로 변경
siyoon210 Jun 26, 2020
c92df75
substring str 수정
siyoon210 Jun 26, 2020
7b50393
응답객체 HttpResponse 하나로 통합
siyoon210 Jun 26, 2020
2542ccb
변수 수정
siyoon210 Jun 26, 2020
017bbfd
매직넘버 제거
siyoon210 Jun 26, 2020
0a349b5
변수명 수정
siyoon210 Jun 26, 2020
0f8d386
location 메서드명 변경
siyoon210 Jun 26, 2020
ac41261
Request get() 메서드 편의성을 위한 리팩토링
siyoon210 Jun 26, 2020
8fa791f
UserListController private 메서드 추출
siyoon210 Jun 26, 2020
201484d
else 문 추가로 2분기 강조
siyoon210 Jun 26, 2020
3219d68
else 문 추가로 2분기 강조
siyoon210 Jun 26, 2020
2ff17ef
if 조건을 성공조건 먼저 표시하도록 수정
siyoon210 Jun 26, 2020
0432530
null 검사 컨벤션에 맞게 수정
siyoon210 Jun 26, 2020
a506a43
parseHttpMethodAndPath() 메서드 추출
siyoon210 Jun 26, 2020
cc4be1d
추상클래스로 요청 메서드 구분하여서 doGet() doPost() 분기처리하기
siyoon210 Jun 27, 2020
2572f11
HttpMethod 매직넘버 enum으로 변경
siyoon210 Jun 29, 2020
2f0a642
MediaType 매직넘버 enum으로 변경
siyoon210 Jun 29, 2020
e523815
양파 3장 TIL 작성
siyoon210 Jun 30, 2020
5e64036
미사용 변수 삭제
siyoon210 Jun 30, 2020
762e8a1
ReuqestHandler가 IllegalAccessException도 캐치하도록 수정
siyoon210 Jun 30, 2020
621fe16
HttpResponse에 forawrd 메서드 생성
siyoon210 Jun 30, 2020
dff0498
ThreadPoolExecutor를 사용한 쓰레드풀 설정
siyoon210 Jul 7, 2020
3e43525
예제 String으로 HttpRequestTest 작성
siyoon210 Jul 7, 2020
fa0fa3e
HttpReqeust 테스트 작성
siyoon210 Jul 7, 2020
9f3384d
HttpReqeust 쿠키파싱 테스트 추가
siyoon210 Jul 7, 2020
350a771
HttpReqeust import최적화, 의미없는 throw 삭제
siyoon210 Jul 7, 2020
9b84004
uuid 생성 테스트
siyoon210 Jul 15, 2020
9723854
쿠키를 사용하여서 세션키 설정
siyoon210 Jul 15, 2020
c3f19f6
controller.model 패키지를 webserver 패키지 하위로 이동
siyoon210 Jul 15, 2020
3b0ec62
HttpCookie 일급객체로 변경
siyoon210 Jul 15, 2020
33d640c
무의미한 null체크삭제
siyoon210 Jul 15, 2020
985b017
UUID를 id로 하는 httpSession 생성
siyoon210 Jul 15, 2020
a931009
getCookies시에 객체를 매번 생성하지 않도록 수정
siyoon210 Jul 15, 2020
88c1f1a
HttpRequest에서 HttpSession 반환하는 getter 생성
siyoon210 Jul 15, 2020
cd27a6d
"JSESSIONID" 매직넘버 상수처리
siyoon210 Jul 15, 2020
8f6b99f
로그인 성공한 경우 세션에 user 정보 추가
siyoon210 Jul 15, 2020
0ccaf27
httpCookie를 getCookies() 메서드로 받아서 사용
siyoon210 Jul 15, 2020
e4e42d2
로그인 여부 체크를 세션방식으로 변경
siyoon210 Jul 15, 2020
450b7f6
bug: UUID 인자로 받아온 것 사용
siyoon210 Jul 15, 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
151 changes: 143 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,157 @@
* 각 요구사항을 구현하는 것이 중요한 것이 아니라 구현 과정을 통해 학습한 내용을 인식하는 것이 배움에 중요하다.

### 요구사항 1 - http://localhost:8080/index.html로 접속시 응답
*
*네트워크 요청 받아드리기*
1. java.net.ServerSocket(portNumber)를 생성하면 해당포트로 연결되는 네트워크 접속을 감지한다.
2. (무한루프를 통해서) 소켓접속이 감지되면, 해당 소켓을 분석하여서 적절한 로직을 수행한다.
3. 소켓의 inputStream에는 요청정보들이 담겨있고, outputStream에 응답정보를 작성할 수 있다.

### 요구사항 2 - get 방식으로 회원가입
*
* 요청 path에 queryString으로 정보들이 들어온다.

### 요구사항 3 - post 방식으로 회원가입
*
*HTTP 프로토콜*
- 요청
```
GET /path HTTP/1.1
Accept: application/json
Authorization: Bearer UExBMDFUMDRQV1MwMnzpdvtYYNWMSJ7CL8h0zM6q6a9ntw
... (`key: value` 형태의 헤더정보)
(빈줄)
...(바디정보)
```

- 응답
```
HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Encoding: UTF-8
... (`key: value` 형태의 헤더정보)
(빈줄)
...(바디정보)
...
```

- (HTTP) 프로토콜이란, 결국 약속! 이다. *문자열*로 요청을 보내지만 클라이언트와 서버간에 *약속되어져 있는 형태*로 보내면 된다. 약속된 형태의 *문자열*을 클라이언트와 서버가 subString하여 파싱하고 정보를 추출하는 것이다.
- 예를들어 헤더정보는 반드시 `key: value` 형태여야 한다. 콜론뒤에 빈칸도 포함되어있어야 한다!
- 예를들어 순서는 반드시 `요청라인(첫줄)-헤더정보-빈줄-바디정보` 여야한다.
- 바디정보에 form 정보가 `key1=value1;key2=value2` 형태로 드러있다.

### 요구사항 4 - redirect 방식으로 이동
*
* HTTP 응답라인(첫줄)에 status 코드가 300번대라면, 클라이언트에게 리다이렉트를 하라는 프로토콜이다.
* 헤더정보에 `Location: /redirect-url` 형태로 보내면 어떤 주소로 리다이렉트 해야하는지 클라이언트에게 알려줄 수 있다.
* *HTTP는 프로토콜(약속)* 이고, 실제 형태는 문자열일뿐이다.

### 요구사항 5 - cookie
*
* 요청 헤더에 `Cookie: key1=value1;key2=value2` 형태로 클라이언트가 가지고 있는 쿠키 값을 서버에게 보낸다.
* 응답 헤더에 `Set-Cookie: key1=value1;key2=value2` 형태로 서버가 클라이언트에게 쿠키 값을 설정하라고 알려준다.
* *HTTP는 프로토콜(약속)* 이고, 실제 형태는 문자열일뿐이다.

### 요구사항 6 - stylesheet 적용
*
* 응답 헤더에 `Content-Type: text/css` 형태로 서버가 클라이언트에게 css 파일이라는 점을 알려준다.
* *HTTP는 프로토콜(약속)* 이고, 실제 형태는 문자열일뿐이다.

### 설계적 영감
- path를 매핑하는 작업에서 전략패턴을 적용하고 각 컨트롤러는 Constructor를 이용하여서 매핑해두었다 :thumbup:
- 컨트롤러들은 하나씩만 필요하여 싱글톤으로 만들었다.
- 스프링은 전략패턴과 싱글톤을 '쉽게' 구현하도록 도와준다. 그래서 개발자들이 이 패턴들을 사용하지 않아도 되는 편의가 생겼다.
- 싱글톤으로 분리하면, 이를 이용하면서 파생되는 DTO(Data,Domain..) 객체들이 *필연적으로* 만들어진다. 이를 잘 인지하고 분리하자! (HttpRuqest, HttpResponse)
- AOP같이 모든 작업들의 선작업 혹은 후작업이 필요하다면, 여기서 상속을 사용해보는 것을 고려해보자! (중복때문에 상속사용하는게 아니다. AOP를 구현하기 위함)

### ec2 서버에 배포 후
*EC2 접속하기*
1. chmod 400 jwp-book.pem
- 키파일 퍼미션을 400으로 두어야 ssh가 작동한다. (라고 aws에서 안내해줌)
2. ssh -i "jwp-book.pem" [ubuntu@ec2-13-124-248-99.ap-northeast-2.compute.amazonaws.com](mailto:ubuntu@ec2-13-124-248-99.ap-northeast-2.compute.amazonaws.com)
- ssh로 키파일을 이용하여서 ec2에 접속
- ubuntu@ 는 계정명을 말하는것 같다. 뒤에 주소는 ec2에서 명시하는 퍼블릭IP
- 그래서 아마존 리눅스로 다시 만드니까 유저명이 ec2-user였고 ec2-user@로 해야 됐다!

*한글 로케일 설정하기*
1. sudo locale-gen ko_KR.EUC-KR ko_KR.UTF-8
2. sudo dpkg-reconfigure locales
3. .bash_profile 파일에 설정추가
```
LANG="ko_KR.UTF-8"
LANGUAGE="ko_KR:ko:en_US:en"
```
4. source .bash_profile

*JDK와 메이븐 설치하기*
1. wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" [https://download.java.net/openjdk/jdk8u41/ri/openjdk-8u41-b04-linux-x64-14_jan_2020.tar.gz](https://download.java.net/openjdk/jdk8u41/ri/openjdk-8u41-b04-linux-x64-14_jan_2020.tar.gz)
- jdk 다운로드
2. gunzip openjdk-8u41-b04-linux-x64-14_jan_2020.tar.gz
- 압축풀기 gz
3. tar -xvf openjdk-8u41-b04-linux-x64-14_jan_2020.tar
- 압축풀기 tar (따로따로 풀어야하네)
4. ln -s java-se-8u41-ri/ java
- 압축푼 디렉토리 'java'로 심볼릭 링크 만들기
5. 프로필 파일에 path 추가 (로케일 설정시에 사용했던 .bash_profile에다가 했다)
```
bash
export JAVA_HOME=~/java
export PATH=$PATH:$JAVA_HOME/bin
```
- JAVA_HOME에 (심볼릭 설정한) java 폴더 설정하고
- PATH설정 추가!
6. source .bash_profile
- 설정 반영
7. java -version
- path설정이 잘 되었나 확인
8. wget [http://apache.mirror.cdnetworks.com/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz](http://apache.mirror.cdnetworks.com/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz)
- 메이븐 다운로드
9. gunzip apache-maven-3.6.3-bin.tar.gz
- 압축풀기
10. tar -xvf apache-maven-3.6.3-bin.tar
- 압축풀기
11. 프로필 파일에 메이븐 path 추가
```
bash
export JAVA_HOME=~/java
export MAVEN_HOME=~/maven
export PATH=$PATH:$JAVA_HOME/bin:$MAVEN_HOME/bin
```
12. source .bash_profile
13. env
- 환경설정 확인
14. mvn -version
- path 설정 잘 됐나 확인

*JDK, 메이븐 설치하기 (apt-get으로)*
- 이렇게 하니까 메이븐 에러가 안남.. 아니면 우분투 버전을 16버전으로 하니까 안나는 걸 수 도있어
- sudo apt-get install openjdk-8-jdk
- sudo apt-get install maven

*Git 설치하고 프로젝트 받기*
1. (sudo apt-get install git)
- 보통 설치가 되어있음
2. git —version
- git 설치되었는지 확인
3. git clone [https://github.com/siyoon210/web-application-server](https://github.com/siyoon210/web-application-server)
- 배포할 레포 git clone
4. mvn clean package
- 클론해온 레포 디렉토리 안에서 실행
- 메이븐으로 삭제(clean) 후 빌드(package)
- 결과물은 target 디렉토리 안에 생김
5. git clone [https://github.com/siyoon210/web-application-server](https://github.com/siyoon210/web-application-server)
- 배포할 레포 git clone
6. mvn clean package
- 클론해온 레포 디렉토리 안에서 실행
- 메이븐으로 삭제(clean) 후 빌드(package)
- 결과물은 target 디렉토리 안에 생김

### heroku 서버에 배포 후
*
*ec2 포트열기*
- ec2 정보에서 `보안그룹`을 선택한다. (보통 launch-wizard-4 이런식에 이름으로 되어있음)
- `사용자 지정 TCP` 8080(원하는 번호) 포트를 공개한다. (혹은 HTTP 80을 오픈해도 됨)
- 소스가 0.0.0.0/0, 이랑 ::/0 두개가 생기는데 뭔지는 잘 모르겠음

*빌드 배포하기*
- mvn clean package
- java -cp target/classes:target/dependecy/* webserver.Webserver 8080
- 재배포하기 위해서 프로세스 끄기
- ps -ef | grep webserver
- 실행한 프로세스 아이디(PID)를 찾고
- kill -9 $PID
- 웹서버 종료
- 위에 배포코드실행
19 changes: 16 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,16 @@
<dependencies>
<!-- unit testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>

Expand All @@ -26,6 +33,12 @@
<version>18.0</version>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.16.1</version>
<scope>test</scope>
</dependency>
<!-- logger -->
<dependency>
<groupId>ch.qos.logback</groupId>
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/controller/AbstractController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package controller;

import webserver.model.HttpRequest;
import webserver.model.HttpResponse;
import model.HttpMethod;

import java.io.IOException;

public abstract class AbstractController implements Controller {
public final HttpResponse process(HttpRequest httpRequest) throws IOException, IllegalAccessException {
final HttpMethod method = HttpMethod.valueOf(httpRequest.getMethod());

switch (method) {
case GET:
return doGet(httpRequest);
case POST:
return doPost(httpRequest);
default:
throw new IllegalArgumentException("Illegal Http Method");
}
}

protected HttpResponse doGet(HttpRequest httpRequest) throws IOException, IllegalAccessException {
throw new IllegalAccessException("doGet() is not overridden.");
}

protected HttpResponse doPost(HttpRequest httpRequest) throws IOException, IllegalAccessException {
throw new IllegalAccessException("doPost() is not overridden.");
}
}
10 changes: 10 additions & 0 deletions src/main/java/controller/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package controller;

import webserver.model.HttpRequest;
import webserver.model.HttpResponse;

import java.io.IOException;

public interface Controller {
HttpResponse process(HttpRequest httpRequest) throws IOException, IllegalAccessException;
}
38 changes: 38 additions & 0 deletions src/main/java/controller/ControllerConstructor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package controller;

import webserver.model.HttpRequest;

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

public class ControllerConstructor {
private final static Map<String, Controller> pathAndControllers = new HashMap<>();

private ControllerConstructor() {
}

static {
pathAndControllers.put("/user/create", UserCreateController.getInstance());
pathAndControllers.put("/user/login", UserLoginController.getInstance());
pathAndControllers.put("/user/list", UserListController.getInstance());
}

public static Controller getOf(HttpRequest request) {
String path = request.getPath();
if (hasQueryString(path)) {
path = subStringQueryString(path);
}

final Controller controller = pathAndControllers.get(path);
return Objects.isNull(controller) ? DefaultController.getInstance() : controller;
}

private static String subStringQueryString(String path) {
return path.split("\\?")[0];
}

private static boolean hasQueryString(String path) {
return path.contains("?");
}
}
35 changes: 35 additions & 0 deletions src/main/java/controller/DefaultController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package controller;

import webserver.model.HttpRequest;
import webserver.model.HttpResponse;

import java.io.IOException;

class DefaultController extends AbstractController {
private static final Controller instance = new DefaultController();

private DefaultController() {
}

public static Controller getInstance() {
return instance;
}

@Override
protected HttpResponse doGet(HttpRequest request) throws IOException {
final String path = getPath(request);

return HttpResponse.builder()
.status(200)
.forward(path)
.build();
}

private String getPath(HttpRequest request) {
final String path = request.getPath();
if (path.equals("/")) {
return "/index.html";
}
return path;
}
}
35 changes: 35 additions & 0 deletions src/main/java/controller/UserCreateController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package controller;

import webserver.model.HttpRequest;
import db.DataBase;
import webserver.model.HttpResponse;
import model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Map;

class UserCreateController extends AbstractController {
private static final Controller instance = new UserCreateController();
private static final Logger log = LoggerFactory.getLogger(UserCreateController.class);

private UserCreateController() {}

public static Controller getInstance() {
return instance;
}

@Override
protected HttpResponse doPost(HttpRequest request) {
final Map<String, String> content = request.getParsedBody();
final User newUser = new User(content);

log.info("Create User: {}", newUser);
DataBase.addUser(newUser);

return HttpResponse.builder()
.status(302)
.redirect("/")
.build();
}
}
Loading