-
Notifications
You must be signed in to change notification settings - Fork 47
[Han, Jay] 웹서버 1단계 구현 #48
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
f4923c4
e6ad562
97f55a9
bffb09e
d7d51d4
c70def8
de6b63d
e7176bc
1f2ab3e
8e70aac
bbf6d71
8bae0f8
24956dc
d9db934
5c412a1
2637a42
63832f2
30dbdf3
52f53b7
53977ff
c3d7eb9
ae01631
d68a164
39e1b94
0600e24
113fa7a
885c742
9c8d051
5741c8b
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,11 +1,4 @@ | ||
# 웹 애플리케이션 서버 | ||
## 진행 방법 | ||
* 웹 애플리케이션 서버 요구사항을 파악한다. | ||
* 요구사항에 대한 구현을 완료한 후 자신의 github 아이디에 해당하는 브랜치에 Pull Request(이하 PR)를 통해 코드 리뷰 요청을 한다. | ||
* 다음 단계를 도전하고 앞의 과정을 반복한다. | ||
* 코드 리뷰가 완료되면 피드백에 대한 개선 작업을 한다. | ||
* 다음 단계 PR 보낼 때 이전 단계 피드백이 잘 반영되었는지 점검한다. | ||
|
||
## 온라인 코드 리뷰 과정 | ||
* [텍스트와 이미지로 살펴보는 코드스쿼드의 온라인 코드 리뷰 과정](https://github.com/code-squad/codesquad-docs/blob/master/codereview/README.md) | ||
* [동영상으로 살펴보는 코드스쿼드의 온라인 코드 리뷰 과정](https://youtu.be/a5c9ku-_fok) | ||
# 웹서버 구현 | ||
### [Ground Rule](https://github.com/beginin15/java-was/wiki/Ground-Rule) | ||
### [Han's README](https://github.com/beginin15/java-was/wiki/%5BHan%5D-README) | ||
### [Jay's README](https://github.com/beginin15/java-was/wiki/%5BJay%5D-README) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,8 @@ | |
|
||
import java.util.Collection; | ||
import java.util.Map; | ||
import java.util.NoSuchElementException; | ||
import java.util.Optional; | ||
|
||
import com.google.common.collect.Maps; | ||
|
||
|
@@ -10,12 +12,16 @@ | |
public class DataBase { | ||
private static Map<String, User> users = Maps.newHashMap(); | ||
|
||
public static int getSizeOfUsers() { | ||
return users.size(); | ||
} | ||
|
||
public static void addUser(User user) { | ||
users.put(user.getUserId(), user); | ||
} | ||
|
||
public static User findUserById(String userId) { | ||
return users.get(userId); | ||
public static User findUserById(String userId) throws NoSuchElementException{ | ||
return Optional.ofNullable(users.get(userId)).orElseThrow(NoSuchElementException::new); | ||
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. 저는 호출 하는 쪽에서 Optional로 받고 예외를 발생시킬 지 말지를 정하도록 하는 것이 좋다고 생각하는 편 입니다. |
||
} | ||
|
||
public static Collection<User> findAll() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package db; | ||
|
||
import com.google.common.collect.Maps; | ||
import model.User; | ||
|
||
import java.util.Collection; | ||
import java.util.Map; | ||
|
||
public class SessionDataBase { | ||
private static Map<String, User> sessions = Maps.newHashMap(); | ||
|
||
public static void addSession(String sessionId, User user) { | ||
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. 위 UserDataBase와 동일한 피드백을 드리고 싶군요 |
||
sessions.put(sessionId, user); | ||
} | ||
|
||
public static User getSessionedUser(String sessionId) { | ||
return sessions.get(sessionId); | ||
} | ||
|
||
public static boolean isSessionIdExist(String sessionId) { | ||
return sessions.containsKey(sessionId); | ||
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,6 @@ | ||
package http; | ||
|
||
public enum HttpMethod { | ||
GET, | ||
POST; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package http; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import util.HttpRequestUtils; | ||
import util.IOUtils; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.IOException; | ||
import java.util.Map; | ||
|
||
public class HttpRequest { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(HttpRequest.class); | ||
|
||
private HttpMethod httpMethod; | ||
private String path; | ||
private Map<String, String> header; | ||
private Map<String, String> parameter; | ||
|
||
public HttpRequest(BufferedReader br) { | ||
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. HttpRequest는 입력을 파싱하여 각각을 필드로 갖고 있는 클래스로 보이는데, BufferedReader를 생성자로 꼭 받을 필요가 있을까요? |
||
parseRequest(br); | ||
} | ||
|
||
public HttpMethod getMethod() { | ||
return httpMethod; | ||
} | ||
|
||
public String getPath() { | ||
return path; | ||
} | ||
|
||
public String getHeader(String key) { | ||
return header.get(key); | ||
} | ||
|
||
public String getParameter(String key) { | ||
return parameter.get(key); | ||
} | ||
|
||
private void parseRequest(BufferedReader br) { | ||
String requestLine; | ||
|
||
try { | ||
requestLine = br.readLine(); | ||
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. 해당 라인 또한 사용자의 요청 입력 방식 변경이 영향 미칠 라인입니다. |
||
httpMethod = HttpMethod.valueOf(HttpRequestUtils.getMethod(requestLine)); | ||
path = HttpRequestUtils.getURL(requestLine); | ||
changePathIfRoot(); | ||
header = HttpRequestUtils.extractHeader(br); | ||
if (httpMethod.equals(HttpMethod.POST)) { | ||
String body = IOUtils.readData(br, Integer.parseInt(getHeader("Content-Length"))); | ||
parameter = HttpRequestUtils.parseQueryString(body); | ||
} | ||
} catch (IOException e) { | ||
log.error("HttpRequest parse 과정 에러"); | ||
} | ||
} | ||
|
||
private void changePathIfRoot() { | ||
if (this.path.equals("/")) { | ||
path = "/index.html"; | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package http; | ||
|
||
|
||
import model.User; | ||
import util.HttpResponseUtils; | ||
|
||
import java.io.DataOutputStream; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class HttpResponse { | ||
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. 여러 리스폰스 코드(200, 302 등)에 맞춰 메소드를 만든 것으로 보입니다. |
||
|
||
private Map<String, String> header; | ||
private DataOutputStream dos; | ||
|
||
public HttpResponse() { | ||
header = new HashMap<>(); | ||
} | ||
|
||
public HttpResponse(DataOutputStream dos) { | ||
header = new HashMap<>(); | ||
this.dos = dos; | ||
} | ||
|
||
// public void addHeader(String key, String value) { | ||
// header.put(key, value); | ||
// } | ||
|
||
public void forward(String path) throws IOException { | ||
byte[] body; | ||
if (HttpResponseUtils.isFileExist(path)) { | ||
body = HttpResponseUtils.readFile(path); | ||
response200Header(body.length); | ||
responseBody(body); | ||
return; | ||
} | ||
body = HttpResponseUtils.notExistPage(); | ||
response404Header(body.length); | ||
responseBody(body); | ||
} | ||
|
||
public void forwardBody(String responseBody) { | ||
|
||
} | ||
|
||
public void response200Header(int lengthOfBodyContent) throws IOException { | ||
dos.writeBytes("HTTP/1.1 200 OK \r\n"); | ||
dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n"); | ||
dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); | ||
dos.writeBytes("\r\n"); | ||
} | ||
|
||
public void sendRedirect(String location) throws IOException { | ||
dos.writeBytes("HTTP/1.1 302 Found \r\n"); | ||
dos.writeBytes("Location: " + location + "\r\n"); | ||
dos.writeBytes("\r\n"); | ||
} | ||
|
||
public void sendRedirect(String location, String sessionId) throws IOException { | ||
dos.writeBytes("HTTP/1.1 302 Found \r\n"); | ||
dos.writeBytes("Location: " + location + "\r\n"); | ||
dos.writeBytes("Set-Cookie: JSESSIONID=" + sessionId + "; Path=/" + "\r\n"); | ||
dos.writeBytes("\r\n"); | ||
} | ||
|
||
public void readUserList(List<User> users) throws IOException { | ||
byte[] body = HttpResponseUtils.readFile("/user/list.html"); | ||
String userListHtml = HttpResponseUtils.getUserListHTML(body, users); | ||
response200Header(userListHtml.length()); | ||
responseBody(userListHtml.getBytes()); | ||
} | ||
|
||
public void readCss(String path) throws IOException { | ||
byte[] body = Files.readAllBytes(new File("./webapp" + path).toPath()); | ||
responseCssHeader(body.length); | ||
responseBody(body); | ||
} | ||
|
||
public String processHeaders() { | ||
StringBuilder sb = new StringBuilder(); | ||
header.entrySet().stream() | ||
.map(entry -> entry.getKey() + ": " + entry.getValue()) | ||
.forEach(header -> sb.append(header).append("\r\n")); | ||
|
||
sb.append("\r\n"); | ||
return sb.toString(); | ||
} | ||
|
||
private void responseCssHeader(int lengthOfBodyContent) throws IOException { | ||
dos.writeBytes("HTTP/1.1 200 OK \r\n"); | ||
dos.writeBytes("Content-Type: text/css;charset=utf-8\r\n"); | ||
dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); | ||
dos.writeBytes("\r\n"); | ||
} | ||
|
||
private void response401Header(int lengthOfBodyContent) throws IOException { | ||
dos.writeBytes("HTTP/1.1 401 Unauthorized \r\n"); | ||
dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n"); | ||
dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); | ||
dos.writeBytes("\r\n"); | ||
} | ||
|
||
private void responseBody(byte[] body) throws IOException { | ||
dos.write(body, 0, body.length); | ||
dos.flush(); | ||
} | ||
|
||
private void response404Header(int lengthOfBodyContent) throws IOException { | ||
dos.writeBytes("HTTP/1.1 404 Not Found \r\n"); | ||
dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n"); | ||
dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n"); | ||
dos.writeBytes("\r\n"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,11 @@ public User(String userId, String password, String name, String email) { | |
this.email = email; | ||
} | ||
|
||
public User(String userId, String password) { | ||
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. 생성자에서 예외처리가 필요해보입니다 |
||
this.userId = userId; | ||
this.password = password; | ||
} | ||
|
||
public String getUserId() { | ||
return userId; | ||
} | ||
|
@@ -29,6 +34,10 @@ public String getEmail() { | |
return email; | ||
} | ||
|
||
public boolean isSameUser(User loginedUser) { | ||
return this.password.equals(loginedUser.password); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "User [userId=" + userId + ", password=" + password + ", name=" + name + ", email=" + email + "]"; | ||
|
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.
User 파라미터가 null인 경우에 대한 처리가 없네요
호출하는 쪽에서 null을 거르면 호출하는 곳마다 null을 체크해야하지만 해당 메소드 내부에서 안전하게 처리를 해둔다면 여러번 처리를 하지 않겠죠?