-
Notifications
You must be signed in to change notification settings - Fork 0
How to use?
현재 Maven/Gradle 중앙 저장소에 배포되어 있지 않으므로, 로컬 빌드 후 의존성으로 추가하거나 소스를 직접 포함하여 사용합니다.
다음은 빌드 시스템에 따른 의존성 설정 예시입니다.
dochi.jar 파일을 프로젝트의 특정 경로에 저장합니다.
project/
├─ build.gradle
├─ settings.gradle
├─ libs/
│ └─ dochi.jar
└─ src/build.gradle에 dochi.jar 파일을 저장한 경로에 대해 의존성을 설정한다.
dependencies {
implementation files('libs/dochi.jar')
}- File
- Project Structure
- Libraries
- Add
dochi.jar
import org.dochi.webserver.bootstrap.WebAppServer;
import org.dochi.webserver.lifecycle.LifecycleException;
public class WebAppServerLauncher {
public static void main(String[] args) throws LifecycleException {
WebAppServer server = new WebAppServer(8080);
server.start();
}
}기본 설정으로 8080 포트에서 서버가 시작되며, webapp/ 디렉터리의 정적 리소스를 서빙합니다.
각 WebAppServer 인스턴스는 포트와 호스트 네임으로 독립적인 서버를 나타냅니다.
- 포트와 호스트 네임이 모두 같은 경우에
LifecycleException이 발생합니다. - JVM 종료시
ShutdownTasksManager가 실행했던 순서대로 각 서버 인스터스를 안전하게 종료합니다. - 스레드풀은 인스턴스마다 독립적으로 생성되므로, 인스턴스 수와 최대 스레드 수를 함께 고려하여 시스템 리소스를 설계해야 합니다.
public class WebAppServerLauncher {
public static void main(String[] args) throws LifecycleException {
// 8080 포트에 바인딩된 독립 서버 인스턴스
WebAppServer server1 = new WebAppServer(80, "localhost");
frontServer.getWebService().setWebResourceRootPath("webapp");
// 9090 포트에 바인딩된 독립 서버 인스턴스
WebAppServer server2 = new WebAppServer(9090, "0.0.0.0");
server2.getWebService()
.addService("/api/user", new UserApiHandler())
.addService("/api/post", new PostApiHandler());
server2.getThreadPool().setMinSpareThreads(200);
server2.getThreadPool().setMaxThreads(2000);
// 각 인스턴스 독립적으로 시작 (포트가 달라야 함)
server1.start();
server2.start();
}
}WebAppServer 객체를 통해 포트, 스레드풀, 소켓, HTTP 제한값 등을 설정할 수 있습니다.
WebAppServer server = new WebAppServer(8080, "localhost");
// 워커 스레드풀 설정
server.getThreadPool().setMinSpareThreads(100);
server.getThreadPool().setMaxThreads(1000);
server.getThreadPool().setUseVirtualThreads(false); // Java 21+에서 true로 설정 가능
// 소켓 설정
server.getSocket().setKeepAliveTimeout(5000); // ms 단위
server.getSocket().setMaxKeepAliveRequests(100);
// HTTP 요청 메세지 크기 제한
server.getHttp().getReqConfig().setRequestHeaderMaxSize(16 * 1024); // 16KB
server.getHttp().getReqConfig().setRequestPayloadMaxSize(10 * 1024 * 1024); // 10MB
// HTTP 응답 메세지 크기 제한
server.getHttp().getResConfig().setResponseHeaderMaxSize(8 * 1024); // 8KB
server.getHttp().getResConfig().setResponseBodyMaxSize(4 * 1024 * 1024); // 4MB
// 정적 리소스 루트 디렉터리 변경 (기본값: "webapp")
server.getWebService().setWebResourceRootPath("static");
server.start();| 항목 | 기본값 |
|---|---|
| 포트 | 8080 |
| 호스트 | localhost |
| 최소 스레드 수 | 500 |
| 최대 스레드 수 | 3000 |
| 가상 스레드 사용 | false |
| Keep-Alive 타임아웃 | 5000ms |
| 최대 Keep-Alive 요청 수 | 50 |
| 요청 헤더 최대 크기 | 8KB |
| 요청 바디 최대 크기 | 2MB |
| 응답 헤더 최대 크기 | 8KB |
| 응답 바디 최대 크기 | 2MB |
| 정적 리소스 루트 디렉터리 | webapp |
AbstractHttpApiHandler를 상속하여 원하는 HTTP 메서드를 오버라이드합니다.
import org.dochi.api.handler.AbstractHttpApiHandler;
import org.dochi.external.ExternalRequest;
import org.dochi.external.ExternalResponse;
import org.dochi.http.utils.HttpStatus;
import java.io.IOException;
public class UserApiHandler extends AbstractHttpApiHandler {
@Override
protected void doGet(ExternalRequest request, ExternalResponse response) throws IOException {
String userId = request.getParameter("userId");
// ...
response.send("userId=" + userId, "text/plain; charset=utf-8");
}
@Override
protected void doPost(ExternalRequest request, ExternalResponse response) throws IOException {
// Content-Type: application/x-www-form-urlencoded 자동 파싱
String name = request.getParameter("name");
String email = request.getParameter("email");
// ...
response.setStatus(HttpStatus.CREATED).send();
}
}WebAppServer server = new WebAppServer(8080);
server.getWebService()
.addService("/api/user", new UserApiHandler())
.addService("/api/post", new PostApiHandler());
server.start();-
/경로에는 기본적으로DefaultHttpApiHandler(정적 리소스 서빙)가 등록되어 있습니다. - 경로가 일치하는 핸들러가 없으면
/핸들러로 폴백됩니다.
ExternalRequest 인터페이스를 통해 HTTP 요청 데이터에 접근합니다.
// 기본 메타데이터
String method = request.getMethod(); // "GET", "POST", ...
String uri = request.getRequestURI(); // "/api/user?id=1"
String path = request.getPath(); // "/api/user"
String query = request.getQueryString(); // "id=1"
String protocol = request.getProtocol(); // "HTTP/1.1"
String contentType = request.getContentType(); // "application/json"
int contentLength = request.getContentLength();
// 헤더 조회
String host = request.getHeader("Host");
String auth = request.getHeader("Authorization");
// 파라미터 조회 (쿼리스트링 + application/x-www-form-urlencoded 자동 파싱)
String userId = request.getParameter("userId");
// 입력 스트림 직접 접근
InputStream in = request.getInputStream();ExternalResponse 인터페이스를 통해 HTTP 응답을 구성합니다.
메서드 체이닝을 지원하며, send() 또는 sendError() 호출 시점에 응답이 커밋됩니다.
// 텍스트 응답
response.send("Hello, World!", "text/plain; charset=utf-8");
// 상태코드와 헤더를 직접 설정
response
.setStatus(HttpStatus.CREATED)
.setHeader("X-Custom-Header", "value")
.setCookie("sessionId=abc123; HttpOnly")
.send(responseBody, "application/json");
// 빈 응답 (헤더만 전송)
response.setStatus(HttpStatus.NO_CONTENT).send();
// 에러 응답
response.sendError(HttpStatus.NOT_FOUND);
response.sendError(HttpStatus.BAD_REQUEST, "잘못된 요청입니다.");
// 스트리밍 응답 (OutputStream 직접 사용)
OutputStream out = response.getOutputStream();
out.write(data);기본적으로 프로젝트 루트의 webapp/ 디렉터리에서 정적 파일을 서빙합니다.
project-root/
└── webapp/
├── index.html -> GET / 요청 시 반환
├── style.css
└── images/
└── logo.png-
/경로 요청 시 자동으로index.html을 반환합니다. - 실행 가능한 JAR로 패키징된 경우, JAR 내부의 리소스도 자동으로 탐색합니다.
- 지원하는 MIME 타입:
text/html,text/css,text/plain,application/javascript,application/json,application/xml,image/png,image/jpeg,image/x-icon등
multipart/form-data 요청에서 getPart() 메서드로 각 파트(필드 또는 파일)에 접근합니다.
@Override
protected void doPost(ExternalRequest request, ExternalResponse response) throws IOException {
// 일반 텍스트 필드
Part namePart = request.getPart("name");
String name = new String(namePart.getContent());
// 파일 업로드
Part filePart = request.getPart("profile");
if (filePart.isFile()) {
String fileName = filePart.getFileName();
String contentType = filePart.getContentType();
byte[] fileData = filePart.getContent();
// 파일 처리 ...
}
response.setStatus(HttpStatus.CREATED).send();
}업로드된 파일은 내부적으로 multipart-tmp-file/ 디렉터리에 파일 이름 증복을 막기 위해 UUID 기반으로 임시 저장되며, 요청 처리가 완료된 후 자동으로 삭제됩니다.
이 프로젝트는 학습 및 실험 목적으로 제작되었습니다.
오른쪽 사이드 바를 참고해주세요.