Skip to content

Commit

Permalink
[또링] 1단계 - HTTP 웹 서버 구현 미션 제출합니다. (#105)
Browse files Browse the repository at this point in the history
* 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 : 변수 정리 및 파일 끝 개행 추가
  • Loading branch information
jnsorn committed Sep 12, 2020
1 parent aad12e4 commit fd1aed0
Show file tree
Hide file tree
Showing 26 changed files with 822 additions and 52 deletions.
45 changes: 37 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
# 웹 애플리케이션 서버
## 진행 방법
* 웹 애플리케이션 서버 요구사항을 파악한다.
* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다.
* 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다.
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다.

## 우아한테크코스 코드리뷰
* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
## 1단계 - HTTP 웹 서버 구현
### 요구사항1
> http://localhost:8080/index.html 로 접속했을 때 webapp 디렉토리의 index.html 파일을 읽어 클라이언트에 응답한다.
*구현 기능 목록*
- [x] Request Header를 파싱하여 원하는 정보 찾기
- [x] 모든 Request header 출력하기
- [x] Request에서 path 분리하기
- [x] path에 해당하는 파일 읽어 응답하기

### 요구사항2
> “회원가입” 메뉴를 클릭하면 http://localhost:8080/user/form.html 으로 이동하면서 회원가입할 수 있다.
*구현 기능 목록*
- [x] Request Parameter 추출
- [x] 사용자가 입력한 값 저장

### 요구사항3
> http://localhost:8080/user/form.html 파일의 form 태그 method를 get에서 post로 수정한 후 회원가입 기능이 정상적으로 동작하도록 구현한다.
*구현 기능 목록*
- [x] form.html 파일의 form 태그 method를 get에서 post로 수정
- [x] Request Body의 값 추출하기

### 요구사항4
> “회원가입”을 완료하면 /index.html 페이지로 이동하고 싶다. 현재는 URL이 /user/create 로 유지되는 상태로 읽어서 전달할 파일이 없다.
> 따라서 redirect 방식처럼 회원가입을 완료한 후 “index.html”로 이동해야 한다.즉, 브라우저의 URL이 /index.html로 변경해야 한다.
*구현 기능 목록*
- [x] 요청에 따라 다른 HttpResponse를 응답
- [x] status code를 302로 변경한 후, Location 값에 리다이렉션 할 페이지를 넣어 응답

### 요구사항5
> 지금까지 구현한 소스 코드는 stylesheet 파일을 지원하지 못하고 있다. Stylesheet 파일을 지원하도록 구현하도록 한다.
*구현 기능 목록*
- [x] 응답에 따라 Content-Type을 변경하여 Stylesheet 파일을 지원하도록 구현
6 changes: 5 additions & 1 deletion src/main/java/db/DataBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@
import java.util.Collection;
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) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public String getEmail() {

@Override
public String toString() {
return "User [userId=" + userId + ", password=" + password + ", name=" + name + ", email=" + email + "]";
return "User [userId=" + userId + ", password=" + password + ", name=" + name + ", email="
+ email + "]";
}
}
25 changes: 22 additions & 3 deletions src/main/java/utils/FileIoUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,32 @@

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class FileIoUtils {
public static byte[] loadFileFromClasspath(String filePath) throws IOException, URISyntaxException {
Path path = Paths.get(FileIoUtils.class.getClassLoader().getResource(filePath).toURI());
return Files.readAllBytes(path);
private static final Logger logger = LoggerFactory.getLogger(FileIoUtils.class);

public static byte[] loadFileFromClasspath(String filePath) throws IOException {
URL resource = FileIoUtils.class.getClassLoader()
.getResource(filePath);
try {
if (resource != null) {
Path path = Paths.get(resource.toURI());
return Files.readAllBytes(path);
}
return null;
} catch (URISyntaxException e) {
logger.error(e.getMessage());
throw new IllegalArgumentException(String.format(
"[%s] is not formatted strictly according to RFC2396 and cannot be converted to a URI",
filePath));
}
}
}

36 changes: 36 additions & 0 deletions src/main/java/utils/HttpRequestParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package utils;

import static web.HeaderProperty.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class HttpRequestParser {
public static final String EMPTY = "";
public static final String HEADER_DATA_DELIMITER = ": ";
public static final int KEY_INDEX = 0;
public static final int VALUE_INDEX = 1;
private static final String KEY_VALUE_DELIMITER = "=";

public static Map<String, String> parsingRequestHeader(BufferedReader br) throws IOException {
Map<String, String> request = new HashMap<>();
request.put(REQUEST_LINE.getName(), br.readLine());
String line = br.readLine();
while (line != null && !EMPTY.equals(line)) {
String[] headerData = line.split(HEADER_DATA_DELIMITER);
request.put(headerData[KEY_INDEX], headerData[VALUE_INDEX]);
line = br.readLine();
}
return request;
}

public static Map<String, String> parsingData(String s) {
return Arrays.stream(s.split("&"))
.collect(Collectors.toMap(param -> param.split(KEY_VALUE_DELIMITER)[0],
param -> param.split(KEY_VALUE_DELIMITER)[1]));
}
}
19 changes: 19 additions & 0 deletions src/main/java/web/HeaderProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package web;

public enum HeaderProperty {
REQUEST_LINE("requestLine"),
HOST("Host"),
CONNECTION("Connection"),
CONTENT_LENGTH("Content-Length"),
ACCEPT("Accept");

private String name;

HeaderProperty(String name) {
this.name = name;
}

public String getName() {
return name;
}
}
45 changes: 45 additions & 0 deletions src/main/java/web/HttpRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package web;

import java.io.BufferedReader;
import java.io.IOException;

import utils.IOUtils;

public class HttpRequest {
private RequestHeader requestHeader;
private RequestBody requestBody;

public HttpRequest(BufferedReader br) throws IOException {
this.requestHeader = new RequestHeader(br);
if (requestHeader.isPost()) {
String data = IOUtils.readData(br, requestHeader.getContentLength());
this.requestBody = new RequestBody(data);
} else {
this.requestBody = null;
}
}

public boolean isPost() {
return requestHeader.isPost();
}

public boolean isStaticFile() {
return requestHeader.isStaticFile();
}

public RequestUri getRequestUri() {
return requestHeader.getRequestUri();
}

public RequestBody getRequestBody() {
return requestBody;
}

@Override
public String toString() {
return "HttpRequest{" +
"requestHeader=" + requestHeader +
", requestBody=" + requestBody +
'}';
}
}
95 changes: 95 additions & 0 deletions src/main/java/web/HttpResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package web;

import static web.RequestUri.*;

import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import db.DataBase;
import model.User;
import utils.FileIoUtils;

public class HttpResponse {
public static final String NEW_LINE = System.lineSeparator();

private static final Logger logger = LoggerFactory.getLogger(HttpResponse.class);

private DataOutputStream dos;

public HttpResponse(DataOutputStream dos) {
this.dos = dos;
}

public void process(HttpRequest httpRequest) {
if (httpRequest.isStaticFile()) {
processFile(httpRequest);
} else {
processApi(httpRequest);
}
}

private void processFile(HttpRequest httpRequest) {
RequestUri requestUri = httpRequest.getRequestUri();
String resourcePath = requestUri.findPath() + requestUri.getUri();
byte[] content = null;
try {
content = FileIoUtils.loadFileFromClasspath(
resourcePath);
} catch (IOException e) {
logger.error(e.getMessage());
}
if (content != null) {
String response = response200Header(requestUri.findContentType(), content);
toDataOutputStream(response);
responseBody(content);
}
}

private void processApi(HttpRequest httpRequest) {
if (httpRequest.isPost()) {
Map<String, String> body = httpRequest.getRequestBody().getFormData();
User user = new User(body.get("userId"), body.get("password"), body.get("name"),
body.get("email"));
DataBase.addUser(user);
String response = response302Header("http://localhost:8080" + INDEX_HTML);
toDataOutputStream(response);
responseBody(new byte[0]);
}
}

private String response200Header(String contentType, byte[] content) {
return "HTTP/1.1 200 OK" + NEW_LINE
+ "Content-Type: " + contentType + NEW_LINE
+ "Content-Length: " + content.length + NEW_LINE
+ NEW_LINE;
}

private String response302Header(String redirectUrl) {
return "HTTP/1.1 302 Found" + NEW_LINE
+ "Location: " + redirectUrl + NEW_LINE
+ NEW_LINE;
}

private void toDataOutputStream(String response) {
try {
dos.writeBytes(response);
logger.debug(response);
} catch (IOException e) {
logger.error(e.getMessage());
e.printStackTrace();
}
}

private void responseBody(byte[] content) {
try {
dos.write(content, 0, content.length);
dos.flush();
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/web/Method.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package web;

public enum Method {
OPTIONS,
HEAD,
POST,
GET,
PUT,
DELETE,
TRACE,
CONNECT;
}
51 changes: 51 additions & 0 deletions src/main/java/web/RequestBody.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package web;

import static utils.HttpRequestParser.*;
import static web.HttpResponse.*;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;

public class RequestBody {
Map<String, String> requestBody;

public RequestBody(String data) {
this.requestBody = parsingData(data);
}

public RequestBody(BufferedReader br) throws IOException {
String line = br.readLine();
while (line == null || line.isEmpty() || NEW_LINE.equals(line)) {
line = br.readLine();
}
this.requestBody = parsingData(line);
}

public Map<String, String> getFormData() {
return requestBody;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
RequestBody that = (RequestBody)o;
return Objects.equals(requestBody, that.requestBody);
}

@Override
public int hashCode() {
return Objects.hash(requestBody);
}

@Override
public String toString() {
return "RequestBody{" +
"requestBody=" + requestBody +
'}';
}
}
Loading

0 comments on commit fd1aed0

Please sign in to comment.