Servlet 기반 API 구현과 JSON 응답 흐름 이해

최정윤·2026년 6월 29일

Spring

목록 보기
4/38

서버가 요청마다 다른 응답을 만들 수 있어야 하는 이유

초기의 웹 페이지는 이미 만들어진 HTML 파일을 그대로 내려주는 방식이었다. 이런 방식은 모든 사용자에게 같은 화면을 보여주기에는 충분하지만, 요청마다 다른 결과를 만들어야 하는 기능에는 한계가 있다.

예를 들어 영화 추천 기능을 만든다고 하면, 서버는 단순히 고정된 문서를 보내는 것이 아니라 요청이 들어온 순간 Java코드를 실행해 영화 목록 중 하나를 선택하고, 그 결과를 클라이언트에게 응답해야 한다.

즉, API 서버는 단순한 파일 전달자가 아니라 요청을 해석하고, 필요한 로직을 실행하고, 응답을 만들어내는 프로그램이어야 한다. Servlet은 이 흐름을 직접 확인하기 좋은 가장 기본적인 Java 웹 기술이다.

Servlet과 Servlet Container의 역할

Servlet클라이언트의 HTTP 요청을 처리하고 응답을 만들어내는 Java 클래스다. 하지만 Servlet 클래스 혼자서는 실행될 수 없다. 요청을 받고, 알맞은 Servlet을 찾아 실행하고, 응답을 다시 클라이언트에게 보내주는 실행 환경이 필요하다. 이 역할을 하는 것이 Servlet Container이며, 대표적으로 Tomcat이 있다.

식당으로 비유하면 다음과 같다.

웹 개념비유역할
Client / Postman손님서버에 요청을 보냄
URL주문 메뉴 이름어떤 기능을 실행할지 지정
Servlet Container매장 관리자요청을 받아 담당 Servlet에게 전달
Servlet요리 담당자실제 로직을 실행하고 결과를 만듦
Request주문서클라이언트가 보낸 요청 정보
Response완성된 음식서버가 클라이언트에게 보내는 응답

클라이언트가 /hello 요청을 보내면 Web Application Server 내부에서 Request가 생성되고, Servlet이 실행된 뒤 Response를 통해 응답이 반환되는 흐름을 보여준다.

/hello API 구현으로 확인한 기본 요청-응답 구조

첫번째 API는 /hello 요청을 받으면 hello 문자열을 응답하는 단순한 Servlet API이다.

핵심 코드는 다음 구조로 동작한다.

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/plain; charset=UTF-8");
        resp.getWriter().print("hello");
    }
}

여기서 중요한 점은 hello를 Java 메서드의 반환값처럼 return "hello"로 돌려주는 것이 아니라는 점이다. Servlet에서는 HTTPServletResponse 객체에 직접 응답 내용을 작성한다.

resp.getWriter().print("hello");

즉, hello는 메서드의 return 값이 아니라 HTTP 응답 body에 작성된 문자열이다.

또한 Spring Boot에서 @WebServlet이 붙은 Servlet을 인식하게 하려면 메인 애플리케이션 클래스에 @ServletComponentScan을 추가해야 한다.

@ServletComponentScan
@SpringBootApplication
public class SpringPracticeApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringPracticeApplication.class, args);
    }
}

HelloServlet 구현 코드, @ServletComponentScan 설정, Spring Boot 내장 Tomcat 서버 실행 로그를 한 화면에서 확인한 이미지다.

서버 실행과 API 성공은 다르다

실습 중 "연결이 안 된 것 같다"는 의문이 있었다. 이때 확인해야 할 기준은 두 가지로 나뉜다.

확인 대상의미
IntelliJ 실행 로그서버가 켜졌는지 확인
Postman 응답 결과API가 실제 요청을 처리했는지 확인

실행 로그에 다음과 같은 문장이 보이면 서버는 정상적으로 실행된 것이다.

Tomcat started on port 8080
Started SpringPracticeApplication

하지만 이것만으로 API 성공을 판단할 수는 없다. 실제 성공 여부는 Postman에서 요청을 보내고 200 OK 응답 body를 확인해야 한다.

GET http://localhost:8080/hello 요청 결과는 다음과 같았다.

Status: 200 OK
Body: hello

Postman에서 GET /hello 요청을 보낸 결과 200 OK와 hello 응답을 확인한 화면이다.

같은 프로젝트 안에서 여러 Servlet이 함께 동작할 수 있는 이유

HelloServlet을 만든 뒤 같은 프로젝트와 같은 기본 패키지 안에 MovieRecommendationServlet을 추가했다. 이때 기존 HelloServlet이 더 이상 작동하지 않는지 의문이 생겼다.

결론은 문제 없이 함께 동작한다였다. 이유는 두 Servlet의 URL 매핑이 다르기 때문이다.

요청 URL처리하는 Servlet응답
/helloHelloServlethello
/movies/recommendationMovieRecommendationServlet영화 추천 JSON

Servlet Container는 클래스가 여러 개 있더라도 요청 URL을 기준으로 어떤 Servlet을 실행할지 결정한다. 따라서 URL 매핑이 다르면 하나의 애플리케이션 안에서 여러 API가 동시에 존재할 수 있다.

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
}
@WebServlet("/movies/recommendation")
public class MovieRecommendationServlet extends HttpServlet {
}

중요한 기준은 클래스가 같은 패키지에 있는지가 아니라, 각 Servlet이 어떤 URL과 연결되어 있는지이다.

영화 추천 API로 확인한 JSON 응답 흐름

두번째 API는 /movies/recommendation 요청이 들어오면 영화 목록 중 하나를 랜덤으로 선택하고, JSON 형태로 응답하는 기능이다.

핵심 흐름은 다음과 같다.

GET /movies/recommendation 요청
→ MovieRecommendationServlet 실행
→ 영화 목록에서 랜덤으로 하나 선택
→ Map에 title이라는 key로 영화 제목 저장
→ ObjectMapper로 JSON 문자열 변환
→ Response body에 JSON 작성

핵심 코드는 다음과 같은 흐름을 가진다.

int randomIndex = (int) (Math.random() * recommendedMovies.size());
String randomMovie = recommendedMovies.get(randomIndex);

Map<String, String> movieMap = Map.of("title", randomMovie);

response.setContentType("application/json; charset=UTF-8");

String json = objectMapper.writeValueAsString(movieMap);
response.getWriter().print(json);

여기서 Map은 JSON이 아니다. Map은 Java 안에서 사용하는 key-value 자료구조이고, ObjectMapper가 이 Map을 JSON 문자열로 변환한다.

단계형태
Map.of("title", randomMovie)Java 자료구조
objectMapper.writeValueAsString(movieMap)JSON 문자열
response.getWriter().print(json)HTTP 응답 body 작성

또한 JSON 응답을 보낼 때는 응답 형식을 명확히 알려주어야 한다.

response.setContentType("application/json; charset=UTF-8");

/hello API에서는 일반 텍스트였기 때문에 text/plain을 사용했고, 영화 추천 API에서는 JSON을 보내기 때문에 application/json을 사용했다.

APIContent-Type응답 예시
/hellotext/plainhello
/movies/recommendationapplication/json{ "title": "쇼탱크 탈출" }

MovieRecommendationServlet을 구현하고, 같은 애플리케이션 안에서 /movies/recommendation API를 추가한 화면이다.

Postman으로 확인한 동적 JSON 응답

Postman에서 GET http://localhost:8080/movies/recommendation 요청을 보낸 결과, 다음과 같은 JSON 응답을 확인했다.

{
  "title": "쇼생크 탈출"
}

또 한 번 요청을 보내면 다음과 같이 다른 영화가 반환되기도 했다.

{
  "title": "대부"
}

또 다른 요청에서는 다음과 같은 결과도 확인했다.

{
  "title": "다크 나이트"
}

이 결과는 같은 URL을 호출해도 서버 내부의 Java 로직이 매번 다시 실행된다는 것을 보여준다. Postman의 Send 버튼을 누를 때마다 새로운 HTTP 요청이 발생하고, 그때마다 doGet() 메서드가 다시 실행된다.

다만 랜덤이기 때문에 매번 반드시 다른 영화가 나오는 것은 아니다. 같은 영화가 연속으로 나와도 정상이다.

GET /movies/recommendation 요청 결과 200 OK와 함께 { "title": "쇼생크 탈출" } JSON 응답을 확인한 화면이다.

같은 URL로 여러 번 요청을 보냈을 때 영화 제목이 달라지는 것을 확인한 화면이다. 이를 통해 응답이 고정된 파일이 아니라 서버 로직에 의해 동적으로 생성된다는 점을 확인할 수 있다.

실습 중 알게된 점

단순 문자열 응답과 JSON 응답은 다르다

hello는 단순 텍스트 응답이고, 영화 추천 결과는 JSON 응답이다. 이 차이는 단순히 겉모양만 다른 것이 아니라 클라이언트가 데이터를 해석하는 방식에도 영향을 준다.

hello
{
  "title": "대부"
}

실제 API에서는 JSON 형식이 더 많이 사용된다. 프론트엔드나 다른 클라이언트가 key를 기준으로 필요한 값을 꺼내기 쉽기 때문이다.

다음 단계에서 연결해서 볼 내용

Servlet 실습은 Spring MVC로 넘어가기 전의 기본 구조를 이해하는 데 의미가 있다. 나중에 @RestController, @GetMapping, ResponseEntity를 사용하면 지금보다 코드가 훨씬 짧아진다.

하지만 내부 흐름은 크게 다르지 않다.

요청 URL 확인
→ 알맞은 처리 코드 실행
→ 응답 데이터 생성
→ 클라이언트에게 반환

Servlet에서는 HttpServletResponse에 직접 응답을 작성했다. 반면 Spring Controller에서는 메서드의 반환값을 Spring이 HTTP 응답으로 변환해준다.

구분Servlet 방식Spring Controller 방식
URL 매핑@WebServlet@GetMapping
요청 처리 메서드doGet()일반 메서드
응답 작성response.getWriter().print()return
JSON 변환직접 ObjectMapper 사용Spring이 자동 처리

따라서 이번 Servlet 실습은 이후 Spring Controller를 이해하기 위한 기초가 된다. Spring이 편하게 감싸주기 전에는 요청과 응답을 개발자가 직접 다뤄야 한다는 점을 확인할 수 있었다.

마무리

Servlet 기반 API 구현을 통해 클라이언트 요청이 서버에 도착한 뒤 어떤 Servlet이 실행되고, 응답이 어떻게 만들어지는지 확인했다. /hello API에서는 단순 문자열 응답을 확인했고, /movies/recommendation API에서는 Java의 List, Map, ObjectMapper를 활용해 JSON 응답을 생성했다.

또한 같은 프로젝트와 같은 기본 패키지 안에 여러 Servlet 클래스를 만들어도 URL 매핑이 다르면 각각 독립적으로 동작한다는 점을 확인했다. /hello는 HelloServlet, /movies/recommendation은 MovieRecommendationServlet이 처리하며, 기존 API가 새 API 때문에 사라지는 것이 아니라는 점도 실습으로 이해했다.

이번 학습의 핵심은 Servlet 코드를 외우는 것이 아니라, 요청 → URL 매핑 → Servlet 실행 → Response 작성 → Postman 확인이라는 흐름을 이해하는 것이었다.

profile
콩떡

0개의 댓글