-
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
[무늬] 3단계 - HTTP 웹 서버 구현 미션 제출합니다. #207
base: jinjumoon
Are you sure you want to change the base?
Changes from all commits
11d008a
55cd4cb
7ab8d8a
c24516c
bb32843
6eb5044
795beeb
167defc
7a3ded5
fcbc968
46d9ba0
70e3829
e2ea74c
5649cdf
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 |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package db; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import webserver.http.request.HttpSession; | ||
|
||
public class SessionDataBase { | ||
private static Map<String, HttpSession> sessions = new HashMap<>(); | ||
|
||
public static void addHttpSession(HttpSession httpSession) { | ||
sessions.put(httpSession.getId(), httpSession); | ||
} | ||
|
||
public static HttpSession findHttpSessionById(String id) { | ||
return sessions.get(id); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package webserver; | ||
|
||
import webserver.http.response.StatusCode; | ||
|
||
public abstract class ClientException extends RuntimeException { | ||
public ClientException(String message) { | ||
super(message); | ||
} | ||
|
||
public abstract StatusCode getStatus(); | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,12 +41,15 @@ public HttpResponse controlRequestAndResponse(HttpRequest httpRequest) { | |
try { | ||
Controller controller = requestMapping.getController(httpRequest); | ||
return controller.service(httpRequest); | ||
} catch (ApplicationBusinessException | RequestException e) { | ||
} catch (ApplicationBusinessException e) { | ||
logger.info(e.getMessage()); | ||
return HttpResponse.badRequest(e.getMessage()).build(); | ||
return HttpResponse.businessException(e).build(); | ||
} catch (ClientException e) { | ||
logger.info(e.getMessage()); | ||
return HttpResponse.clientException(e).build(); | ||
} catch (ServerException e) { | ||
logger.error(e.getMessage()); | ||
return HttpResponse.internalServer(e.getMessage()).build(); | ||
return HttpResponse.internalServerError(e).build(); | ||
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,7 @@ | ||
package webserver; | ||
|
||
public class ResourceCannotLoadException extends ServerException { | ||
public ResourceCannotLoadException(String message) { | ||
super(message); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
package webserver; | ||
|
||
import webserver.http.response.StatusCode; | ||
|
||
public class ServerException extends RuntimeException { | ||
public ServerException(String message) { | ||
super(message); | ||
} | ||
|
||
public StatusCode getStatus() { | ||
return StatusCode.INTERNAL_SERVER_ERROR; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package webserver; | ||
|
||
import java.io.IOException; | ||
|
||
import com.github.jknack.handlebars.Handlebars; | ||
import com.github.jknack.handlebars.Template; | ||
import com.github.jknack.handlebars.io.ClassPathTemplateLoader; | ||
import com.github.jknack.handlebars.io.TemplateLoader; | ||
|
||
public class TemplateFactory { | ||
public static String of(String location, Object context) { | ||
TemplateLoader loader = new ClassPathTemplateLoader(); | ||
loader.setPrefix("/templates"); | ||
loader.setSuffix(".html"); | ||
Handlebars handlebars = new Handlebars(loader); | ||
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. Handlebars를 매번 생성하는 것은 어떠한 단점이 있을지 고민해보고 재사용할 수 있다면 어떠한 장점이 있을지 고민해보세요! |
||
|
||
try { | ||
Template template = handlebars.compile(location); | ||
return template.apply(context); | ||
} catch (IOException e) { | ||
throw new ResourceCannotLoadException("자원을 로드할 수 없습니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package webserver; | ||
|
||
import webserver.http.response.StatusCode; | ||
|
||
public class UnsupportedClientUrlException extends ClientException { | ||
public UnsupportedClientUrlException(String requestUrl) { | ||
super(String.format("해당 URL에 대한 요청은 지원하지 않습니다. {'URL' : %s}", requestUrl)); | ||
} | ||
|
||
@Override | ||
public StatusCode getStatus() { | ||
return StatusCode.NOT_FOUND; | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,13 @@ | ||
package webserver.controller; | ||
|
||
import webserver.http.response.StatusCode; | ||
|
||
public class ApplicationBusinessException extends RuntimeException { | ||
public ApplicationBusinessException(String message) { | ||
super(message); | ||
} | ||
|
||
public StatusCode getStatus() { | ||
return StatusCode.BAD_REQUEST; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,32 @@ | ||
package webserver.controller; | ||
|
||
import java.util.Optional; | ||
|
||
public enum ContentType { | ||
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. ContentType의 옵션(charset, boundary, ...)들은 상황에 따라 추가될 수 있을 것 같아요. |
||
HTML("text/html", Optional.of("charset=utf-8"), Optional.empty()), | ||
CSS("text/css", Optional.empty(), Optional.empty()), | ||
JAVASCRIPT("application/javascript", Optional.empty(), Optional.empty()), | ||
TEXT("text/plain", Optional.empty(), Optional.empty()); | ||
HTML("text/html", "charset=utf-8", ""), | ||
CSS("text/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. 위 피드백을 반영하면 필요없을 수 있는 내용이지만 빈 값들을 추가하기 위해서 불필요한 값들이 많이 추가됐는데요. |
||
JAVASCRIPT("application/javascript", "", ""), | ||
TEXT("text/plain", "", ""), | ||
APPLICATION_JSON("application/json", "", ""), | ||
ICO("image/x-icon", "", ""), | ||
TTF("font/ttf", "", ""), | ||
WOFF("font/woff", "", ""), | ||
WOFF2("font/woff2", "", ""); | ||
|
||
private final String mediaType; | ||
private final Optional<String> charset; | ||
private final Optional<String> boundary; | ||
private final String charset; | ||
private final String boundary; | ||
|
||
ContentType(String mediaType, Optional<String> charset, Optional<String> boundary) { | ||
ContentType(String mediaType, String charset, String boundary) { | ||
this.mediaType = mediaType; | ||
this.charset = charset; | ||
this.boundary = boundary; | ||
} | ||
|
||
public String value() { | ||
StringBuilder stringBuilder = new StringBuilder(); | ||
String charsetToAppend = (charset == "") ? "" : (";" + charset); | ||
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이 객체라는 사실을 생각하며 객체의 비교에서 왜 equals를 사용하는지 공부해보아요! |
||
String boundaryToAppend = (boundary == "") ? "" : ( ";" + boundary); | ||
|
||
stringBuilder.append(mediaType); | ||
charset.ifPresent(charset -> stringBuilder.append(String.format(";%s", charset))); | ||
boundary.ifPresent(boundary -> stringBuilder.append(String.format(";%s", boundary))); | ||
String value = mediaType + charsetToAppend + boundaryToAppend; | ||
|
||
return stringBuilder.toString(); | ||
return value; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package webserver.controller; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import db.SessionDataBase; | ||
import db.UserDataBase; | ||
import model.User; | ||
import webserver.http.request.HttpRequest; | ||
import webserver.http.request.HttpSession; | ||
import webserver.http.response.HttpResponse; | ||
|
||
public class LoginController extends AbstractController { | ||
private static final Logger logger = LoggerFactory.getLogger(LoginController.class); | ||
|
||
@Override | ||
protected HttpResponse post(HttpRequest httpRequest) { | ||
Map<String, String> data = 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. Body의 정보를 파싱하는 것은 Request를 사용하는 곳에서 자주 사용되는데요. |
||
String body = httpRequest.getBody(); | ||
logger.debug("body : {}", body); | ||
String[] inputs = body.split("&"); | ||
for (String input : inputs) { | ||
logger.debug("bodyParam : {}", input); | ||
String[] keyAndValue = input.split("="); | ||
if (keyAndValue.length < 2) { | ||
continue; | ||
} | ||
logger.debug("key : {}", keyAndValue[0]); | ||
logger.debug("value : {}", keyAndValue[1]); | ||
data.put(keyAndValue[0], keyAndValue[1]); | ||
} | ||
|
||
String userId = data.get("userId"); | ||
String password = data.get("password"); | ||
|
||
User user = UserDataBase.findUserById(userId); | ||
HttpSession httpSession = httpRequest.getHttpSession(); | ||
httpSession.setAttribute("user", user); | ||
SessionDataBase.addHttpSession(httpSession); | ||
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. 만약 해당 프로젝트의 정보를 잘 몰라 SessionDataBase의 존재를 모른다면 Session은 어떻게 관리될까요? |
||
|
||
if (user != null && password.equals(user.getPassword())) { | ||
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. getter를 사용하지 않고 메시지를 던져보면 어떨까요? |
||
return HttpResponse.ok() | ||
.bodyByPath("./templates/index.html") | ||
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. ./templates의 경로를 매번 입력해주는 것 보다 ViewTemplate에서 공통으로 처리해보면 어떨까요? |
||
.setCookie("JSESSIONID", httpRequest.getSessionId(), "/") | ||
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. JSESSIONID는 매우 특별한 의미를 가진만큼 상수로 분리해보면 어떨까요? |
||
.build(); | ||
} | ||
|
||
return HttpResponse.ok() | ||
.bodyByPath("./templates/user/login_failed.html") | ||
.setCookie("JSESSIONID", httpRequest.getSessionId(), "/") | ||
.build(); | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package webserver.controller; | ||
|
||
import webserver.ClientException; | ||
import webserver.http.response.StatusCode; | ||
|
||
public class UndefinedExtensionException extends ClientException { | ||
public UndefinedExtensionException(String extension) { | ||
super(String.format("정의되지 않은 확장자입니다. {'extension' : %s}", extension)); | ||
} | ||
|
||
@Override | ||
public StatusCode getStatus() { | ||
return StatusCode.BAD_REQUEST; | ||
} | ||
} |
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.
여러 스레드에서 접근할 수 있는 경우 동시성을 보장해주는 것이 좋습니다!