Skip to content

How to use?

John Park edited this page Mar 23, 2026 · 2 revisions

시작하기

의존성 추가

현재 Maven/Gradle 중앙 저장소에 배포되어 있지 않으므로, 로컬 빌드 후 의존성으로 추가하거나 소스를 직접 포함하여 사용합니다.

다음은 빌드 시스템에 따른 의존성 설정 예시입니다.

예시1) Gradle

dochi.jar 파일을 프로젝트의 특정 경로에 저장합니다.

project/
 ├─ build.gradle
 ├─ settings.gradle
 ├─ libs/
 │   └─ dochi.jar
 └─ src/

build.gradledochi.jar 파일을 저장한 경로에 대해 의존성을 설정한다.

dependencies {  
    implementation files('libs/dochi.jar')  
}

예시2) Intellij

  1. File
  2. Project Structure
  3. Libraries
  4. Add dochi.jar

WAS 실행

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/ 디렉터리의 정적 리소스를 서빙합니다.

다중 WAS 인스턴스 동시 실행

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

HTTP API 핸들러 작성

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) 처리

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 기반으로 임시 저장되며, 요청 처리가 완료된 후 자동으로 삭제됩니다.


라이선스

이 프로젝트는 학습 및 실험 목적으로 제작되었습니다.

Clone this wiki locally