2002년 1월 10일에 틀린 내용을 수정하였습니다. 지적해주셔서 감사합니다.
문제가 있는 글을 올리게 돼서 참으로 죄송합니다. ㅡㅡ;;
1. HTTP 헤더 응답번호의 의미
HTTP 프로토콜을 다루는 RFC 1945번을 읽어보신 분들은 아시겠지만,
(사실 안읽어봐도 알 수 있습니다. 아래에서 설명 드릴꺼니까요. ^^)
특정 URL에 대한 요청에 대한 답변(보통 html 파일이거나 gif 파일이거나 하겠죠)은
헤더 + 몸통으로 구성되어 있습니다.
헤더에는 사용자의 요청이 어떻게 처리되었는가를 알려주는 번호가 하나 붙어오죠.
자주 사용되는 헤더 응답번호에는 다음과 같은 것들이 있습니다.
200: 정상 응답을 의미. 몸통의 내용을 브라우저 창에 표시할 것을 지시
204: No Content. 몸통이 없으니, 현재 보여주고 있는 화면을 계속 유지할 것을 지시
302: Temporarily Moved. 현재 페이지를 다른 페이지로 이동시킬 것을 브라우저에 지시
404: Not Found. 요청한 자원이 존재하지 않는 자원임을 의미
500: Internal Server Error. 서버 내부 오류가 발생하였음을 의미
물론 프로그래머의 입장에서 이 헤더번호를 직접 지정해주어야 하는 경우는 별로
없습니다. Servlet/JSP에서 catch되지 않은 예외가 발생하면 자동으로 서블릿 컨테이너가
500번 헤더를 붙여서 브라우저에 돌려주고, response.sendRedirect()를 호출하면
자동으로 302 헤더를 붙여서 브라우저에 돌려주는 등, 대개의 경우 헤더의 응답번호는
서블릿 컨테이너/웹서버가 관장하는 영역에 속합니다.
2. 302번 응답코드가 의미하는 내용
그런 헤더 가운데 302번 헤더가 있습니다. 얘는 Temporarily Moved, 즉
'이 자원을 가져가시려구요? 요청하신 자원은 잠시 딴데 가있으니, 다른 쪽에서
다시 찾아보시기 바랍니다'라는 응답을 가리킵니다. 이때 다른 자원의 주소를 같이
동봉하게 되구요.
이게 언제 쓰는 거냐 하면 말이죠, 지금 한번 http://www.icoda.co.kr 로
접속해보세요. 바로 브라우저의 주소창이 http://www.icoda.co.kr/shopping/?tmp=1010563887 로
변경될 겁니다. (tmp 뒤의 값은 달라질 수 있습니다)
사실 어떤 사이트는 사이트 주소만 넣었는데 뒤에 자동으로 index.html이 붙는다든가
아니면 index.php가 붙는다든가 하는 경우가 많죠. 다 같은 경우입니다.
이 때, 내부적으로 일어나는 일은 다음과 같습니다.
1) 브라우저가 웹서버에 '/' URI (루트디렉토리겠죠)를 요청합니다.
2) 웹서버가 '/'는 독자적으로는 없고 사실은 '/index.php'에 있다고 302 답변
3) 브라우저가 다시 웹서버에 '/index.php'를 요청 (이때 주소창이 바뀝니다)
4) 웹서버가 '/index.php'의 내용을 브라우저에게 200 답변
그럼 302헤더는 언제 어떤 용도로 사용할 수 있을까요? 사용자가 요청한 URL을
다른쪽으로 연결시켜주는 용도로 훌륭하게 사용할 수 있을 겁니다.
모든 경우를 다 확인해본 것은 아닙니다만, 이런 식의 페이지 전환기능은 대부분
이 302 헤더를 사용하고 있습니다. 궁금하신 분들은 직접 웹서버의 80번 포트로
telnet 접속하셔서 확인해보세요.
아까 위에서 말씀드렸던 icoda의 예제를 동봉하겠습니다.
[weblogic@psm1p] kshm > telnet www.icoda.co.kr 80
Trying 211.174.185.180...
Connected to www.icoda.co.kr.
Escape character is '^]'.
GET / HTTP/1.0
HTTP/1.1 302 Found
Date: Wed, 09 Jan 2002 07:51:09 GMT
Server: Apache/1.3.22 (Unix) PHP/4.1.1 mod_ssl/2.8.5 OpenSSL/0.9.6c
X-Powered-By: PHP/4.1.1
Location: http://www.icoda.co.kr/shopping/?tmp=1010562669
Connection: close
Content-Type: text/html; charset=euc-kr
Connection closed by foreign host.
위의 'Location' 부분을 보시면, 참조할 다른 주소가 나와있죠?
브라우저가 이 주소를 다시 요청하게 됩니다. 새로운 페이지를 다시 요청하는
것이기 때문에 브라우저의 주소창에 나와있는 주소값도 변하게 되죠.
3. forward와 sendRedirect
Servlet/JSP/HTML 환경에서 현재 보고있는 페이지를 다른 페이지로 이동시키는 방법은
대표적으로 세가지가 있습니다.
1) javascript에서 location.href값을 변경
-> 통상 브라우저 내부캐쉬에 저장된 값을 보여준다. 캐쉬를 꺼주어야 함.
2) <jsp:forward>를 사용하거나(JSP), RequestDispatcher.forward()를 사용(Servlet)
-> 브라우저 주소창의 주소는 바뀌지 않으면서 페이지만 변경된다.
3) response.sendRedirect()를 사용
-> 브라우저 주소창의 주소가 바뀌면서 페이지도 변경된다.
여기에서 3) response.sendRedirect() 방식이 위에서 말씀드린 302 헤더를 사용하는
방식이라고 할 수 있습니다. (브라우저의 주소창 값이 변하니까요)
그리고 2) forward 방식은 forward시킬 Servlet/JSP의 service() 메쏘드를 내부적으로
호출하여 그 결과를 브라우저에 전송해주는 방식이라고 할 수 있습니다. (따라서,
브라우저의 주소창 값은 변화하지 않습니다. 언제까지나 서블릿 엔진이 내부적으로
처리해주는 영역이니까요)
4. forward, sendRedirect와 IllegalStateException
그런데, 위에서 보셨듯, HTTP 응답은 한번에 하나의 응답코드(200이든 404든 302든)만을
가질 수 있습니다. 그리고, [헤더 + 몸통] 구조로 되어있기 때문에 항상 헤더가 몸통보다
앞에 오게 되죠. (이게 아주 중요합니다)
Servlet/JSP에서는 내부적으로 stream을 사용하여 HTML의 내용을 앞에서부터 끝까지
순차적으로 print하죠. 그런데, 만일 페이지 처음에는 문제가 없는데, 페이지 중간에
있는 어떤 로직에서 Exception이 발생했다고 생각해보죠. 이건 아주 심각한 문제를
야기합니다.
페이지 앞부분은 문제가 없었기 때문에, 서블릿 엔진은 이미 200(정상응답) 헤더를
브라우저에 모두 전송한 뒤, 페이지 앞부분의 내용을 막 전송하고 있는 중이었을 겁니다.
그런데 갑자기 페이지 중간에서 Exception이 발생하면서 헤더 응답코드를 200에서
500(서버에러)으로 바꿔야 되는 상황이 생기게 되는거죠.
이건 사실 500(서버에러)에 국한되는 문제만은 아닙니다. Request.sendRedirect()가
발생시키는 302 헤더 역시 마찬가지죠. 막 페이지를 출력하고 있었는데 갑자기 중간에
다른 페이지로 보내려고 한다면, 역시 응답코드를 200에서 302로 뒤늦게 바꿔주어야
할 것이기 때문입니다.
직접 HTTP 302 헤더를 사용하지는 않는 <jsp:forward>나 RequestDispatcher.forward()의
경우도 약간 다릅니다만 비슷합니다. 원래페이지에서는 오류가 없었지만, forward시킨
페이지에서 만일 500헤더나 302헤더를 사용해야 하는 경우가 생길 수 있는 것 아닙니까?
그러니 아직 버퍼에만 내용이 담겨있을 때는 괜찮지만, 이미 브라우저에게 버퍼의 내용을
전송해버린 뒤에는 옴쭉달싹할 수 없는 상황이 생기는거죠.
그래서 Servlet/JSP에서 버퍼를 사용하는 겁니다. (JSP page indirective에 보면
버퍼사용여부, 버퍼의 크기를 설정해주는 항목이 있죠?) 실제로 사용자가 200번 헤더를
붙여서 몸통의 내용을 출력하기 시작한 뒤라고 하더라도, 그 때까지의 모든 출력값은
내부 버퍼에만 담겨있을 뿐 아직 브라우저에게 보내주지는 않은 상황이 되는거죠.
즉, 그 때에 가서, 웹서버는 (내정되어있던) 200헤더를 버리고 302헤더나 500헤더를
붙여서 브라우저에게 응답을 보내주는 겁니다. 버퍼에 들어있던 내용들은 다 버려지죠.
이렇게 해서 forward를 사용할 수 있는 가능성이 아주 넓어지게 된 겁니다.
Servlet/JSP 스펙의
"If the page output was unbuffered and anything has been written to it, an attempt
to forward the request will result in an IllegalStateException."
라는 구절은, 이래서 들어간거죠. 이미 응답코드를 변경할 수 없는 상황이 됐기 때문에,
IllegalStateException을 발생시키도록 서블릿 엔진을 설계하라는 Sun 측의 의도가
이 안에 녹아있습니다.
보충설명해주실 분 계시면, 부족한 글에 댓글 달아주시면 고맙겠습니다 |