HttpConnectionHandler.java
package sprout.server.builtins;
import sprout.mvc.dispatcher.RequestDispatcher;
import sprout.mvc.http.HttpRequest;
import sprout.mvc.http.HttpResponse;
import sprout.mvc.http.parser.HttpRequestParser;
import sprout.server.*;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
public class HttpConnectionHandler implements ReadableHandler, WritableHandler {
private final SocketChannel channel;
private final Selector selector;
private final RequestDispatcher dispatcher;
private final HttpRequestParser parser;
private final RequestExecutorService requestExecutorService;
private final ByteBuffer readBuffer = ByteBuffer.allocate(8192);
private volatile ByteBuffer writeBuffer;
private HttpConnectionStatus currentState = HttpConnectionStatus.READING;
public HttpConnectionHandler(SocketChannel channel, Selector selector, RequestDispatcher dispatcher, HttpRequestParser parser, RequestExecutorService requestExecutorService, ByteBuffer initialBuffer) {
this.channel = channel;
this.selector = selector;
this.dispatcher = dispatcher;
this.parser = parser;
this.requestExecutorService = requestExecutorService;
if (initialBuffer != null && initialBuffer.hasRemaining()) {
this.readBuffer.put(initialBuffer);
}
System.out.println("connection established from " + channel.socket() + " with initial buffer size: " + readBuffer.remaining() + " bytes");
}
@Override
public void read(SelectionKey key) throws Exception {
System.out.println("Try to read from " + channel.socket() + " with current state: " + currentState + " and buffer size: " + readBuffer.remaining() + " bytes");
if (currentState != HttpConnectionStatus.READING) return;
System.out.println("Read from " + channel.socket() + " with current state: " + currentState + " and buffer size: " + readBuffer.remaining() + " bytes");
int bytesRead = channel.read(readBuffer);
if (bytesRead == -1) {
System.out.println("Bytes read is -1. Closing connection...");
closeConnection(key);
return;
}
// FIX : '읽기 모드'로 전환
readBuffer.flip();
if (HttpUtils.isRequestComplete(readBuffer)) {
System.out.println("Request is Completed!");
// 3. 완전한 요청이 왔다면, 처리 상태로 변경
this.currentState = HttpConnectionStatus.PROCESSING;
key.interestOps(0); // 이벤트 감지 일단 중지
// readBuffer에서 요청 전문(raw request)을 추출
byte[] requestBytes = new byte[readBuffer.remaining()];
readBuffer.get(requestBytes);
String rawRequest = new String(requestBytes, StandardCharsets.UTF_8);
System.out.println("--- Parsing Request ---");
System.out.println(rawRequest);
System.out.println("--- End of Request ---");
// 4. 비즈니스 로직은 스레드 풀에 위임 (기존과 동일)
requestExecutorService.execute(() -> {
try {
HttpRequest<?> req = parser.parse(rawRequest);
HttpResponse res = new HttpResponse();
dispatcher.dispatch(req, res);
// 5. 응답 준비 및 쓰기 상태 전환
this.writeBuffer = HttpUtils.createResponseBuffer(res.getResponseEntity());
this.currentState = HttpConnectionStatus.WRITING;
// 6. Selector에 쓰기 이벤트 감지 요청
key.interestOps(SelectionKey.OP_WRITE);
selector.wakeup();
} catch (Exception e) {
closeConnection(key);
e.printStackTrace();
}
});
// 버퍼 초기화 (다음 요청을 위해)
readBuffer.clear();
}
}
private void closeConnection(SelectionKey key) {
try {
key.cancel();
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void write(SelectionKey key) throws IOException {
if (currentState != HttpConnectionStatus.WRITING || writeBuffer == null) return;
channel.write(writeBuffer);
if (!writeBuffer.hasRemaining()) {
// 버퍼의 모든 데이터를 전송 완료
this.currentState = HttpConnectionStatus.READING;
this.writeBuffer = null;
// keep-alive 지원: 다음 요청을 기다리기 위해 READ 모드로 전환
key.interestOps(SelectionKey.OP_READ);
selector.wakeup();
// readBuffer 초기화 (다음 요청 수신 준비)
readBuffer.clear();
}
// 버퍼에 데이터가 남아있다면 아무것도 하지 않음
// 채널이 다시 쓸 준비가 되면 셀렉터가 알려줄 것
}
}