[WEB] 웹 어플리케이션과 HTTP 프로토콜
웹서버와 웹 어플리케이션 아키텍처
기존의 서버-클라이언트 아키텍처는 내부 동작과 UI 파트를 분리함으로써 내부 동작과 관련된 코드가 수정 되더라도 클라이언트의 재설치가 필요 없으며, 클라이언트가 내부DB에 직접 접근하는 것을 방지할 수 있었다. 하지만 서버-클라이언트 아키텍처도 여전히 클라이언트가 UI를 담당하므로 UI가 변경되면 재설치가 필요했다. 또한 멀티스레드를 관리해야 하는 소켓프로그래밍은 어플리케이션 개발의 생산성을 저해하였다.
웹기술을 적용하여 만들어진 웹 어플리케이션 아키텍처는 위에서 언급한 이러한 문제들을 해결 할 수 있었다. 먼저 기존의 서버는 어플리케이션 서버와 웹 서버로 나뉘게 된다. 어플리케이션 서버는 기존의 서버처럼 내부 작업을 수행하고 웹서버에게 응답한다. 웹서버는 화면을 띄울 웹 브라우저(클라이언트)와 통신하며 UI를 생성하고 브라우저로 부터 온 요청을 어플리케이션 서버에게 위임한다. 이때 웹서버와 어플리케이션 서버를 합쳐서 Web Application Server, WAS 라고 부른다.
클라이언트인 웹 브라우저는 UI 생성에 관여하지 않고 UI까지 서버에게 요청하여 받게 된다. 그렇기 때문에 UI가 변경된다 하더라도 재설치가 필요없다. 또한 실제 개발자가 개발하는 어플리케이션 서버 단에서는 클라이언트와의 통신을 담당하지 않으므로 개발자는 소켓 프로그래밍으로부터 탈출할 수 있다. 오로지 내부 비지니스 로직이나 데이터 관리에만 집중할 수 있다.
HTTP 프로토콜
직접적인 통신이 이루어지는 웹 서버와 웹 브라우저 사이에는 적절한 통신규칙이 필요하다. 이 규칙이 바로 HTTP 프로토콜이다. 사용자가 웹 브라우저를 통해 어떤 요청을 보낼 경우, 웹 브라우저는 해당 요청을 HTTP 요청 형식에 맞게 웹서버로 보낸다. 그러면 웹 서버는 요청의 결과를 HTTP 응답 형식에 맞게 웹 브라우저에게 보낸다.
HTTP 요청
http 요청형식은 요청라인, 요청헤더, 공백라인과 요청 데이터로 구성되어있다. 먼저 요청라인은 메서드와 요청하는 자원, 프로토콜 버전으로 구성되어있다.
GET /example/index.html HTTP/1.1
위에 요청라인의 예시를 보면 GET 요청을 한다. 이때 요청하는 자원의 경로는 /example/index.html이 되고 프로토콜 버전은 HTTP1.1 이다. 요청 메서드의 경우 GET 외에도 POST, HEAD, PUT, DELETE 등이 있다.
요청라인 아래로는 요청헤더들이 작성되게 된다. 요청헤더는 헤더 이름과 헤더 값으로 구성되어 있으며 요청에 필요한 정보를 담고 있다. 이후 공백라인이 있으며 그 뒤에는 요청 데이터들이 작성되게 된다. 이는 아래 예시를 통해 알아보겠다.
GET / HTTP/1.1
Host: www.naver.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
위에 작성된 요청 예시는 네이버를 주소창을 통해 접속시 요청하는 요청 형식이다. 보다시피 첫번째 줄에는 요청라인이 있고(GET요청), 2~15라인까지가 모두 요청헤더이다. 호스트, 유저 에이전트, 사용하는 언어 및 인코딩 방식등이 적혀있다.
HTTP 응답
HTTP 응답 형식은 상태 라인, 응답 헤더, 공백라인과 응답 데이터로 구성되어있다.
먼저 상태 라인은 HTTP 버전, 상태 코드, 상태 설명으로 구성되어 있다.
HTTP/1.1 200 OK
예시 상태 라인을 보면 프로토콜 버전은 HTTP1.1 상태 코드는 200(응답 성공), 상태 설명은 OK이다. 상태 코드는 세자리 숫자로 구성되어 있으며 종류는 아래와 같다.
1XX | Information Responses : 요청을 받고 클라이언트는 작업을 계속 진행한다. |
2XX | Sucessful Responses : 요청이 성공적으로 처리했다. |
3XX | Redirection : 요청을 마치기전에 추가적인 작업이 필요하다. |
4XX | Client Error : 클라이언트의 에러가 발생해 요청에 실패하였다. |
5XX | Server Error : 서버의 에러가 발생해 요청에 실패하였다. |
상태 라인 아래에는 요청 형식과 마찬가지로 데이터 처리에 필요한 데이터를 응답 헤더를 통해 제공하고, 한줄을 띄우고 (공배라인) 응답 데이터가 작성된다. 다음은 네이버 접속 성공시 웹 서버가 브라우저에게 보내는 응답 형식이다.
HTTP/1.1 200 OK
Server: NWS
Date: Wed, 26 Jan 2022 01:07:13 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
P3P: CP="CAO DSP CURa ADMa TAIa PSAa OUR LAW STP PHY ONL UNI PUR FIN COM NAV INT DEM STA PRE"
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Encoding: gzip
Strict-Transport-Security: max-age=63072000; includeSubdomains
Referrer-Policy: unsafe-url
2e8b
{wW 8 w STԫ & tx
8 N *I%[A Ԓ̣ ,c ` ` [L6ig" r V 3 Jk~ OU $ْm t J c } Gۇ դ gb
상태라인은 보면 상태코드가 200으로 네이버 접속 요청이 성공했음을 볼 수 있다. 그리고 그 아래에는 데이터 처리에 필요한 응답 헤더가 작성되어 있다. 그리고 한줄 띈다음 응답데이터가 작성된것을 볼 수 있다.(글자가 깨져 보이지 않지만 아마 브라우저에 렌더링할 html 문서일것이다...)
GET 요청
HTTP 요청 중 먼저 GET 요청에 대해 알아보자. GET 요청은 요청에 필요한 데이터를 URL에 포함하여 전송한다. 그렇기 때문에 URL 길이의 한계로 인해 대용량 데이터를 보내는데는 한계점이 있다. 그러므로 이름처럼 데이터를 조회하는데 사용하기 적합하다.
GET 요청 1 : 링크 클릭 또는 주소창 입력을 통해 특정 URL에 접속할때
'네이버 증권'에서 '국내 증시'를 클릭하면 국내 증시 URL로 이동하게 된다. 그러면 웹브라우저는 웹서버에게 GET 요청을 하게 되고 이때 이동할 링크를 식별자로 주게 된다. 실제 요청 형식을 살펴보자.
GET /sise/ HTTP/1.1
Host: finance.naver.com
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Referer: https://finance.naver.com/
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
요청 라인을 보면 GET 요청임을 알 수 있고, 접속 URL인 /size/(국내 증시 링크)가 두번째 인자로 적혀있는 것을 볼 수 있다.
GET 요청 2 : 웹에서 데이터를 조회할 경우
검색이나 버튼 클릭을 통해 데이터를 조회하는 경우, GET 요청을 하게 된다. 이때는 조회에 필요한 데이터를 함께 보내게 되는데 해당 데이터는 요청 URL에 포함되어 있다. 아래는 네이버 증권 검색창을 통해 삼성전자를 검색하여 조회 했을때 일어나는 요청이다.
GET /item/main.naver?code=005930 HTTP/1.1
Host: finance.naver.com
Connection: keep-alive
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://finance.naver.com/search/searchList.naver?query=%BB%EF%BC%BA%C0%FC%C0%DA
Accept-Encoding: gzip, deflate, br
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
URL을 살펴보면 /item/main.naver?code=005930 이라고 나와 있는데 '?' 뒤에 작성된 것이 바로 웹 브라우저가 서버에게 보내는 데이터이다. 네이버 증권은 종목 코드를 통해 종목을 조회하므로 헤더는 code 헤더 값은 종목코드인 데이터를 url을 통해 서버로 보낸다. 즉 위 요청은 종목코드(code)가 005930인 종목을 조회하여 브라우저에 해당 html 문서를 렌더링하라는 요청이다.
GET 요청의 적용
GET 요청을 실제로 사용하기 위해서는 html문서의 form 태그에 method 속성으로 get을 넣어주면 된다. 물론 method 속성의 디폴트 값이 get이므로 생략해도 문제는 없다.
<form action="ExampleServlet" method="get">
<input type="text" name="x" size="4">
<input type="text" name="y" size="4">
</form>
GET /example/ExampleServlet?x=20&y=10 HTTP/1.1
위 html 문서는 두 개의 입력 창을 가진 입력 폼이다. 입력 후 해당 웹 서버에게 요청하면 아래와 같이 요청라인이 전송 된것을 확인 할 수 있다.
GET 요청의 문제점과 개선
GET 요청은 데이터의 정보를 URL에 삽입하여 전송하기 때문에 URL만 공유하면 화면의 모든 내용을 공유할 수 있다. 하지만 URL에 정보를 담는 특성상 길이의 한계가 있어 이미지나 동영상 같은 바이너리 데이터를 인코딩해 보내기는 어렵다. 또한, URL에 데이터가 노출되기 때문에 보안상 좋지 못하다.
이러한 데이터가 URL에 노출되는 문제를 해결하고, 길이가 긴 데이터를 보내기 위해서는 POST 요청을 사용하는 것이 좋다.
POST 요청
POST 요청은 GET 요청과 달리 URL에 데이터가 포함되지 않는다. 메세지 본문에 데이터를 포함하기 때문에 다른 사람과 URL을 통해 데이터를 공유할 수 없다. 하지만 이로 인해 대용량 바이너리 데이터를 전송할 수 있다는 장점이 있고, 보안에도 더 강하다.
아래와 코드는 post 요청을 통해 로그인을 하는 코드이다. form 태그의 method 속성을 'post'로 지정해주면 /login url로 post 요청을 보내게 된다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h1>로그인</h1>
<form action='login' method='post'>
아이디: <input type="text" name="id"><br>
암호: <input type="password" name="password"><br>
<input type="submit" value="로그인">
</form>
</body>
</html>
이제는 HTTP 프록시를 통해 요청 형식을 살펴보자.
로그인 버튼을 누르면 /login 으로 POST 요청을 보내게 된다. 맨 아랫줄을 보면 공백라인 이후에 내가 입력한 데이터를 넣어준 것을 볼 수 있다(id=orly&password=1111)