Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
96a93c7
[Merge] develop <- feat/web/handler/static/#6
codingbaraGo Jan 1, 2026
dca7e16
fix(http): HttpRequestConverter 안정성 수정
codingbaraGo Jan 1, 2026
cd6784d
feat(http): HttpResponseConverter 구현체 개발
codingbaraGo Jan 1, 2026
f391917
feat(http): HttpRequestConverter, HttpResponseConverter 단위 테스트(성공 테스트…
codingbaraGo Jan 1, 2026
c478ced
Merge pull request #15 from codingbaraGo/feat/http/converter/#12
codingbaraGo Jan 1, 2026
acfcdc2
refactor(exception): ExceptionHandler, ExceptionHandlerMapping 시그니처 변경
codingbaraGo Jan 2, 2026
67c50a8
feat(exception): ErrorCode 정의
codingbaraGo Jan 2, 2026
0a7cc20
feat(exception): Custom Exception 클래스 정의
codingbaraGo Jan 2, 2026
6c07dc3
feat(exception): ExceptionHandler 구현체 개발
codingbaraGo Jan 2, 2026
7db1099
feat(http): Http Status 코드 추가
codingbaraGo Jan 2, 2026
5f846ed
Merge pull request #17 from codingbaraGo/feat/exception/#16
codingbaraGo Jan 2, 2026
ae9a404
refactor(http): HttpRequest.requestAddress 필드 추가
codingbaraGo Jan 2, 2026
a89a6c4
refactor(Was): WasServlet 수정
codingbaraGo Jan 2, 2026
45b5c7e
chore: HttpServlet 필드 명 변경
codingbaraGo Jan 2, 2026
63306c6
fix(web): Static content handler 수정
codingbaraGo Jan 2, 2026
7c3ef19
feat(web): 비지니스 로직 + 정적 뷰 반환 Handler & Post Handler 개발
codingbaraGo Jan 2, 2026
ba49f70
feat(dependency): DI 매니저 개발
codingbaraGo Jan 2, 2026
4a9b6ec
feat(web): 로그인 핸들러 개발
codingbaraGo Jan 2, 2026
9900f74
refactor(webserver): Thread -> Concurrent 패키지 교체
codingbaraGo Jan 2, 2026
7fc82cc
test(http): HttpRequest, HttpRequestConverter 성공 테스트 추가
codingbaraGo Jan 2, 2026
e08f705
refactor(view): 회원가입 뷰 수정
codingbaraGo Jan 2, 2026
bbbe0d5
test(web): Outdated test 삭제
codingbaraGo Jan 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ dependencies {
implementation 'ch.qos.logback:logback-classic:1.2.3'
testImplementation 'org.assertj:assertj-core:3.16.1'

testImplementation 'org.mockito:mockito-core:4.11.0'
testImplementation('org.mockito:mockito-junit-jupiter:4.11.0') {
exclude group: 'org.junit.jupiter'
exclude group: 'org.junit.platform'
}


}

Expand Down
94 changes: 94 additions & 0 deletions src/main/java/dependency/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package dependency;

import webserver.RegisterHandlerImpl;
import webserver.exception.ExceptionHandlerMapping;
import webserver.exception.handler.ErrorExceptionHandler;
import webserver.exception.handler.ServiceExceptionHandler;
import webserver.exception.handler.UnhandledErrorHandler;
import webserver.http.request.HttpBufferedReaderRequestConverter;
import webserver.http.request.HttpRequestConverter;
import webserver.http.response.HttpBufferedStreamResponseConverter;
import webserver.http.response.HttpResponseConverter;
import webserver.web.WasServlet;
import webserver.web.handler.StaticContentHandler;
import webserver.web.handler.WebHandler;
import webserver.web.handler.response.handler.StaticContentResponseHandler;
import webserver.web.handler.response.handler.ViewResponseHandler;
import webserver.web.handler.response.handler.WebHandlerResponseHandler;

import java.util.List;

public class AppConfig {
//Http
public HttpBufferedReaderRequestConverter httpBufferedReaderRequestConverter(){
return new HttpBufferedReaderRequestConverter();
}

public HttpBufferedStreamResponseConverter httpBufferedStreamResponseConverter(){
return new HttpBufferedStreamResponseConverter();
}

public HttpRequestConverter httpRequestConverter(){
return httpBufferedReaderRequestConverter();
}
public HttpResponseConverter httpResponseConverter(){
return httpBufferedStreamResponseConverter();
}


//Web
public WasServlet wasServlet(){
return new WasServlet(
webHandlerList(),
webHandlerResponseHandlerList()
);
}

private List<WebHandler> webHandlerList(){
return List.of(
staticContentHandler(),
registerHandlerImpl()
);
}
private RegisterHandlerImpl registerHandlerImpl(){
return new RegisterHandlerImpl();
}

private List<WebHandlerResponseHandler> webHandlerResponseHandlerList(){
return List.of(
staticContentResponseHandler(),
viewResponseHandler()
);
}
private StaticContentHandler staticContentHandler(){
return new StaticContentHandler();
}
private ViewResponseHandler viewResponseHandler(){
return new ViewResponseHandler();
}
private StaticContentResponseHandler staticContentResponseHandler(){
return new StaticContentResponseHandler();
}


//Exception
public ExceptionHandlerMapping exceptionHandlerMapping(){
return new ExceptionHandlerMapping(
List.of(
serviceExceptionHandler(),
errorExceptionHandler(),
unhandledErrorHandler()
)
);
}

private ServiceExceptionHandler serviceExceptionHandler(){
return new ServiceExceptionHandler();
}
private UnhandledErrorHandler unhandledErrorHandler(){
return new UnhandledErrorHandler();
}
private ErrorExceptionHandler errorExceptionHandler(){
return new ErrorExceptionHandler();
}
}
23 changes: 23 additions & 0 deletions src/main/java/dependency/DependencyLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dependency;

import webserver.exception.ExceptionHandlerMapping;
import webserver.http.request.HttpRequestConverter;
import webserver.http.response.HttpResponseConverter;
import webserver.web.WasServlet;

public class DependencyLoader {
private final AppConfig appConfig;

public final HttpRequestConverter httpRequestConverter;
public final HttpResponseConverter httpResponseConverter;
public final ExceptionHandlerMapping exceptionHandlerMapping;
public final WasServlet wasServlet;

public DependencyLoader(){
this.appConfig = new AppConfig();
this.httpRequestConverter = appConfig.httpRequestConverter();
this.httpResponseConverter = appConfig.httpResponseConverter();
this.exceptionHandlerMapping = appConfig.exceptionHandlerMapping();
this.wasServlet = appConfig.wasServlet();
}
}
25 changes: 25 additions & 0 deletions src/main/java/webserver/RegisterHandlerImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package webserver;

import db.Database;
import model.User;
import webserver.http.HttpMethod;
import webserver.http.request.HttpRequest;
import webserver.web.handler.DynamicViewHandler;
import webserver.web.handler.response.view.ViewResponse;

public class RegisterHandlerImpl extends DynamicViewHandler {
public RegisterHandlerImpl() {
super(HttpMethod.GET,
"/create");
}

@Override
public ViewResponse handle(HttpRequest request) {
String userId = request.getQueryValue("userId");
String password = request.getQueryValue("password");
String name = request.getQueryValue("name");
String email = request.getQueryValue("email");
Database.addUser(new User(userId, password, name, email));
return ViewResponse.of("/login");
}
}
17 changes: 15 additions & 2 deletions src/main/java/webserver/WebServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import dependency.DependencyLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webserver.http.HttpServlet;

public class WebServer {
private static final Logger logger = LoggerFactory.getLogger(WebServer.class);
private static final int DEFAULT_PORT = 8080;
private static final DependencyLoader LOADER = new DependencyLoader();
private static final ExecutorService executor = Executors.newFixedThreadPool(32);

public static void main(String args[]) throws Exception {
int port = 0;
Expand All @@ -25,8 +31,15 @@ public static void main(String args[]) throws Exception {
// 클라이언트가 연결될때까지 대기한다.
Socket connection;
while ((connection = listenSocket.accept()) != null) {
Thread thread = new Thread(new RequestHandler(connection));
thread.start();
Socket singleConnection = connection;
executor.submit(() -> {
HttpServlet httpServlet = new HttpServlet(LOADER.wasServlet,
LOADER.exceptionHandlerMapping,
LOADER.httpResponseConverter,
LOADER.httpRequestConverter,
singleConnection);
httpServlet.run();
});
}
}
}
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/webserver/exception/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package webserver.exception;


import webserver.http.HttpStatus;

public enum ErrorCode {
/* Internal Error */
INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "500_INTERNAL", "서버 내부 오류가 발생했습니다."),

/* Request Error */
INVALID_INPUT(HttpStatus.BAD_REQUEST, "400_INVALID_INPUT", "입력 값이 올바르지 않습니다."),
MISSING_PARAMETER(HttpStatus.BAD_REQUEST, "400_MISSING_PARAM", "필수 파라미터가 누락되었습니다."),
VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "400_VALIDATION_FAIL", "유효성 검증에 실패했습니다."),

FORBIDDEN(HttpStatus.FORBIDDEN, "403_FORBIDDEN", "권한이 없습니다."),

NO_SUCH_RESOURCE(HttpStatus.NOT_FOUND, "404_NO_SUCH_RESOURCE", "요청한 리소스를 찾을 수 없습니다."),

METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "405_METHOD_NOT_ALLOWED", "허용되지 않은 HTTP 메서드입니다."),
;


private final HttpStatus status;
private final String code;
private final String message;

public HttpStatus getStatus() {
return status;
}

public String getCode() {
return code;
}

public String getMessage() {
return message;
}

ErrorCode(HttpStatus status, String code, String message) {
this.status = status;
this.code = code;
this.message = message;
}
}
26 changes: 26 additions & 0 deletions src/main/java/webserver/exception/ErrorException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package webserver.exception;

public class ErrorException extends RuntimeException {
private final ErrorCode errorCode;
private final Throwable throwable;

public ErrorException(String message) {
super(message);
this.errorCode = ErrorCode.INTERNAL_ERROR;
this.throwable = null;
}

public ErrorException(String message, Throwable t) {
super(message);
this.errorCode = ErrorCode.INTERNAL_ERROR;
this.throwable = t;
}

public ErrorCode getErrorCode() {
return errorCode;
}

public Throwable getThrowable() {
return throwable;
}
}
4 changes: 2 additions & 2 deletions src/main/java/webserver/exception/ExceptionHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import java.net.Socket;

public interface ExceptionHandler {
boolean support(Exception e);
void handle(Exception e, Socket connection);
boolean support(Throwable e);
void handle(Throwable e, Socket connection);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ public ExceptionHandlerMapping (List<ExceptionHandler> handlers){
this.handlers = handlers;
}

public void handle(Exception e, Socket connection){
ExceptionHandler handlerAdaptor = handlers.stream().filter(handler -> handler.support(e)).findFirst().orElseThrow();
handlerAdaptor.handle(e, connection);
public void handle(Throwable e, Socket connection){
ExceptionHandler handler = handlers.stream().filter(h -> h.support(e)).findFirst().orElseThrow();
handler.handle(e, connection);
}

}
19 changes: 19 additions & 0 deletions src/main/java/webserver/exception/ServiceException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package webserver.exception;

public class ServiceException extends RuntimeException {
private final ErrorCode errorCode;

public ServiceException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}

public ServiceException(ErrorCode errorCode, String customMsg) {
super((customMsg == null || customMsg.isBlank()) ? errorCode.getMessage() : customMsg);
this.errorCode = errorCode;
}

public ErrorCode getErrorCode() {
return errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package webserver.exception.handler;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import webserver.exception.ErrorCode;
import webserver.exception.ErrorException;
import webserver.exception.ExceptionHandler;
import webserver.http.HttpStatus;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;

public class ErrorExceptionHandler implements ExceptionHandler {
private final Logger logger = LoggerFactory.getLogger(ErrorException.class);
@Override
public boolean support(Throwable e) {
return e instanceof ErrorException;
}

@Override
public void handle(Throwable t, Socket connection) {
ErrorException error = (ErrorException) t;
logger.debug(error.getThrowable().toString());
ErrorCode errorCode = error.getErrorCode();
HttpStatus status = errorCode.getStatus();

String body = toJson(errorCode.getCode(), error.getMessage());
byte[] bodyBytes = body.getBytes(StandardCharsets.UTF_8);

StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 ")
.append(status.getCode()).append("\r\n");

sb.append("Date: ")
.append(DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now()))
.append("\r\n");
sb.append("Server: be-was\r\n");
sb.append("Connection: close\r\n");

sb.append("Content-Type: application/json; charset=utf-8\r\n");
sb.append("Content-Length: ").append(bodyBytes.length).append("\r\n");
sb.append("\r\n");

try (OutputStream out = connection.getOutputStream()) {
out.write(sb.toString().getBytes(StandardCharsets.US_ASCII));
out.write(bodyBytes);
out.flush();
} catch (IOException io) {
throw new RuntimeException(io);
}
}

private String toJson(String code, String message) {
String safeMsg = message == null ? "" : message
.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r");

return "{\"code\":\"" + code + "\",\"message\":\"" + safeMsg + "\"}";
}
}
Loading