11.5절: 웹 서버 (Web Servers)
이 절에서는 지금까지 학습한 네트워크와 소켓 프로그래밍 기술을 바탕으로 실제 작동하는 웹 서버인 Tiny를 구현하는 방법을 설명한다. Tiny는 기능이 간단하면서도 실제 웹 브라우저로 접근할 수 있는 완전한 서버이다. 이를 통해 서버의 핵심 동작 원리를 배울 수 있다.
11.5.1절: 웹의 기초 (Basic Concepts of the Web)
웹은 클라이언트-서버 모델에 기반한다. 이때 웹 서버는 정적(static) 콘텐츠(예: HTML, JPEG 파일)를 클라이언트에 전송하거나, 동적(dynamic) 콘텐츠(예: 데이터베이스 쿼리 결과)를 생성해 전달한다.
웹 트랜잭션의 흐름
- 사용자가 웹 브라우저에 URL을 입력하면 브라우저는 해당 웹 서버에 HTTP 요청 메시지를 보낸다.
- 서버는 이 요청을 분석하여 요청된 자원이 정적인지 동적인지를 판단한다.
- 정적이면 해당 파일을 열어 내용을 응답 메시지로 보내고,
- 동적이면 CGI 프로그램이나 다른 백엔드 프로그램을 실행하여 그 출력을 응답으로 보낸다.
HTTP 요청 예
웹 브라우저가 다음과 같은 요청 메시지를 보낼 수 있다:
GET /index.html HTTP/1.1 // 요청 라인
Host: example.com // 요청 헤더 (여러개)
...
- 첫 줄은 요청 라인이며, 그 뒤는 여러 개의 요청 헤더가 따른다.
- 서버는 이 요청 메시지를 읽고 요청된 자원을 해석하여 응답한다.
Tiny 서버는 이러한 HTTP 요청을 처리하고 그에 맞는 HTTP 응답을 생성하는 구조로 동작한다.
11.5.2절: 웹 콘텐츠(Web Content)
이 절에서는 Tiny 웹 서버가 처리할 수 있는 두 가지 유형의 콘텐츠인 정적 콘텐츠(static content)와 동적 콘텐츠(dynamic content)에 대해 설명한다.
정적 콘텐츠 (Static Content)
- 정적 콘텐츠는 서버의 디스크에 저장되어 있는 파일 그대로를 클라이언트에게 전송하는 것.
- 예:. html,. gif,. jpg 등의 파일
- Tiny는 serve_static 함수를 통해 파일을 열고, MIME 타입을 헤더에 지정한 뒤, 파일 내용을 그대로 클라이언트에게 전송한다.
정적 콘텐츠는 다음 조건을 충족하면 제공된다:
- HTTP 요청의 URI가 /cgi-bin/으로 시작하지 않음
- 요청된 파일이 실제 존재하고, 읽기 권한이 있음
동적 콘텐츠 (Dynamic Content)
- 동적 콘텐츠는 서버가 요청 시마다 외부 프로그램(CGI, Common Gateway Interface)을 실행하여 생성하는 출력을 클라이언트에게 전달하는 것.
- 예: /cgi-bin/adder?1&2 같은 URI는 adder라는 프로그램을 실행해 1, 2를 인자로 넘기고 결과를 출력함
Tiny는 serve_dynamic 함수에서 fork()와 execve()를 사용하여 CGI 프로그램을 실행하며, 프로그램의 출력을 클라이언트 소켓으로 리디렉션하여 전송한다.
이 경우 Tiny는:
- 환경 변수로 인자 문자열(QUERY_STRING)을 설정하고,
- execve()로 프로그램 실행 후,
- 결과를 클라이언트로 보냄
11.5.3절: HTTP 트랜잭션 (HTTP Transactions)
이 절에서는 웹 서버와 클라이언트 간의 실제 HTTP 트랜잭션이 어떻게 이루어지는지를 telnet을 사용한 예제를 통해 구체적으로 설명한다. HTTP는 텍스트 기반 프로토콜이므로, 단순한 텍스트 입출력 도구인 telnet을 사용해 요청과 응답의 흐름을 직접 실험하고 관찰할 수 있다.
HTTP 요청 (HTTP Request)
HTTP 요청은 다음 세 요소로 구성된다:
- 요청 라인(request line)
형식: method URI version
예: GET /index.html HTTP/1.1 - 요청 헤더(request headers)
예: Host: www.aol.com- HTTP/1.1에서는 Host 헤더가 반드시 필요하다.
- 브라우저 종류, 수용 가능한 MIME 타입 등의 정보를 담는다.
- 빈 줄
요청 헤더의 끝을 나타냄 (\r\n)
예제에서는 telnet www.aol.com 80 명령으로 연결 후, 다음 요청을 수동으로 보낸다:
GET / HTTP/1.1
Host: www.aol.com
<빈 줄>
HTTP 응답 (HTTP Response)
서버는 요청을 받고 다음과 같은 형식으로 응답을 보낸다:
- 응답 라인(response line)
형식: version status-code status-message
예: HTTP/1.0 200 OK - 응답 헤더(response headers)
- 빈 줄
헤더 끝을 나타냄 - 본문(response body)
요청된 실제 콘텐츠(HTML 페이지 등)
응답 헤더 예시
MIME-Version: 1.0
Content-Type: text/html
Content-Length: 42092
실습의 목적
이 절의 핵심은 HTTP 트랜잭션을 직접 관찰해 봄으로써:
- HTTP 요청 구조를 이해하고
- 서버 응답 형식을 익히며
- 웹 서버 동작 원리를 실험적으로 검증하는 것이다.
11.5.4절: 동적 콘텐츠 제공 (Serving Dynamic Content)
이 절에서는 Tiny 웹 서버가 동적 콘텐츠를 처리하는 방식을 설명한다. 동적 콘텐츠는 CGI 프로그램을 실행해서 실시간으로 생성된 결과를 클라이언트에게 전달하는 방식이다.
1. 클라이언트는 프로그램 인자들을 서버에 어떻게 전달하는가?
클라이언트가 브라우저 또는 telnet 등으로 URI 요청을 보낼 때, 보통 다음과 같은 형식을 따른다:
GET /cgi-bin/adder?1&2 HTTP/1.0
- 여기서 /cgi-bin/adder는 실행할 프로그램의 경로
- ?1&2는 인자 부분 (쿼리 문자열)
이처럼 URI의 ? 뒤에 오는 문자열은 CGI 프로그램의 인자로 사용된다.
Tiny 서버는 이 URI를 파싱 하여:
- 프로그램 파일 경로 (/cgi-bin/adder)
- 인자 문자열 (1&2)
로 나눈다.
2. 서버는 그 인자들을 자식 프로세스에 어떻게 전달하는가?
서버는 자식 프로세스를 fork()로 생성하고, execve()로 CGI 프로그램을 실행하기 전에, 환경변수 QUERY_STRING을 설정한다:
setenv("QUERY_STRING", cgiargs, 1);
- cgiargs는 URI에서 추출한 인자 문자열
- 이 환경 변수는 CGI 프로그램에서 getenv("QUERY_STRING")으로 접근할 수 있다.
즉, URI 인자 문자열을 환경변수로 자식에게 넘기는 방식이다.
3. 서버는 자식에게 다른 정보를 어떻게 전달하는가?
서버는 CGI 프로그램에 단순히 인자만이 아니라 다양한 환경 정보를 함께 전달할 수 있다. 이 정보들은 환경변수(environment variables)의 형태로 전달되며, 다음은 그 예시이다 (그림 11.26 참조):
이러한 값들은 필요에 따라 setenv() 함수로 설정되며, CGI 프로그램은 이를 통해 요청에 대한 더 많은 정보를 얻을 수 있다.
4. 자식은 자신의 출력을 어디로 보내는가?
CGI 프로그램은 실행 결과를 표준 출력(stdout)으로 출력한다. 이 출력을 클라이언트로 보내기 위해 서버는 다음처럼 설정한다:
Dup2(fd, STDOUT_FILENO);
- fd는 클라이언트 소켓 디스크립터
- 이를 표준 출력에 복사하면, CGI 프로그램의 출력이 곧바로 클라이언트로 전송된다
이렇게 하면 서버는 CGI 프로그램에게 소켓 연결을 출력 대상으로 설정해 주고, 별도의 복잡한 입출력 코드를 작성하지 않아도 된다.
부록 : 그림 11.27 두 개의 정수를 더하는 CGI 프로그램 설명
그림 11.27은 Tiny 웹 서버에서 동적 콘텐츠를 생성하는 CGI 프로그램 예제로, /cgi-bin/adder?15000&213처럼 두 정수를 더하는 기능을 수행한다. 이 그림에 나온 코드는 adder.c 파일에 구현되어 있으며, 다음의 기능을 수행한다:
#include "csapp.h"
int main(void) {
char *buf, *p;
char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
int n1=0, n2=0;
/* 1. QUERY_STRING에서 인자 추출 */
if ((buf = getenv("QUERY_STRING")) != NULL) {
p = strchr(buf, '&');
*p = '\0'; // 첫 번째 숫자와 두 번째 숫자를 나눔
strcpy(arg1, buf); // 첫 번째 인자 저장
strcpy(arg2, p+1); // 두 번째 인자 저장
n1 = atoi(arg1); // 문자열을 정수로 변환
n2 = atoi(arg2);
}
/* 2. HTML 응답 본문 구성 */
sprintf(content, "QUERY_STRING=%s", buf);
sprintf(content, "Welcome to add.com: ");
sprintf(content, "%sTHE Internet addition portal.\r\n<p>", content);
sprintf(content, "%sThe answer is: %d + %d = %d\r\n<p>", content, n1, n2, n1 + n2);
sprintf(content, "%sThanks for visiting!\r\n", content);
/* 3. HTTP 응답 헤더 출력 */
printf("Connection: close\r\n");
printf("Content-length: %d\r\n", (int)strlen(content));
printf("Content-type: text/html\r\n\r\n");
/* 4. 응답 본문 출력 */
printf("%s", content);
fflush(stdout);
exit(0);
}
주요 흐름
- 인자 추출: 환경변수 QUERY_STRING에서 두 정수(예: "15000&213")를 가져와 파싱 한다.
- 처리: 두 정수를 더한다.
- 출력 생성: 간단한 HTML 콘텐츠로 사용자에게 결과를 보여준다.
- HTTP 응답: 웹 브라우저가 이해할 수 있는 형식의 HTTP 헤더와 본문을 출력한다.
추가 부록 : strcpy, sprintf, stdout 이 무엇인가?
strcpy(dest, src) – 문자열 복사
- src 문자열을 dest에 복사한다.
- 주의: dest는 충분한 크기의 배열이어야 한다.
예시:
char s1[100], s2[] = "hello";
strcpy(s1, s2); // s1은 이제 "hello"
sprintf(buf, "형식", 값들) – 문자열 출력 (버퍼에)
- printf와 유사하지만, 출력을 화면이 아니라 문자열 버퍼에 저장한다.
- 여러 번 호출해 하나의 buf에 문자열을 덧붙일 수 있다.
예시:
char buf[100];
sprintf(buf, "Sum: %d + %d = %d", 1, 2, 3);
stdout – 표준 출력 스트림
- 터미널, 또는 웹 서버에서는 클라이언트로 연결된 소켓을 의미한다.
- printf()는 기본적으로 stdout에 출력한다.
- dup2(fd, STDOUT_FILENO)로 리디렉션하면 printf 결과가 클라이언트에게 직접 전달된다.
strchr(buf, '&') – 특정 문자 위치 찾기
- buf 문자열에서 '&' 문자의 첫 위치를 찾고, 그 포인터를 반환한다.
- 이 위치를 기준으로 인자 문자열을 나눌 수 있다.
atoi(str) – 문자열 → 정수
- 문자열로 표현된 정수를 실제 정수로 변환한다.
- 예: atoi("123") → 123
getenv("QUERY_STRING") – 환경 변수 가져오기
- 현재 프로세스의 환경변수 중 QUERY_STRING이라는 이름의 값을 반환한다.
- 동적 콘텐츠에서는 클라이언트가 보낸 요청 인자를 여기서 받아온다.
fflush(stdout) - 출력 버퍼를 비워서 즉시 출력이 이루어지도록 강제하는 함수
- stdout에 쌓여 있는 모든 데이터를 즉시 출력한다.
- 일반적으로 printf() 등은 내부 버퍼에 데이터를 쌓아두고, 줄바꿈 문자(\n)가 있거나 버퍼가 가득 차거나 프로그램이 종료될 때 출력한다.
- 그러나 네트워크 소켓이나 파이프에서는 이러한 자동 플러시가 일어나지 않으므로, fflush(stdout);으로 수동으로 비워줘야 한다.
예제 실행 결과 (그림 11.28)
GET /cgi-bin/adder?15000&213 요청을 보냈을 때, 응답은 다음과 같이 구성된다:
HTTP/1.0 200 OK
Server: Tiny Web Server
Content-length: 115
Content-type: text/html
Welcome to add.com: THE Internet addition portal.
<p>The answer is: 15000 + 213 = 15213
<p>Thanks for visiting!
'크래프톤 정글 (컴퓨터 시스템: CSAPP) > 11장 네트워크 프로그래밍' 카테고리의 다른 글
컴퓨터 시스템 : CSAPP 11장 정리 - 11.6 종합설계 :소형 웹 서버 Part.2 (2) | 2025.05.03 |
---|---|
컴퓨터 시스템 : CSAPP 11장 정리 - 11.6 종합설계 :소형 웹 서버 Part.1 (0) | 2025.05.03 |
컴퓨터 시스템 : CSAPP 11장 정리 - 11.4 소켓 인터페이스 (0) | 2025.05.02 |
컴퓨터 시스템 : CSAPP 11장 정리 - 11.3 글로벌 IP 인터넷 (0) | 2025.05.02 |
컴퓨터 시스템 : CSAPP 11장 정리 - 11.1 ~ 11.2 (1) | 2025.05.02 |