본문 바로가기
books/HTTP 완벽가이드

[HTTP 완벽가이드 5장] 웹 서버

by Moonsc 2020. 5. 24.
728x90

2부.HTTP : HTTP 아키텍처



5장.웹 서버



5.1 다채로운 웹 서버

  • 웹서버는 HTTP 요청을 처리하고 응답을 제공한다.
    '웹 서버'라는 용어는 웹 서버 소프트웨어와 웹페이지 제공에 특화된 장비 양쪽 모두를 가르킨다.
  • 웹 서버는 기능, 형태, 크기가 다양하다.
  • 모든 웹 서버는 리소스에 대한 HTTP 요청을 받아서 콘텐츠를 클라이언트에 돌려준다.


1) 웹 서버 구현

  • 웹 서버는 자신이 제공하는 리소스를 관리하고 웹 서버를 설정, 통제, 확장하기 위한 관리 기능을 제공

  • 웹 서버는 HTTP 프로토콜을 구현하고, 웹 리소스를 관리하고, 웹 서버 관리 기능을 제공

  • 웹 서버는 TCP 커넥션 관리의 책임을 운영체제와 나눠 갖는다.

    운영체제

    • 운영체제는 TCP/IP 네트워크 지원, 파일 시스템, 연산 활동 제어를 위한 프로세스 관리를 제공

    2) 다목적 소프트웨어 웹 서버

  • 네트워크에 연결된 표준 컴퓨터 시스템에서 동작

  • 거의 모든 컴퓨터와 운영체제에서 동작

    점유율

    • 마이크로소프트 37% , 아파치 35% , nginx 14% 정도를 점유하고 있다.

    3) 임베디드 웹 서버

  • 일반 소비자용 제품에 내장될 목적으로 만들어진 작은 웹 서버 (프린터, 가전제품)

5.2 간단한 펄 웹 서버

  • 완전한 기능을 갖춘 HTTP 서버를 만들고자 한다면 50,000줄이 넘는 코드로 되어있고, 부가적인 처리 모듈들을 더 하면 훨씬 커짐
  • HTTP/1.1의 기능들을 지원하려면, 풍부한 리소스 지원, 가상 호스팅, 접근 제어, 로깅, 설정, 모니터링, 그 외 성능을 위한 기능 필요
  • But. 최소한으로 기능하는 HTTP 서버라면 30줄 이하의 펄 코드로도 만들 수 있다.
#!/usr/bin/perl

use Socket;
use Carp;
use FileHandle;

/* 1. 기본 포트 설정 8080 */
$port = (@ARGV ? $ ARGV[0] : 8080);

/* 2. 로컬 TCP 소켓을 생성하고 커넥션을 기다리도록 listen 설정 */
$porto = getprotobyname('tcp');
socket(S, PRF_INET, SOCK_STREAM, $proto) || die;
setsockopt(S, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die;
bind(S, sockaddr_in($port, INADDR_ANY)) || die;
listen(S, SOMAXCONN) || die;

/* 3. 시작메세지 출력 */
printf("Type - O - Server Accepting on Port", $port);

while(1){
    /* 4. 커넥션을 기다린다. */
    $cport_caddr = accept(C,S);
    ($cport, $caddr) = sockaddr_in($cport_caddr);
    C->autoflush(1);


    /* 5. 누구로부터의 커넥션인지 출력 */
    $cname = gethostbyaddr($caddr, AF_INET);
    printf("Request From", $cname);

    /* 6. 빈 줄이 나올때까지 요청 메세지를 읽어서 출력 (요청메세지의 마지막은 빈 줄로 끝나기 때문.)*/
    while($line = <C>){
        print $line;
        if($line =~ /^\r/) {last;}
    }


    /* 7. 응답 메세지를 위한 프롬프트를 만들고 응답줄을 입력받는다.
          "." 하나만으로 되어있는 줄이 입력되기 전까지 입력된 줄을 클라이언트에게 보낸다. */
    printf("Type Response Followed by '.'>>>\n");

    while($line = <STDIN>){
        $line =~ s/\r//;
        $line =~ s/\n//;
        if($line =~ /^\./) {last;}
        print C $line . "\r\n";
    }
    close(C);
};

5.3 진짜 웹 서버가 하는 일

1) 커넥션을 맺는다. : 클라이언트의 접속을 받아들이거나, 원치 않는 클라이언트라면 닫는다.
2) 요청을 받는다. : HTTP 요청 메세지를 네트워크로부터 읽어 들인다.
3) 요청을 처리한다. : 요청 메세지를 해석하고 행동을 취한다.
4) 리소스에 접근한다. : 메세지에서 지정한 리소스에 접근한다.
5) 응답을 만든다. : 올바른 헤더를 포함한 HTTP 응답 메세지를 생성한다.
6) 응답을 보낸다. : 응답을 클라이언트에게 돌려준다.
7) 트랜잭션을 로그에 남긴다. : 로그파일에 트랜잭션 완료에 대한 기록을 남긴다.

5.4 단계1 : 클라이언트 커넥션 수락

  • 클라이언트가 이미 서버에 대해 열려있는 지속적인 커넥션을 갖고 있다면, 클라이언트는 이것을 사용할 수 있다.
    그렇지 않다면 클라이언트는 서버에 대한 새 커넥션을 열 필요가 있다.

    1) 새 커넥션 다루기

    • 클라이언트가 서버에 TCP 커넥션을 요청하면, 서버는 그 커넥션을 맺고 TCP 커넥션에서 IP 주소를 추출하여
      커넥션 맞은편에 어떤 클라이언트가 있는지 확인한다.
    • 새 커넥션이 맺어지고 받으들여지면, 서버는 새 커넥션을 커넥션 목록에 추가하고 오가는 데이터를 지켜보기 위한 준비를 한다.
    • 웹 서버는 어떤 커넥션이든 마음대로 거절하거나 즉시 닫을 수 있다.

    2) 클라이언트 호스트 명 식별

    • 대부분의 웹 서버는 '역방향 DNS(Reverse DNS)'를 사용해서 클라이언트의 IP 주소를 클라이언트의 호스트 명으로 변환하도록 설정

    • 웹 서버는 클라이언트의 호스트명을 구체적인 접근 제어와 로깅을 위해 사용할 수 있다.

    • 호스트 명 룩업(hostname lookup)은 꽤 시간이 많이 걸릴 수 있어 웹 트랜잭션을 느려지게 할 수 있음을 미리 경고한다.

    • 많은 대용량 웹 서버는 호스트명 분석을 꺼두거나 특정 콘텐츠에 대해서만 켜놓는다.

      아파치

      • IdentityCheck 지시어를 이용해 ident 룩업을 사용하게 할 수 있다.
      • HostnameLookups 설정 지시자로 호스트 명 룩업을 켤 수 있다.
      • 아래를 통해 아파치 설정 지시자는 HTML과 CGI 리소스만을 위해 호스트명 분석을 켠다.
        HostnameLookups off
        <Files ~ "\.(HTML|htm|cgi)$">
        HostnameLookups on
        </Files>

    3) ident를 통해 클라이언트 사용자 알아내기 (생략)

5.5 단계2 : 요청 메세지 수신

  • 커넥션에 데이터가 도착하면, 웹 서버는 네트워크 커넥션에서 데이터를 읽어 들이고 파싱하여 요청 메세지를 구성


    GET /specials/saw-blade.gif HTTP/1.0CRLF
    Accept: image/gifCRLF
    Host: www.j

  • 요청 메세지를 파싱할 때 웹 서버는 다음과 같은 일을 한다.

    • 요청줄을 파싱하여 요청 메서드, 지정된 리소스의 식별자(URI), 버전 번호를 찾는다.
      각 값은 스페이스 한 개로 분리되어 있으며, 요청줄은 캐리지 리턴 줄바꿈 CRLF 문자열로 끝난다.
    • 메세지 헤더들을 읽는다. 마찬가지로 CRLF로 끝난다.
    • 헤더의 끝을 의미하는 CRLF로 끝나는 빈 줄이 있다면 찾는다.
    • 요청 본문이 있다면, 읽어 들인다.(길이는 Content-Length 헤더로 정의)
  • 요청 메세지를 파싱할 때, 웹 서버는 입력 데이터를 네트워크로부터 불규칙적으로 받는다. 네트워크 커넥션은 언제라도 무효화될 수 있다.

  • 웹 서버는 파싱해서 이해하는 것이 가능한 수준의 분량을 확보할 때까지 데이터를 읽어서 메시지 일부분을 메모리에 임시로 저장해 둘 필요가 있다.

    1) 메시지의 내부 표현

    • 몇몇 웹 서버는 요청 메시지를 쉽게 다를 수 있도록 내부의 자료 구조에 저장한다.
    • 그 자료 구조는 요청 메시지의 각 조각에 대한 포인터와 길이를 담을 수 있고, 헤더는 속도가 빠른 룩업 테이블에 저장되어
      각 필드에 신속하게 접근할 수 있을 것이다.

2) 커넥션 입력 / 출력 처리 아키텍처

  • 공성능 웹 서버는 수천 개의 커넥션을 동시에 열 수 있도록 지원

  • 수천 개의 커넥션들은 웹 서버가 전 세계의 클라이언트들과 각각 한 개 이상의 커넥션을 통해 통신할 수 있게 해준다.

  • 웹 서버 아키텍처의 차이에 따라 요청을 처리하는 방식도 달라진다.

    단일 스레드 웹 서버

    • 한 번에 하나씩 요청을 처리
    • 트랙잭션이 완료되면 다음 커넥션 처리
    • 구현하기 간단하지만 처리 도중에 모든 다른 커넥션이 무시되고, 이것은 심각한 성능 문제를 만들어내므로 오직 로드가 적은 서버나
      type - o -server와 같은 진단도구에서만 적당하다.

    멀티프로세스와 멀티스레드 웹 서버

    • 여러 요청을 동시에 처리하기 위해 여러 개의 프로세스 혹은 고효율 스레드를 할당한다.
    • 스레드 / 프로세스는 필요할 때마다 만들어질 수도 있고 미리 만들어질 수 있다.
    • 몇몇 서버는 매 커넥션마다 스레드 / 프로세스 하나를 할당하지만, 서버가 수천 개의 동시 커넥션을 처리할 때
      그로 인해 만들어진 수많은 프로세스나 스레드는 많은 메모리나 시스템 리소스를 소비 하므로 최대 개수를 제한을 건다.

    다중 I/O 서버

    • 대량의 커넥션을 지원하기 위해, 많은 웹 서버는 다중 아키텍처를 채택
    • 모든 커넥션은 동시에 그 활동을 감시당한다.
    • 커넥션 상태가 바뀌면, 그 커넥션에 대한 작은 양의 처리가 수행된다.
    • 처리가 완료되면, 커넥션은 다음번 상태 변경을 위해 열린 커넥션 목록으로 돌아간다.
    • 커넥션에 대한 작업을 수행하는 것은 그 커넥션에 해야 할 일이 있을 때 뿐이다.
    • 스레드와 프로세스는 유휴 상태의 커넥션에 매여 기다리느라 리소스를 낭비하지 않는다.

    다중 멀티스레드 웹 서버

    • CPU 여러개의 이점을 살리기 위해 멀티스레딩과 다중화를 결합
    • 여러개의 스레드(보통 하나의 물리적 프로세스)는 각각 열려있는 커넥션(혹은 열려있는 커넥션의 부분 집합)
      을 감시하고 각 커넥션에 대해 조금씩 작업을 수행

5.6 단계3 : 요청 처리

  • 서버가 요청을 받으면, 서버는 요청으로부터 메서드, 리소스, 헤더, 본문(없는 경우도 있다)을 얻어내어 처리
  • POST를 비롯한 몇몇 메서드는 요청 메시지에 엔터티 본문이 있을 것을 요구하고
    그 외 OPTIONS를 비롯한 다수의 메서드는 요청에 본문이 있는 것을 허용하되 요구하진 않는다.
    많지는 않지만 GET과 같이 요청 메시지에 엔터티 본문이 있는 것을 금지하는 메서드도 있다.

5.7 단계4 : 리소스의 매핑과 접근

  • 웹 서버는 리소스 서버다.

  • 서버 위에서 동작하는 리소스 생성 애플리케이션을 통해 만들어진 동적 콘텐츠도 제공

  • 웹 서버가 클라이언트에 콘텐츠를 전달하려면, 요청 메시지에 URI에 대응하는 콘텐츠나 생성기를 웹 서버에서 찾아서
    콘텐츠 원천을 식별해야한다.

    1) Docroot

    • 리소스 매핑의 가장 단수한 형태는 요청 URI를 웹 서버의 파일 시스템 안에 있는 파일 이름으로 사용하는 것이다
      일반적으로 웹 서버 파일 시스템의 특별한 폴더를 웹 콘텐츠를 위해 예약해둔다.
      이 폴더는 문서 루트 혹은 Docroot라고 부른다.
    • httpd.conf 설정 파일에 docroot 경로를 설정해두면 요청 헤더의 URI를 문서 루트 뒤에 붙인다.
      GET /specials/joy.gif HTTP/1.0
      HOST: m3252.github.io
      

DocumentRoot /usr/local/httpd/files
/usr/local/httpd/files + /specials/joy.gif

서버는 docroot 이외의 부분이 노출되는 일이 생기지 않도록 주의해야 한다.
https://m3252.github.io/.../

```

가상 호스팅 된 docroot

  • 가상 호스팅 웹서버는 각 사이트에 그들만의 분리된 문서 루트를 주는 방법으로,
  • 한 웹서버에서 여러 개의 웹사이트를 호스팅한다.
    가상 호스팅 웹서버는 URI나 Host 헤더에서 얻은 IP 주소나 호스트 명을 이용해 올바른 문서 루트를 식별한다.

사용자 홈 디렉터리 docroots

  • 사용자들이 한대의 웹서버에서 각자의 개인 웹사이트를 만들 수 있도록 해주는 것이다.

  • 보통 /과 물결표~ 다음에 사용자 이름이 오는 것으로 시작하는 URI는 그 사용자의 개인 문서 루트를 가리킨다.

  • 개인 docroot는 주로 사용자 홈 디렉터리 안에 있는 public_html로 불리는 디렉터리지만, 설정에 따라 다르다.

    2) 디렉터리 목록

    • 대부분의 웹 서버는 클라이어트가 디렉터리 URL을 요청했을 때 다음과 같이 몇 가지 다른 행동을 취하도록 설정할 수 있다.

      • 에러를 반환한다.
      • 디렉터리 대신 특별한 '색인 파일'을 반환한다.
      • 디렉터리를 탐색해서 그 내용을 담은 HTML 페이지를 반환한다.

    3) 동적 콘텐츠 리소스 매핑

    • 웹 서버는 URI를 동적 리소스에 매핑할 수 있다. 즉, 요청에 맞게 콘텐츠를 생성하는 프로그램에 URI를 매핑하는 것이다.
    • 웹 서버들 중에서 애플리케이션 서버라 불리는 것들은 웹 서버를 복잡한 백엔드 애플리케이션과 연결하는 일을 한다.
    • 오늘날의 애플리케이션 서버는, 마소의 액티브 서버 페이지와 자바 서블릿과 같은 한층 더 강력하고 효과적인 서버사이드 동적 콘텐츠를 제공한다.

    4) 서버사이드 인클루드(Server-Side Includes, SSI)

    • 어떤 리소스가 서버사이드 인클루드를 포함하도록 설정되어 있다면 서버는 그 리소스의 콘텐츠를 클라이언트에 보내기전에 처리한다.
    • 서버는 콘텐츠에 변수 이름이나 내장된 스크립트가 될 수 있는 어떤 특별한 패턴이 있는지 검사하고 특별한 패턴은 변수 값이나
      실행가능한 스크립트의 출력값으로 치환된다.

    5) 접근 제어

    • 웹 서버는 각각의 리소스에 접근 제어를 할당할 수 있다.
    • 접근 제어된 리소스에 요청이 왔을때 웹 서버는 클라이언트의 IP주소를 근거하여 접근을 제어하거나 비밀번호를 물어볼 수 있다.

5.8 단계5 : 응답만들기

  • 한번 서버가 리소스를 식별하면, 서버는 요청 메서드로 서술되는 동작을 수행한 뒤 응답 메시지를 반환한다.

    1) 응답 엔터티

    • 트랜잭션이 응답 본문을 생성하면, 그 내용을 응답 메시지와 함께 돌려보낸다.
    • 응답 본문이 있다면 응답 메시지는 다음을 포함한다.
      • MIME 타입을 서술하는 Content-Type 헤더
      • 길이를 서술하는 Content-Length 헤더
      • 실제 응답 본문의 내용

    2) MIME 타입 결정하기

    • 웹 서버에게는 응답 본문의 MIME 타입을 결정해야하는 책임이 있다.

    • 다음은 MIME 타입과 리소스를 결정하는 여러가지 방법이다.

      mime.types

      • 웹 서버는 MIME 타입을 나타내기 위해 파일 이름의 확장자를 사용할 수 있다.

      매직 타이핑

      • 아파치는 각 파일의 MIME 타입을 알아내기 위해 파일의 내용을 검사해서 알려진 패턴에 대한 테이블에 핻ㅇ하는 패턴이 있는지 찾는다.
      • 느리지만 파일이 표준 확장자 없이 이름 지어진 경우에 편리하다.

      유형 명시

      • 특정 파일이나 디렉터리 안의 파일들이 파일 확장자나 내용에 상관없이 어떤 MIME 타입을 갖도록 웹 서버를 설정할 수 있다.

      유형 협상

      • 한 리소스가 여러 종류의 무서 형식에 속하도록 설정할 수 있다.
      • 웹 서버가 사용자와의 협상 과정을 통해 사용하기 가장 좋은 형식을 판별할 것인지의 여부도 설정할 수 있다.

    3) 리다이렉션

    • 웹 서버는 요청을 수행하기 위해 브라우저가 다른 곳으로 가도록 리다이렉트 할 수 있다.

    • 리다이렉션 응답은 3xx 상태코드로 지칭된다.

    • Location 응답 헤더는 콘텐츠의 새로운 혹은 선호하는 위치에 대한 URI를 포함한다.

      영구히 리소스가 옮겨진 경우

      • 클라이언트에게 리소스의 이름이 바뀌었으므로, 클라이어느는 북마크를 갱신할 수 있다고 말해줄 수 있다.
      • 301 Moved Permanently 상태 코드를 사용한다.

      임시로 리소스가 옮겨진 경우

      • 위치가 옮겨졌거나 이름이 임시로 변경된 경우 서버는 클라이언트를 새 위치로 리다이렉트하길 원할 것이다.
      • 303 See Other와 307 Temporary Redirect 상태 코드를 사용한다.

      URL 증강

      • 문맥 정보를 포함하기 위해 재 작성된 URL로 리다이렉트를 한다.
      • 요청이 도착하면 서버는 상태 정보를 내포한 새 URL을 생성하고 사용자를 이 새 URL로 리다이렉트한다.
      • 클라이언트는 상태 정보가 포함된 완전한 URL을 포함한 요청을 다시 한다.
      • 303 See Other와 307 Temporary Redirect 상태 코드를 사용한다.

      부하 균형

      • 과부하된 서버가 요청을 받으면, 서버는 클라이언트에를 좀 덜 부하가 걸린 서버로 리다이렉트 할 수 있다.
      • 303 See Other와 307 Temporary Redirect 상태 코드를 사용한다.

      친밀한 다른 서버가 있을 때

      • 웹 서버는 어떤 사용자에 대한 정보를 가질 수 있고, 서버는 클라이언트를
        그 클라이언트에 대한 정보를 갖고 있는 다른 서버로 리다이렉트 할 수 있다.
      • 303 See Other와 307 Temporary Redirect 상태 코드를 사용한다.

      디렉터리 이름 정규화

      • 클라이언트가 디렉터리 이름에 대한 URI 요청을 하는데 빗금(/) 을 빠트렸다면, 웹 서버는 이것이 잘 작동하도록 추가한 URI로 리다이렉트 한다.

5.9 단계6 : 응답 보내기

  • 웹 서버는 요청들 받을 때와 마찬가지로 커넥션 너머로 데이터를 보낼 때도 비슷한 이슈에 직면한다.
  • 서버는 커넥션 상태를 추적해야 하며 지속적인 커넥션은 특별히 주의해서 다뤄야한다.
  • 비지속적인 커넥션이라면, 서버는 모든 메시지를 전송하고 자신의 커넥션을 닫는다.
  • 지속적인 커넥션이라면, 서버가 Content-Length 헤더를 바르게 계산하기 위해 특별한 주의를 필요로 하는 경우나,
    클라이언트가 응답이 언제 끝나는지 알 수 없는 경우에 커넥션은 열린 상태를 유지할 것이다.

5.10 단계7 : 로깅

  • 트랙잭션이 완료되면 웹 서버는 트랙잭션이 어떻게 수행되었는지에 대한 로그를 로그파일에 기록한다.

댓글