gyuhong | seulee2 | yunjcho | hujeong |
---|---|---|---|
홍규선 | 이승효 | 조윤주 | 정희호 |
HTTP/1.1 프로토콜 기반으로 소켓 통신을 하는 웹서버를 C++로 구현하는 프로젝트입니다.
웹서버를 가상 호스팅, 라우팅 등을 설정 파일을 통해 동작시킬 수 있습니다.
I/O Multiplexing 함수인 kqueue()를 사용해서 들어오는 요청을 비동기적으로 처리합니다.
webserv.mp4
이 프로젝트는 mac os 환경에서 동작합니다.
make
export WEBSERV_ROOT=$PWD
./webserv [Config File]
docker를 이용해서 윈도우나 리눅스 환경에서도 테스트 가능합니다.
docker compose up -d
웹 브라우저로 127.0.0.1(포트 80)으로 접속하여 테스트할 수 있습니다.
- 설정 파일을 파싱해서 port, host, location 등 설정대로 웹 서버를 동작시킵니다.
- 클라이언트가 연결을 요청하면 요청을 수락하고 연결을 맺습니다.
- 요청을 맺은 후 클라이언트의 메시지를 해석하고 클라이언트의 요청 url이 일반 파일인지 CGI인지 확인합니다.
- 일반 파일에 대한 요청이면 파일을 읽은 후 응답 메시지에 담아 클라이언트에게 전송합니다.
- CGI 요청이면 CGI 프로세스를 동작시킨 후 CGI 프로세스와 통신하여 응답 메시지를 생성하고 클라이언트에게 전송합니다.
- 클라이언트에게 응답 메시지를 전송한 뒤 다시 클라이언트의 요청 메시지를 기다립니다(keep-alive).
- 하나의 웹서버에서 여러 도메인 이름을 호스팅하는 방법입니다.
- 크게 이름 기반(name-based), IP 기반(IP-based) 가상 호스팅으로 나뉩니다.
- 이름 기반 가상 호스팅에서는 같은 IP 주소에 대해 여러 호스트 이름을 사용할 수 있고, 클라이언트는 요청메시지에 Host 헤더를 명시해야 합니다.
- IP 기반 가상 호스팅은 웹사이트마다 다른 IP 주소 또는 Port를 가지는 방식입니다.
- 이 프로젝트는 이름 기반 가상 호스팅을 지원하며, nginx랑 유사하게 server block 내의 server_name을 다르게 함으로써 작동합니다.
- URI 요청 수신: 요청이 들어오면 이름 기반 호스팅에 따라 적절한 server block을 찾습니다.
- location block 매칭: 요청된 URI와 부분 일치되는 server block 내의 location block URI를 찾습니다.
- URI 처리: 찾은 location block의 지시어에 따라 요청된 URI를 처리합니다.
- 301 redirection 처리: 특정 조건에 따라 요청된 URI에 대해 301 redirection을 처리할 수 있습니다. 1. return 지시어가 있는 경우 2. 같은 URI에 대한 요청이 '/' 없이 들어온 경우, '/'가 추가된 URI로 처리됩니다.
- 기본 server block 처리: 적절한 server block을 찾지 못하면 첫 번째 server block으로 처리됩니다.
- 기본 location block 처리: 적절한 location block을 찾지 못하면 해당 server block의 지시어에 따라 처리됩니다.
- 정규식 지원 여부: 정규식을 사용한 URI 매칭을 지원하지 않습니다.
구성파일 내에 다음의 지시어들을 지원합니다.
-
listen: 서버가 클라이언트의 요청을 수신할 포트 번호를 지정합니다. 예: listen 80;
-
server_name: 서버의 도메인 이름을 지정합니다. 예: server_name example.com;
-
root: nginx의 root보다는 alias에 가깝습니다. 대체할 경로를 지정합니다.
-
예: location /i/ { root /data/w3/images/; } 이었을 때 “/i/top. gif”는 "/data/w3/images/top. gif"가 됩니다.
-
index: 서버에 요청이 들어왔을 때 제공할 기본 파일을 제공합니다. 기본값은 index.html입니다. 예: index test.html;
-
error_page: 특정 HTTP 에러 코드에 대해 사용자에게 제공할 페이지를 지정합니다.
-
error_page 지시어가 없거나 에러 페이지를 open 하는데 에러가 나면 하드 코딩된 기본 에러 페이지를 제공합니다. 예: error_page 404. html
-
client_max_body_size: 클라이언트 요청의 max body 크기를 제한합니다. 최대 크기는 2GB입니다. M, K, G 유닛을 지원합니다. 예: client_max_body_size 8M;
-
return: 요청에 대한 응답으로 301 redirection 합니다. return www.example.com;
-
autoindex: 폴더를 요청했을 때 디렉터리 내용을 나열할 건지 결정합니다. 기본값은 off입니다. 예: autoindex on;
-
limit_except: 특정 HTTP 메서드를 허용하거나 금지합니다. 기본값은 get, post 허용입니다. 예: limit_except deny POST;
-
cgi_extension: 처리할 CGI 파일의 확장자를 지정합니다. 예: cgi_extension .cgi;
-
cgi_path: 처리할 CGI 파일 위치를 지정합니다. 기본값은 '/' 입니다. 예: /cgi-bin/login.py;
-
cgi_upload_dir: 파일 올릴 폴더를 지정합니다. 예: /www/member/;
-
각 블록 유형(HTTP, server, location)에 따라 사용할 수 있는 지시어가 다르며, 하위 블록에 같은 이름의 지시어가 선언되면, 해당 지시어는 상위 블록의 설정을 덮어쓰고 새로운 값을 적용합니다.
지시어 | HTTP_BLOCK | SERVER_BLOCK | LOCATION_BLOCK | 중복 허용 여부 |
---|---|---|---|---|
root | O | O | O | X |
index | O | O | O | O |
error_page | O | O | O | O |
client_max_body_size | O | O | O | X |
listen | X | O | X | X |
server_name | X | O | X | X |
return | X | O | O | X |
autoindex | X | X | O | X |
limit_except | X | X | O | O |
cgi_extension | X | X | O | X |
cgi_path | X | X | O | X |
cgi_upload_dir | X | X | O | X |
- 이 표에서 "O"는 해당 블록에서 지시어가 허용됨을, "X"는 허용되지 않음을 나타냅니다.
- "중복 허용 여부"는 해당 지시어가 같은 블록 내에서 여러 번 선언될 수 있는지를 나타냅니다.
- I/O Multiplexing으로 여러 클라이언트의 요청을 동시에 처리합니다.
- I/O Multiplexing은 커널에서 fd를 감시하고 fd에 처리할 데이터가 발생했을 때 유저 프로세스에게 callback 신호가 오고 이를 처리하는 방식입니다.
- 데이터를 처리할 수 있을 만큼만 처리하기 때문에 여러 요청을 동시적으로 다룰 수 있습니다.
mKq = kqueue();
- kqueue 함수는 event를 저장할 새로운 queue를 커널에 요청하는 함수입니다. 해당 큐의 파일 디스크립터를 반환합니다.
int n = kevent(mKq, &mNewEvents[0], mNewEvents.size(), mHandleEvents, MAX_EVENT_CNT, NULL);
for (int i = 0; i < n; ++i)
{
reinterpret_cast<AEvent *>(mHandleEvents[i].udata)->handle();
}
- kevent 함수에서 mNewEvents로 감시해야 하는 이벤트를 커널 큐에 등록시키고, mHandleEvents로 데이터가 발생한 요청을 반환받아 반환된 요청을 처리합니다.
- HTTP 메시지는 요청 메시지와 응답 메시지 두 종류로 분류할 수 있습니다.
// 요청 메시지
GET / HTTP/1.1
Host: localhost
// 응답 메시지
HTTP/1.1 200 OK
Content-type: text/html; =UTF-8
Content-length: 2134
(바디 생략)
- HTTP 메시지는 시작줄 헤더 바디로 나누어 해석합니다.
- HTTP 요청 메시지의 시작 줄은 메소드, 경로, 프로토콜로 구성되어 있습니다.
- 이 프로젝트는 GET, POST, DELETE 메소드를 지원하며, HTTP/1.1에 따라 응답을 보낸 뒤에도 소켓 연결을 지속합니다.
- 헤더의 Host와 시작줄의 경로를 확인하고, 요청에 따른 응답 메세지를 시작줄 헤더 바디로 구성해서 소켓을 통해 전송합니다.
- Host 헤더는 필수 필드로 요청 메시지 Host 헤더가 없을 시 400 Bad Request를 응답합니다.
- Content-length로 전달된 길이만큼 본문을 읽습니다.
- 응답 메시지의 시작줄 상태코드로 요청이 성공적으로 완료되었는지 확인할 수 있고, 이 프로젝트는 200번대부터 500번대까지 다양한 상태코드를 지원합니다.
- 웹서버가 요청마다 동적인 페이지를 생성해주기 위해 CGI를 사용했습니다.
- 유저가 CGI에 해당하는 url을 전송했을 때 CGI에 해당하는 디렉토리일 경우 웹서버는 파일을 읽어 전송하는 것이 아닌 프로그램을 실행시키고 프로그램의 출력이 클라이언트에게 전달합니다.
- HTTP 헤더 내용은 환경변수로 변환되어 CGI 프로세스에 전달되고 요청 본문(BODY)은 표준입력을 통해 CGI 프로세스에 전달됩니다.
- 동적으로 상호 작용하는 POST나 DELETE 같은 메소드는 CGI로 처리한 다음 결과를 웹서버가 클라이언트에게 반환합니다.
- CGI 프로세스는 다양한 언어로 개발될 수 있고 이 프로젝트는 python과 perl을 사용했습니다.