Skip to content

Latest commit

Β 

History

235 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Dochi WAS (Web Application Server)

WASλ₯Ό 직접 κ΅¬ν˜„ν•˜λ©° ꡬ쑰와 μ„±λŠ₯을 λΆ„μ„ν•˜κ³  κ°œμ„ ν•œ μ—”μ§€λ‹ˆμ–΄λ§ 쀑심 ν”„λ‘œμ νŠΈ


κ°œμš”

ν”„λ‘œμ νŠΈ κ°œμš”

λ³‘μ—­νŠΉλ‘€λ‘œ ν˜ΈμŠ€νŒ… νšŒμ‚¬μ—μ„œ μ‹œμŠ€ν…œ μ—”μ§€λ‹ˆμ–΄λ‘œ κ·Όλ¬΄ν•˜λ©° μ„œλ²„ ν•˜λ“œμ›¨μ–΄λ‚˜ μ†Œν”„νŠΈμ›¨μ–΄λ₯Ό 처음 μ ‘ν•˜κ²Œ λ˜λ©΄μ„œ μ„œλ²„ κ°œλ°œμ— 관심이 μƒκ²ΌμŠ΅λ‹ˆλ‹€. 업무 쀑 ν•˜λ‚˜λŠ” Apache, Nginx, Tomcat을 μ„€μΉ˜μ™€ μš΄μ˜μ„ ν–ˆμ—ˆμŠ΅λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ μ΄λŸ¬ν•œ μž‘μ—…λ§ŒμœΌλ‘œλŠ” λ™μž‘ 원리λ₯Ό μ΄ν•΄ν•˜κΈ° μ–΄λ ΅λ‹€κ³  λŠκΌˆκΈ°μ— 직접 WAS(Web Application Server)λ₯Ό Java둜 κ΅¬ν˜„ν•˜κ³  μ§€μ†μ μœΌλ‘œ ꡬ쑰와 μ„±λŠ₯을 κ°œμ„ ν•΄μ™”μŠ΅λ‹ˆλ‹€.

핡심 μ‹œν€€μŠ€ λ‹€μ΄μ–΄κ·Έλž¨

sequenceDiagram

  

  

actor Developer

  

  

participant WAS as WebAppServer

  

participant Srv as Server

  

participant WS as WebService

  

participant HPH as HttpProtocolHandler

  

participant AEP as AbstractEndpoint

  

participant ACC as Acceptor

  

participant TP as ThreadPoolExecutor

  

participant PROC as AbstractHttpProcessor

  

participant ADP as InternalAdapter

  

participant API as HttpApiHandler

  

actor Client

  

  

%% 1. Startup

  

Developer->>WAS: start()

  

WAS->>Srv: start()

  

Srv->>WS: init() β†’ HTTP APIs

  

Srv->>HPH: init() β†’ Endpoint

  

HPH->>AEP: bind & configure

  

AEP->>ACC: start() β†’ acceptor

  

AEP->>TP: start() β†’ executor

  

  

%% 2. Accept

  

Client->>ACC: TCP connect

  

ACC->>AEP: socket

  

AEP->>TP: submit task

  

  

%% 3. Request Processing

  

TP->>PROC: process(socket)

  

  

loop Persistent Connection

  

PROC->>PROC: parse request

  

PROC->>ADP: service(request, response)

  

  

ADP->>API: dispatch

  

API-->>ADP: result

  

  

ADP-->>PROC: commit

  

PROC->>PROC: serialize response

  

PROC->>Client: send response

  

PROC->>PROC: recycle

  

end

  

  

%% 4. Close

  

PROC->>Client: close

  

  

%% 5. Shutdown

  

Developer->>WAS: stop()

  

WAS->>Srv: stop()

  

  

Srv->>HPH: stop()

  

HPH->>AEP: stop()

  

AEP->>ACC: close acceptor

  

AEP->>TP: shutdown executor

  

  

Srv->>WS: destroy() β†’ HTTP APIs

  

Srv->>HPH: destroy()

  

HPH->>AEP: unbind resources
Loading

핡심 클래슀 λ‹€μ΄μ–΄κ·Έλž¨

classDiagram

direction TB

  

namespace lifecycle {

class Lifecycle {

<<interface>>

+init()

+start()

+stop()

+destroy()

}

  

class AbstractLifecycle {

<<abstract>>

-state

-lifecycles

#initInternal()*

#startInternal()*

#stopInternal()*

#destroyInternal()*

}

}

  

namespace bootstrap {

class WebAppServer {

+start()

+stop()

}

  

class Server

  

class WebService {

+addService(path, handler) WebService

}

}

  

namespace net {

class AbstractEndpoint {

<<abstract>>

#int port

#String hostName

#Executor executor

#Deque~AbstractSocketTask~ socketTaskPool

+setHandler(Handler)

#bind()*

#createSocketTask(AbstractSocketWrapper)* AbstractSocketTask

}

  

class BioEndpoint

  

class Acceptor

  

class AbstractSocketWrapper {

<<abstract>>

+read()

+write()

+close()

}

  

class BioSocketWrapper

  

class AbstractSocketTask {

<<abstract>>

+reset(AbstractSocketWrapper)

+run()

}

  

class BioSocketTask

}

  

namespace internal {

class HttpProtocolHandler

  

class AbstractHttpProcessor {

<<abstract>>

#Request request

#Response response

#Adapter adapter

+process(AbstractSocketWrapper) SocketState

}

  

class Http11Processor {

+service(AbstractSocketWrapper) SocketState

+recycle()

}

  

class internal_Request["internal.Request"]

class internal_Response["internal.Response"]

}

  

namespace connector {

class Adapter {

<<interface>>

+service(internal.Request, internal.Response)

}

  

class InternalAdapter {

-ensureRequestFacade(internal.Request)

-ensureResponseFacade(internal.Response)

}

  

class connector_Request["connector.Request"]

class connector_Response["connector.Response"]

}

  

namespace external {

class ExternalRequest {

<<interface>>

}

  

class ExternalResponse {

<<interface>>

}

}

  

namespace api_handler {

class HttpApiHandler {

<<interface>>

+init(WebServiceConfig)

+service(ExternalRequest, ExternalResponse)

+destroy()

}

  

class AbstractHttpApiHandler {

<<abstract>>

#doGet(req, res)

#doPost(req, res)

#doPut(req, res)

#doPatch(req, res)

#doDelete(req, res)

}

}

  

%% lifecycle

Lifecycle <|.. AbstractLifecycle

AbstractLifecycle <|-- Server

AbstractLifecycle <|-- WebService

AbstractLifecycle <|-- HttpProtocolHandler

AbstractLifecycle <|-- AbstractEndpoint

  

%% bootstrap

WebAppServer --> Server : creates & starts

Server --> WebService : manages lifecycle

Server --> HttpProtocolHandler : manages lifecycle

  

%% net

AbstractEndpoint <|-- BioEndpoint

AbstractSocketWrapper <|-- BioSocketWrapper

AbstractSocketTask <|-- BioSocketTask

  

BioEndpoint --> Acceptor : creates & runs

BioEndpoint --> BioSocketWrapper : wraps socket

BioEndpoint --> BioSocketTask : creates

  

AbstractSocketTask --> AbstractSocketWrapper : uses

  

%% internal

HttpProtocolHandler --> AbstractEndpoint : owns & manages lifecycle

AbstractHttpProcessor <|-- Http11Processor

Http11Processor --> InternalAdapter : delegates service

AbstractHttpProcessor --> internal_Request

AbstractHttpProcessor --> internal_Response

AbstractHttpProcessor --> AbstractSocketWrapper : reads/writes

  

%% connector

Adapter <|.. InternalAdapter

InternalAdapter --> WebService : routes by path

  

%% 핡심 계측 μ—°κ²°

InternalAdapter --> connector_Request : ensures facade

InternalAdapter --> connector_Response : ensures facade

  

%% external μ—°κ²°

connector_Request ..|> ExternalRequest

connector_Response ..|> ExternalResponse

  

%% api

HttpApiHandler <|.. AbstractHttpApiHandler

WebService --> HttpApiHandler : dispatches
Loading

핡심 문제 ν•΄κ²°

Dochi WASλ₯Ό κ°œλ°œν•˜λ©΄μ„œ λ°œμƒν•œ μ„±λŠ₯ 병λͺ©μ„ κ³„μΈ‘ν•˜κ³ , 원인을 λΆ„μ„ν•œ λ’€ ꡬ쑰적으둜 κ°œμ„ ν–ˆμŠ΅λ‹ˆλ‹€. 이에 λŒ€ν•œ μƒμ„Έν•œ μ‹€ν—˜ κ³Όμ •, 병λͺ© 뢄석, κ΅¬ν˜„ λ‚΄μš©, μ„±λŠ₯ λΉ„κ΅λŠ” λ‹€μŒ μœ„ν‚€ λ¬Έμ„œμ—μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

상세 μœ„ν‚€ λ¬Έμ„œ: Dochi WAS Wiki

1. μ›Œμ»€ μŠ€λ ˆλ“œ ν’€ 증가 μ‹€ν—˜ 기반으둜 ν™•μž₯ μŠ€λ ˆλ“œ ν’€ κ΅¬ν˜„

  • 문제 ν˜„μƒ: λΆ€ν•˜ ν…ŒμŠ€νŠΈμ‹œ P99 Latency λ†’κ³  일정 주기둜 급증/급감 νŒ¨ν„΄ λ°œμƒ
  • 병λͺ© 계츑: 문제 λ°œμƒ μ‹œμ μ˜ 둜그둜 μž‘μ—…μ΄ 큐에 주기적으둜 λ°€λ Έλ‹€κ°€ ν•œκΊΌλ²ˆμ— λΉ„μ›Œμ§€λŠ” 것을 확인
  • 원인 뢄석: λ™μ ‘μžμ˜ μˆ˜κ°€ μŠ€λ ˆλ“œν’€μ˜ 크기보닀 μ»Έμ„λ•Œ μŠ€λ ˆλ“œ ν’€ 병λͺ© λ°œμƒ
    1. λͺ¨λ“  μŠ€λ ˆλ“œκ°€ λ™μ‹œμ— λΈ”λ‘œν‚Ή λ˜λŠ” μˆœκ°„μ΄ λ°œμƒ
    2. μŠ€λ ˆλ“œ 풀이 μΌμ‹œμ μœΌλ‘œ 멈좀
    3. κ·Έ μ‹œκ°„ λ™μ•ˆ 큐에 μš”μ²­ μž‘μ—…μ΄ 계속 μŒ“μ—¬μ„œ P99 급증
    4. μš”μ²­μ΄ μˆ˜μ‹ λ˜κ³  μŠ€λ ˆλ“œλ“€μ΄ κΉ¨μ–΄λ‚˜λ©΄μ„œ ν•œκΊΌλ²ˆμ— μ²˜λ¦¬ν•˜μ—¬ P99 급락
  • ν•΄κ²° λ°©μ•ˆ: λ™μ ‘μž μˆ˜μ™€ λΉ„λ‘€ν•˜λŠ” λ™μ μœΌλ‘œ ν™•μž₯κ°€λŠ₯ν•œ μ›Œμ»€ μŠ€λ ˆλ“œ ν’€ ν•„μš”
  • κ°œμ„  μ„±κ³Ό: P99 Latency 급증/급감 νŒ¨ν„΄ 완화와 μ•½ 4.81λ°° κ°œμ„  및 λ©”λͺ¨λ¦¬ νš¨μœ¨ν™”
  • 상세 κ³Όμ •: 1. μŠ€λ ˆλ“œ ν’€ 증가 μ‹€ν—˜ 기반 ν™•μž₯ μŠ€λ ˆλ“œ ν’€ κ΅¬ν˜„

2. μš”μ²­ 처리 λ‘œμ§μ—μ„œ 병λͺ© 지점 발견 ν›„ 응닡 속도 ν–₯상

3. Blocking I/O 병λͺ© ν•΄μ†Œ

  • 문제 ν˜„μƒ: λ™μ ‘μž μˆ˜κ°€ λŠ˜μ–΄λ‚¨μ— 따라 Tail Latency 증가
  • 병λͺ© 계츑: λ™μ ‘μžμ™€ λΉ„λ‘€ν•˜κ²Œ μ›Œμ»€ μŠ€λ ˆλ“œκ°€ λŠ˜μ–΄λ‚˜λ„ P99 Latency 증가
  • 원인 뢄석: 컀널 μŠ€λ ˆλ“œ μˆ˜κ°€ λŠ˜μ–΄λ‚¨μ— 따라 μ»¨ν…μŠ€νŠΈ μŠ€μœ„μΉ­ λΉ„μš©λ„ 증가
  • ν•΄κ²° λ°©μ•ˆ: λ™μ ‘μž 증가에도 컀널 μŠ€λ ˆλ“œ ν’€μ˜ ν™•μž₯없이 μš”μ²­μˆœ 처리
  • κ°œμ„  μ„±κ³Ό: P99 Latency μ•½ 6.95λ°° κ°œμ„ 
  • 상세 κ³Όμ •:

μ‹œμž‘ν•˜κΈ°

μ‚¬μš© κΈ°λŠ₯

  • HTTP/1.1 지원: HTTP/1.1 λ©”μ„Έμ§€ νŒŒμ‹±/직렬화 , Keep-Alive νƒ€μž„μ•„μ›ƒ/μ΅œλŒ€ 개수 μ„€μ •, μš”μ²­ 처리 νŒŒμ΄ν”„λΌμ΄λ‹,
  • BIO 기반 λ„€νŠΈμ›Œν¬ I/O: java.net.Socket을 μ‚¬μš©ν•œ Blocking I/O
  • **HTTP API 등둝 및 λΌμš°νŒ… **
    • 경둜 별 HttpApiHandler 등둝 (/ κΈ°λ³Έ ν•Έλ“€λŸ¬ μžλ™ 등둝, λ―Έλ§€μΉ­ μ‹œ 루트 ν•Έλ“€λŸ¬ λ°˜ν™˜)
    • GET, POST, PUT, PATCH, DELETE 처리 및 미지원 λ©”μ„œλ“œμ— λŒ€ν•œ μ—λŸ¬ 응닡(405/501)
  • HTML Form 데이터 νŒŒμ‹±
    • application/x-www-form-urlencoded νŒŒλΌλ©”ν„°λ‘œ μ ‘κ·Ό κ°€λŠ₯
    • multipart/form-data: 각 νŒŒνŠΈλ³„λ‘œ μ ‘κ·Ό κ°€λŠ₯, μž„μ‹œ 파일 관리
  • 정적 λ¦¬μ†ŒμŠ€ μ„œλΉ™: 루트 디렉터리 λ‚΄ 파일 탐색 및 읽기, JAR 파일 λ‚΄λΆ€ λ¦¬μ†ŒμŠ€ 탐색(Embedded JAR 지원)
  • λ‹€μ΄λ‚˜λ―Ή μ›Œμ»€ μŠ€λ ˆλ“œν’€: μš”μ²­λœ μ†ŒμΌ“ μž‘μ—… μˆ˜μ— 따라 μ§€μ •ν•œ μŠ€λ ˆλ“œ ν’€ μ΅œμ†Œ/μ΅œλŒ€ 크기만큼 λ™μ μœΌλ‘œ ν™•μž₯/μΆ•μ†Œ
  • 가상 μŠ€λ ˆλ“œ 지원: Java 21 μ΄μƒμ—μ„œ useVirtualThreads μ˜΅μ…˜μœΌλ‘œ ν™œμ„±ν™”
    • BIO둜 μΈν•œ 컀널 μŠ€λ ˆλ“œμ˜ λΈ”λ‘œν‚Ήμ„ ν”Όν•˜μ—¬ NIO λͺ¨λΈ 기반의 WAS μ„±λŠ₯ 도달
    • 단, μ›Œμ»€ μŠ€λ ˆλ“œ λ‚΄λΆ€ λ‘œμ§μ— synchronized μ‚¬μš©μ‹œ μ„±λŠ₯ μ €ν•˜ λ°œμƒ

μ˜μ‘΄μ„± μΆ”κ°€

ν˜„μž¬ Maven/Gradle 쀑앙 μ €μž₯μ†Œμ— λ°°ν¬λ˜μ–΄ μžˆμ§€ μ•ŠμœΌλ―€λ‘œ, 둜컬 λΉŒλ“œ ν›„ μ˜μ‘΄μ„±μœΌλ‘œ μΆ”κ°€ν•˜κ±°λ‚˜ μ†ŒμŠ€λ₯Ό 직접 ν¬ν•¨ν•˜μ—¬ μ‚¬μš©ν•©λ‹ˆλ‹€.

λ‹€μŒμ€ λΉŒλ“œ μ‹œμŠ€ν…œμ— λ”°λ₯Έ μ˜μ‘΄μ„± μ„€μ • μ˜ˆμ‹œμž…λ‹ˆλ‹€.

μ˜ˆμ‹œ1) Gradle

dochi.jar νŒŒμΌμ„ ν”„λ‘œμ νŠΈμ˜ νŠΉμ • κ²½λ‘œμ— μ €μž₯ν•©λ‹ˆλ‹€.

project/
 β”œβ”€ build.gradle
 β”œβ”€ settings.gradle
 β”œβ”€ libs/
 β”‚   └─ dochi.jar
 └─ src/

build.gradle에 dochi.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 was = new WebAppServer(8080);
        was.start();
    }
}

κΈ°λ³Έ μ„€μ •μœΌλ‘œ 8080 ν¬νŠΈμ—μ„œ μ„œλ²„κ°€ μ‹œμž‘λ˜λ©°, webapp/ λ””λ ‰ν„°λ¦¬μ˜ 정적 λ¦¬μ†ŒμŠ€λ₯Ό μ„œλΉ™ν•©λ‹ˆλ‹€.

닀쀑 WAS μΈμŠ€ν„΄μŠ€ λ™μ‹œ μ‹€ν–‰

각 WebAppServer μΈμŠ€ν„΄μŠ€λŠ” ν¬νŠΈμ™€ 호슀트 λ„€μž„μœΌλ‘œ 독립적인 μ„œλ²„λ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€.

  • ν¬νŠΈμ™€ 호슀트 λ„€μž„μ΄ λͺ¨λ‘ 같은 κ²½μš°μ— LifecycleException이 λ°œμƒν•©λ‹ˆλ‹€.
  • JVM μ’…λ£Œμ‹œ ShutdownTasksManagerκ°€ μ‹€ν–‰ν–ˆλ˜ μˆœμ„œλŒ€λ‘œ 각 μ„œλ²„ μΈμŠ€ν„°μŠ€λ₯Ό μ•ˆμ „ν•˜κ²Œ μ’…λ£Œν•©λ‹ˆλ‹€.
  • μŠ€λ ˆλ“œν’€μ€ μΈμŠ€ν„΄μŠ€λ§ˆλ‹€ λ…λ¦½μ μœΌλ‘œ μƒμ„±λ˜λ―€λ‘œ, μΈμŠ€ν„΄μŠ€ μˆ˜μ™€ μ΅œλŒ€ μŠ€λ ˆλ“œ 수λ₯Ό ν•¨κ»˜ κ³ λ €ν•˜μ—¬ μ‹œμŠ€ν…œ λ¦¬μ†ŒμŠ€λ₯Ό 섀계해야 ν•©λ‹ˆλ‹€.
public class WebAppServerLauncher {
    public static void main(String[] args) throws LifecycleException {
        // 8080 ν¬νŠΈμ— λ°”μΈλ”©λœ 독립 μ„œλ²„ μΈμŠ€ν„΄μŠ€
        WebAppServer remoteServer = new WebAppServer(8080, "0.0.0.0");
        remoteServer.getWebService().setWebResourceRootPath("webapp");
        remoteServer.getWebService()
            .addService("/api/user", new UserApiHandler())
            .addService("/api/post", new PostApiHandler());
        remoteServer.getThreadPool().setMinSpareThreads(200);
        remoteServer.getThreadPool().setMaxThreads(2000);

        // 9090 ν¬νŠΈμ— λ°”μΈλ”©λœ 독립 μ„œλ²„ μΈμŠ€ν„΄μŠ€
        WebAppServer localServer = new WebAppServer(9090, "localhost");

        // 각 μΈμŠ€ν„΄μŠ€ λ…λ¦½μ μœΌλ‘œ μ‹œμž‘ (ν¬νŠΈκ°€ 달라야 함)
        remoteServer.start();
        localServer.start();
    }
}

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();
    }
}

HTTP API ν•Έλ“€λŸ¬ 등둝

WebAppServer server = new WebAppServer(8080);

server.getWebService()
    .addService("/api/user", new UserApiHandler())
    .addService("/api/post", new PostApiHandler());

server.start();
  • / κ²½λ‘œμ—λŠ” 기본적으둜 DefaultHttpApiHandler(정적 λ¦¬μ†ŒμŠ€ μ„œλΉ™)κ°€ λ“±λ‘λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
  • κ²½λ‘œκ°€ μΌμΉ˜ν•˜λŠ” ν•Έλ“€λŸ¬κ°€ μ—†μœΌλ©΄ / ν•Έλ“€λŸ¬λ‘œ ν΄λ°±λ©λ‹ˆλ‹€.

μ„œλ²„ μ„€μ •

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

μš”μ²­ 처리

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 기반으둜 μž„μ‹œ μ €μž₯되며, μš”μ²­ μ²˜λ¦¬κ°€ μ™„λ£Œλœ ν›„ μžλ™μœΌλ‘œ μ‚­μ œλ©λ‹ˆλ‹€.


λΌμ΄μ„ μŠ€

이 ν”„λ‘œμ νŠΈλŠ” ν•™μŠ΅κ³Ό μ‹€ν—˜ λͺ©μ μœΌλ‘œ μ œμž‘λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages