Skip to content
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,17 @@ Java Web Server Project for CodeSquad Members 2022
- [x] POST 로 회원가입 기능이 정상적으로 동작하도록 구현
- [x] 중복아이디를 처리하기 위해서 `Map<Id, User>` 로 회원목록을 관리
- [x] 가입 후 페이지 이동을 위해 redirection 기능을 구현

## 4단계. 쿠키를 이용한 로그인 구현
### 기능요구사항
- [x] 회원가입한 사용자로 로그인을 할 수 있어야 한다.
- [x] “로그인” 메뉴를 클릭하면 `http://localhost:8080/user/login.html` 으로 이동해 로그인할 수 있다.
- [x] 로그인이 성공하면 index.html로 이동하고, 로그인이 실패하면 /user/login_failed.html로 이동해야 한다.

### 프로그래밍 요구사항
- [x] 정상적으로 로그인 되었는지 확인하려면 앞 단계에서 회원가입한 데이터를 유지해야 한다.
- [x] 앞 단계에서 회원가입할 때 생성한 User 객체를 DataBase.addUser() 메서드를 활용해 메모리에 저장한다.
- [x] 필요에 따라 Database 클래스의 메소드나 멤버변수를 수정해서 사용한다.
- [x] 아이디와 비밀번호가 같은지를 확인해서 로그인이 성공하면 응답 header의 Set-Cookie 값을 sessionId=적당한값으로 설정한다.
- [x] Set-Cookie 설정시 모든 요청에 대해 Cookie 처리가 가능하도록 Path 설정 값을 /(Path=/)로 설정한다.
- [x] 응답 header에 Set-Cookie값을 설정한 후 요청 header에 Cookie이 전달되는지 확인한다.
22 changes: 22 additions & 0 deletions src/main/java/db/SessionDataBase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package db;

import com.google.common.collect.Maps;
import java.util.Map;

public class SessionDataBase {

private static Map<String, String> sessions = Maps.newHashMap();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final 을 사용하지 않고, HashMap 을 사용해주신건가요?


public static void add(String sessionId, String userId) {
sessions.put(sessionId, userId);
}

public static String findBySessionId(String sessionId) {
return sessions.get(sessionId);
}

public static void remove(String sessionId) {
sessions.remove(sessionId);
}
Comment on lines +10 to +20
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static 메서드가 아니어도 동작하게끔 하는편이 좋을 것 같습니다.


}
29 changes: 18 additions & 11 deletions src/main/java/webserver/Request.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,39 @@

public class Request {

private String methodType;
private String requestLine;
private RequestLine requestLine;
private Map<String, String> headers;
private String messageBody;
private URL url;

public Request(String requestLine, String messageBody, String methodType, URL url) {
public Request(RequestLine requestLine, Map<String, String> headers, String messageBody) {
this.requestLine = requestLine;
this.headers = headers;
this.messageBody = messageBody;
this.methodType = methodType;
this.url = url;
}

public String getMessageBody() {
return messageBody;
}

public String getRequestLine() {
public RequestLine getRequestLine() {
return requestLine;
}

public URL getURL() {
return url;
public Map<String, String> getHeaders() {
return headers;
}

public String getMethodType() {
return methodType;
public boolean isGetMethodType() {
return requestLine.isGetMethodType();
}

public boolean isPostMethodType() {
return requestLine.isPostMethodType();
}
Comment on lines +29 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


public URL getUrl() {
return requestLine.getUrl();
}

}

95 changes: 5 additions & 90 deletions src/main/java/webserver/RequestHandler.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package webserver;

import db.DataBase;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
Expand All @@ -9,17 +8,8 @@
import java.io.OutputStream;
import java.net.Socket;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import model.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.HttpRequestUtils;
import util.IOUtils;
import util.RequestLineUtil;

public class RequestHandler extends Thread {

Expand All @@ -38,39 +28,21 @@ public void run() {
try (InputStream in = connection.getInputStream(); OutputStream out = connection.getOutputStream()) {
BufferedReader br = new BufferedReader(new InputStreamReader(in));

Request request = makeRequest(br);
RequestReader requestReader = new RequestReader(br);
Request request = requestReader.getRequest();

// URL init
URL url = request.getURL();

// user save
if (request.getMethodType().equals("POST") && url.comparePath("/user/create")) {
String messageBody = request.getMessageBody();
userSave(messageBody, url);
}
UserManager userManager = new UserManager(request);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 방식의 구현은 약간 의아하네요~request 를 의존성으로 받을 이유가 있을까요..?

String sessionId = userManager.action();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userManager.action의 응답이 sessionId 를 리턴해주어야 했나요?


Comment on lines +34 to 36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분의 구현만 봐서는 전체적인 정상 동작이 UserManager 쪽에 전부 이동한 느낌이 드네요. 좋은 방향일까요?

// Response Message
DataOutputStream dos = new DataOutputStream(out);
Response response = new Response(request.getRequestLine(), url);
Response response = new Response(request.getRequestLine(), sessionId);
sendResponse(dos, response);
} catch (IOException e) {
log.error(e.getMessage());
}
}

private void userSave(String messageBody, URL url) {
Map<String, String> userInfo = HttpRequestUtils.parseQueryString(messageBody);
User user = new User(userInfo.get("userId"), userInfo.get("password"), userInfo.get("name"),
userInfo.get("email"));

if (DataBase.findUserById(userInfo.get("userId")) == null) {
DataBase.addUser(user);
url.setRedirectHomePage();
} else {
url.setRedirectSignUpPage();
}
}

private void sendResponse(DataOutputStream dos, Response response) throws IOException {
response.action();

Expand All @@ -82,61 +54,4 @@ private void sendResponse(DataOutputStream dos, Response response) throws IOExce
dos.write(body, 0, body.length);
}
}


private Request makeRequest(BufferedReader bufferedReader) throws IOException {
String requestLine = initRequestLine(bufferedReader);
Map<String, String> headers = new HashMap<>();

initRequestHeaders(bufferedReader, headers);
outputLog(headers, requestLine);

URL url = initURL(requestLine, headers);

String messageBody = "";
String methodType = RequestLineUtil.getMethodType(requestLine);

if (methodType.equals("POST")) {
messageBody = IOUtils.readData(bufferedReader,
Integer.parseInt(headers.get("Content-Length:")));
log.debug(messageBody);
}

return new Request(requestLine, messageBody, methodType, url);
}

private void outputLog(Map<String, String> headers, String requestLine) {
// Request Line Log
log.debug(requestLine);
// Request Headers Log
for (Entry<String, String> entry : headers.entrySet()) {
log.debug("Request: {} {}", entry.getKey(), entry.getValue());
}
}

private String initRequestLine(BufferedReader bufferedReader) throws IOException {
String line;
if ("".equals(line = bufferedReader.readLine()) || line == null) {
throw new IllegalArgumentException("Request.bufferedReader.readLine == null 입니다.");
}
return line;
}

private void initRequestHeaders(BufferedReader bufferedReader, Map<String, String> headers)
throws IOException {
String line;
while (!"".equals(line = bufferedReader.readLine())) {
if (line == null) {
throw new IllegalArgumentException("Request.bufferedReader.readLine == null 입니다.");
}
String[] splitLine = line.split(" ");
headers.put(splitLine[0], splitLine[1]);
}
}

private URL initURL(String requestLine, Map<String, String> headers) {
String decodedUrl = URLDecoder.decode(requestLine, StandardCharsets.UTF_8);
String host = headers.get("Host:");
return new URL(RequestLineUtil.getURL(decodedUrl), host);
}
}
32 changes: 32 additions & 0 deletions src/main/java/webserver/RequestLine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package webserver;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

public class RequestLine {

private String methodType;
private URL url;

public RequestLine(String methodType, String url) {
this.methodType = methodType;
this.url = makeURL(url);
}

private URL makeURL(String url) {
String decodedUrl = URLDecoder.decode(url, StandardCharsets.UTF_8);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTF-8 을 사용해주신 이유는 무엇인가요?

return new URL(decodedUrl);
}

public boolean isGetMethodType() {
return "GET".equals(methodType);
}

public boolean isPostMethodType() {
return "POST".equals(methodType);
}

public URL getUrl() {
return url;
}
}
72 changes: 72 additions & 0 deletions src/main/java/webserver/RequestReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package webserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.IOUtils;
import util.RequestLineUtil;

public class RequestReader {

private static final Logger log = LoggerFactory.getLogger(RequestReader.class);

private final BufferedReader bufferedReader;

public RequestReader(BufferedReader bufferedReader) {
this.bufferedReader = bufferedReader;
}

public Request getRequest() throws IOException {
RequestLine requestLine = makeRequestLine(bufferedReader);

Map<String, String> headers = makeRequestHeaders(bufferedReader);
outputLog(headers);

String messageBody = "";
if (requestLine.isPostMethodType()) {
messageBody = IOUtils.readData(bufferedReader,
Integer.parseInt(headers.get("Content-Length:")));
log.debug(messageBody);
}

return new Request(requestLine, headers, messageBody);
}

private void outputLog(Map<String, String> headers) {
// Request Headers Log
for (Entry<String, String> entry : headers.entrySet()) {
log.debug("Request: {} {}", entry.getKey(), entry.getValue());
}
}
Comment on lines +39 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳이 필요없는 메서드로 보여요. 그냥 headers에 대한 로그를 찍으면 됩니다.


private RequestLine makeRequestLine(BufferedReader bufferedReader) throws IOException {
String line;
if ("".equals(line = bufferedReader.readLine()) || line == null) {
Comment on lines +47 to +48
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런식의 할당과 비교를 동시에 수행하는 코드는 지양해주세요. (이런건 어디서 알려주나요??)

throw new IllegalArgumentException("Request.bufferedReader.readLine == null 입니다.");
}
// Request Line Log
log.debug(line);

String url = RequestLineUtil.getURL(line);
String methodType = RequestLineUtil.getMethodType(line);
return new RequestLine(methodType, url);
}

private Map<String, String> makeRequestHeaders(BufferedReader bufferedReader)
throws IOException {
Map<String, String> headers = new HashMap<>();
String line;
while (!"".equals(line = bufferedReader.readLine())) {
if (line == null) {
throw new IllegalArgumentException("Request.bufferedReader.readLine == null 입니다.");
}
String[] splitLine = line.split(" ");
headers.put(splitLine[0], splitLine[1]);
}
return headers;
}
}
47 changes: 38 additions & 9 deletions src/main/java/webserver/Response.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,36 @@ public class Response {

private byte[] body;
private String headers;
private String requestLine;
private String sessionId;
private RequestLine requestLine;
private URL url;

public Response(String requestLine, URL url) {
public Response(RequestLine requestLine, String sessionId) {
this.requestLine = requestLine;
this.url = url;
this.url = requestLine.getUrl();
this.sessionId = sessionId;
}

public void action() throws IOException {
if (requestLine.contains("POST") && requestLine.contains("/user/create")) {
headers = response302Header(url);
} else {
body = Files.readAllBytes(new File("./webapp" + url.getPath()).toPath());
headers = response200Header(body.length);
if (requestLine.isPostMethodType()) {
if (url.comparePath("/user/create")) {
headers = response302Header(url);
}
if (url.comparePath("/user/login")) {
headers = response302Header(url, sessionId, true);
}
}
if (requestLine.isGetMethodType()) {
if (url.comparePath("/user/logout")) {
headers = response302Header(url, sessionId, false);
} else {
body = Files.readAllBytes(new File("./webapp" + url.getPath()).toPath());
headers = response200Header(body.length);
}
}
}
Comment on lines 21 to 38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

depthelse 문이 복합적으로 사용되었죠, 복잡도를 해소해봅시다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Response 가 스스로 주체적으로 판단하는 것은 좋은데, action 이 호출될 때 수행되는 점이 아쉽네요. 생성시점에 만들어질 수는 없나요?



private String response200Header(int lengthOfBodyContent) {
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 200 OK \r\n");
Expand All @@ -39,7 +52,23 @@ private String response302Header(URL url) {
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 302 Found \r\n");
sb.append("Content-Type: text/html;charset=utf-8\r\n");
sb.append("Location: " + url.getPath() + "\r\n");
sb.append("Location: " + url.getRedirectPath() + "\r\n");
sb.append("\r\n");

return sb.toString();
}

private String response302Header(URL url, String sessionId, boolean isLogin) {
StringBuilder sb = new StringBuilder();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sb 같은 네이밍은 아주 나쁜 네이밍입니다. 지양해주세요.

sb.append("HTTP/1.1 302 Found \r\n");
sb.append("Content-Type: text/html;charset=utf-8\r\n");
sb.append("Location: " + url.getRedirectPath() + "\r\n");
Comment on lines +63 to +65
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

중복되는 코드가 등장하는데 중복제거를 해주세요.


if (isLogin) {
sb.append("Set-Cookie: sessionId = " + sessionId + "; Path=/\r\n");
} else {
sb.append("Set-Cookie: sessionId = " + sessionId + "; max-age=0; Path=/\r\n");
}
Comment on lines +67 to +71
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굳이 나눌 필요가 있을까요?

sb.append("\r\n");

return sb.toString();
Expand Down
Loading