회원가입 폼에서 값을 입력하고 전송하면, 서버로는 HTTP 요청 메시지가 날아온다.
핵심은 이거다.
- HTTP 요청은 첫 줄에 “어떤 메서드로, 어떤 URL로, 어떤 버전으로” 요청하는지가 들어있다.
- 그 첫 줄에서 요청 URL을 뽑아내고
- URL에서 ? 기준으로 path와 query string 을 분리한 다음
- 쿼리스트링을 userId=...&password=... 형태로 파싱해서
- model.User 객체로 만들어 저장(혹은 로그로 확인)하면 된다.
서버가 실제로 받는 HTTP 요청의 첫 줄
브라우저가 GET으로 요청을 보내면 서버는 대략 이런 요청 라인을 받는다.
GET /user/create?userId=1space&password=1234&name=woojoo&email=woojoo%40naver.com HTTP/1.1
여기서 내가 필요한 건 두 번째 토큰(요청 URL) 이다.
즉, GET 과 HTTP/1.1 사이에 있는 /user/create?... 이 녀석을 뽑으면 된다.
BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
String line = br.readLine(); // 요청의 첫 번째 라인
- in
→ 소켓에서 들어오는 바이트 스트림 - InputStreamReader(in, "UTF-8")
→ 바이트를 UTF-8 문자로 변환 - BufferedReader(...)
→ 문자를 줄 단위로 읽기 쉽게 버퍼링
결과적으로 br.readLine()으로
HTTP 요청의 첫 줄을 읽을 수 있게 해주는 준비이다.
br.readLine()으로 첫 줄을 읽고, 이 첫 줄에서 URL만 추출하는 역할을 HttpRequestUtils.getUrl(line)에게 맡긴다.
String url = HttpRequestUtils.getUrl(line);
getUrl메소드는 split(" ") 해서 두 번째 값을 꺼내는 방식이다.
URL에서 “경로”와 “쿼리스트링” 분리하기
요구사항의 포인트 중 하나가 이거다.
- 요청 URL과 이름=값 데이터를 분리해야 한다.
? 기준으로 왼쪽은 “경로”, 오른쪽은 “파라미터 문자열”이다.
- 경로: /user/create
- 파라미터 문자열: userId=...&password=...&name=...&email=...
먼저, 회원가입 url만 처리해야하니 조건을 걸어두겠다.
if (url.startsWith("/user/create")) {
회원가입폼(form.html)을 보면 action="/user/create" 라고 되어있기 때문에 이것만 들어오도록 하였다.
그 후에
int index = url.indexOf("?");
String queryString = url.substring(index + 1);
이런식으로 ?오른쪽만 뽑아서 queryString변수에 담았다.
쿼리스트링을 Map으로 파싱하기
이제 오른쪽에 있는 문자열은 이런 형태이다.
userId=1space&password=1234&name=woojoo&email=woojoo%40naver.com
이걸 직접 &로 자르고 =로 또 자르는 코드를 또 만들 수도 있는데,
이미 유틸이 있다.
- util.HttpRequestUtils.parseQueryString() 사용
그래서 그대로 쓴다.
Map<String, String> params = HttpRequestUtils.parseQueryString(queryString);
그러면 params.get("userId"), params.get("password") 이런 식으로 꺼낼 수 있게 된다.
User 객체 생성하기
파싱된 Map에서 필요한 필드를 꺼내 User로 만든다.
User user = new User(
params.get("userId"),
params.get("password"),
params.get("name"),
params.get("email")
);
log.debug("User : {}", user);
여기서 중요한 점
- “HTML form 입력값”이 결국 서버에서는 “문자열”로 들어온다.
- 그 문자열을 내가 원하는 구조(User)로 모델링해서 담는 과정이 필요하다.
- 지금 단계에서는 DB 저장까지 안 가더라도, 일단 User로 만들어서 “파싱이 맞게 됐는지” 확인하는 게 1단계이다.
그리고 email 값에 %40 같은 게 보일 수 있는데, 이건 URL 인코딩 결과로 @가 %40으로 전달되는 케이스다. (나중에 디코딩까지 처리하면 더 깔끔해진다)
처리 후에는 index.html로 보내기 (간단한 리다이렉트 흉내)
회원가입이 끝나면(정확히는 “회원가입 요청을 파싱해서 User를 만들면”) 다시 메인 페이지로 보이게 하고 싶다.
그래서 지금은 “진짜 HTTP Redirect(302)”를 하지 않고, 그냥 url을 강제로 바꿔서 정적 파일을 내려주도록 했다.
url = "/index.html";
즉, /user/create?... 요청을 받았지만 서버가 응답 바디로는 index.html 파일을 내려준다.
최종적으로 정적 파일을 읽어서 200 OK로 응답하기
이 서버는 “동적 처리”를 엄청 정교하게 하는 단계가 아니라,
기본은 정적 파일(webapp 폴더)을 읽어서 그대로 내려주는 구조이다.
그래서 마지막에 이렇게 된다.
byte[] body = Files.readAllBytes(Path.of("webapp", url.substring(1)));
response200Header(dos, body.length);
responseBody(dos, body);
- url.substring(1) 하는 이유: /index.html에서 맨 앞의 /를 제거해서 파일 경로처럼 쓰기 위함이다.
- Files.readAllBytes(...)로 파일을 통째로 읽어서 바디로 보낸다.
- 헤더는 최소한의 형태로 직접 쓴다.
private void response200Header(DataOutputStream dos, int lengthOfBodyContent) {
try {
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");
} catch (IOException e) {
log.error(e.getMessage());
}
}
\r\n은 HTTP 헤더 줄바꿈 규칙이다.
헤더 끝에는 빈 줄 한 줄(\r\n)이 한 번 더 들어가고, 그 다음이 바디이다.
이렇게 GET 방식으로 사용자 입력 데이터를 보내면 문제점이 있다.
- URL에 데이터가 그대로 노출된다. /user/create?userId=...&password=...처럼 브라우저 주소창, 히스토리, 로그에 남을 수 있어 비밀번호 같은 민감 정보에 취약하다.
- 요청 라인(URL) 길이 제한이 있다. URL 길이에 한계가 있으므로 GET으로 보낼 수 있는 데이터 크기도 제한된다.
그래서 회원가입/로그인처럼 민감 정보가 포함되거나 데이터가 긴 경우는 보통 POST를 사용한다.
'Book > 자바 웹 프로그래밍 Next Step' 카테고리의 다른 글
| 3장_ 메모: 요청 메시지의 형태 (0) | 2026.01.31 |
|---|---|
| 3장_ 메모: HTTP 요청 라인 파싱과 멀티 스레드 처리 (0) | 2026.01.31 |
| 3장_ 요구사항1: index.html 응답하기 (0) | 2026.01.30 |
| 3장_ 코드 이해: RequestHandler 클래스 (0) | 2026.01.29 |
| 3장_ 코드 이해: WebServer 클래스 (0) | 2026.01.29 |
