-
Notifications
You must be signed in to change notification settings - Fork 90
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
[엘리] WAS 1단계 미션 제출합니다. #96
Changes from all commits
7a202d3
02f2a27
fb079ac
ef5ef6a
545ff6b
7a5cb5a
6d314e9
bac63db
3e2fb39
6458065
7c53076
ab3de5a
449c0e4
e7797e3
0994a26
39f1b3a
d55dc5e
c39700e
7beea60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,25 @@ | ||
# 웹 애플리케이션 서버 | ||
## 진행 방법 | ||
* 웹 애플리케이션 서버 요구사항을 파악한다. | ||
* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다. | ||
* 코드 리뷰 피드백에 대한 개선 작업을 하고 다시 PUSH한다. | ||
* 모든 피드백을 완료하면 다음 단계를 도전하고 앞의 과정을 반복한다. | ||
|
||
## 우아한테크코스 코드리뷰 | ||
* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) | ||
|
||
## 기능요구사항 | ||
### 1단계 | ||
#### 요구사항 1 | ||
- [x] http://localhost:8080/index.html 로 접속했을 때 webapp 디렉토리의 index.html 파일을 읽어 클라이언트에 응답한다. | ||
|
||
#### 요구사항 2 | ||
- [x] “회원가입” 메뉴를 클릭하면 http://localhost:8080/user/form.html 으로 이동하면서 회원가입할 수 있다. | ||
|
||
회원가입을 하면 다음과 같은 형태로 사용자가 입력한 값이 서버에 전달된다. | ||
|
||
```http request | ||
/create?userId=javajigi&password=password&name=%EB%B0%95%EC%9E%AC%EC%84%B1&email=javajigi%40slipp.net | ||
``` | ||
- [x] HTML과 URL을 비교해 보고 사용자가 입력한 값을 파싱해 model.User 클래스에 저장한다. | ||
|
||
#### 요구사항 3 | ||
- [x] http://localhost:8080/user/form.html 파일의 form 태그 method를 get에서 post로 수정한 후 회원가입 기능이 정상적으로 동작하도록 구현한다. | ||
|
||
#### 요구사항 4 | ||
- [x] “회원가입”을 완료하면 /index.html 페이지로 이동하고 싶다. 현재는 URL이 /user/create 로 유지되는 상태로 읽어서 전달할 파일이 없다. 따라서 redirect 방식처럼 회원가입을 완료한 후 “index.html”로 이동해야 한다. 즉, 브라우저의 URL이 /index.html로 변경해야 한다. | ||
|
||
#### 요구사항 5 | ||
- [x] 지금까지 구현한 소스 코드는 stylesheet 파일을 지원하지 못하고 있다. Stylesheet 파일을 지원하도록 구현하도록 한다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package web; | ||
|
||
import web.servlet.Servlet; | ||
import web.servlet.UserCreateServlet; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class HandlerMapping { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 클래스 설계 👍 |
||
private static final Map<RequestMapping, Servlet> handlerMapping = new HashMap<>(); | ||
|
||
static { | ||
handlerMapping.put(new RequestMapping("/user/create", HttpMethod.POST), new UserCreateServlet()); | ||
} | ||
|
||
public static Servlet find(HttpRequest httpRequest) { | ||
RequestMapping key = handlerMapping.keySet().stream() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그런 방법도 있군요! |
||
.filter(mapping -> mapping.match(httpRequest)) | ||
.findFirst() | ||
.orElseThrow(() -> new IllegalArgumentException(String.format("요청(%s: %s)을 처리할 수 없습니다.", httpRequest.getMethod(), httpRequest.getRequestPath()))); | ||
return handlerMapping.get(key); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package web; | ||
|
||
public enum HttpMethod { | ||
GET, | ||
POST; | ||
|
||
public boolean isGet() { | ||
return this == GET; | ||
} | ||
|
||
public boolean isPost() { | ||
return this == POST; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package web; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
|
||
public class HttpRequest { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 책임 분리 💯 |
||
private RequestUri requestUri; | ||
private RequestHeader requestHeader; | ||
private RequestBody requestBody; | ||
|
||
public HttpRequest(final BufferedReader bufferedReader) throws IOException { | ||
this.requestUri = new RequestUri(bufferedReader); | ||
this.requestHeader = new RequestHeader(bufferedReader); | ||
if (HttpMethod.POST == getMethod()) { | ||
this.requestBody = new RequestBody(bufferedReader, requestHeader.getContentLength()); | ||
} | ||
Comment on lines
+12
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 각 클래스로
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그렇군요! 테스트할 때마다 매번 BufferedReader를 만드는 과정이 번거로웠는데, 알려주셔서 감사합니다! |
||
} | ||
|
||
public String getRequestPath() { | ||
return requestUri.getPath().getRequestPath(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 디미터 법칙을 위반하고 있었네요! |
||
} | ||
|
||
public String getParam(final String key) { | ||
return requestUri.getParam(key); | ||
} | ||
|
||
public int getContentLength() { | ||
return requestHeader.getContentLength(); | ||
} | ||
|
||
public RequestBody getRequestBody() { | ||
return requestBody; | ||
} | ||
|
||
public HttpMethod getMethod() { | ||
return requestUri.getMethod(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package web; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.io.DataOutputStream; | ||
import java.io.IOException; | ||
|
||
public class HttpResponse { | ||
|
||
private static final Logger logger = LoggerFactory.getLogger(HttpResponse.class); | ||
|
||
private DataOutputStream dataOutputStream; | ||
|
||
public HttpResponse(DataOutputStream dataOutputStream) { | ||
this.dataOutputStream = dataOutputStream; | ||
} | ||
|
||
public void response200Header(int lengthOfBodyContent) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 중복 코드를 제거할 수 있지 않을까요? |
||
try { | ||
this.dataOutputStream.writeBytes("HTTP/1.1 200 OK \r\n"); | ||
this.dataOutputStream.writeBytes("Content-Type: text/html;charset=utf-8\r\n"); | ||
this.dataOutputStream.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); | ||
this.dataOutputStream.writeBytes("\r\n"); | ||
} catch (IOException e) { | ||
logger.error(e.getMessage()); | ||
} | ||
} | ||
|
||
public void response200Header(int lengthOfBodyContent, String contentType) { | ||
try { | ||
this.dataOutputStream.writeBytes("HTTP/1.1 200 OK \r\n"); | ||
this.dataOutputStream.writeBytes("Content-Type: " + contentType + "\r\n"); | ||
this.dataOutputStream.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); | ||
this.dataOutputStream.writeBytes("\r\n"); | ||
} catch (IOException e) { | ||
logger.error(e.getMessage()); | ||
} | ||
} | ||
|
||
public void response302Header(String url) { | ||
try { | ||
this.dataOutputStream.writeBytes("HTTP/1.1 302 Found \r\n"); | ||
this.dataOutputStream.writeBytes("Location: " + url + "\r\n"); | ||
this.dataOutputStream.writeBytes("\r\n"); | ||
} catch (IOException e) { | ||
logger.error(e.getMessage()); | ||
} | ||
} | ||
|
||
public void responseBody(byte[] body) { | ||
try { | ||
this.dataOutputStream.write(body, 0, body.length); | ||
this.dataOutputStream.flush(); | ||
} catch (IOException e) { | ||
logger.error(e.getMessage()); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package web; | ||
|
||
import utils.IOUtils; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class RequestBody { | ||
|
||
private String body; | ||
|
||
public RequestBody(BufferedReader bufferedReader, int contentLength) throws IOException { | ||
if (contentLength == 0) { | ||
return; | ||
} | ||
this.body = IOUtils.readData(bufferedReader, contentLength); | ||
} | ||
|
||
public String getBody() { | ||
return body; | ||
} | ||
|
||
public Map<String, String> parse() { | ||
String[] splitBody = body.split("&"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "&" 를 상수로 선언해 의도가 명확히 드러나도록 바꿔보면 어떨까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
Map<String, String> bodies = new HashMap<>(); | ||
for (String body : splitBody) { | ||
String[] keyValue = body.split("="); | ||
bodies.put(keyValue[0], keyValue[1]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 클래스로 분리해 적용해보았습니다 🙂 |
||
} | ||
return bodies; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package web; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class RequestHeader { | ||
private final Map<String, String> headers = new HashMap<>(); | ||
|
||
public RequestHeader(BufferedReader bufferedReader) throws IOException { | ||
String line = bufferedReader.readLine(); | ||
while (!isEmpty(line)) { | ||
String[] tokens = line.split(": "); | ||
headers.put(tokens[0], tokens[1]); | ||
line = bufferedReader.readLine(); | ||
} | ||
} | ||
|
||
private boolean isEmpty(String line) { | ||
return line == null || "".equals(line); | ||
} | ||
|
||
public Map<String, String> getHeaders() { | ||
return headers; | ||
} | ||
|
||
public int getContentLength() { | ||
String contentLength = headers.get("Content-Length"); | ||
return Integer.parseInt(contentLength); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package web; | ||
|
||
public class RequestMapping { | ||
private final String uri; | ||
private final HttpMethod httpMethod; | ||
|
||
public RequestMapping(final String uri, final HttpMethod httpMethod) { | ||
this.uri = uri; | ||
this.httpMethod = httpMethod; | ||
} | ||
|
||
public boolean match(HttpRequest httpRequest) { | ||
return this.uri.equals(httpRequest.getRequestPath()) && this.httpMethod == httpRequest.getMethod(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package web; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class RequestPath { | ||
private final String fullPath; | ||
private final String requestPath; | ||
private final Map<String, String> requestParameters = new HashMap<>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파라미터와 관련된 일급 컬렉션을 만들어 관리해보면 어떨까요? |
||
|
||
public RequestPath(String requestPath) { | ||
this.fullPath = requestPath; | ||
String[] splitPath = requestPath.split("\\?"); | ||
this.requestPath = splitPath[0]; | ||
if (hasNoParameters()) { | ||
return; | ||
} | ||
String parameters = splitPath[1]; | ||
String[] splitParameters = parameters.split("&"); | ||
for (String parameter : splitParameters) { | ||
String[] key = parameter.split("="); | ||
requestParameters.put(key[0], key[1]); | ||
} | ||
} | ||
|
||
private boolean hasNoParameters() { | ||
return this.fullPath.equals(this.requestPath); | ||
} | ||
|
||
public String getRequestPath() { | ||
return this.requestPath; | ||
} | ||
|
||
public String getRequestParameter(String key) { | ||
return requestParameters.get(key); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package web; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
|
||
public class RequestUri { | ||
private HttpMethod method; | ||
private RequestPath path; | ||
private String protocol; | ||
|
||
public RequestUri(BufferedReader bufferedReader) throws IOException { | ||
String uri = bufferedReader.readLine(); | ||
if (uri.isEmpty()) { | ||
return; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. exception을 던지도록 했습니다! |
||
} | ||
String[] splitUri = uri.split(" "); | ||
this.method = HttpMethod.valueOf(splitUri[0]); | ||
this.path = new RequestPath(splitUri[1]); | ||
this.protocol = splitUri[2]; | ||
} | ||
|
||
public String getParam(final String key) { | ||
return this.path.getRequestParameter(key); | ||
} | ||
|
||
public HttpMethod getMethod() { | ||
return method; | ||
} | ||
|
||
public RequestPath getPath() { | ||
return path; | ||
} | ||
|
||
public String getProtocol() { | ||
return protocol; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package web.filter; | ||
|
||
import web.HttpRequest; | ||
import web.HttpResponse; | ||
|
||
import java.io.IOException; | ||
|
||
public interface Filter { | ||
|
||
void doFilter(HttpRequest httpRequest, HttpResponse httpResponse, FilterChain filterChain) throws IOException; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package web.filter; | ||
|
||
import web.HttpRequest; | ||
import web.HttpResponse; | ||
import web.servlet.DispatcherServlet; | ||
import web.servlet.Servlet; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
public class FilterChain { | ||
|
||
private static final List<Filter> filters = new ArrayList<>(); | ||
private static final Servlet servlet = new DispatcherServlet(); | ||
|
||
static { | ||
filters.add(new ResourceFilter()); | ||
} | ||
|
||
private int filterIndex = 0; | ||
|
||
public void doFilter(HttpRequest httpRequest, HttpResponse httpResponse) throws IOException { | ||
if (filterIndex < filters.size()) { | ||
Filter filter = filters.get(filterIndex); | ||
filterIndex++; | ||
filter.doFilter(httpRequest, httpResponse, this); | ||
return; | ||
} | ||
servlet.doService(httpRequest, httpResponse); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RuntimeException
보다IllegalArgumentException
과 같은 구체적인 예외 클래스를 활용해보면 어떨까요?예외 메시지와 함께 구체적인 예외 클래스를 통해 발생한 원인을 좀 더 빨리 파악할 수 있게 해줄 수 있을 것 같습니다!