-
Notifications
You must be signed in to change notification settings - Fork 385
[톰캣 구현하기 - 1단계] 기론(김규철) 미션 제출합니다. #174
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
Changes from all commits
eee6a89
a20bff1
ec5567d
d146e68
64c0756
a9b4db9
c3f6b1b
ebbb073
0db723b
9ce2a01
bcc60c6
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 +1,7 @@ | ||
| # 톰캣 구현하기 | ||
|
|
||
| ## 기능 요구 사항 | ||
|
|
||
| - [x] http://localhost:8080/index.html 페이지에 접근 가능하다. | ||
| - [x] 접근한 페이지의 js, css 파일을 불러올 수 있다. | ||
| - [x] uri의 QueryString을 파싱하는 기능이 있다. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package org.apache.coyote.http11; | ||
|
|
||
| import java.util.Arrays; | ||
|
|
||
| public enum Extension { | ||
| HTML("html", "text/html"), | ||
| CSS("css", "text/css"), | ||
| JS("js", "text/js"), | ||
| CSV("svg", "image/svg+xml"), | ||
| ICO("ico", "image/x-icon"), | ||
| ; | ||
| private final String name; | ||
| private final String contentType; | ||
|
|
||
| Extension(String name, String contentType) { | ||
| this.name = name; | ||
| this.contentType = contentType; | ||
| } | ||
|
|
||
| public static String convertToContentType(String url) { | ||
| int index = url.indexOf("."); | ||
| if (index == -1) { | ||
| return HTML.contentType; | ||
| } | ||
| Extension extension1 = Arrays.stream(values()) | ||
| .filter(extension -> url.endsWith(extension.name)) | ||
| .findFirst() | ||
| .orElseThrow(() -> new IllegalArgumentException("알맞은 확장자가 없습니다.")); | ||
| return extension1.contentType; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,25 @@ | ||
| package org.apache.coyote.http11; | ||
|
|
||
| import java.io.BufferedReader; | ||
| import java.io.File; | ||
| import java.io.IOException; | ||
| import java.io.InputStreamReader; | ||
| import java.net.Socket; | ||
| import java.net.URISyntaxException; | ||
| import java.net.URL; | ||
| import java.nio.file.Files; | ||
| import java.util.Objects; | ||
| import nextstep.jwp.exception.UncheckedServletException; | ||
| import org.apache.coyote.Processor; | ||
| import org.apache.coyote.http11.url.HandlerMapping; | ||
| import org.apache.coyote.http11.url.Url; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.io.IOException; | ||
| import java.net.Socket; | ||
|
|
||
| public class Http11Processor implements Runnable, Processor { | ||
|
|
||
| private static final Logger log = LoggerFactory.getLogger(Http11Processor.class); | ||
| private static final String STATIC_DIRECTORY = "static/"; | ||
|
|
||
| private final Socket connection; | ||
|
|
||
|
|
@@ -26,21 +35,55 @@ public void run() { | |
| @Override | ||
| public void process(final Socket connection) { | ||
| try (final var inputStream = connection.getInputStream(); | ||
| final var outputStream = connection.getOutputStream()) { | ||
| final var outputStream = connection.getOutputStream(); | ||
| InputStreamReader inputStreamReader = new InputStreamReader(inputStream); | ||
| BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { | ||
|
Comment on lines
+39
to
+40
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. 놓칠 수 있는 부분인데 Reader도 try with resources로 처리해준 것 좋네요👍🏻 |
||
|
|
||
| final var responseBody = "Hello world!"; | ||
| String uri = getUri(bufferedReader); | ||
| String contentType = Extension.convertToContentType(uri); | ||
| String responseBody = getResponseBody(uri); | ||
|
|
||
| final var response = String.join("\r\n", | ||
| "HTTP/1.1 200 OK ", | ||
| "Content-Type: text/html;charset=utf-8 ", | ||
| "Content-Length: " + responseBody.getBytes().length + " ", | ||
| "", | ||
| responseBody); | ||
| final var response = createResponse(contentType, responseBody); | ||
|
|
||
| outputStream.write(response.getBytes()); | ||
| outputStream.flush(); | ||
| } catch (IOException | UncheckedServletException e) { | ||
| } catch (IOException | UncheckedServletException | URISyntaxException e) { | ||
| log.error(e.getMessage(), e); | ||
| } | ||
| } | ||
|
|
||
| private String getUri(final BufferedReader bufferedReader) throws IOException { | ||
| return bufferedReader.readLine() | ||
| .split(" ")[1] | ||
| .substring(1); | ||
| } | ||
|
|
||
| private String getResponseBody(final String uri) throws IOException, URISyntaxException { | ||
|
|
||
|
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. 의도하신 개행인가요?? |
||
| if (uri.isEmpty()) { | ||
| return "Hello world!"; | ||
| } | ||
| Url url = HandlerMapping.from(uri); | ||
| URL resource = this.getClass() | ||
| .getClassLoader() | ||
| .getResource(STATIC_DIRECTORY + url.getRequest().getPath()); | ||
|
Comment on lines
+66
to
+69
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. 오 아주 적절한 추상화인 것 같네요! 한 수 배워갑니다.👍🏻
Author
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. 앗 해당 부분이 getRequest내부에서 path경로를 수정한 후, path를 반환하는 것이라 하나로 합치기에 어렵더라구요.. 아예 http11Request에서 분리를 해야할 것 같은데 2단계 수정하면서 반영해보겠습니다! |
||
|
|
||
| validatePath(resource); | ||
| return new String(Files.readAllBytes(new File(resource.getFile()).toPath())); | ||
| } | ||
|
|
||
| private void validatePath(URL resource) { | ||
| if (Objects.isNull(resource)) { | ||
| throw new IllegalArgumentException("경로가 잘못 되었습니다. : null"); | ||
|
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 String createResponse(String contentType, String responseBody) { | ||
| return String.join("\r\n", | ||
| "HTTP/1.1 200 OK ", | ||
| "Content-Type: " + contentType + ";charset=utf-8 ", | ||
| "Content-Length: " + responseBody.getBytes().length + " ", | ||
| "", | ||
| responseBody); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package org.apache.coyote.http11.dto; | ||
|
|
||
| public class Http11Request { | ||
|
|
||
| private final String account; | ||
| private final String password; | ||
| private final String path; | ||
|
|
||
| public Http11Request(String path) { | ||
| this(null, null, path); | ||
| } | ||
|
|
||
| public Http11Request(String account, String password, String path) { | ||
| this.account = account; | ||
| this.password = password; | ||
| this.path = path; | ||
| } | ||
|
|
||
| public String getAccount() { | ||
| return account; | ||
| } | ||
|
|
||
| public String getPath() { | ||
| return path; | ||
| } | ||
| } | ||
|
Comment on lines
+1
to
+26
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. 위 클래스에서 accout, password 변수는 사실 상 Login할 때 queryString 처리 용으로만 쓰고 있네요.
Author
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. 넵! 위에 남긴 것 처럼 분리가 필요할 것 같네요! |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package org.apache.coyote.http11.url; | ||
|
|
||
| import org.apache.coyote.http11.dto.Http11Request; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public class Empty extends Url { | ||
| private static final Logger log = LoggerFactory.getLogger(Empty.class); | ||
|
|
||
| public Empty(final String url) { | ||
| super(url); | ||
| } | ||
|
|
||
| @Override | ||
| public Http11Request getRequest() { | ||
| log.info("path : {} ", getPath()); | ||
| throw new IllegalArgumentException("경로가 비어있습니다."); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package org.apache.coyote.http11.url; | ||
|
|
||
| public class HandlerMapping { | ||
|
|
||
| public static Url from(String uri) { | ||
| if (isHomeUrl(uri)) { | ||
| return new HomePage(uri); | ||
| } | ||
| if (uri.startsWith("login")) { | ||
| return new Login(uri); | ||
| } | ||
|
Comment on lines
+9
to
+11
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.
Author
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 new Empty(uri); | ||
| } | ||
|
|
||
| private static boolean isHomeUrl(String uri) { | ||
| return uri.startsWith("index") || uri.endsWith("css") || uri.endsWith("csv") || uri.endsWith("js") | ||
| || uri.endsWith("ico"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package org.apache.coyote.http11.url; | ||
|
|
||
| import org.apache.coyote.http11.dto.Http11Request; | ||
|
|
||
| public class HomePage extends Url { | ||
|
|
||
| public HomePage(final String url) { | ||
| super(url); | ||
| } | ||
|
|
||
| @Override | ||
| public Http11Request getRequest() { | ||
| return new Http11Request(getPath()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package org.apache.coyote.http11.url; | ||
|
|
||
| import nextstep.jwp.db.InMemoryUserRepository; | ||
| import nextstep.jwp.model.User; | ||
| import org.apache.coyote.http11.dto.Http11Request; | ||
| import org.apache.coyote.http11.utils.StringParser; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| public class Login extends Url { | ||
| private static final Logger log = LoggerFactory.getLogger(Login.class); | ||
|
|
||
| public Login(final String url) { | ||
| super(url); | ||
| } | ||
|
|
||
| @Override | ||
| public Http11Request getRequest() { | ||
| Http11Request http11Request = StringParser.loginQuery(getPath()); | ||
|
|
||
| User user = InMemoryUserRepository.findByAccount(http11Request.getAccount()) | ||
| .orElseThrow(() -> new IllegalArgumentException("아이디 또는 비밀번호를 잘못 입력하였습니다.")); | ||
|
Comment on lines
+21
to
+22
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. 현재 이 로직이면 account는 올바른데 password가 틀린 경우에도 정상 동작하는 것으로 보입니다.
Author
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. 해당 부분이 다음 단계 적용으로 알고있어서 적용하다가 뺐었습니다! 다음 단계에서 적용해볼게요~! |
||
| log.info("user : {}", user); | ||
|
|
||
| return http11Request; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package org.apache.coyote.http11.url; | ||
|
|
||
| import org.apache.coyote.http11.dto.Http11Request; | ||
|
|
||
| public abstract class Url { | ||
| private final String path; | ||
|
|
||
| protected Url(String path) { | ||
| this.path = path; | ||
| } | ||
|
|
||
| public abstract Http11Request getRequest(); | ||
|
|
||
| public String getPath() { | ||
| return path; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package org.apache.coyote.http11.utils; | ||
|
|
||
| import org.apache.coyote.http11.dto.Http11Request; | ||
|
|
||
| public class StringParser { | ||
|
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. 클래스 책임을 보니까
Author
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 String PATH_STANDARD = "?"; | ||
| private static final String REQUEST_STANDARD = "&"; | ||
| private static final String DATA_STANDARD = "="; | ||
| private static final int VALUE_INDEX = 1; | ||
|
|
||
| public static Http11Request loginQuery(String uri) { | ||
| int point = uri.indexOf(PATH_STANDARD); | ||
| String path = uri.substring(0, point) + ".html"; | ||
|
|
||
| String queryRequest = uri.substring(point + 1); | ||
| if (queryRequest.isEmpty()) { | ||
| throw new IllegalArgumentException("요청으로 들어오는 값이 없습니다."); | ||
| } | ||
| String[] dataMap = queryRequest.split(REQUEST_STANDARD); | ||
| String account = dataMap[0].split(DATA_STANDARD)[VALUE_INDEX]; | ||
| String password = dataMap[1].split(DATA_STANDARD)[VALUE_INDEX]; | ||
| return new Http11Request(account, password, path); | ||
| } | ||
|
|
||
| } | ||

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.
오 Extension enum 멋있네요! 근데 클래스 네이밍을 ContentType으로 바꾸는게 좀 더 가독에 좋을 것 같기도??
추가로 convertToContentType()메서드에서 extension1이라는 변수명 바꾸면 더 좋을 것 같습니다👍🏻
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.
넵 Extension을 보고 ContentType으로 바꿔주는 클래스였는데 가독성이 오히려 떨어졌군요..! ContentType으로 클래스 네이밍 수정했습니다!