-
Notifications
You must be signed in to change notification settings - Fork 309
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
[톰캣 구현하기 - 1, 2단계] 푸우(백승준) 미션 제출합니다. #311
Changes from all commits
dca2d56
e563f31
781453a
6815d77
fe895a7
352e3ce
01777dc
b423dce
91a21d9
c99c922
9e766d4
0c0458e
f2ef5a9
33d8368
be73de7
055a841
f5d67b7
6d77acc
db76d4f
91d0958
b122950
e5553ba
d3cc7bc
d76ad73
c22ee77
b0ffc0c
c3c3853
9db4b00
14c3a37
22f500e
2bc7165
ffa5280
3131965
f961503
76ccbe5
0bac4df
0941449
7151c94
6221614
d695d9d
40cf300
29d5233
a3c0fae
70b48fd
d062222
e9130dd
79f36c6
d7b4556
c14371f
ac5e6ac
eece233
d258d7e
0c23a5e
2909da4
2aa6ba1
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,54 +1,59 @@ | ||
package study; | ||
|
||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.net.URISyntaxException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import java.util.stream.Collectors; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
|
||
/** | ||
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. | ||
* File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. | ||
* 웹서버는 사용자가 요청한 html 파일을 제공 할 수 있어야 한다. File 클래스를 사용해서 파일을 읽어오고, 사용자에게 전달한다. | ||
*/ | ||
@DisplayName("File 클래스 학습 테스트") | ||
class FileTest { | ||
|
||
/** | ||
* resource 디렉터리 경로 찾기 | ||
* | ||
* File 객체를 생성하려면 파일의 경로를 알아야 한다. | ||
* 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. | ||
* resource 디렉터리의 경로는 어떻게 알아낼 수 있을까? | ||
* <p> | ||
* File 객체를 생성하려면 파일의 경로를 알아야 한다. 자바 애플리케이션은 resource 디렉터리에 HTML, CSS 같은 정적 파일을 저장한다. resource 디렉터리의 경로는 어떻게 알아낼 수 | ||
* 있을까? | ||
*/ | ||
@Test | ||
void resource_디렉터리에_있는_파일의_경로를_찾는다() { | ||
void resource_디렉터리에_있는_파일의_경로를_찾는다() throws URISyntaxException { | ||
final String fileName = "nextstep.txt"; | ||
|
||
// todo | ||
final String actual = ""; | ||
File file = new File(getClass().getClassLoader().getResource("nextstep.txt").toURI()); | ||
final String actual = file.getName(); | ||
|
||
assertThat(actual).endsWith(fileName); | ||
} | ||
|
||
/** | ||
* 파일 내용 읽기 | ||
* | ||
* 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. | ||
* File, Files 클래스를 사용하여 파일의 내용을 읽어보자. | ||
* <p> | ||
* 읽어온 파일의 내용을 I/O Stream을 사용해서 사용자에게 전달 해야 한다. File, Files 클래스를 사용하여 파일의 내용을 읽어보자. | ||
*/ | ||
@Test | ||
void 파일의_내용을_읽는다() { | ||
void 파일의_내용을_읽는다() throws IOException { | ||
final String fileName = "nextstep.txt"; | ||
|
||
// todo | ||
final Path path = null; | ||
String file = getClass().getClassLoader().getResource(fileName).getFile(); | ||
Path path = Path.of(file); | ||
|
||
// todo | ||
final List<String> actual = Collections.emptyList(); | ||
BufferedReader bufferedReader = Files.newBufferedReader(path); | ||
final List<String> actual = bufferedReader.lines().collect(Collectors.toList()); | ||
|
||
// then | ||
assertThat(actual).containsOnly("nextstep"); | ||
} | ||
} |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
## Mission 1 : HTTP 서버 구현하기 | ||
|
||
1. [x] Get /index.html 응답하기 | ||
2. [x] CSS 지원하기 | ||
- [x] 정적 파일을 지원하는 ResourceProvider 를 생성한다. | ||
- [x] 해당 정적 파일을 읽어온다. | ||
- [x] 해당 정적 파일의 타입을 반환한다. | ||
3. [x] Query String 파싱 | ||
- [x] 먼저 정적 파일을 찾는다. | ||
- [x] 요청을 handle 할 수 있는 핸들러를 찾는다. | ||
- [x] 아무것도 못 찾았다면 not Found 페이지 반환. | ||
|
||
## Mission 2 : 로그인 구현하기 | ||
|
||
1. [x] HTTP Status Code 302 | ||
2. [x] POST 방식으로 회원가입 및 로그인 | ||
- [x] Request 를 생성한다 | ||
- [x] 요청을 해결할 수 있는 Contoller 를 Mapper 로 엮어둔다. | ||
3. [x] Cookie에 JSESSIONID 값 저장하기 | ||
4. [x] Session 구현하기 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,29 @@ | ||
package nextstep.jwp.db; | ||
|
||
import nextstep.jwp.model.User; | ||
|
||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import nextstep.jwp.model.User; | ||
|
||
public class InMemoryUserRepository { | ||
|
||
private static final Map<String, User> database = new ConcurrentHashMap<>(); | ||
private static Long publishedId = 1L; | ||
|
||
static { | ||
final User user = new User(1L, "gugu", "password", "[email protected]"); | ||
database.put(user.getAccount(), user); | ||
} | ||
|
||
public static void save(User user) { | ||
database.put(user.getAccount(), user); | ||
private InMemoryUserRepository() { | ||
} | ||
|
||
public static Optional<User> findByAccount(String account) { | ||
return Optional.ofNullable(database.get(account)); | ||
} | ||
|
||
private InMemoryUserRepository() {} | ||
public static void save(User user) { | ||
User saveUser = new User(++publishedId, user.getAccount(), user.getPassword(), user.getEmail()); | ||
database.put(saveUser.getAccount(), saveUser); | ||
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. 꼼꼼하게 auto increment까지 👍 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 |
---|---|---|
@@ -1,21 +1,28 @@ | ||
package org.apache.coyote.http11; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.io.InputStreamReader; | ||
import java.net.Socket; | ||
import nextstep.jwp.exception.UncheckedServletException; | ||
import org.apache.coyote.Processor; | ||
import org.apache.coyote.http11.controller.HandlerMapper; | ||
import org.apache.coyote.http11.request.HttpRequest; | ||
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 final Socket connection; | ||
private final ResourceProvider resourceProvider; | ||
private final HandlerMapper handlerMapper; | ||
|
||
public Http11Processor(final Socket connection) { | ||
this.connection = connection; | ||
this.resourceProvider = new ResourceProvider(); | ||
this.handlerMapper = new HandlerMapper(this.resourceProvider); | ||
} | ||
|
||
@Override | ||
|
@@ -26,22 +33,29 @@ public void run() { | |
|
||
@Override | ||
public void process(final Socket connection) { | ||
try (final var inputStream = connection.getInputStream(); | ||
final var outputStream = connection.getOutputStream()) { | ||
|
||
final var responseBody = "Hello world!"; | ||
|
||
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); | ||
|
||
try (final var inputReader = new BufferedReader(new InputStreamReader(connection.getInputStream())); | ||
final var outputStream = connection.getOutputStream()) { | ||
HttpRequest httpRequest = HttpRequest.makeRequest(inputReader); | ||
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. 감사합니다 ㅎㅎ |
||
String response = getResponse(httpRequest); | ||
outputStream.write(response.getBytes()); | ||
outputStream.flush(); | ||
} catch (IOException | UncheckedServletException e) { | ||
log.error(e.getMessage(), e); | ||
} | ||
} | ||
|
||
private String getResponse(HttpRequest httpRequest) { | ||
if (resourceProvider.haveResource(httpRequest.getRequestLine().getPath())) { | ||
return resourceProvider.staticResourceResponse(httpRequest.getRequestLine().getPath()); | ||
} | ||
if (handlerMapper.haveAvailableHandler(httpRequest)) { | ||
return handlerMapper.controllerResponse(httpRequest); | ||
} | ||
return String.join("\r\n", | ||
"HTTP/1.1 200 OK ", | ||
"Content-Type: text/html;charset=utf-8 ", | ||
"Content-Length: 12 ", | ||
"", | ||
"Hello world!"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package org.apache.coyote.http11; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.net.URL; | ||
import java.nio.file.Files; | ||
import java.util.Optional; | ||
|
||
public class ResourceProvider { | ||
|
||
public boolean haveResource(String resourcePath) { | ||
return findFileURL(resourcePath).isPresent(); | ||
} | ||
|
||
public String resourceBodyOf(String resourcePath) { | ||
try { | ||
Optional<URL> fileURL = findFileURL(resourcePath); | ||
if (fileURL.isPresent()) { | ||
URL url = fileURL.get(); | ||
return new String(Files.readAllBytes(new File(url.getFile()).toPath())); | ||
} | ||
throw new IllegalArgumentException("파일이 존재하지 않습니다."); | ||
} catch (IOException e) { | ||
throw new IllegalArgumentException("존재하지 않는 파일 입니다."); | ||
} | ||
} | ||
|
||
private Optional<URL> findFileURL(String resourcePath) { | ||
URL resourceURL = getClass().getClassLoader().getResource("static" + resourcePath); | ||
if (resourceURL == null) { | ||
return Optional.empty(); | ||
} | ||
if (new File(resourceURL.getFile()).isDirectory()) { | ||
return Optional.empty(); | ||
} | ||
return Optional.of(resourceURL); | ||
} | ||
|
||
public String contentTypeOf(String resourcePath) { | ||
File file = getFile(resourcePath); | ||
String fileName = file.getName(); | ||
if (fileName.endsWith(".js")) { | ||
return "Content-Type: text/javascript "; | ||
} | ||
if (fileName.endsWith(".css")) { | ||
return "Content-Type: text/css;charset=utf-8"; | ||
} | ||
if (fileName.endsWith(".html")) { | ||
return "Content-Type: text/html;charset=utf-8 "; | ||
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. text/css 도 해주는게 좋을 것 같네요!! css 파일 내에서 인코딩 순서를 다음과 결정한다하네요!! 링크 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 "Content-Type: text/plain"; | ||
} | ||
|
||
private File getFile(String resourcePath) { | ||
return new File( | ||
findFileURL(resourcePath).orElseThrow((() -> new IllegalArgumentException("파일이 존재하지 않습니다."))) | ||
.getFile()); | ||
} | ||
|
||
public String staticResourceResponse(String resourcePath) { | ||
String responseBody = resourceBodyOf(resourcePath); | ||
return String.join("\r\n", | ||
"HTTP/1.1 200 OK ", | ||
contentTypeOf(resourcePath), | ||
"Content-Length: " + responseBody.getBytes().length + " ", | ||
"", | ||
responseBody); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package org.apache.coyote.http11.controller; | ||
|
||
import org.apache.coyote.http11.request.HttpRequest; | ||
import org.apache.coyote.http11.response.HttpResponse; | ||
|
||
public interface Controller { | ||
|
||
HttpResponse<? extends Object> handle(HttpRequest httpRequest); | ||
} |
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.
상수 부분 위에서 정의했던 변수 그대로 사용하는게 좀더 안정적일 것 같아요! (취향인 것 같슴니당)
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.
참고:
. toURI()
대신에.getFile()
을 사용하면URISyntaxException
예외를 처리하지 않아도 되는 것 같습니다.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.
저도 원래 그걸 선호하는데... 급하게 하다보니.. 크흠 ㅋㅋ
오 대박이네요👍 ResourceProvider 에서도 toURI 를 사용해서 try catch 문이 있었는데 제거할 수 있었네요 ㅎㅎ 감사합니다!!