Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 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
9b8e68d
refactor(web): PostHandler 파일 읽기 관련 로직 최적화
codingbaraGo Jan 3, 2026
e1ea87b
Chore: 메서드 라인 변경
codingbaraGo Jan 3, 2026
577acc7
refactor(app): 유저 어플리케이션 관련 패키지 구축
codingbaraGo Jan 5, 2026
9e9b479
refactor(config): 서버 구동 설정 관련 패키지 구축
codingbaraGo Jan 5, 2026
319ded6
refactor(http): HTTP 관련 패키지 분리
codingbaraGo Jan 5, 2026
72bf62f
refactor(web): 주요 클래스 명 변경
codingbaraGo Jan 5, 2026
0176b22
refactor(web, exception): Web 패키지 구조 변경
codingbaraGo Jan 5, 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
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package db;
package app.db;

import model.User;
import app.model.User;

import java.util.Collection;
import java.util.HashMap;
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/app/handler/RegisterHandlerImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package app.handler;

import app.db.Database;
import app.model.User;
import http.HttpMethod;
import http.request.HttpRequest;
import web.handler.DynamicViewHandler;
import web.response.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");
Comment on lines +17 to +23
Copy link

Choose a reason for hiding this comment

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

사용자 입력값(userId, password, name, email)에 대한 유효성 검증이 없습니다. 빈 문자열, null, SQL 인젝션 패턴 등을 검증하지 않으면 런타임 오류나 데이터 무결성 문제가 발생할 수 있습니다. \n\n최소한 다음을 추가하세요:\njava\nif (userId == null || userId.isBlank()) throw new ServiceException(ErrorCode.MISSING_PARAMETER);\n\n\n또한 RegisterHandlerImpl의 필드명과 HTML 폼의 name 속성이 불일치합니다(nickname vs name)."

}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package model;
package app.model;

public class User {
private String userId;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package webserver;
package bootstrap;

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

import config.DependencyLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import web.dispatch.ConnectionHandler;

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();
Copy link

Choose a reason for hiding this comment

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

static 필드로 DependencyLoader를 선언하는 것은 문제가 될 수 있습니다. 클래스 로딩 시점에 모든 의존성이 초기화되므로, 향후 의존성 주입 패턴 도입 시나 테스트 시 수정하기 어렵습니다. 가능하면 main 메서드 내에서 인스턴스화하고 필요한 의존성만 전달하는 방식을 고려하세요."

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(() -> {
ConnectionHandler connectionHandler = new ConnectionHandler(LOADER.dispatcher,
LOADER.exceptionHandlerMapping,
LOADER.httpResponseConverter,
LOADER.httpRequestConverter,
singleConnection);
connectionHandler.run();
});
}
}
}
Expand Down
94 changes: 94 additions & 0 deletions src/main/java/config/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package config;

import app.handler.RegisterHandlerImpl;
import exception.ExceptionHandlerMapping;
import exception.handler.ErrorExceptionHandler;
import exception.handler.ServiceExceptionHandler;
import exception.handler.UnhandledErrorHandler;
import http.request.HttpBufferedReaderRequestConverter;
import http.request.HttpRequestConverter;
import http.response.HttpBufferedStreamResponseConverter;
import http.response.HttpResponseConverter;
import web.dispatch.Dispatcher;
import web.handler.StaticContentHandler;
import web.handler.WebHandler;
import web.posthandler.StaticContentResponseHandler;
import web.posthandler.ViewResponseHandler;
import web.posthandler.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 Dispatcher wasServlet(){
return new Dispatcher(
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/config/DependencyLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package config;

import exception.ExceptionHandlerMapping;
import http.request.HttpRequestConverter;
import http.response.HttpResponseConverter;
import web.dispatch.Dispatcher;

public class DependencyLoader {
private final AppConfig appConfig;

public final HttpRequestConverter httpRequestConverter;
public final HttpResponseConverter httpResponseConverter;
public final ExceptionHandlerMapping exceptionHandlerMapping;
public final Dispatcher dispatcher;

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

import java.util.List;

public class VariableConfig {
public static final List<String> STATIC_RESOURCE_ROOTS = List.of(
"./src/main/resources",
"./src/main/resources/static");
Comment on lines +6 to +8
Copy link

Choose a reason for hiding this comment

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

하드코딩된 경로는 상대 경로로 시스템 동작에 따라 불안정할 수 있습니다. 프로젝트 루트 기준으로 경로를 동적으로 계산하거나, 환경 변수/설정 파일에서 읽도록 변경하세요. 현재는 서버가 다른 디렉토리에서 실행될 경우 리소스를 찾지 못할 수 있습니다."

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


import 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/exception/ErrorException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package 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;
}
}
8 changes: 8 additions & 0 deletions src/main/java/exception/ExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package exception;

import java.net.Socket;

public interface ExceptionHandler {
boolean support(Throwable e);
void handle(Throwable e, Socket connection);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package webserver.exception;
package exception;

import java.net.Socket;
import java.util.List;
Expand All @@ -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/exception/ServiceException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package 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;
}
}
Loading
Loading