3장_ 요구사항1: index.html 응답하기

2026. 1. 30. 15:12·Book/자바 웹 프로그래밍 Next Step

 

지금까지는 http웹 서버에 접속하면 어떤 url로 접속하더라도 "Hello World"문자열만 출력하는데 http://localhost:8080/index.html로 접속했을 때 webapp디렉토리의 index.html 파일을 읽어 클라이언트에 응답하게 해야한다.

 

요청 읽기

일단 먼저 요청라인에서 path를 읽어야한다.

BufferedReader br =
    new BufferedReader(
        new InputStreamReader(in, StandardCharsets.UTF_8)
    );

이 한 줄은 “바이트로 들어오는 데이터를 UTF-8 문자로 해석해서, 줄 단위로 읽을 수 있게 준비한다”다.

 

여기서 in은

InputStream in = connection.getInputStream();
  • connection = 클라이언트 1명과 연결된 Socket
  • in = 그 연결로 “클라이언트가 보내는 바이트”가 들어오는 통로

 

StandardCharsets.UTF_8

  • “UTF-8 문자 인코딩”을 의미하는 상수다.
  • 바이트를 문자로 바꿀 때(디코딩할 때) 어떤 규칙으로 바꿀지 지정한다.
  • 요청 라인은 대부분 ASCII라 큰 차이가 안 날 수 있지만, 안정성을 위해 명시

new InputStreamReader(in, UTF_8) 의 역할은

바이트 스트림(InputStream)을 문자 스트림(Reader)로 변환한다.

  • in은 바이트만 읽을 수 있다.
  • 그런데 BufferedReader는 Reader(문자 기반)를 감싸서 동작한다.
  • 그래서 중간 변환기인 InputStreamReader가 필요하다.

이때 중요한 건 “변환”이 그냥 캐스팅이 아니라는 것:

  • 입력으로 들어온 바이트들을
  • UTF-8 규칙으로 디코딩해서
  • 자바의 char/문자열로 만들어준다.

요청의 시작이 이런 바이트라면:

47 45 54 20 2F 20 48 54 54 50 ...
(G  E  T     /     H  T  T  P ...)

이걸 문자 "GET / HTTP..."로 바꿔준다.

 

new BufferedReader(Reader)

BufferedReader는 “문자 스트림을 더 편하게/효율적으로 읽게 해주는 래퍼”다.

(A) 버퍼링(Buffering)

  • 네트워크에서 1글자씩 읽으면 너무 비효율적이다.
  • BufferedReader는 내부적으로 한 번에 어느 정도를 버퍼에 모아두고,
  • 너가 필요할 때 그 버퍼에서 꺼내준다.

(B) readLine() 기능 제공

가장 핵심.

  • HTTP 요청은 처음부터 “줄 단위”로 온다.
  • readLine()은 한 줄을 문자열로 읽어준다.
  • “한 줄”의 기준은 줄바꿈(\n)이다. (\r\n이 와도 내부적으로 처리한다)

즉 BufferedReader를 쓰는 1순위 이유는 readLine() 때문이다.

 

요청 첫 줄 읽기

String requestLine = br.readLine();

전에 말했던 것처럼 readLine()을 사용해

br이 소켓으로부터 문자를 계속 읽다가 줄 끝(개행) 을 만나면 거기까지를 한 덩어리 문자열로 반환한다.

브라우저가 보내는 HTTP 요청은 보통 이런 식이다:

GET /index.html HTTP/1.1\r\n
Host: 15.165.xx.xx:8080\r\n
User-Agent: ...\r\n
Accept: ...\r\n
\r\n
(바디가 있으면 여기부터)

requestLine == "GET /index.html HTTP/1.1"

그리고 중요한 성질 2개:

(1) readLine()은 블로킹될 수 있다

  • 아직 클라이언트가 데이터를 안 보냈으면 기다린다.
  • 즉 접속은 됐는데 요청이 안 오면 여기서 멈춰 있을 수 있음.

(2) 반환값이 null일 수 있다

  • 상대가 연결을 닫아버리면 null이 올 수 있다. (예: 요청을 보내기 전에 탭을 닫거나, 네트워크가 끊기거나, 프로그램이 죽어버리면)
  • 그래서 보통 if (requestLine == null) 체크를 한다.

 

로그를 찍어보자

log.debug("Request Line: {}", requestLine);

"Request Line: {}"의 {}는

SLF4J 스타일의 플레이스홀더다.

  • {} 자리에 뒤의 값(requestLine)이 들어간다.
  • 문자열 덧셈("..." + requestLine)보다 깔끔하고 성능도 유리할 때가 많다.

debug 레벨이라 안 보일 수도 있다

로그 설정이 INFO 이상이면 debug가 출력 안 된다.

  • 안 찍히면 log.info(...)로 바꾸면 무조건 보인다.

현재 콘솔에 로그가

Request Line: GET /index.html HTTP/1.1

이렇게 잘 찍히는 것을 확인했다.

 

 

요청 첫 줄에서 path(index.html) 뽑기

브라우저가 http://서버:8080/index.html로 오면 로그에:

  • requestLine: GET /index.html HTTP/1.1
  • path: /index.html

이렇게 찍히도록 해보겠다.

requestLine 아래부분 부터 시작해보면

if (requestLine == null || requestLine.isBlank()) {
    return;
}

먼저 이렇게 해서 

요청 라인이 없거나 비정상이면 아래 파싱 코드로 넘어가지 않게 하겠다.

 

그리고 요청 첫 줄은 규칙이 항상 이거라서:

METHOD  PATH  VERSION
GET     /index.html  HTTP/1.1

공백 기준으로 자르면 된다.

String[] parts = requestLine.split(" ");
String method = parts[0];
String path = parts[1];
  • requestLine.split(" ")
    요청 첫 줄(예: "GET /index.html HTTP/1.1")을 공백 기준으로 잘라서 배열로 만든다.
    그래서 parts는 보통 이렇게 됨:
    • parts[0] = "GET"
    • parts[1] = "/index.html"
    • parts[2] = "HTTP/1.1"
  • String method = parts[0];
    잘린 것 중 첫 번째(0번째)를 꺼내서 HTTP 메서드로 저장한다. (GET, POST 같은 거)

String path = parts[1];
두 번째(1번째)를 꺼내서 요청 경로(URL path) 로 저장한다. (/, /index.html 같은 거)

즉, 한 줄에서 “메서드랑 경로를 뽑아내는” 파싱이다.

log.debug("method={}, path={}", method, path);

그리고 이렇게 로그를 추가해서 확인해보겠다.

14:44:41.205 [DEBUG] [Thread-0] [webserver.RequestHandler] - method=GET, path=/index.html
14:44:41.268 [DEBUG] [Thread-1] [webserver.RequestHandler] - method=GET, path=/favicon.ico

이렇게 잘 찍히는 것을 볼 수 있다.

 

여기서 왜 쓰레드 다른게 궁금했었다.

찾아보니

  • 브라우저는 페이지를 보여주려고 여러 리소스를 동시에 받아와야 함
    (HTML, CSS, JS, 이미지, favicon 등)
  • 그래서 동시에 여러 TCP 연결을 열어 병렬로 요청함
  • 그 결과 스레드가 여러 개 생기고 로그가 여러 줄 찍힘

즉, “사용자 1명 = 요청 1개”가 아니라
“사용자 1명 = 여러 요청을 병렬로 보낼 수 있음” 이라고 한다.

 

그리고 

  • 요청을 보낸다 = 이미 열린 소켓에 HTTP 데이터를 보낸다.
  • 그런데 연결이 없으면 먼저 소켓(TCP 연결)을 새로 만들고 요청을 보낸다.

이 서버는 요청 1개 처리 후 연결을 끊으니까,
브라우저는 요청마다 새 소켓을 만드는 것처럼 보이는 것이었다.

 

 

 

index.html파일 응답

이제 path가 /index.html일 때 실제로 webapp/index.html 파일을 읽어서 바이트로 만들고, 200 응답(헤더+바디)으로 내려주겠다.

 

먼저 이렇게 파일을 읽어서 byte[]로 만들겠다.

byte[] body = Files.readAllBytes(Path.of("webapp", "index.html"));

Path = “파일 경로”를 표현하는 객체

  • 문자열로 "webapp/index.html"을 그냥 쓰는 대신,
  • 자바가 OS에 맞게 경로를 다루도록 Path 객체로 만든다.

Path.of("webapp", "index.html")의 의미

  • "webapp"과 "index.html"을 경로 조각으로 받아서 합친다.
  • 결과적으로 경로는 이런 의미가 된다:
  • mac/linux: webapp/index.html
  • windows: webapp\index.html

즉, 운영체제 경로 구분자(/, \)를 신경 안 쓰게 해준다.

그리고 이 경로는 상대경로다.
“현재 프로그램을 실행한 디렉토리(working directory) 기준”으로 webapp/index.html을 찾는다.

Files = 파일 관련 기능 모음(유틸 클래스)

java.nio.file.Files는 파일을 읽고/쓰고/복사하는 기능들이 모여있다.

readAllBytes = “파일을 끝까지 전부 읽어라”

  • 인자로 받은 Path가 가리키는 파일을 열고
  • 파일의 내용을 처음부터 끝까지 읽어서
  • 그 내용을 byte[]로 반환한다.

그리고 파일 내용을 byte[]로 들고 있으면 그대로 dos.write(body)로 보낼 수 있다.

 

그리고 응답 헤더/바디를 기존 함수로 내려주면 된다:

response200Header(dos, body.length);
responseBody(dos, body);

 

 

webapp 디렉토리 위치

프로그램을 어디에서 실행하느냐에 따라 webapp/index.html 경로가 달라질 수 있다.

가장 간단한 기준은 이거:

  • 서버 실행하는 현재 작업 디렉토리(보통 프로젝트 루트)에 webapp/index.html이 있어야 함

구조 예:

프로젝트루트/
  webapp/
    index.html
  src/
  target/

 

 

 

확인

이렇게 잘 뜨는것을 확인할 수 있다.

 

 

 

 


출처 : 《자바 웹 프로그래밍 Next Step》, 박재성, 로드북

'Book > 자바 웹 프로그래밍 Next Step' 카테고리의 다른 글

3장_ 메모: HTTP 요청 라인 파싱과 멀티 스레드 처리  (0) 2026.01.31
3장_ 요구사항2 : GET 방식으로 회원가입하기  (0) 2026.01.31
3장_ 코드 이해: RequestHandler 클래스  (0) 2026.01.29
3장_ 코드 이해: WebServer 클래스  (0) 2026.01.29
3장_ 소스코드 재배포  (0) 2026.01.29
'Book/자바 웹 프로그래밍 Next Step' 카테고리의 다른 글
  • 3장_ 메모: HTTP 요청 라인 파싱과 멀티 스레드 처리
  • 3장_ 요구사항2 : GET 방식으로 회원가입하기
  • 3장_ 코드 이해: RequestHandler 클래스
  • 3장_ 코드 이해: WebServer 클래스
sqaxe1
sqaxe1
woojoo-devlog 님의 블로그 입니다.
  • sqaxe1
    Woojoo's Devlog
    sqaxe1
  • 전체
    오늘
    어제
    • 분류 전체보기 (148)
      • Backend (9)
        • Servlet (7)
        • Spring (2)
      • Frontend (1)
      • CS (0)
      • Book (33)
        • 자바 웹 프로그래밍 Next Step (30)
        • 테스트 주도 개발: 고품질 쾌속개발을 위한 TDD.. (1)
        • 성공과 실패를 결정하는 1%의 네트워크 원리 (2)
      • Engineering (0)
        • Testing (0)
      • Infra (6)
        • AWS (6)
      • Java (4)
      • Network (1)
      • 김영한 (28)
        • 자바 입문 (8)
        • 실전 자바 - 기본편 (6)
        • 실전 자바 - 중급편 (10)
        • 실전 자바 - 고급편 (4)
      • Web (39)
        • Web Basics (39)
      • Project (24)
        • NeoSquare (0)
        • Memo Evolution (24)
      • 정보처리기사 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    java
    aws
    개발서적
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
sqaxe1
3장_ 요구사항1: index.html 응답하기
상단으로

티스토리툴바