-
Notifications
You must be signed in to change notification settings - Fork 90
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[카프카] 1단계 - HTTP 웹 서버 구현 미션 제출합니다. (#132)
* feat: RequestUtils 구현 - Request에 대한 Util 정적 메서드를 저장하는 RequestUtils 클래스 구현 - RequestUtils 클래스에 대한 테스트 클래스 구현 * feat: index.html로 연결되도록 구현 - 요구사항 1에 맞춰 구현 - RequestHeader의 내용을 RequestUtils를 통해 분석하여, 적절한 파일을 반환함 - js, css 파일에 관련된 에러 이슈가 있어, 일단 html은 template로, 나머지는 static으로 경로를 잡도록 지정함 * feat: 잘못된 파일 경로 전달 시 예외 발생 구현 - RequestUtils에 전달되는 requestHeader의 값을 String으로 변경 - RequestUtils에 잘못된 파일 경로가 GET으로 요청될 시, InvalidFilePathException 발생 - 커스텀 예외 클래스인 InvalidFilePathException 구현 - RequestUtilsTest에 예외 테스트 추가 * feat: RequestUtils의 로직 수정 및 css 로딩 이슈 해결 - 요구사항 5에 해당하는 css 로딩 이슈 해결. RequestHeader의 Accept 값을 response시 전달하도록 구현함 - RequestUtils에 transferRequestHeaderToMap 메서드 추가. 아직 RequestHeader 클래스가 없어서, 그 대신 HashMap을 사용한다. RequestHeader의 메서드, 경로, 버전, 그리고 각각의 파라미터들을 매핑하여 반환한다. * feat: HttpRequest 클래스 구현, 기존 로직 일부 대체 - HttpRequest 클래스를 구현함. 여기에는 Http Request의 메서드 타입, 경로, 버전 정보 및 기타 인자 정보가 저장됨 - HttpRequest에 잘못된 값이 전달된 채로 생성되면, InvalidHttpRequestException 가 발생하도록 구현 - HttpRequest의 생성과 예외처리에 대한 테스트 코드 구현 - RequestUtils의 Map 생성 로직을 HttpRequest 클래스로 대체함. 추후 경로 계산 로직 역시 점진적으로 대체할 예정 * test: HttpRequest에 대한 테스트 코드 보강 - HttpRequestTest의 테스트 코드 리팩토링 - HttpRequest의 getContentType에 대한 테스트 코드 추가 * docs: README.md 작성 - README.md에 진행내역 체크리스트 작성 * feat: HttpRequest의 요청 타겟을 별도 클래스로 분리 - HttpRequest에 포함되는 요청 타겟을 RequestPath로 분리하였음 - RequestPath는 타겟(파일명이나 요청명)과 파라미터(존재하지 않을 경우 빈 맵으로 저장)를 가짐 * feat: 회원가입 로직 구현 - RequestPath를 이용해 parameters를 mapping함 - User 클래스에 맵을 입력받아 생성하는 로직을 구현함 - RequestHandler에 로직 구현. user/create가 요청될 경우, 기존 로직 대신 user를 생성해 DB에 넣는 로직 수행함 - 2단계 요구사항 해결 * feat: RequestUtils 삭제, RequestPath에 대한 테스트 구현 - RequestPath의 생성, 예외, pathParams에 대한 테스트 구현 - RequestUtils는 RequestPath와 HttpRequest로 대체되어 삭제 - RequestPath의 parameters라는 필드변수를 pathParameters로 이름 변경하여 의미를 명확히 함 * feat: Parameters에 대한 연산을 ParameterMapper에 담고, 이를 상속하여 RequestPath를 구현하도록 리팩토링 - 추상 클래스 ParameterMapper를 만들어, Parameter HashMap, Getter, validator 등을 담도록 구현하였음. 또한 Parameter를 쪼개는 메인 로직을 옮겨, 추후 RequestBody 구현 시 재활용하도록 함 - RequestPath가 ParameterMapper를 상속하도록 구조 변경 * feat: ParameterMapper에 대한 테스트 코드 구현, RequestBody 구현 - RequestBody 구현. ParameterMapper의 로직을 그대로 쓰고, 별도의 validator만 사용함 - ParameterMapper에 대한 테스트 코드 구현. ParameterMapper가 추상 클래스이므로 테스트 클래스 내에 inner class를 만들어 테스트에 활용하였음. 다양한 예외 경우에 대해 테스트 수행함 * feat: URI 처리를 위한 URIUtils 구현, TEST 작성 * feat: 회원가입을 POST로 변경, body가 없는 경우 대응 - 회원가입을 수행하는 form.html의 form method를 POST로 변경하였음 - HttpRequest에 RequestBody를 넣어, POST로 온 요청이 있을 경우 body에 있는 값을 받도록 함 - 만약 body가 빈 request가 올 경우, RequestBody는 빈 생성자를 호출함. 이는 오류로 인해 빈 값을 호출하는 것과 분리됨 * feat: 회원가입 완료시 302를 response로 반환, index로 리다이렉트 구현 - 기존 RequestBody에 관련된 오류 수정, IOUtils를 사용하여 기존 로직 대체 - 회원가입 완료 시, 로그를 출력한 뒤 response의 메서드를 302로 반환, Location 값을 index로 부여 * refactor: HttpRequest의 헤더 생성 로직 수정 - HttpRequest에서 헤더의 값이 올바르지 않아도 수행되는 오류 발견, 테스트와 함께 수정 * docs: README.md 작성, 현재 진행해야 할 사항 todo로 정리 * feat: 파일의 경로를 정리해서 구하기 위해 FileType Enum 클래스 구현 - URIUtils에서 파일의 경로를 구할 때, 그 파일이 static인지 template인지 구별하는 로직 필요. 이를 enum 클래스로 구현 * style: 코드 전체의 인덴트와 import 정리, 불필요한 log 코드 삭제
- Loading branch information
Showing
21 changed files
with
714 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
# 웹 애플리케이션 서버 | ||
## 진행 방법 | ||
* 웹 애플리케이션 서버 요구사항을 파악한다. | ||
* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다. | ||
* 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다. | ||
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다. | ||
## 요구사항 | ||
- [x] http://localhost:8080/index.html 로 접속했을 때 webapp 디렉토리의 index.html 파일을 읽어 클라이언트에 응답한다. | ||
- [x] “회원가입” 메뉴를 클릭하면 http://localhost:8080/user/form.html 으로 이동하면서 회원가입할 수 있다. | ||
- [x] http://localhost:8080/user/form.html 파일의 form 태그 method를 get에서 post로 수정한 후 회원가입 기능이 정상적으로 동작하도록 구현한다. | ||
- [x] “회원가입”을 완료하면 /index.html 페이지로 이동하고 싶다. 현재는 URL이 /user/create 로 유지되는 상태로 읽어서 전달할 파일이 없다. 따라서 redirect 방식처럼 회원가입을 완료한 후 “index.html”로 이동해야 한다. | ||
- [x] 지금까지 구현한 소스 코드는 stylesheet 파일을 지원하지 못하고 있다. Stylesheet 파일을 지원하도록 구현하도록 한다. | ||
|
||
## 우아한테크코스 코드리뷰 | ||
* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package exception; | ||
|
||
public class InvalidHttpRequestException extends RuntimeException { | ||
private static final String INVALID_FILE_PATH_MASSAGE = "Http Request의 값이 올바르지 않습니다"; | ||
|
||
public InvalidHttpRequestException() { | ||
this(INVALID_FILE_PATH_MASSAGE); | ||
} | ||
|
||
public InvalidHttpRequestException(String message) { | ||
super(message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package exception; | ||
|
||
public class InvalidRequestBodyException extends RuntimeException { | ||
private static final String INVALID_PATH_MESSAGE = "Request Body의 값이 올바르지 않습니다"; | ||
|
||
public InvalidRequestBodyException() { | ||
this(INVALID_PATH_MESSAGE); | ||
} | ||
|
||
public InvalidRequestBodyException(String message) { | ||
super(message); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/exception/InvalidRequestParamsException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package exception; | ||
|
||
public class InvalidRequestParamsException extends RuntimeException { | ||
private static final String INVALID_PARAMS_MESSAGE = "Request에 포함된 인자의 값이 올바르지 않습니다"; | ||
public InvalidRequestParamsException() { | ||
this(INVALID_PARAMS_MESSAGE); | ||
} | ||
|
||
public InvalidRequestParamsException(String message) { | ||
super(message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package exception; | ||
|
||
public class InvalidRequestPathException extends RuntimeException { | ||
private static final String INVALID_PATH_MESSAGE = "Request Path의 값이 올바르지 않습니다"; | ||
|
||
public InvalidRequestPathException() { | ||
this(INVALID_PATH_MESSAGE); | ||
} | ||
|
||
public InvalidRequestPathException(String message) { | ||
super(message); | ||
} | ||
} |
13 changes: 13 additions & 0 deletions
13
src/main/java/exception/RequestParameterNotFoundException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package exception; | ||
|
||
public class RequestParameterNotFoundException extends RuntimeException { | ||
private static final String NOT_FOUND_PARAMS_MESSAGE = "해당 값을 인자 목록에서 찾을 수 없습니다"; | ||
|
||
public RequestParameterNotFoundException() { | ||
super(NOT_FOUND_PARAMS_MESSAGE); | ||
} | ||
|
||
public RequestParameterNotFoundException(String key) { | ||
super(key + " : " + NOT_FOUND_PARAMS_MESSAGE); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package utils; | ||
|
||
import exception.InvalidRequestPathException; | ||
import web.FileType; | ||
|
||
import java.util.Objects; | ||
|
||
public class URIUtils { | ||
|
||
/** | ||
* getFilePathInRequest는 전달받은 파일 경로가 유효한지 확인하고, 파일 경로를 생성하여 반환한다. | ||
* | ||
* @param path Request에 포함되어 전달되는 파일 경로 값으로, String 객체가 전달된다. | ||
* @return 파일의 경로가 String 객체로 반환된다. | ||
* @throws InvalidRequestPathException 유효하지 않은 경로가 전달될 시 발생한다. | ||
*/ | ||
public static String getFilePath(String path) { | ||
try { | ||
Objects.requireNonNull(path); | ||
String resourcePath = FileType.findFileType(path).getResourcePath(); | ||
Objects.requireNonNull(resourcePath); | ||
|
||
if (path.isEmpty() || resourcePath.isEmpty()) { | ||
throw new ArrayIndexOutOfBoundsException(); | ||
} | ||
|
||
return resourcePath + path; | ||
} catch (ArrayIndexOutOfBoundsException | NullPointerException e) { | ||
throw new InvalidRequestPathException(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package web; | ||
|
||
import exception.InvalidRequestPathException; | ||
|
||
import java.util.Arrays; | ||
|
||
public enum FileType { | ||
HTML(".html", "./templates", "text/html"), | ||
ICO(".ico", "./templates", "image/vnd.microsoft.icon"), | ||
CSS(".css", "./static", "text/css"), | ||
JS(".js", "./static", "text/javascript"), | ||
WOFF(".woff", "./static", "text/woff"), | ||
PNG(".png", "./static", "image/png"), | ||
JPEG(".jpeg", "./static", "image/jpeg"), | ||
SVG(".svg", "./static", "image/svg_xml"); | ||
|
||
private final String fileType; | ||
private final String resourcePath; | ||
private final String contentType; | ||
|
||
FileType(String fileType, String resourcePath, String contentType) { | ||
this.fileType = fileType; | ||
this.resourcePath = resourcePath; | ||
this.contentType = contentType; | ||
} | ||
|
||
public static FileType findFileType(String path) { | ||
return Arrays.stream(FileType.values()) | ||
.filter(fileType -> path.endsWith(fileType.getFileType())) | ||
.findAny() | ||
.orElseThrow(InvalidRequestPathException::new); | ||
} | ||
|
||
public String getFileType() { | ||
return fileType; | ||
} | ||
|
||
public String getResourcePath() { | ||
return resourcePath; | ||
} | ||
|
||
public String getContentType() { | ||
return contentType; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package web.request; | ||
|
||
import exception.InvalidHttpRequestException; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import utils.IOUtils; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
public class HttpRequest { | ||
private static final Logger logger = LoggerFactory.getLogger(HttpRequest.class); | ||
|
||
private final String method; | ||
private final RequestPath requestPath; | ||
private final String version; | ||
private final Map<String, String> requestHeader; | ||
private final RequestBody requestBody; | ||
|
||
public HttpRequest(BufferedReader request) { | ||
try { | ||
String requestHeaderFirstLine = request.readLine(); | ||
logger.debug(requestHeaderFirstLine); | ||
String[] tokens = requestHeaderFirstLine.split(" "); | ||
method = tokens[0].trim(); | ||
requestPath = new RequestPath(tokens[1].trim()); | ||
version = tokens[2].trim(); | ||
|
||
requestHeader = mappingHeaders(request); | ||
requestBody = mappingBodies(request); | ||
} catch (IndexOutOfBoundsException | NullPointerException | IOException e) { | ||
throw new InvalidHttpRequestException(); | ||
} | ||
} | ||
|
||
private Map<String, String> mappingHeaders(BufferedReader request) throws IOException { | ||
Map<String, String> headers = new HashMap<>(); | ||
|
||
String line = request.readLine(); | ||
while (!Objects.isNull(line) && !line.isEmpty()) { | ||
logger.debug(line); | ||
String[] splitLine = line.split(":"); | ||
String key = splitLine[0].trim(); | ||
String value = splitLine[1].trim(); | ||
|
||
headers.put(key, value); | ||
line = request.readLine(); | ||
} | ||
return headers; | ||
} | ||
|
||
private RequestBody mappingBodies(BufferedReader request) throws IOException { | ||
if (!method.equals("POST")) { | ||
return new RequestBody(); | ||
} | ||
int contentLength = Integer.parseInt(requestHeader.get("Content-Length")); | ||
String requestBodyData = IOUtils.readData(request, contentLength); | ||
if (Objects.isNull(requestBodyData) || requestBodyData.isEmpty()) { | ||
return new RequestBody(); | ||
} | ||
return new RequestBody(requestBodyData); | ||
} | ||
|
||
public String getMethod() { | ||
return method; | ||
} | ||
|
||
public RequestPath getRequestPath() { | ||
return requestPath; | ||
} | ||
|
||
public String getVersion() { | ||
return version; | ||
} | ||
|
||
public RequestBody getRequestBody() { | ||
return requestBody; | ||
} | ||
|
||
public String getContentType() { | ||
String acceptInfo = requestHeader.get("Accept"); | ||
|
||
return acceptInfo.split(",")[0]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package web.request; | ||
|
||
import exception.InvalidRequestParamsException; | ||
import exception.RequestParameterNotFoundException; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
public abstract class ParameterMapper { | ||
private final Map<String, String> parameters; | ||
|
||
public ParameterMapper() { | ||
this.parameters = new HashMap<>(); | ||
} | ||
|
||
protected void mappingParameters(String params) { | ||
validateParameters(params); | ||
for (String parameter : params.split("&")) { | ||
validateParameter(parameter); | ||
String[] tokens = parameter.split("="); | ||
String key = tokens[0]; | ||
String value = tokens[1]; | ||
|
||
parameters.put(key, value); | ||
} | ||
} | ||
|
||
private void validateParameters(String params) { | ||
if (Objects.isNull(params) || params.isEmpty() || !params.contains("=")) { | ||
throw new InvalidRequestParamsException(); | ||
} | ||
} | ||
|
||
private void validateParameter(String parameter) { | ||
if (parameter.isEmpty() || !parameter.contains("=") || parameter.equals("=")) { | ||
throw new InvalidRequestParamsException(); | ||
} | ||
try { | ||
String key = parameter.split("=")[0]; | ||
String value = parameter.split("=")[1]; | ||
Objects.requireNonNull(key); | ||
Objects.requireNonNull(value); | ||
if (key.isEmpty() || value.isEmpty()) { | ||
throw new InvalidRequestParamsException(); | ||
} | ||
} catch (NullPointerException | ArrayIndexOutOfBoundsException e) { | ||
throw new InvalidRequestParamsException(); | ||
} | ||
} | ||
|
||
public Map<String, String> getParameters() { | ||
return parameters; | ||
} | ||
|
||
public String getParameterByKey(String key) { | ||
if (Objects.isNull(key) || key.isEmpty()) { | ||
throw new RequestParameterNotFoundException(); | ||
} | ||
if (!parameters.containsKey(key)) { | ||
throw new RequestParameterNotFoundException(key); | ||
} | ||
return parameters.get(key); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package web.request; | ||
|
||
import exception.InvalidRequestBodyException; | ||
|
||
import java.util.Objects; | ||
|
||
public class RequestBody extends ParameterMapper { | ||
public RequestBody() { | ||
super(); | ||
} | ||
|
||
public RequestBody(String requestBody) { | ||
validateBody(requestBody); | ||
mappingParameters(requestBody); | ||
} | ||
|
||
private void validateBody(String requestBody) { | ||
if (Objects.isNull(requestBody) || requestBody.isEmpty()) { | ||
throw new InvalidRequestBodyException(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package web.request; | ||
|
||
import exception.InvalidRequestPathException; | ||
|
||
import java.util.Objects; | ||
|
||
public class RequestPath extends ParameterMapper { | ||
private final String target; | ||
|
||
public RequestPath(String path) { | ||
validatePath(path); | ||
target = path.split("\\?")[0]; | ||
if (path.contains("?")) { | ||
mappingParameters(path.split("\\?")[1]); | ||
} | ||
} | ||
|
||
private void validatePath(String path) { | ||
if (Objects.isNull(path) || path.isEmpty()) { | ||
throw new InvalidRequestPathException(); | ||
} | ||
} | ||
|
||
public String getTarget() { | ||
return target; | ||
} | ||
} |
Oops, something went wrong.