많은 어플리케이션들에서 사용되는 캐시 로직과 마찬가지로 Web browser 역시 캐시를 사용한다.


웹브라우저에서의 캐시 사용은 단순히 웹페이지의 빠른 로딩을 가능하게 할 뿐 아니라, 이미 저장된 리소스의 경우

서버측에 불필요한 재요청을 하지 않는 방법을 통해 네트워크 비용의 절감도 가져올 수 있다.


이는 아주 중요한 부분으로 리소스를 캐시했다가 재활용함은 트래픽 절감과 클라이언트 측에서의 반응성 향상 및 서버의 부하 감소라는 이점까지도 가져올 수 있다.


그렇다면 웹브라우저는 어떤 항목을 어떻게 캐시하는 것일까?


HTTP Spec 에 의하면, 모든 HTTP Request / Response 는 이러한 캐싱 동작과 관련한 설정을 Header 에 담을 수 있다.

주로 사용되는 HTTP Response Header 로는 Cache-Control 과 ETag 가 있다.


(1) Cache-Control


Cache-Control 헤더는 어떻게, 얼마나 오래 응답을 브라우저가 캐싱하면 좋을지를 브라우저에게 알려준다.


Cache-Control 헤더는 브라우저 또는 다른 캐시가 얼마나 오래 어떤 방식으로 캐싱을 할지를 정의한다. 


Web browser 가 서버에 리소스를 첫 요청할 때, 브라우저는 반환되는 리소스를 캐시에 저장한다. 

Cache-Control 헤더는 몇가지 서로다른 Pair 를 Parameter 를 가질 수 있다.


 - no-cache / no-store : no-cache 파라미터는 브라우저가 캐시를 사용하지 않고 무조건 서버에서 리소스를 받아오게끔 한다.

 하지만 여전히 ETags 헤더를 체크하기 때문에, ETags 헤더의 조건에 맞는다면 서버에 직접 요청하지 않고 캐시에서 리소스를 가져온다.

 no-store 파라미터는 ETags 에 상관없이 Cache 를 사용하지 않고 모든 리소스를 다운받도록 하는 옵션이다.


 - public / private : public 은 리소스가 공개적으로 캐싱될 수 있음을 말하고 private 은 유저마다 리소스에 대한 캐싱을 하도록 한다.

 private 옵션은 특히 캐시에 개인정보가 담길 경우 중요하다.


 - max-age : max-age 는 캐시의 유효시간을 의미한다. 초단위로 입력된다.


(2) ETag


ETag 헤더는 캐시된 리소스가 마지막으로 캐시된 이후에 변했는지를 체크해주는 헤더이다.

전체 리소스를 재다운로드하는 대신, 수정된 부분을 체크하고, Same Resource 는 재다운로드하지 않는다.

ETag 는 서버에서 리소스에 대해 할당하는 Random String 으로 할당이 되며, 이값을 비교함으로써 revision 을 체크한다.

이 유효성 검사 토큰을 사용하면 리소스가 변경되지 않은 경우 이므로 추가 데이터 전송 요청을 전송하지 않는다.


ETag 값은 다시 서버에 전송해야하며, 서버는 리소스의 토큰값과 비교해서 변경되지 않은 경우 304 Not Modified 응답을 반환한다.



이를 바탕으로 브라우저 캐싱 동작을 정리해보자.




웹브라우저는 서버의 응답값을 바탕으로 재사용가능한 Response 인지 확인하고, Validation 과 Cache 의 성질, Expiration Time 에 따라 캐시 정책을 결정한다.

이에 대해 구글은 위와 같은 명확한 형태의 Decision Tree 를 제공한다.


이외에도 웹에서 사용되는 캐시 로직을 좀 더 이해하기 위해선 서버사이드의 Cache 로직 역시 이해할 필요가 있다.


캐시 정책에 있어서 왕도는 없으며, 트래픽 패턴, 데이터 유형 및 서비스 종류에 따라 알맞게 설계하는 것이 중요하다.

그 중에서도 최적의 캐시 Lifetime 의 사용, Resource Validation, 캐시 계층 구조의 결정은 반드시 고려되어야 한다.




좀 더 자세한 자료는 다음을 참고한다.

[https://thesocietea.org/2016/05/how-browser-caching-works/]

[https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=ko]



HTTP는 현재 세계에서 가장 널리 쓰이는 프로토콜 중 하나이다. 우리가 보통 사용하는 인터넷을 위한 기본 프로토콜이기도 하며, 최근래에는 다루는 기술도 비약적으로 발전하여, 예전에 웹의 영역이 아니라고 불렸던 게임 영역, 실시간 대용량 처리, 대용량 메시지 처리 등에서도 HTTP 기반의 웹 스택을 사용하는 경우를 흔히 몰 수 있다.


HTTP TCP 계층의 위에 HTTP 프로토콜 스택을 쌓아올린 Network Layer로 현재 가장 널리 사용되고 있는 Stateless / Connectless 형식의 프로토콜이다.


 여기서 Stateless 란, TCP와 다르게 상호간의 연결된 소켓이 연결을 유지하지 않는다는 의미이다.

 즉, 서로 요청과 응답만 처리하고 "상태" 는 기록하지 않는다. 서로 간에 지속적인 "연결" 이 유지되지 않기때문에 지속적이면서 연속적인 통신에는 적합하지 않다. 일반적으로 채팅 서비스를 구현할때 HTTP가 고려되지 않는 이유이기도 하다.


 대신에 단순한 정보 전달에 있어서는 가장 효율적인 프로토콜이라고 봐도 무방하다. 요청한대로 응답만 보내주면 되기 때문에, 서버 입장에서도 연결 관리에 대한 부담이 덜어지고, 클라이언트 측면에서도 원하는 정보만 얻을 수 있으므로 효율적이다.


 HTTP 역시 네트워크 프로그래밍이기 때문에 당연히 소켓을 이용하여 통신을 하게 되며, HTTP를 서비스하는 웹서버는 특유의 성질을 구현하기 위하여 일반적인 TCP 서버 등과는 다른 HTTP 프로토콜에 특화된 형태를 취하게 된다.


소켓을 이용하여 RAW한 방식으로 HTTP 웹서버를 구축할 때는 연결이 시작된 이후에(Accept) 바로 해당 소켓의 연결이 요청(Request)을 받고, 응답(Response)한 후에 연결이 끊어지게 만드는 것을 잊지 않아야 한다.


 HTTP URI Method 등을 기반으로 작동하게 된다.

 HTTP 프로토콜은 별다른 것이 있는 것이 아니라 말그대로 Socket 의 통신 버퍼에 특유의 프로토콜 스택을 쌓아올리는 것을 말한다. 다음은 요청과 응답에 따른 프로토콜 형태이다.

 

<HTTP Request Header>


 위의 요청 포맷에서 첫번째 라인은 Request Line이라고 해서 요청에 대한 포맷 정보를 명시하는 필수 요소이다. 해당 라인은 3가지의 필드로 이루어져 있으며 각 필드는 다음을 명시한다.


(1)  요청 메서드 : GET, POST, OPTIONS(UPDATE, DELETE), PUSH 등의 요청 방식이 온다.


(2)  요청 URI : 요청하는 자원의 위치를 명시한다.


(3)  HTTP 프로토콜 버전 : 프로토콜의 버전으로 1.0 1.1이 있다.



그 아래로 요청 헤더의 내용이 CRLF Delimeter로 하여 열거된다.

General Header : Cache-Control, Connection, Date, Pragma, Trailer, Transfer-Enco, Upgrade, Via, Warning

Request Header : Accept, Accept-Charset, Accept-Encoding, Accept-Language, Authorization, Expect, From, Host, If-Match, If-Modified-Since, If-None-Match, If-Range, If-Unmodified-Since, Max-Forwards, Proxy-Authorization, Range, Referer, TE, User-Agent

Entity Header : Allow, Content-Encoding, Content-Language, Content-Length, Content-Location, Content-MD5, Content-Range, Content-Type, Expires, Last-Modified, extension-header

요청 헤더의 내용이 전부 명시가 된 이후에는 Message Body두 개의 CRLF 아래에 명시된다. 두개의 CRLF 뒤에는 Request Body 가 포함되게 되며, 이부분은 HTTP 스펙에 따라서 해석의 여부가 나뉜다. (이부분은 REST API 소개 및 HTTP Method 분석 포스팅에서 자세히 다루겠다.)


 다음은 응답 헤더의 모습이다.


<HTTP Response Header>

 

첫번째 라인은 요청헤더의 Request Line 처럼 Response Header에서는 Status Line 이라 불리며 필수 정보를 포함한다.


(1)  응답 프로토콜과 버전 : HTTP/1.0, HTTP/1.1, HTTP/2.0 이 현재 버전으로 존재한다.


(2)  응답 코드 : 1xx, 2xx, 3xx, 4xx, 5xx 등의 번호가 응답 코드로 사용된다.


(3)  응답 메시지 : OK, Not Found, Internal Server Error 등의 메시지를 출력한다.


 역시 해당 라인 아래에 응답 헤더의 내용들이 포함되는데, Accept-Range, Age, Etag, Location, Proxy-Authenticate, Retry-After, Server, Vary, WWW-Authenticate 등의 정보가 포함된다. 이후에 두 개의 CRLF 라인 다음에 Message Body 가 첨부된다.

 


 복잡해보이지만 내부 구성원리는 간단하다. 

일반적인 TCP 서버를 구성하고, 프로토콜을 만드는데, 클라이언트로부터 요청을 받아서 해석하는 부분에 Request Parser를, 서버쪽에서 처리를 마치고 클라이언트에 응답을 내려줄 부분에 Response Builder 를 메시지의 머리에 붙여주면 된다.


 그렇게만 하면 웹서버가 HTTP Speculation 상에 약속한대로 메시지를 해석한다. 그렇다면 웹서버 작동의 기본 로직을 정리해보자.


(1) TCP 소켓을 열어 클라이언트의 접속을 받는다. (Accept)


(2) 커넥션을 관리할 수 있는 객체를 만들어, 쓰레드에 할당한다.


(3) 쓰레드가 해당 커넥션에 대해 HTTP Request 를 분석한다. Method 에 따라 서버 내에서 url Handler 를 라우팅해주고, 해당 라우터 메서드에서 요청에 대한 로직을 구현한다.


(4) 로직에 따라 구현한 HTTP Response 를 클라이언트에 반환하고, 접속을 끊는다. (Stateless)



좀 더 내부 동작 원리가 궁금하다면 자세한 예제는 다음 소스를 확인하면 도움이 될 것이다. 오래전에 작성한 소스라 허접하지만 웹서버 구현에 있어 기본에 충실한 좋은 예제라고 생각한다.

(https://github.com/ParkJinSang/Jinseng-Server)





가끔 대화를 하다보면 은근히 웹서버(Web Server), 어플리케이션 서버(Application Server) 혹은 웹 어플리케이션 서버(Web Application Server) 간에 단어의 사용이 혼동되어 쓰이는 경우가 많다.


실제로 "웹 프로젝트" 를 한다고 했을때 많은 학생들은 물론 가끔은 실무자 조차도 더러 자신이 만든 프로젝트가 어떤 종류의 서버인지 긴가민가 여기는 때가 있다.


가령 토이 프로젝트로 게시판을 만들었을 때, 이를 웹서버 경험이 있다고 해야하는지, 아니면 웹 뷰가 없을 때 이를 App Server 라고 봐야하는지 헷갈린다거나, 실무에서 해당 웹 프로젝트 아키텍처의 일부분으로 구성된 서버로 어떤 종류의 서버가 붙어야하는지 헷갈린다면 이 문서가 도움이 될 수 있다.




<굉장히 간단히 표현한 Application Server>



먼저 Application Server란 위에 보이는 것 처럼 말그대로 서버 그자체를 나타낸다. 그림에서 보이는 것처럼, 네트워크가 연결되어있기만 하다면, 그 네트워크를 통해 서버와 Endpoint 간의 통신을 할 수 있는 Server 이다.


즉, HTTP 뿐 아니라 TCP, UDP 등 다양한 프로토콜을 전달받아 클라이언트에 다양한 서비스를 제공한다. 

당연히 복잡한 비즈니스 로직의 처리를 할 수 있으며 이에 따라 Client는 단순히 정보를 Display하는 것 이상의 동작이 자유롭다. Java 진영에서는 트랜잭션 처리, 보안 처리, 리소스 풀링, 메시징 등을 처리해주는 EJB, J2EE 등이 대표적이다. 


많은 Application Server는 단순히 Web page를 띄우는 이상을 처리한다.(DB와의 동적인 연동, 클러스터링, fail-over, 로드밸런싱 등) 대표적인 Java 진영의 예로는 레진서버, Spring framework, Expresso 등이 J2EE의 대표적인 Application Server Framework이다. 

Application Server는 엄밀히 말하면 Web Server와 Web Application Server를 포함하는 상위 개념이라고 할 수 있다.




 Web Server 는 HTTP 프로토콜을 주로 처리하는 서버이다. 즉, Web Server는 Application Server에 포함된다. 

HTTP Request를 받아 HTTP Response를 주며, Request를 처리하기 위해 Static HTML, Image 또는 JSON 등을 이용한다. JSP, 서블릿, ASP 등이 이용되어 요청에 대한 단순 응답을 반환하는 간단한 구조를 갖는다. 인터넷 발달 초기, 단순히 인터넷을 통해 문서 조회만 가능하던 시절의 HTTP 서버들은 주로 정적인 동작만 하는 Web Server 였다고 할 수 있다.


 대표적으로 Apache가 있으며, Apache는 정적인 처리에 특화된 웹서버이고, 세트로 있는 Tomcat은 Servelet Container로 정적인 데이터와 동적인 데이터 처리가 가능하지만, 주로 동적인 데이터의 처리를 하는 Web Application Server이다. 


 WAS 가 태어나게 된 배경은, 인터넷의 발달을 예로 들 수 있다. 인터넷의 편한 접근성과 HTTP 라는 단순하면서도 효율적인 프로토콜의 발달로, 인터넷을 통해 단순히 문서 조회 이상의 많은 것들을 요구하게 되었다. 


기존에 TCP / UDP 등의 프로토콜들이 처리하던 전자상거래, 파일 공유 등의 기능 들을 HTTP 로 수행하려다 보니 나타난 새로운 형태의 서버라 볼 수 있다.


 추가로 말하자면 정적인 HTTP 데이터 처리에 특화된 Web server에 동적인 데이터를 이용하게끔 하는 Container를 엮으면 WAS가 되며, WAS는 HTTP 를 이용하는 Application Server로 볼 수 있다.


이러한 미세한 차이 때문에 일반적으로 위에 언급한 Static Server 의 경우 WAS라고 말하기 보다는 Web Server 혹은 스태틱 서버 라고 표현을 많이 하고, HTTP 프로토콜을 사용하지 않는 TCP 서버 등은 WAS가 아닌 App Server 혹은 Application Server 로 표현한다.



+ Recent posts