스프링이 아닌 JDK내장 웹서버로 개발 중 (com.sun.net.httpserver.HttpServer)

ttttom1·약 15시간 전

프로그래머스 과제 테스트 <api 서버만들기에서도 봤고, 이번 관통플젝에서도 보여셔 쓴다. 또한 이번에 네트워크 공부하고 보니까 또 다르게 보이는 것,백엔드 처리 흐름 이해도 강화하게 되었고, 나와 같은 사람에게 도움이 되겠다 싶어 적는다.

이 코드 다 볼 필요보다. 아래의 사진과, 몇 가지 메소드만 보면 되지 싶다.

package com.ssafy.yumyumcoach.app;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.ssafy.yumyumcoach.manager.FoodManager;
import com.ssafy.yumyumcoach.manager.MealManager;
import com.ssafy.yumyumcoach.manager.NutritionAnalysisManager;
import com.ssafy.yumyumcoach.manager.UserManager;
import com.ssafy.yumyumcoach.repository.FoodRepository;
import com.ssafy.yumyumcoach.repository.MealRepository;
import com.ssafy.yumyumcoach.repository.UserRepository;
import com.ssafy.yumyumcoach.web.AuthController;
import com.ssafy.yumyumcoach.web.FoodController;
import com.ssafy.yumyumcoach.web.HttpSupport;
import com.ssafy.yumyumcoach.web.MealController;
import com.ssafy.yumyumcoach.web.SessionManager;
import com.ssafy.yumyumcoach.web.StaticFileHandler;
import com.ssafy.yumyumcoach.web.UserController;
import com.sun.net.httpserver.HttpServer;

/** Spring 없이 JDK HttpServer로 실행하는 웹 애플리케이션 진입점이다. */
public class YumYumWebApplication {

    private static final int DEFAULT_PORT = 8080;

    private YumYumWebApplication() {
    }

    public static void main(String[] args) throws IOException {
        int port = Integer.getInteger("server.port", DEFAULT_PORT);

        // 모든 화면과 API가 같은 객체를 사용하도록 한 번만 생성해 연결한다.
        UserRepository userRepository = new UserRepository();
        FoodRepository foodRepository = new FoodRepository();
        MealRepository mealRepository = new MealRepository();

        UserManager userManager = new UserManager(userRepository);
        FoodManager foodManager = new FoodManager(foodRepository);
        MealManager mealManager = new MealManager(
                mealRepository,
                foodRepository
        );
        NutritionAnalysisManager nutritionManager =
                new NutritionAnalysisManager(
                        mealRepository,
                        foodRepository
                );
        SessionManager sessionManager = new SessionManager(userRepository);

        HttpServer server = HttpServer.create(
                new InetSocketAddress(
                        InetAddress.getLoopbackAddress(),
                        port
                ),
                0
        );

        server.createContext(
                "/api/auth",
                new AuthController(sessionManager)
        );
        server.createContext(
                "/api/users",
                new UserController(userManager, sessionManager)
        );
        server.createContext(
                "/api/foods",
                new FoodController(foodManager, sessionManager)
        );
        server.createContext(
                "/api/meals",
                new MealController(
                        mealManager,
                        nutritionManager,
                        foodRepository,
                        sessionManager
                )
        );
        server.createContext("/api/health", exchange ->
                HttpSupport.handle(exchange, () -> {
                    if (!exchange.getRequestMethod().equals("GET")) {
                        HttpSupport.methodNotAllowed(exchange, "GET");
                        return;
                    }

                    HttpSupport.sendJson(
                            exchange,
                            200,
                            Map.of("status", "UP")
                    );
                })
        );
        server.createContext(
                "/",
                new StaticFileHandler(Path.of("web"))
        );

        int threadCount = Math.max(
                4,
                Runtime.getRuntime().availableProcessors()
        );
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);
        server.setExecutor(executor);

        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            server.stop(1);
            executor.shutdown();
        }));

        server.start();

        System.out.println("YumYumCoach 웹 서버가 시작되었습니다.");
        System.out.println("접속 주소: http://localhost:" + port);
        System.out.println("종료: Ctrl+C");
    }
}


기본적인 구조도는 이러하다.

그리고 번거로운 메서드들은 나는 다음과 같았다.
지금까지 대화 나누면서 질문하신 YumYumWebApplication.java 파일 내의 주요 구성 요소 및 메서드 목록입니다.


질문하신 주요 메서드 및 구문 리스트

  1. YumYumWebApplication() (생성자)
  • 질문: 빈 생성자는 왜 만들어? 스프링도 아닌데
  • 내용: 외부에서 new YumYumWebApplication()으로 객체를 생성하지 못하게 막는 인스턴스화 방지(private 생성자) 패턴.
  1. HttpServer.create(...)
  • 질문: 이거 설명 파라미터 (InetSocketAddress, 0)
  • 내용: IP/포트 지정(InetSocketAddress) 및 요청 대기 큐 크기(backlog) 설정.
  1. server.createContext("/api/auth", new AuthController(...))
  • 질문: 이거 파서랑 어떻게 매치되고 되는거지?
  • 내용: URL 경로(Prefix)와 담당 컨트롤러(HttpHandler) 매핑 및 파싱/라우팅 원리.
  1. server.createContext("/api/meals", new MealController(...))
  • 질문: 왜 레포지토리를 직접 참조해? 괜찮은거야?
  • 내용: Controller가 Repository를 직접 주입받아 참조하는 구조에 대한 계층형 아키텍처 관점 해석.
  1. server.createContext("/api/health", exchange -> ...)
  • 질문: 이건 뭐야? 라이브러리 가져다가 쓴 거야?
  • 내용: 별도 컨트롤러 파일 없이 인라인 람다식으로 작성한 Health Check API 및 internal 유틸(HttpSupport) 활용.
  1. if (!exchange.getRequestMethod().equals("GET")) (Health Check 내부)
  • 질문: 이거 해석좀 (겟이 아니면?)
  • 내용: GET 이외의 HTTP 메서드로 요청이 들어왔을 때 405 Method Not Allowed를 응답하고 차단하는 예외 처리.
  1. server.createContext("/", new StaticFileHandler(Path.of("web")))
  • 질문: 이 코드의 역할은?
  • 내용: web 폴더 내 정적 파일(HTML/CSS/JS)을 클라이언트에 응답하기 위한 루트 경로 핸들러 연결.
  1. Runtime.getRuntime().addShutdownHook(...)
  • 질문: 이 코드의 역할은?
  • 내용: 서버 종료 시(Ctrl+C 등) 진행 중인 요청을 정리하고 스레드 풀 및 서버를 안전하게 닫는 셧다운 후크(Graceful Shutdown).

관련해서 추가로 주고받은 개념 질문

  • Executors.newFixedThreadPool(...) / `server.setExecutor(...)`
  • 질문: HttpServer 기본 스레드 그거 없어? 우리가 executor service 만든 거야? 왜? / 스프링에서는 알아서 했던 건가?
  • 내용: JDK HttpServer는 기본 멀티스레딩 스레드 풀이 없어서 직접 ExecutorService를 넘겨줘야 함과 스프링 부트 내장 Tomcat과의 비교.
  • 프로젝트 구조 질문
  • 질문: Application.java가 왜 두 개야? 시작점은 하나 아니야?
  • 내용: 실행 환경(웹 실행용 vs 콘솔/CLI 및 테스트 실행용)에 따라 main 진입점을 나누어 둘 수 있음.
profile
World Class, The Beginning.

0개의 댓글