# 05. 웹 서버

## 5.1 다채로운 웹 서버

- 웹 서버: 웹 서버 소프트웨어 / 웹 페이지 제공에 특화된 장비(컴퓨터 같은). 둘 다 지칭
- 웹 서버는 기능 형태 크기가 다양. 기능은 다 달라도 리소스에 대한 HTTP 요청을 받아서 콘텐츠를 클라이언트에 돌려주는 것은 동일.

### 5.1.1 웹 서버 구현

- 웹 서버는 HTTP 및 그와 관련된 TCP 처리를 구현한 것.
    - HTTP 프로토콜 구현
    - 자신이 제공하는 리소스 관리
    - 웹 서버를 설정, 통제, 확장하기 위한 관리 기능 제공
- TCP  커넥션 관리에 대한 책임은 웹 서버와 운영체제가 나눠 갖음.
    - 운영체제는 컴퓨터 시스템의 하드웨어 관리, TCP/IP 네트워크 지원, 웹 리소스를 유지하기 위한 파일시스템, 현재 연산 활동을 제어하기 위한 프로세스 관리를 제공
- 웹 서버의 여러가지 형태
    1. 다목적 소프트웨어 웹 서버를 표준 컴퓨터 시스템에 설치 및 실행할 수 있음
    2. 마이크로프로세서의 기적으로, 회사들이 사용자에게 판매할 기기안에 구현된 웹 서버를 내장 시켜 완전한 관리 콘솔로 제공. (예: 공유기가 웹 브라우저로 접근 가능한 관리 기능 제공)

### 5.1.2 다목적 소프트웨어 웹 서버

- 네트워크에 연결된 표준 컴퓨터 시스템에서 동작. 거의 모든 컴퓨터와 운영체제에서 동작.
    - 아파치 같은 오픈 소스 소프트웨어나, 마이크로소프트 같은 상용 소프트웨어가 있음
- 수만 가지 종류의 웹 서버 프로그램이 존재하지만, 그중 몇 가지 웹 서버 소프트웨어만이 널리 사용
    - [2014년 기준](https://news.netcraft.com/archives/2014/08/27/august-2014-web-server-survey.html): 마이크로소프트(37%), 아파치(35%), nginx(14%), ...
    - [2020년 기준](https://news.netcraft.com/archives/category/web-server-survey/): nginx(34%), 아파치(27%), 마이크로소프트(8%), ...

### 5.1.3 임베디드 웹서버

- 일반 소비자용 제품에 내장될 목적으로 만들어진 작은 웹 서버.(예: 프린터나 가전제품)
    - 몇몇 임베디드 웹 서버는 매우 작은 크기로 구현되어 있고, 보통 최소한의 기능만 제공

## 5.2 간단한 펄 웹 서버

- 완전한 기능을 갖춘(풍부한 리소스 지원, 가상 호스팅, 접근 제어, 로깅, 설정, 모니터링, 그 외 성능을 위한 각종 기능) HTTP 서버 개발은 할일이 좀 많지만, 최소한으로 기능하는 HTTP 서버는 30줄 이하의 펄(Perl)코드로 개발 가능
- 다음 코드는 HTTP 디버깅을 사용 되는 type-o-serve 라고 하는 펄 프로그램이다.
    - HTTP 커넥션을 기다리고, 요청 메시지를 받아 화면에 출력하고, 클라이언트에게 답할 응답을 기다렸다가 전송.

```perl
#!/usr/bin/perl

use Socket;
use Carp;
use FileHandle;

# (1) 명령줄에서 덮어쓰지 않는 이상 8080 포트를 기본으로 사용
$port = (@ARGV? @ARGV[0] : 8080);

# (2) 로컬 TCP 소켓을 생성하고 커넥션을 기다리도록(listen) 설정한다.
$proto = getprotobyname('tcp');
socket(S, PF_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-Serve Accepting on Port %d>>>\n\n",$port);

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

	# (5) 누구로부터의 커넥션인지 출력한다.
	$cname = gethostbyaddr($caddr, AF_INET);
	printf("   <<<Request From '%s'>>>\n",$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: 클라이언트 커넥션 수락

- 클라이언트가 이미 서버에 대해 열린 지속 커넥션을 갖고 있다면 그 커넥션을 사용할 수 있지만, 그렇지 않다면 새 커넥션을 열 필요가 있음

### 5.4.1 새 커넥션 다루기

- 클라이언트 → 서버 TCP 커넥션 요청시, 웹 서버는 TCP 커넥션에서 IP를 추출하여 어떤 클라이언트가 있는지 확인.
- 새 커넥션을 맺고 받아들여지면, 서버는 새 커넥션을 커넥션 목록에 추가하고 커넥션에서 오가는 데이터를 지켜보기 위한 준비를 함.
- 웹 서버는 어떤 커넥션이든 마음대로 거절하거나 닫을 수 있음.
    - 어떤 웹 서버는 클라이언트의 IP 주소, 호스트 명이 인가 되지 않았거나 악의적인 것으로 알려진 경우 커넥션을 닫음. 다른 신원 식별 기법 또한 사용할 수 있음.

### 5.4.2 클라이언트 호스트 명 식별

- 대부분의 웹 서버는 '역방향 DNS(reverse DNS)'를 사용해서 클라이언트의 IP 주소를 클라이언트의 호스트 명으로 변환하도록 설정 되어있음
- 클라이언트 호스트 명을 구체적인 접근 제어, 로깅을 위해 사용할 수 있음
- 호스트 명 룩업(hostname lookup)은 많은 시간이 걸릴 수 있어 웹 트랜잭션을 느리게 할 수 있음
    - 많은 대용량 웹 서버는 꺼두거나 특정 콘텐츠에 대해서만 켜놓음

### 5.4.3 ident를 통해 클라이언트 사용자 알아내기

- 몇 웹서버는 IETF ident 프로토콜을 지원.
    - ident 프로토콜: 서버에게 어떤 사용자의 이름이 HTTP  커넥션을 초기화 했는지 찾아낼 수 있게 해줌
    - 웹 서버 로깅에서 유용한 정보로, 널리 쓰이는 일반 로그 포맷(Common Log Format)의 두번째 필드는 각 HTTP 요청의 ident 사용자 이름을 담고 있음. ([rfc931](https://tools.ietf.org/html/rfc931), [rfc1413](https://tools.ietf.org/html/rfc1413) 참조)
- HTTP 클라이언트 사용자의 이름을 알아내기 위한 ident 프로토콜 사용하기
    1. 클라이언트가 ident 프로토콜을 지원한다면, 클라이언트는 ident 결과를 위해 TCP 포트 133번을 listen.
    2. 클라이언트가 HTTP 커넥션을 연다
    3. 서버는 자신의 커넥션을 클라이언트의 identd 서버 포트(133)을 향해 연다
    4. 서버는 새 커넥션에 대응하는 사용자 이름을 묻는 간단한 요청을 보냄
- ident는 조직 내부에서는 유용하지만, 공공 인터넷에서는 잘 동작하지 않음
    - 많은 클라이언트 PC는 identd 신원확인 프로토콜 데몬 소프트웨어를 실행하지 않음
    - ident 프로토콜은 HTTP 트랜잭션을 유의미하게 지연
    - 방화벽이 ident 트래픽이 들어오는 것을 막는 경우가 많음
    - ident 프로토콜은 안전하지 않고 조작하기 쉬움
    - ident 프로토콜은 가상 IP주소를 잘 지원하지 않음
    - 클라이언트 사용자의 이름 노출로 인한 프라이버시 침해 우려
- 아파치의 경우 identityCheck 지시어를 이용해 ident 룩업을 사용하게 할 수 있음.
    - 가용한 ident 정보가 없으면 ident 로그 필드를 -으로 채움.
    - 보통은 ident 정보가 없기 때문에 일반 로그 포맷 로그 파일의 두 번쨰 필드는 -로 채워짐.

## 5.5 단계 2: 요청 메시지 수신

- 커넥션에 데이터가 도착하면, 웹 서버는 네트워크 커넥션에서 그 데이터를 읽어들이고 파싱하여 요청 메시지를 구성.
- 요청 메시지 파싱 시에 웹서버가 하는 일:
    - 요청 줄을 파싱하여 요청 메서드, 지정된 리소스 식별자(URI), 버전번호(없으면 HTTP/0.9로 인지하기도 함)를 찾음. 각 값은 스페이스 한 개로 분리되어 있으며, 요청줄은 캐리지 리턴 줄바꿈(CRLF) 문자열로 끝남
    - 메시지 헤더를 읽는다. 각 메시지 헤더는 CRLF 로 끝남.
    - 헤더의 끝을 의미하는 CRLF로 끝나는 빈 줄을 찾아낸다. (존재한다면)
    - 요청 본문이 있다면 읽어 들인다(길이는 Content-Length 헤더로 정의됨).
- 요청 메시지 파싱할 때, 웹 서버는 입력 데이터를 네트워크로부터 불규칙적으로 받음. 네트워크 커넥션은 언제라도 무효화 될 수 있음.
    - 웹 서버는 파싱해서 이해하는 것이 가능할 분량을 확보할 때까지 데이터를 네트워크로부터 읽어서 메시지 일부분을 메모리에 임시로 저장할 필요가 있음.

### 5.5.1 메시지의 내부 표현

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

### 5.5.2 커넥션 입력/출력 처리 아키텍처

- 고성능 웹 서버는 수천 개의 커넥션을 동시에 열 수 있음
    - 커넥션은 빠르게 요청을 보내거나, 요청이 느리거나, 요청이 가끔 오거나, 요청을 대기하고 있음
- 언제든지 요청이 도착할 수 있으므로, 웹 서버는 항상 새 요청을 주시함.
    - 웹 서버 아키텍처에 따라 요청을 처리하는 방식이 다름

***단일 스레드 웹 서버***

- 한번에 하나의 요청을 처리. 트랜잭션이 완료되면 다음 커넥션 처리
- 구현하기 간단한 아키텍처이지만, 처리 중에 다른 커넥션은 무시됨.
    - 심각한 성능 문제를 만들어내므로, 로드가 적은 서버나 type-o-serve같은 진단도구에만 적당

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

- 여러 요청을 동시에 처리하기 위해 여러 개의 프로세스, 혹은 고 효율 스레드를 할당
    - 스레드/프로세스는 필요할 때마다 만들어질 수도 있고, 미리 만들어지기도 함(워커 풀)
- 일부 서버는 매 커넥션마다 스레드/프로세스를 하나씩 할당하지만, 대부분은 최대 개수에 제한을 둠
    - 수만 개의 동시 커넥션을 처리하게 되면 수많은 프로세스나 스레드가 메모리나 시스템 리소스를 소비함.

***다중 I/O 서버***

- 많은 웹서버가 대량의 커넥션을 지원하기위해 채택
- 모든 커넥션은 동시에 그 활동을 감시 당하며, 상태가 바뀌면 그 커넥션에 대한 작은 양의 처리를 수행
    - 그 처리가 완료되면, 커넥션은 다음 번 상태 변경을 위해 열린 커넥션 목록으로 돌아감.
    - 커넥션에 대해 작업을 수행하는 것은 커넥션에 해야할 일이 있을 때뿐, 유휴상태의 커넥션을 기다리지 않음.

***다중 멀티스레드 웹 서버***

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

## 5.6 단계 3: 요청 처리

- 웹 서버가 요청을 받으면, 요청으로부터 메서드, 리소스, 헤더, 본문(없을 수 있음)을 얻어 처리
    - POST와 몇 메서드는 요청 메시지에 엔터티 본문이 있을 것을 요구 함.
    - OPTIONS와 몇 메서드는 본문을 허용하지만 요구하지 않음.
    - GET은 엔터티 본문이 있는 것을 금지
- 상세한 내용은 다른 챕터에서 추후 진행

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

- 웹 서버는 리소스서버임. HTML, JPEG와 같은 미리 만들어진 콘텐츠를 제공하며, 서버 위에 동작하는 리소스 생성 애플리케이션을 통해 만들어진 동적 콘텐츠도 제공.
- 클라이언트에 콘텐츠를 전달하려면, 요청메시지의 URI에 대응하는 알맞는 콘텐츠나 콘텐츠 생성기를 찾아서 식별해야함.

### 5.7.1 Docroot

- 웹 서버는 여러 종류의 리소스 매핑을 지원.
    - 그 중 가장 단순한 형태는 요청 URI = 웹서버의 파일 시스템 내 파일 이름으로 사용하는 것
- 일반적으로 웹 서버 파일 시스템의 특별한 폴더(**문서 루트, docroot**)를 웹 콘텐츠를 위해 예약 함
    - 웹 서버는 요청 메시지에서 URI를 가져와 문서 루트 뒤에 붙임
- `httpd.conf` 파일에 `DocumentRoot` 줄을 추가해 아파치 웹 서버의 문서 루트를 설정할 수 있음

    ```jsx
    DocumentRoot /usr/local/httpd/files
    ```

- 서버는 상대적인 URL이 docroot를 벗어나 이외의 부분이 노출되지 않도록 주의해야 함. (예: `http://www.google.com/../`) 대부분의 성숙한 웹서버는 미허용.

***가상 호스팅된 docroot***

- 가상 호스팅 웹 서버는, 각 사이트에 그들만의 분리된 문서 루트를 주어 한 서버에서 여러 개의 웹사이트를 호스팅함
- 가상 호스팁 웹서버는 URI/Host 헤더에서 얻은 IP주소나 호스트 명을 이용해 문서 루트 식별
- 가상으로 호스팅 되는 docroot 설정은 대부분 웹 서버에서 간단함

    ```
    # 아파치 웹 서버 가상 호스트 docroot 설정
    <VirtualHost www.joes-hardware.com>
    	ServerName www.joes-hardware.com
    	DocumentRoot /docs/joe
    	TransferLog /logs/joe.access_log
    	ErrorLog /logs/joe.error_log
    </VirtualHost>

    <VirtualHost www.marys-antiques.com>
    	ServerName www.marys-antiques.com
    	DocumentRoot /docs/mary
    	TransferLog /logs/mary.access_log
    	ErrorLog /logs/mary.error_log
    </VirtualHost>
    ...
    ```

***사용자 홈 디렉터리 docroots***

- 사용자들이 한 대의 웹 서버에 각자의 개인 웹 사이트를 만들 수 있도록 하는 것.
- `/~` 다음에 사용자 이름이 오는 것으로 시작하는 URI는 그 사용자의 개인 문서 루트를 가리킴
- 개인 docroot는 주로 사용자 홈 디렉터리 안에 있는 `public_html` 으로 불리는 디렉터리. (설정따라 다름)
    - `/~bob/index.html` → `/home/bob/public_html/...`

### 5.7.2 디렉터리 목록

- 웹 서버는 경로가 파일이 아닌 디렉터리를 가리키는 URL에 대한 요청을 받을 수 있음
- 대부분의 웹 서버는 디렉터리 URL 요청에 대해 다음과 같이 행동을 취하도록 설정 가능
    - 에러 반환
    - 디렉터리 대신할 특별한 '색인 파일' 반환
    - 디렉터리를 탐색해 그 내용을 담은 HTML 파일 반환. (대부분의 경우)
- 아파치 웹 서버에서 DirectoryIndex 설정 지시자를 사용하여 기본 디렉터리 파일로 사용될 파일 이름의 집합 설정 가능

    ```
    DirectoryIndex index.html index.htm home.html home.htm index.cgi
    ```

- 사용자가 디렉터리 URI를 요청했을 때 기본 색인 파일 없고, 디렉터리 색인 기능이 꺼져 있지 않다면, 많은 웹 서버는 자동으로 그 디렉터리 내의 파일들의 정보를 열거한 HTML 파일을 반환
    - 편리하지만, 보안 문제가 있다. 아파치 지시자로 색인 파일 자동 생성 끄기 가능.
    - `Option -Indexes`

### 5.7.3 동적 콘텐츠 리소스 매핑

- 대부분의 웹 서버는 URI를 동적 리소스(요청에 맞게 콘텐츠 생성)에 매핑 가능.
- 아파치는 URI의 경로명이 실행 가능한 프로그램이 위치한 디렉터리로 매핑되도록 설정하는 기능 제공

    ```
    ScriptAlias /cgi-bin/ /usr/local/etc/httpd/cgi-programs/
    ```

    - URI경로가 `/cgi-bin/` 으로 시작하면 `/usr/.../cgi-programs/` 에서 프로그램을 찾아 실행하라는 뜻
- 아파치는 특정 확장자의 파일만 실행하도록 설정할 수 도 있음. 이 방법은 실행 가능한 스크립트를 아무 디렉터리에나 위치 시킬 수 있음.

    ```
    AddHandler cgi-script .cgi
    ```

    - `.cgi` 로 끝나는 모든 웹 리소스가 실행되어야 함을 명시.
- CGI는 웹 초창기에 널리 쓰인 서버사이드 애플리케이션 실행을 위한 인터페이스.

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

- 많은 웹 서버가 지원하며, 어떤 리소스가 서버사이드 인클루드를 포함하고 있는 것으로 설정되어있다면, 서버는 그 리소스 콘텐츠를 보내기 전에 처리.
- 서버는 콘텐츠에 변수 이름이나 내장된 스크립트가 될 수 있는 어떤 특별한 패턴이 있는지(주로 특별한 HTML 주석 안에 포함) 검사.
- 특별한 패턴은 변수 값이나 실행 가능한 스크립트의 출력 값으로 치환. 동적 콘텐츠를 만드는 용도 사용.

### 5.7.5 접근 제어

- 웹 서버는 각각의 리소스에 접근 제어를 할당할 수 있음.
- 접근 제어되는 리소스에 대한 요청이 왔을 때, 클라이언트 IP 주소에 근거하여 접근을 제어 할 수 있고, 또는 리소스에 접근하기 위한 비밀번호를 물어볼 수 있음

## 5.8 단계 5: 응답 만들기

- 리소스 식별 후, 서버는 요청 메서드의 동작을 수행한 뒤 응답 메시지 반환
    - 응답 상태 코드, 응답 헤더, 응답 본문(생성되면) 포함

### 5.8.1 응답 엔터티

- 트랜잭션이 응답 본문을 생성한다면, 함께 반환.
- 응답메시지는 당므을 포함
    - 응답 본문의 MIME 타입을 기술하는 Content-Type
    - 응답 본문의 길이를 서술하는 Content-Length 헤더
    - 실제 응답 본문 내용

### 5.8.2 MIME 유형 결정하기

- 웹 서버는 응답 본문의 MIME 타입을 결정해야 함

***mime.types***

- 파일 이름의 확장자를 사용. 파일확장자별 MIME 타입이 담긴 파일을 탐색함.
- 가장 흔한 방법

***매직 타이핑(Magic typing)***

- 아파치 웹 서버는 MIME 타입을 알아내기 위해 파일 내용을 검사해서 알려진 패턴에 대한 테이블(매직 파일이라 불림)에 해당 패턴이 있는지 탐색.
- 느리지만 파일이 표준확장자 없이 이름이 지어진 경우에 편리

***유형 명시(Explit typing)***

- 파일 확장자나 내용에 상관없이 어떤 MIME 타입을 갖도록 웹 서버가 설정.

***유형 협상(Type negotiation)***

- 한 리소스가 여러 종류의 문서 형식에 속하도록 설정. 웹 서버가 사용자와의 협상 과정에서 가장 사용하기 좋은 형식(그리고 대응하는 MIME 타입)을 판별할 것인지 여부 설정 가능.
- 특정 파일이 특정 MIME 타입을 갖게끔 설정도 가능

### 5.8.3 리다이렉션

- 웹 서버는 요청을 수행하기 위해 브라우저가 다른 곳으로 가도록 성공 매시지 대신 리다이렉션 응답을 반환
- 리다이렉션 응답의 상태코드는 3XX
- Location응답 헤더는 콘텐츠의 새로운 선호하는 위치에 대한 URI를 포함

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

- 클라이언트에게 리소스 이름이 바뀌었으므로 북마크를 갱신하라고 전달.
- 301 Moved Permanently 상태코드 사용

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

- 클라이언트에게 임시적으로 바뀌었으니, 이후에도 원래 URL로 찾아오고 북마크도 갱신하지 않으라고 전달
- 303 See Other, 307 Temporary Redirect 상태코드 사용

***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 헤더를 바르게 계산하기위해 특별한 주의를 필요로 하는 경우나, 클라이언트가 언제 끝나는지 알 수 없는 경우(4장 참조)에, 열린 상태 유지

## 5.10 단계 7: 로깅

- 트랜잭션 완료 후, 어떻게 수행 되었는지 로그 파일에 기록.
- 대부분의 웹서버는 로깅에 대한 여러가지 설정양식 제공.
- 자세한 내용은 21장