3장_ 요구사항3: POST방식으로 회원가입

2026. 2. 1. 00:41·Book/자바 웹 프로그래밍 Next Step

 

기존에는 회원가입이 GET 방식으로 동작했다. 즉 `/user/create?userId=...&password=...` 같은 형태로 데이터가 URL에 붙어서 들어왔다. 그런데 요구사항은 `http://localhost:8080/user/form.html`의 `<form>` 태그에서 method를 get에서 post로 바꾸고도 회원가입이 정상 동작하도록 서버를 고치라는 것이다.

POST로 바뀌면 중요한 변화가 생긴다.

  • GET: 데이터가 URL 쿼리스트링에 붙어서 온다.
  • POST: 데이터가 요청 바디(request body) 로 온다. (URL에는 보통 /user/create까지만 온다)

즉 서버는 더 이상 URL에서 ? 뒤를 파싱하면 안 되고, 헤더에서 Content-Length를 읽고, 그 길이만큼 바디를 읽어서 파싱해야 한다.

이걸 RequestHandler에서 직접 구현한 것이 아래 코드이다.

 

 

요구사항 전제: form 태그가 이렇게 바뀜

`/user/form.html`에서 대략 이런 식으로 바뀌는 상황이다.

  • 기존(예시)
<form action="/user/create" method="get">
  • 변경 후
<form action="/user/create" method="post">

이렇게 되면 브라우저는 `/user/create`로 POST 요청을 날리고, 입력값은 URL이 아니라 body에 담아서 보낸다.

 

 

서버는 요청을 “첫 줄(요청라인)”부터 읽는다

HTTP 요청은 텍스트이고, 첫 줄이 제일 중요하다.

  • 요청라인 형식
<메소드> <URL> <HTTP버전>

그래서 서버는 먼저 첫 줄을 읽는다.

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

여기서 빈 줄이면 아예 요청이 없는 것으로 보고 종료한다.

그리고 첫 줄을 공백으로 분리해서 method와 url을 뽑는다.

String[] parts = requestLine.split(" ");
String method = parts[0];
String url = parts[1];

이제 서버는 “GET인지 POST인지”, “어떤 경로인지” 판단할 수 있게 된다.

 

 

POST에서 핵심: 헤더를 읽고 Content-Length를 찾아야 한다

POST는 body가 있다.
그런데 body를 읽을 때 얼마나 읽어야 하는지가 중요하다. 그 길이는 보통 헤더의 `Content-Length`에 있다.

요청 헤더는 “두 번째 줄부터 빈 줄 전까지”이다.
그래서 빈 줄이 나올 때까지 헤더를 읽는다.

int contentLength = 0;
requestLine = br.readLine();
while (requestLine != null && !requestLine.isEmpty()) {
    log.debug("header : {}", requestLine);
    if (requestLine.startsWith("Content-Length:")) {
        contentLength = getContentLength(requestLine);
    }
    requestLine = br.readLine();
}

여기서 중요한 포인트 2개

  1. `requestLine.isEmpty()`가 되는 순간이 “빈 줄”이다.
    → HTTP에서 헤더 끝을 의미하는 그 빈 줄이다.
  2. 헤더를 읽는 동안 `Content-Length:`를 발견하면 숫자를 뽑아서 저장한다.
    → 이 값이 있어야 body를 정확히 읽을 수 있다.

`getContentLength()`는 헤더 한 줄에서 `:` 기준으로 나눠서 길이 숫자를 추출한다.

private int getContentLength(String line) {
    String headTokens[] = line.split(":");
    return Integer.parseInt(headTokens[1].trim());
}

 

 

POST /user/create 인 경우: body를 읽고 파싱해서 User 생성

이제 조건 분기가 들어간다.

  • method가 POST인지
  • url이 `/user/create`인지
if ("POST".equals(method) && url.startsWith("/user/create")) {
    String requestBody = IOUtils.readData(br, contentLength);
    Map<String, String> params = HttpRequestUtils.parseQueryString(requestBody);
    User user = new User(
        params.get("userId"),
        params.get("password"),
        params.get("name"),
        params.get("email")
    );
    log.debug("User : {}", user);
    url = "index.html";
}

여기서 흐름을 딱 나누면 이렇다.

(1) body 읽기

String requestBody = IOUtils.readData(br, contentLength);

`IOUtils.readData()`는 “contentLength만큼” 읽어서 문자열로 반환하는 함수이다.

POST로 회원가입 폼이 전송되면 body는 대체로 이런 형태로 온다.

userId=javajigi&password=password&name=JaeSung&email=javajigi%40slipp.net

즉 GET의 querystring이 URL에 붙어있던 게, POST에서는 body로 내려온 것뿐이다. 포맷은 거의 같다.

(2) body 파싱

Map<String, String> params = HttpRequestUtils.parseQueryString(requestBody);

`parseQueryString()`은 &로 쪼개고, =로 키/값을 나눠서 Map으로 만들어준다.

(3) User 생성

User user = new User(...);
log.debug("User : {}", user);

여기까지 오면 “폼에서 입력한 값이 제대로 서버로 들어와서 User로 만들어졌다”를 확인할 수 있다.

(4) 처리 후 index.html 응답

url = "index.html";

“응답으로 index.html 파일을 내려준다”는 의미로 url을 바꾸었다.
사용자는 회원가입 후 메인 페이지를 보는 흐름을 경험하게 된다.

 

 

정적 파일 응답 처리: `/`는 `index.html`로 매핑

브라우저가 처음 접속할 때 `GET /`를 보내는 경우가 많다.
이때 /를 그대로 파일로 읽으면 디렉터리 문제가 터질 수 있다.

그래서 홈 요청은 `index.html`로 강제 매핑한다.

if ("/".equals(url)) url = "index.html";

 

 

응답은 “상태라인 + 헤더 + 빈 줄 + 바디”로 구성된다

정적 파일을 내려줄 때는 byte 배열로 읽고, 200 OK 응답을 만들어서 내려준다.

byte[] body = Files.readAllBytes(Path.of("webapp", url));
response200Header(dos, body.length);
responseBody(dos, body);
 

(1) 200 OK 헤더 작성

 
dos.writeBytes("HTTP/1.1 200 OK \r\n");
dos.writeBytes("Content-Type: text/html;charset=utf-8\r\n");
dos.writeBytes("Content-Length: " + lengthOfBodyContent + "\r\n");
dos.writeBytes("\r\n");
  • HTTP/1.1 200 OK는 상태라인이다.
  • 그 다음은 헤더이다.
  • 마지막 \r\n 한 번 더가 “빈 줄”이다.
    → 이 빈 줄 다음부터가 바디라는 뜻이다.

(2) 바디 전송

dos.write(body, 0, body.length);
dos.flush();

 

 

 

근데 이렇게 하면 문제가 있다.

지금은 `POST /user/create`의 응답 바디로 index.html 내용을 그대로 내려주는 방식인데,

이 방식은 브라우저의 주소창이 `/user/create`로 남아 "이동한 것처럼 보이지만 실제로는 이동이 아닌 상태"이다.

그래서 새로고침 시 POST가 재전송 되면서, 사용자가 입력한 데이터가 그대로 쌓이거나 에러가 나게 된다.

 

다음에는 index.html로 리다이렉트 하는 방식으로 개선해 보겠다.

 

 

 

 


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

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

3장_ 로깅: System.out.println() 대신 로깅을 써야 하는 이유  (0) 2026.02.02
3장_ 요구사항 4: 302 status code 적용  (0) 2026.02.01
3장_ 메모: 요청 메시지의 형태  (0) 2026.01.31
3장_ 메모: HTTP 요청 라인 파싱과 멀티 스레드 처리  (0) 2026.01.31
3장_ 요구사항2 : GET 방식으로 회원가입하기  (0) 2026.01.31
'Book/자바 웹 프로그래밍 Next Step' 카테고리의 다른 글
  • 3장_ 로깅: System.out.println() 대신 로깅을 써야 하는 이유
  • 3장_ 요구사항 4: 302 status code 적용
  • 3장_ 메모: 요청 메시지의 형태
  • 3장_ 메모: HTTP 요청 라인 파싱과 멀티 스레드 처리
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장_ 요구사항3: POST방식으로 회원가입
상단으로

티스토리툴바