1. 웹서버 프로그램

- 외부로부터 웹페이지 요청이 들어왔을 때 그 요청을 처리하여 웹페이지를 전송하는 일련의 과정을 수행하는 프로그램을 웹서버라 한다. 웹페이지 요청에 응답하는 컴퓨터 시스템 하드웨어까지 포함하여 포괄적 표현으로서 웹서버라 칭하는 경우도 있지만, 보통 웹서버라 하면 동적 컨텐츠를 생성, 배포하는 기능 없이 정적 컨텐츠만을 클라이언트에 전송하는 프로그램의 개념으로서 칭하는 경우가 많다.

  • 사실 영문 위키백과의 경우 웹서버를 정적, 동적 페이지를 가리지 않고 모두 전송하는 기능이 있는 프로그램으로 소개하고 있는 등을 고려할 때, 웹서버가 동적 컨텐츠를 생성, 배포하는 기능이 없는지 같은 구분에 관해 엄밀한 기준이 있는 것은 아니다. 다만 국내외를 가리지 않고 많은 경우 web server는 정적 페이지를 전송하는 서버로, application server는 동적 페이지를 전송하는 서버로 구분해 보는 경향이 있다. (그런데 해외에서는 100% application server라 부르는 개념을 왜인지 국내 웹에서는 100% web application server, 줄여서 WAS라 지칭한다.)

2. 웹서버 프로그램의 종류

1) Apache HTTP Server

- WWW 역사 초기에 널리 쓰인 웹서버 프로그램으로 미국 국립 슈퍼컴퓨팅 응용 센터(NCSA)에서 개발한 NCSA HTTPd가 있는데, 이 프로그램은 소스코드가 공개되어 누구나 무료로 사용할 수 있는 프로그램이었다. 그런데 1994년 이 프로젝트가 중단되자, 이 프로젝트가 지속되어야 한다고 생각한 개발자 몇몇이 모여 1995년 2월 NCSA HTTPd의 코드에 기반한 새 웹서버 프로그램을 개발하기 시작했다. 이들은 아메리카 원주민인 아파치족의 이름을 따 자신들을 아파치 그룹(Apache group)이라 칭했으며, 자신들의 웹서버 프로그램을 아파치 HTTP 서버라 이름지었다. 아파치 HTTP 서버는 이후 계속 개발되어 현재도 세계적으로 널리 쓰이는 웹서버 프로그램이며, 이 아파치 그룹은 1999년 아파치 소프트웨어 재단으로 발전해 현재도 여러 오픈소스 프로젝트를 운영하고 있다.

- 아파티 HTTP 서버는 호환성 및 확장성이 뛰어나고 사용자 커뮤니티가 강력하다는 등 많은 장점이 있지만, 동시에 접속하는 클라이언트 수가 크게 증가했을 때 메모리 자원 낭비가 심하고 이를 처리하는 성능이 매우 떨어진다는 점(C10k problem) 때문에 대규모 트래픽을 처리하는 웹서버의 경우 다른 웹서버 프로그램을 사용한다.

  • 아파치 HTTP 서버는 새 클라이언트와 연결을 형성할 때마다 매번 새 스레드와 소켓을 생성하는 방식으로 클라이언트와의 연결을 관리한다(process-driven). 그런데 이와 같은 관리 방식은 메모리 자원을 너무 많이 사용할 뿐더러 네트워크 관련 I/O를 할 때(버퍼에서 데이터를 받아오거나 보낼 때) 스레드 전체가 blocked 상태로 전환돼야 하고, 이 경우 그 스레드가 그 연결에 관한 처리를 할 수 없고 대기 상태에 오래 머무르게 된다. 프로세스/스레드가 아주 많지는 않다면 대기 상태에 머무르는 시간이 아주 길지는 않을 수 있으나 클라이언트 연결 수가 많아진다면 CPU는 여유가 있는데 I/O로 인해 스레드 전체가 blocked 상태에 머무르는 시간이 크게 늘어나게 된다. 이것이 아파치 HTTP 서버가 갖는 C10k 문제의 구조적인 원리이다.

  • 특히 아파치 HTTP 서버는 HTTP/1.1부터 기본 설정이 된 HTTP Keep-Alive에 관한 문제가 있다. (Keep-Alive On 상태는 TCP 통신이 끝난 후 바로 TCP 연결을 끊지 않고 일정 시간 대기하는 상태인데) 바로 Keep-Alive On 상태가 기본 설정이 돼 종전보다 추가로 스레드가 blocked 상태에 머무르는 시간이 길어졌다는 점이다. 이 문제를 해소하기 위해 아파치 HTTP 서버는 클라이언트 연결을 스레드를 통해 관리하는 새로운 방식을 사용했으나(클라이언트로부터 데이터를 전달받는 작업만을 처리하는 스레드를 별개로 두어 그 스레드가 TCP 연결을 형성하는 작업을 전담으로 처리하도록 하고, TCP 연결이 생성되면 그때 그 연결에 대한 작업을 수행하는 스레드로 TCP 연결을 넘기는 구조로, 이를 event MPM라 한다) 이 역시 C10k 문제를 해결하는 데는 한계가 있다.

2) NGINX

- 2004년 러시아의 프로그래머 이고르 시쇼브가 C10k 문제를 겪는 아파치 HTTP 서버의 대안으로 발표한 오픈소스 웹서버 프로그램으로, 서버-클라이언트 연결을 관리하는 구조가 아파치 HTTP 서버와는 전혀 다르다. 굉장히 가볍고 뛰어난 성능으로 C10k 문제가 없어 현재 아파치 HTTP 서버 못잖은 수준으로 큰 인기를 누리고 있다.

- 새 클라이언트와 연결을 새로 형성할 때마다 이를 전담하는 스레드를 새로 생성하는 아파치 HTTP 서버와 달리, NGINX는 하나의 스레드(+하나의 소켓)가 여러 클라이언트와의 연결을 한꺼번에 관리한다. 클라이언트로부터 데이터가 들어오거나 내보내는 사건(event) 하나를 한 단위로 해서, 일련의 이벤트를 순서대로 하나씩 대기열에 넣은 후 대기열에서 이벤트를 순서대로 하나씩 꺼내 처리하는 방식(event-driven)을 사용한다. (각 클라이언트로부터 전달받은 이벤트를 대기열에 넣는 작업을 수행하는 부분을 event handler라 하며, 스레드가 대기 상태에 있다가 대기열에 이벤트가 들어오면 이를 처리한 후 다시 대기 상태로 돌아간다는 점에서 이를 수행하는 부분을 event loop라 한다. 대기열에서 이벤트를 순서대로 꺼내오지만 한 이벤트를 꼭 끝까지 다 처리한 다음 다음 이벤트를 가져오는 건 아니라는 점에서 NGINX의 이벤트 처리방식은 비동기적이다.)

  • thread pool: 대기열에서 가져온 어떤 이벤트를 처리하는데 I/O에 소모되는 시간이 긴 경우, 그 이벤트의 I/O만을 처리하는 스레드를 새로 만들고 그 이벤트를 그 스레드가 전담하여 처리하도록 한다. 이처럼 I/O에 소모되는 시간이 긴 이벤트를 전담 처리를 하는 스레드들을 묶어서 thread pool이라 한다.

  • NGINX의 장점: 하나의 스레드가 클라이언트와의 연결을 모두 관리하므로 context switch로 인한 오버헤드가 없다. I/O로 인해 스레드가 blocked 상태가 되어 다른 작업을 수행하지 못하는 일이 일어나지 않는다.

  • NGINX의 단점: 프로세스 자체의 오류로 인해 프로세스가 종료되면 그와 연결돼 있던 클라이언트와 연결이 모두 끊어지고 클라이언트로부터 요청받아 처리중이던 작업도 모두 중단된다.

- 아파치 HTTP 서버는 자체적으로 동적 컨텐츠도 처리하는 기능도 있지만, NGINX는 기능이 매우 간소해 동적 컨텐츠 생성/배포 기능 없이 정적 컨텐츠만을 처리한다는 차이가 있다.

- 로드밸런싱, 무중단배포 같은 기능은 일반적으로는 이를 위해 만들어진 하드웨어 장비를 통해 구현하나, NGINX가 설치된 컴퓨터를 이용해 NGINX의 기능으로써 구현할 수도 있다. 이를 구현하는 하드웨어 장비는 가격이 비싸고 구현이 어렵다는 점에서 NGINX를 이용하는 이점이 있으나, NGINX가 이러한 기능을 위해 만들어진 전용 프로그램이 아니라는 점에서 비효율적일 수 있다는 단점이 있다.

3. 웹 애플리케이션 서버의 개념

1) 동적 페이지와 CGI

- WWW가 처음 제시되었을 때 웹서버 프로그램은 정적인 하이퍼텍스트나 사진, 영상, 음악 등만을 클라이언트에 보낼 수 있었고, 클라이언트의 권한을 인증해 데이터 접근권에 차등을 둔다거나 외부 DB를 조작하고 그 결과를 통해 생성해낸 동적 페이지를 내보내는 것 같은 복잡한 기능을 갖고 있지는 않았다. 그러나 웹이 발전하면서 점차 동적 페이지를 생성하는 기능 등을 하는 외부 프로그램과 웹서버 프로그램 사이를 연결해야 할 필요가 생기자, 여러 개발자들이 그 둘을 잇는 인터페이스를 개발해 사용하기 시작했다. 이때 1993년 NCSA가 이러한 인터페이스가 갖춰야 할 명세를 제시했고, 여러 개발자들이 이를 받아들여 NCSA가 제시한 인터페이스 명세가 당시 개발자들 사이 사실상 표준이 되었다. 이를 CGI(common gateway interface)라 불렀으며, CGI를 표준화하기 위한 개발자 모임이 1997년 처음 시작돼 2004년 CGI 버전 1.1이 규정되었다.

- CGI는 아파치 HTTP 서버와 비슷하게 새 클라이언트로부터 요청이 들어올 때마다 새 프로세스를 메모리에 올리는 방식이기 때문에, 아파치 HTTP 서버와 마찬가지로 동시에 접속하는 클라이언트 수가 크게 증가했을 때 메모리 자원 낭비가 심하고 이를 처리하는 성능이 매우 떨어진다는 단점이 있다. 현재는 이를 대체하는 기술이 많이 나타나 CGI는 현재 거의 쓰이지 않는 기술이다.

2) Java Servlet과 WAS

- Java Servlet은 Java로 작성된 웹 어플리케이션이 갖춰야 할 명세를 규정한 것으로, 이에 따라 작성된 프로그램을 서블릿이라 한다. 서블릿을 실행할 수 있는 서버 프로그램을 Java application server라 하며, Java application server의 일부분으로서 서블릿을 실행하는 기능이 있는 구성요소를 서블릿 컨테이너 또는 웹 컨테이너, 서블릿 엔진이라 한다.

  • Java Servlet이 동적 페이지를 생성하는 기능에 관한 명세라는 점 때문에 이를 흔히 ‘Java 플랫폼에서의 CGI’라 부른다. Java Servlet은 새로운 연결을 형성할 때마다 새로 프로세스를 생성하는 CGI와 달리 프로세스보다 훨씬 가벼운 Java 스레드를 생성하기 때문에 CGI보다 뛰어난 성능을 보인다.

  • 이와 같은 구조는 Java 플랫폼뿐 아니라 PHP, ASP 등 여러 플랫폼에도 있으며, 이처럼 각 플랫폼에서 동작하는 서버 프로그램들을 application server 또는 WAS라고 한다. 다만 각각의 구조나 내부 구성요소를 일컫는 용어는 각각 차이가 있다. 예를 들어 ‘웹 컨테이너’라 하면 다른 언어 플랫폼이 아닌 Java 플랫폼의 서블릿 컨테이너를 뜻한다.

- Java Servlet은 1996년 처음 제시되었으며, 이후 이를 구동할 수 있는 여러 서블릿 컨테이너(또는 이를 포함한 WAS)가 제시되었다. 대표적으로 아파치 재단의 Tomcat, 이클립스 재단의 Jetty 등이 있다.

3) WSGI(web server gateway interface)

- WSGI는 2003년 제시된 웹서버 프로그램과 파이썬 프로그램이 서로 소통할 수 있게 하는 인터페이스 명세로서, CGI와 비슷한 개념이지만 파이썬으로 작성한 웹 어플리케이션을 위해 만들어진 것으로서 CGI의 단점을 개선했다(CGI와 달리 새 연결이 형성됐다 해서 프로세스를 새로 만들지는 않는다). 이 규격에 따라 구현한 파이썬 어플리케이션을 WSGI 호환 어플리케이션 또는 WSGI 어플리케이션이라 하며, 그 중에서도 다른 WSGI 어플리케이션과 웹서버를 연결하는 WSGI 어플리케이션을 WSGI middleware 또는 WSGI container, WSGI 서버(HTTP 요청에 대응되는 동적 페이지를 리턴하는 서버라는 의미)라 한다. WSGI 어플리케이션을 만들 수 있는 대표적인 파이썬 프레임워크로 Django, Flask, FastAPI 등이 있으며, 대표적인 WSGI middleware로 uWSGI, Gunicorn 등이 있다.

  • 많은 WSGI 호환 프레임워크들이 자체적으로 웹서버를 실행시킬 수 있는 기능이 있으나, 이는 개발 과정에서 테스트하기 위한 목적으로서 최소한의 기능을 갖춘 데 불과하며 안정성, 보안, 대량 트래픽 처리 등의 기능을 위해서는 적절한 middleware를 갖춰 서버를 구현할 필요가 있다.

4. 프록시 서버

1) 포워드 프록시 서버

- 클라이언트가 HTTP 요청 메시지를 이를 요청하고자 하는 웹서버에 직접 보내지 않고 제3의 서버에 보내 그 서버로부터 그 요청 메시지에 대한 응답을 받을 때, 그 HTTP 요청에 대응되는 원래의 웹서버를 대리해 그 응답을 클라이언트에 보내는 그 제3의 서버를 포워드 프록시 서버(forward proxy server) 또는 프록시 서버라 한다.

- 다음 경우에 프록시 서버를 사용한다.

  • 캐시 서버: 클라이언트가 접속하고자 하는 웹서버가 물리적으로 먼 거리에 있는 경우에, 그 클라이언트가 속한 로컬 네트워크에 프록시 서버를 두어 클라이언트가 로컬 네트워크 외부 웹서버로 HTTP 요청을 하는 경우 프록시 서버인 캐시 서버를 거치도록 네트워크를 구성할 수 있다. 이후 만약 서로 다른 클라이언트에서 짧은 시간 내에 같은 내용의 HTTP 메시지가 들어온다면, 맨 처음 들어온 요청만 외부 웹서버로 보내 그에 대한 응답을 받고 이를 캐싱해두었다 이후 요청들에 대해 같은 응답을 전송하도록 해 네트워크의 반응속도를 높일 수 있다. 처음 등장한 캐싱 서버가 최초의 프록시 서버로 여겨진다. 이러한 형태의 캐시 서버는 여러 ISP와 구글, 아마존, 넷플릭스 등 대형 IT 기업도 갖추고 있으며, 캐시 서버 제공만을 목적으로 하는 IT 기업도 여럿 있다.

  • 익명화: 다음 경우에 프록시 서버를 거쳐 익명으로 해당 웹서버에 접속

    • 접속하고자 하는 웹서버를 신뢰할 수 없는 경우

    • 접속하고자 하는 웹서버가 내가 속한 로컬 네트워크로부터의 HTTP 요청을 차단하는 경우

  • 로컬 네트워크 관리자 우회: 다음 경우에 이를 뚫기 위해 프록시 서버를 거쳐 해당 웹서버로 우회 접속

    • 외부 웹서버가 방화벽에 의해 차단된 경우

    • 로컬 네트워크 관리자가 로컬 네트워크에 속한 클라이언트들의 외부 사이트 접속을 감시하고 있는 경우

2) 리버스 프록시 서버

- 외부 클라이언트들의 요청을 받는 웹서버가 사실 외부 클라이언트로부터 받은 요청을 내부 네트워크에 있는 서로 다른 여러 웹서버로 분배한 후 그에 대한 응답 메시지를 다시 각 웹서버로부터 받아 다시 외부 클라이언트로 보내는 기능을 하고 있을 때, 외부 클라이언트들의 요청을 받는 역할을 하는 그 웹서버를 리버스 프록시 서버(reverse proxy server)라 한다.

- 리버스 프록시 서버를 이용해 웹서버 구축 시 다음 이점이 있다.

  • 캐싱: 외부 클라이언트로부터 짧은 시간 내에 같은 HTTP 요청이 들어왔을 때, 이를 여러 서버로 일일이 보내지 않고 처음 받은 요청에 대한 응답을 캐싱했다가 여러 클라이언트에 똑같이 전송한다.

  • 로드 밸런싱: 어느 한 웹서버가 고장이 났거나 수많은 요청을 처리하느라 여유가 없다는 등 현재 사용 불가일 때, 현재 들어온 요청을 그 웹서버가 아닌 다른 웹서버에 보내는 식으로 대량의 트래픽을 효과적으로 처리할 수 있다. 로드 밸런싱 기능은 보통 로드 밸런싱을 하는 전용 하드웨어 장비(로드 밸런서)로 구현하나 리버스 프록시 서버를 통해 구현하는 것도 가능하다.

  • 보안: 리버스 프록시 서버에 대해서만 보안 기능을 강화하면 내부 웹서버들에 대해서는 상대적으로 보안이 완화돼 있어도 위험부담이 작다.

  • SSL/TLS로 인한 오버헤드 감소: SSL/TLS 암호화는 리버스 프록시 서버에서만 하고 내부망에서는 암호화 없이 통신하도록 하면 SSL/TLS 암호화로 인한 오버헤드를 줄일 수 있다.