main 함수
웹 서버가 클라이언트의 요청을 처리하기 위한 전형적인 서버 루프를 구현
// argv: 문자열 포인터의 배열
int main(int argc, char **argv) {
// listenfd: 리스팅 소켓 파일 드스크립터, connfd: 클라와 연결을 위한 소켓 파일 디스크립터
int listenfd, connfd;
// 호스트 및 포트 저장할 버퍼
char hostname[MAXLINE], port[MAXLINE];
// 클라 주소 길이 저장 변수
socklen_t clientlen;
// 클라 주소 저장할 구조체
struct sockaddr_storage clientaddr;
/* 명령줄 인수 확인 (하나의 인수만 요구 (제목 포함 2))*/
if (argc != 2) {
fprintf(stderr, "usage: %s <port>\n", argv[0]); // argv[0]은 실행된 프로그램의 이름
exit(1);
}
// 리스닝 소켓 생성
listenfd = Open_listenfd(argv[1]);
// 무한루프 안에서 클라 연결을 계속 수락, clientaddr 에는 클라의 주소 정보가 저장
while (1) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
// 클라 정보 출력: clientaddr에서 호스트, 포트 추출해서 hostname, port에 저장
Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE,
port, MAXLINE, 0);
printf("Accepted connection from (%s, %s)\n", hostname, port);
doit(connfd); // 클라이언트 요청 처리
Close(connfd); // 클라이언트 연결 종료
}
}
1. 변수 선언 및 초기화
- listenfd : 리스닝 소켓의 파일 디스크립터를 저장합니다. (연결 요청 수신 소켓)
- connfd : 클라이언트와 연결된 소켓의 파일 디스크립터를 저장합니다.
- hostname과 port : 클라이언트의 호스트명과 포트 번호를 저장할 버퍼입니다.
- clientlen : 클라이언트 주소의 길이를 저장합니다.
- clientaddr : 클라이언트 주소를 저장할 구조체입니다.
2. 명령줄 인수 확인
- 프로그램이 실행될 때 포트 번호가 인수로 제공되었는지 확인.
그렇지 않으면 오류 메시지 출력하고 종료
3. 리스닝 소켓 열기
- Open_listenfd 함수를 호출하여 지정된 포트에서 클라이언트 연결 요청을 수신하기 위해
리스닝 소켓을 연다.
4. 무한 루프
- 서버는 무한 루프를 돌면서 클라이언트의 연결 요청을 기다립니다.
- Accept 함수를 호출하여 클라이언트의 연결 요청을 수락하고, 클라이언트의 주소 정보를 clientaddr에 저장합니다.
- Getnameinfo 함수를 사용하여 클라이언트의 호스트명과 포트 번호를 가져옵니다.
- printf를 사용하여 클라이언트의 연결 정보를 로그로 출력합니다.
- doit 함수를 호출하여 클라이언트의 요청을 처리합니다.
- 처리 후 Close 함수를 호출하여 연결을 종료합니다.
Tiny는 반복적인 서버로, 클라이언트의 요청을 기다리고 처리하는 무한 루프를 실행.
이 방식은 서버가 동시에 여러 클라이언트를 처리할 수 없지만, 단일 클라이언트의 요청을 순차적으로 처리.
# 추가
1. 파일 디스크립터(File Descriptor)
- 운영 체제에서 파일, 소켓, 파이프 등과 같은 입출력 리소스에 대한 식별자. (파일 관리를 위해)
해당 리소스에 접근하거나 조작할 때 사용.
- 파일 : 디스크에 저장된 파일
- 소켓 : 네트워크 통신을 위한 소켓
- 파이프 : 프로세스 간 통신을 위한 파이프
운영 체제의 커널이 관리하며, 프로그램이 파일을 열 때 또는 소켓을 생성할 때
이 디스크립터를 반환받는다
2. 소켓 파일 디스크립터
- 네트워크 소켓을 식별하는 데 사용되는 파일 디스크립터.
클라이언트와 서버 간의 데이터 전송을 위한 인터페이스를 제공.
소켓을 생성하고, 연결을 수립하고, 데이터를 송수신하는 작업을 수행할 때 파일 디스크립터가 사용됨.
doit 함수
웹 서버가 클라이언트의 요청을 처리하는 함수.
클라이언트의 HTTP 요청을 분석하고, 정적/동적 판단하고 응답
// 서버에서 클라이언트의 HTTP 요청을 처리 역할.
// 요청 분석하고, 정적 파일을 제공하거나 CGI 프로그램 실행하여 동적 컨텐츠 생성
void doit(int fd)
{
printf("doit함수호출\n");
int is_static; // 동적 정적?
struct stat sbuf; // 파일의 상태 정보를 담는 stat 구조체
// buf: HTTP 요청 라인을 저장하는 버퍼
// method, uri, version: 요청의 메소드(GET, POST등),URI, HTTP 버전을 저장하는 버퍼
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
// 요청된 파일 이름과 CGI 인수 저장 버퍼
char filename[MAXLINE], cgiargs[MAXLINE];
rio_t rio; // 리드 연산을 위한 구조체
Rio_readinitb(&rio,fd); // rio 구조체 초기화
Rio_readlineb(&rio, buf, MAXLINE); // 요청 라인 읽고 buf에 저장
printf("요청라인:%s", buf);
sscanf(buf, "%s %s %s", method, uri, version); // 메소드, URI, HTTP 버전 추출
// 요청 메소드가 GET이 아니면 오류 반환하고 종료
if (strcasecmp(method, "GET")) {
clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
return;
}
// 요청 헤더 읽기:
printf("Request headers:\n");
read_requesthdrs(&rio);
// parse_uri 호출해서 URI분석 후, 파일이름, CGI 인수 추출
// is_static을 설정하여 정적인지 동적인지 반환
is_static = parse_uri(uri,filename,cgiargs);
if (stat(filename, &sbuf) < 0) { //파일이 디스크 상에 없을 경우
clienterror(fd, filename, "404", "Not found","Tiny couldn’t find this file");
return;
}
// 동적 정적? 인지 구별
if (is_static) {
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) { //보통파일인지? 이 파일을 읽을 권한을 가지고 있는지?
clienterror(fd, filename, "403", "Forbidden","Tiny couldn’t read the file");
return;
}
serve_static(fd,filename,sbuf.st_size); //정적 컨텐츠 제공
}
else {
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { // 보통파일인지? 실행가능한지?
clienterror(fd, filename, "403", "Forbidden","Tiny couldn’t run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs); //동적 컨텐츠 제공
}
}
1. 함수 선언 및 변수 초기화
- int is_static; : 이 변수는 요청된 자원이 정적(static)인지 동적(dynamic)인지 식별하는 데 사용됩니다.
- struct stat sbuf; : 파일의 상태 정보를 저장하는 구조체로, 파일의 존재 여부 및 권한 등을 확인하는 데 사용됩니다.
- char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE]; : HTTP 요청의 각 요소를 저장
- char filename[MAXLINE], cgiargs[MAXLINE]; : 요청된 파일 이름과 CGI 인수를 저장
buf - 요청 라인이나 헤더 라인을 읽음
method - HTTP 요청 메소드(GET, POST)를 저장하는 버퍼
uri - HTTP 요청에서 요청된 자원의 URI를 저장하는 버퍼 (/index.html)
version - HTTP 요청의 버전을 저장 (HTTP/1.1)
filename - URI를 분석하여 요청된 파일의 경로르 변수에 저장
cgiargs - CGI 스크립트에 전달할 인수를 저장. URI에 포함된 쿼리문자열을 저장 - rio_t rio; : 파일 디스크립터와 관련된 I/O 작업을 수행하는 데 사용되는 구조체입니다.
2. rio 구조체 초기화
- Rio_readinitb : rio 구조체를 초기화하여 fd(파일 디스크립터)와 연관
3. HTTP 요청 라인 읽기
- Rio_readlineb : 클라이언트로부터 HTTP 요청의 첫 번째 라인을 읽어 buf에 저장.
보통 GET /index.html HTTP/1.1
4. 요청 라인 분석
- sscanf : 요청 라인에서 메소드(GET), URI(/index.html), HTTP 버전(HTTP/1.1) 등을 추출하여 각 변수에 저장합니다.
5. 메소드 검증
- 메소드가 "GET"이 아닌 경우, clienterror 함수를 호출하여 "501 Not Implemented" 오류를 반환
6. 요청 헤더 읽기
- read_requesthdrs : 요청 헤더를 읽어 출력. 헤더는 여러 줄로 이루어져 있으며,
빈 줄이 나타날 때까지 계속 읽음
7. URI 분석 및 파일 이름 및 CGI 인수 추출
- parse_uri : URI를 분석하여 파일 이름과 CGI 인수를 추출합니다.
- is_static : 변수는 요청된 자원이 정적인지 동적인지를 반환합니다.
8. 파일 존재 여부 확인
- stat : filename에 해당하는 파일의 상태 정보를 sbuf에 저장합니다.
- 파일이 존재하지 않으면, clienterror 함수를 호출하여 "404 Not Found" 오류를 반환합니다.
9. 정적(static) 또는 동적(dynamic) 콘텐츠 제공
- is_static이 참이면 serve_static 함수를 호출하여 정적 콘텐츠를 제공합니다.
- is_static이 거짓이면 serve_dynamic 함수를 호출하여 동적 콘텐츠를 제공합니다.
10. 정적 파일 처리 (is_static이 true 일 경우)
- 정적 파일의 조건 확인
- S_ISREG(sbuf.st_mode): 파일이 일반 파일인지 확인. sbuf.st_mode는 stat 함수로 얻은 파일의
상태 정보를 담고 있으며, 파일이 일반 파일인지 여부를 판단합니다. - S_IRUSR & sbuf.st_mode: 파일의 읽기 권한을 확인. S_IRUSR는 파일 소유자에게 읽기 권한이 있는지를 체크
참이면, 파일을 읽을 권한이 있는 것
- 파일 읽기 권한이 없는 경우
- 위의 조건이 만족하지 않으면 clienterror 함수를 호출
- 정적 파일 제공
- 파일이 정상적으로 읽을 수 있는 경우, serve_static 함수를 호출하여 정적 파일을 클라이언트에게 제공합니다. filename은 요청된 파일의 경로, sbuf.st_size는 파일의 크기입니다.
11. 동적 파일 처리 (is_static이 false일 경우)
- 동적 파일의 조건 확인
- S_ISREG(sbuf.st_mode): 파일이 일반 파일인지 확인
- S_IXUSR & sbuf.st_mode: 파일이 실행 가능한지 확인합니다. S_IXUSR는 파일 소유자에게 실행 권한이 있는지를 체크하는 매크로
- 파일이 실행 불가능한 경우
- 위의 조건이 만족하지 않으면 clienterror 함수를 호출
- 동적 파일 제공
- 파일이 실행 가능하면, serve_dynamic 함수를 호출하여 동적 컨텐츠를 클라이언트에게 제공.
filename은 요청된 CGI 스크립트의 경로, cgiargs는 CGI 프로그램에 전달할 인수입니다.
#추가
strcasecmp : 대소문자 구분 않고 문자열 비교해서 같으면 0반환
uri(Uniform Resource Identifier) : 인터넷 자원을 식별하기 위한 문자열.
웹 자원의 위치나 그 자원을 참조하는 방법을 명시
버퍼 : 데이터를 일시적으로 저장하는 메모리 공간
- 속도 차이 보완, 데이터 조각화, I/O 작업의 효율성 향상
cgi(Common Gateway Interface) : 웹 서버와 외부 프로그램(스크립트) 간의 인터페이스를 정의하는 표준.
동적 컨텐츠를 생성하기 위해 CGI 스크립트를 실행
clienterror 함수
HTTP 오류 응답을 클라이언트에 전송하는 역할을 수행
// HTTP 에러 응답을 클라이언트에 전송하는 역할
void clienterror(int fd, char *cause, char *errnum,char *shortmsg, char *longmsg)
{
// buf: HTTP 헤더와 관련 정보 저장 버퍼, body: HTML 에러 메시지 포함할 버퍼
char buf[MAXLINE] , body[MAXBUF];
/* Build the HTTP response body */
sprintf(body, "<html><title>Tiny Error</title>");
sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body);
/* Print the HTTP response */
// 상태 줄을 작성 (ex. HTTP/1.0 404 Not Found)
sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
// 상태 줄을 클라이언트에 전송
Rio_writen(fd, buf, strlen(buf));
// 응답 본문이 HTML 형식임을 나타내는 Content-type: text/html 헤더를 전송
sprintf(buf, "Content-type: text/html\r\n");
Rio_writen(fd, buf, strlen(buf));
// 컨텐츠 길이 헤더 전송:
sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
Rio_writen(fd, buf, strlen(buf));
// 응답 본문 전송
Rio_writen(fd, body, strlen(body));
}
함수 정의
- fd: 클라이언트와의 연결을 나타내는 파일 디스크립터. 이 디스크립터를 통해 클라이언트와 통신합니다.
- cause: 오류의 원인으로, 오류가 발생한 자원이나 요청을 설명하는 문자열입니다.
- errnum: HTTP 오류 상태 코드를 나타내는 문자열입니다 (예: "404").
- shortmsg: 오류 상태 코드에 대한 간단한 메시지입니다 (예: "Not Found").
- longmsg: 오류에 대한 자세한 설명입니다 (예: "Tiny couldn’t find this file")
1. HTTP 응답 본문 구성
- body: 오류 응답의 HTML 본문을 구성하는 버퍼.
sprintf 함수는 body에 문자열을 추가하여 오류 메시지와 HTML 태그를 만듦
- 첫 번째 줄은 HTML 문서의 제목을 설정
- 두 번째 줄은 HTML 문서의 배경색을 설정
- 세 번째 줄은 오류 상태 코드와 간단한 메시지를 포함
- 네 번째 줄은 오류에 대한 자세한 설명을 포함
- 마지막 줄은 페이지 하단에 서버 이름을 추가
2. HTTP 응답 상태 줄 전송
- buf: HTTP 응답의 상태 줄을 저장하는 버퍼
sprintf를 사용하여 상태 줄을 형성. ex) "HTTP/1.0 404 Not Found". - Rio_writen(fd, buf, strlen(buf)): 상태 줄을 클라이언트에 전송합니다.
3. Content-type 헤더 전송
- Content-type: text/html: 응답 본문이 HTML 형식임을 나타내는 헤더.
클라이언트는 이 헤더를 보고 본문을 어떻게 처리할지 알 수 있습니다.
4. Content-length 헤더 전송
- Content-length: 응답 본문의 길이를 바이트 단위로 지정하는 헤더.
클라이언트는 이 값을 통해 응답 본문의 전체 길이를 파악할 수 있습니다. - \r\n\r\n: 헤더와 본문을 구분하는 빈 줄입니다.
5. 응답 본문 전송
- body: 앞에서 구성한 HTML 본문을 클라이언트에 전송합니다.
# 추가
1. sprintf 함수 : 문자열을 포맷하여 특정 형식의 문자열을 생성하는데 사용되는 함수
read_requesthdrs 함수
HTTP 요청의 헤더를 읽고 출력하는 역할
// HTTP 요청의 헤더를 읽고 출력하는 역할
void read_requesthdrs(rio_t *rp) // 요청을 읽기 위한 rio 구조체의 포인터
{
printf("read_requesthdrs함수호출\n");
char buf[MAXLINE]; // HTTP 요청 헤더를 읽기 위한 버퍼
// 첫 번째 요청 헤더 읽기:
// 요청 헤더의 첫 번째 줄을 buf에 읽어온다. rp는 rio_t 구조체의 포인터로 요청 데이터 읽는데 씀
Rio_readlineb(rp, buf, MAXLINE);
// buf와 빈 줄이 아닐 경우, 계쏙 다음 헤더 라인을 읽음
while(strcmp(buf, "\r\n")) { //buf 와 \r\n이 같으면 0.
// 요청 헤더의 다음 줄을 읽고 출력
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
}
return;
}
1. 헤더 읽기 준비
- char buf[MAXLINE] : HTTP 요청 헤더를 저장하기 위한 버퍼
MAXLINE은 이 버퍼의 크기로, HTTP 요청 헤더 한 줄의 최대 길이를 정의
2. 첫 번째 요청 헤더 읽기
- Rio_readlineb(rp, buf, MAXLINE)를 호출하여 요청 헤더의 첫 번째 줄을 읽어 buf에 저장
rp는 rio_t 구조체의 포인터로, 이 구조체는 리드 연산을 수행하는 데 필요한 정보를 담고 있습니다.
3. 헤더 출력
- while(strcmp(buf, "\r\n"))는 읽어온 줄이 빈 줄이 아닌 동안 반복
HTTP 요청 헤더는 각 헤더가 한 줄로 되어 있고, 헤더의 끝은 빈 줄("\r\n")로 표시 - strcmp(buf, "\r\n")는 buf의 내용과 빈 줄을 비교하여, 같으면 0을 반환
이 비교 결과가 0이 아니면, 빈 줄이 아니므로 반복문이 계속
4. 다음 헤더 읽기
- 빈 줄이 아닐 경우, Rio_readlineb(rp, buf, MAXLINE)를 다시 호출하여 다음 헤더 라인을 읽음
- printf("%s", buf)로 읽어온 헤더 라인을 출력합니다.
5. 함수 종료
- 헤더의 끝인 빈 줄을 만날 때까지 반복문이 실행되며, 모든 요청 헤더를 읽고 출력한 후 함수가 종료
parse_uri 함수
주어진 URI를 분석하여 파일 경로아 CGI 인자를 추출하는 역할을 함
// URI 분석하여 파일 경로와 CGI인자들을 추출
int parse_uri(char *uri, char *filename, char *cgiargs)
{
printf("parse_uri함수호출\n");
char *ptr; // 문자열 검색 결과를 저장할 포인터
if (!strstr(uri, "cgi-bin")) { //uri가 cgi-bin을 포함하고 있지 않으면 static content
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
if (uri[strlen(uri)-1] == '/') // URI가 '/' 로 끝나면
strcat(filename, "home.html"); // finename에 "home.html"을 추가하여 기본 파일을 지정
return 1;
}
else { /* Dynamic content */
ptr = index(uri,'?'); // URI에 쿼리 문자열이 있는지 확인후 있으면 prt은 ? 의 위치 가리킴
if (ptr) {
strcpy(cgiargs, ptr+1);
*ptr = '\0'; // 문자열의 끝으로 바꾸어 쿼리 문자열을 URI에서 제거
// 이로 인해 filename이 CGI 프로그램의 경로를 정확하게 가리키게 됨
}
else // 쿼리 문자열이 없으면
strcpy(cgiargs,""); // cgiargs를 빈 문자열로 설정
strcpy(filename,".");
strcat(filename,uri);
return 0;
}
}
함수 설명
- 입력 파라미터 :
- char *uri: 분석할 URI 문자열.
- char *filename: 결과로서 파일 경로를 저장할 버퍼.
- char *cgiargs: CGI 인자를 저장할 버퍼
- URI 분석 :
- if (!strstr(uri, "cgi-bin")): URI에 "cgi-bin"이 포함되어 있지 않으면 정적 콘텐츠로 간주
- strcpy(cgiargs, "");: CGI 인자는 없으므로 빈 문자열로 설정.
- strcpy(filename, ".");: 현재 디렉토리(".")를 기본으로 설정.
- strcat(filename, uri);: URI를 filename에 추가
- if (uri[strlen(uri)-1] == '/'): URI가 '/'로 끝나면, 기본 파일인 home.html을 filename에 추가
- return 1;: 정적 콘텐츠를 반환하는 신호로 1을 반환
- if (!strstr(uri, "cgi-bin")): URI에 "cgi-bin"이 포함되어 있지 않으면 정적 콘텐츠로 간주
- 동적 컨텐츠 처리 :
- else: URI에 "cgi-bin"이 포함된 경우 동적 콘텐츠로 간주합니다.
- ptr = index(uri, '?');: URI에서 쿼리 문자열의 시작 위치('?')를 찾습니다.
- if (ptr): 쿼리 문자열이 있으면:
- strcpy(cgiargs, ptr + 1);: 쿼리 문자열을 cgiargs에 복사합니다.
- *ptr = '\0';: ptr 위치의 문자를 '\0'으로 바꾸어 URI에서 쿼리 문자열을 제거합니다.
- else: 쿼리 문자열이 없으면:
- strcpy(cgiargs, "");: CGI 인자를 빈 문자열로 설정.
- strcpy(filename, ".");: 현재 디렉토리(".")로 초기화.
- strcat(filename, uri);: URI를 filename에 추가합니다.
- return 0;: 동적 콘텐츠를 반환하는 신호로 0을 반환합니다.
- else: URI에 "cgi-bin"이 포함된 경우 동적 콘텐츠로 간주합니다.
# 추가
쿼리 문자열이 없는 동적 컨텐츠 요청은 특정한 동작을 하지 않을 수 있지만,
여전히 서버는 해당 URI를 처리하기 위해 프로그램을 실행한다
cgiargs의 역할
- 클라이언트가 웹 폼을 통해 제출한 데이터나 URL 쿼리 문자열에서 가져온 정보를 저장
- URI가 /cgi-bin/script.cgi?param1=value1¶m2=value2 인 경우,
cgiargs에는 "param1=value1¶m2=value2 "가 저장
strstr() : 문자열 안에서 문자열로 검색
strcpy() : 문자열 복사
strcat() : 문자열을 서로 붙일 수 있다
serve_static 함수
클라이언트에 정적 컨텐츠(HTML 파일, 이미지 파일 등) 제공하는 역할
// 정적 컨텐츠를 클라에게 제공
void serve_static(int fd, char *filename, int filesize)
{
printf("serve_static함수호출\n");
int srcfd; // 파일의 디스크 상의 식별자
// srcp: 파일을 메모리에 매핑한 시작 주소
// filetype: 파일의 MIME 타입을 저장하는 버퍼
// buf: HTTP 응답 헤더와 바디를 저장하는 버퍼
char *srcp, filetype[MAXLINE], buf[MAXBUF];
/* Send response headers to client */
// 파일의 MIME 타입을 결정하여 filetype에 저장
get_filetype(filename, filetype);
// sprintf를 사용하여 HTTP 응답 헤더 구성
sprintf(buf, "HTTP/1.0 200 OK\r\n");
sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
sprintf(buf, "%sConnection: close\r\n", buf);
// 파일의 크기를 바이트 단위로 표시
sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
// 파일의 MIME 타입을 지정
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
// 생성한 응답 헤더를 클라이언트에게 전송
Rio_writen(fd, buf, strlen(buf));
printf("Response headers:\n");
printf("%s", buf);
/* Send response body to client */
srcfd = Open(filename, O_RDONLY, 0); // 요청된 파일을 읽기 전용으로 연다 // srcfd는 파일의 디스크 식별자
srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); //요청한 파일을 가상메모리에 매핑 // srcp는 매핑된 메모리의 시작 주소
Close(srcfd); //매핑했으니 이제 식별자 필요없음
Rio_writen(fd, srcp, filesize); // 주소 srcp에서 시작하는 filesize byte를 클라이언트의 연결식별자로 복사. (파일 전송)
Munmap(srcp, filesize); //매핑된 가상메모리 영역을 free
}
1. 변수 선언
- int srcfd;: 요청된 파일을 열 때 사용하는 파일 식별자
- char *srcp;: 파일을 메모리에 매핑한 시작 주소를 저장
- char filetype[MAXLINE];: 파일의 MIME 타입을 저장하는 버퍼
- char buf[MAXBUF];: HTTP 응답 헤더와 본문을 저장하는 버퍼
2. HTTP 응답 헤더 전송
- 요청된 파일의 MIME 타입을 결정하여 filetype에 저장
- HTTP 응답 상태 줄을 구성. 여기서는 요청이 성공했음을 나타내는 200ok를 사용
- 서버 정보, 연결 상태, 콘텐츠 길이, MIME 타입을 포함한 HTTP 헤더를 구성
- Rio_writen(fd, buf, strlen(buf));를 사용하여 생성한 응답 헤더를 클라이언트에게 전송
3. 응답 헤더 출력
- 구성된 응답 헤더를 출력하여 디버깅에 도움을 줌
4. 응답 본문 전송
- 요청된 파일을 읽기 전용으로 열어 파일 식별자를 srcfd에 저장
- Mmap 함수를 사용하여 파일을 가상 메모리에 매핑
이제 srcp는 파일 내용이 위치하는 메모리 주소를 가리킴 - 파일 식별자는 더 이상 필요하지 않으므로 닫음
- 매핑된 메모리 주소에서 시작하는 filesize 바이트를 클라이언트의 소켓으로 전송
- 매핑된 가상 메모리 영역을 해제하여 메모리를 정리
get_filetype 함수
주어진 파일 이름에 따라 적절한 MIME 타입을 결정, 결과를 filetype 변수에 저장
void get_filetype(char *filename, char *filetype)
{
printf("get_filetype함수호출\n");
if (strstr(filename, ".html"))
strcpy(filetype, "text/html");
else if (strstr(filename, ".gif"))
strcpy(filetype, "image/gif");
else if (strstr(filename, ".png"))
strcpy(filetype, "image/png");
else if (strstr(filename, ".jpg"))
strcpy(filetype, "image/jpeg");
else
strcpy(filetype, "text/plain");
}
1. 매개변수:
- char *filename: MIME 타입을 결정할 파일의 이름을 포함하는 문자열
- char *filetype: 결정된 MIME 타입을 저장할 버퍼
2. 동작:
- printf("get_filetype함수호출\n");: 함수가 호출되었음을 알리는 로그 메시지를 출력
- strstr(filename, ".html"): 파일 이름에 .html이 포함되어 있는지 확인
- 포함되어 있다면, filetype에 "text/html"을 저장
- 이와 유사하게, .gif, .png, .jpg 확장자를 체크하고, 각각에 대해 적절한 MIME 타입을 저장
- 만약 어떤 확장자에도 해당하지 않으면, 기본값으로 "text/plain"을 저장
MIME 타입의 중요성
- MIME 타입: 웹 브라우저가 서버에서 전송된 파일의 타입을 이해하고,
그에 따라 적절한 방식으로 처리할 수 있도록 도와줌
예를 들어, HTML 파일은 웹 페이지로 렌더링되고, 이미지 파일은 시각적으로 표시 - 정적 파일 서버에서의 사용: 이 함수는 웹 서버에서 정적 파일을 제공할 때,
클라이언트가 요청한 파일에 대한 적절한 응답을 구성하는 데 필수적
serve_dynamic 함수
클라이언트에게 동적 컨텐츠를 제공하기 위해 CGI 프로그램을 실행하는 역할
// 동적 컨텐츠를 클라이언트에 제공하기 위해 CGI 프로그램을 실행하는 기능 수행
void serve_dynamic(int fd, char *filename, char *cgiargs)
{
char buf[MAXLINE], *emptylist[] = { NULL };
/* Return first part of HTTP response */
sprintf(buf, "HTTP/1.0 200 OK\r\n");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Server: Tiny Web Server\r\n");
Rio_writen(fd, buf, strlen(buf));
if (Fork() == 0) { //자식 프로세스 fork (자식 프로세스의 context에서 cgi 프로그램을 돌린다)
setenv("QUERY_STRING", cgiargs, 1); // QUERY_STRING 환경변수를 CGI 인자로 설정
Dup2(fd, STDOUT_FILENO); // 자식의 표준 출력을 연결 식별자로 redirect.
Execve(filename, emptylist, environ); // cgi 프로그램 실행.
// Execve가 성공하면, 다음 줄의 코드는 실행되지 않음
}
Wait(NULL); // 부모 프로세스는 자식 프로세스가 종료될 때까지 대기
}
1. HTTP 응답 헤더 전송:
- sprintf를 사용하여 응답 헤더를 설정하고 Rio_writen으로 클라이언트에게 전송
- 첫 번째로 HTTP 상태 줄인 "HTTP/1.0 200 OK\r\n"을 전송하여 요청이 성공했음을 알림
- 두 번째로 서버 정보를 포함하는 헤더 "Server: Tiny Web Server\r\n"을 전송
2. 자식 프로세스 생성:
- Fork() 함수를 호출하여 새로운 프로세스를 생성
- 이때 반환값이 0이면 자식 프로세스의 실행 흐름에 있다
3. 환경 변수 설정:
- setenv("QUERY_STRING", cgiargs, 1)를 호출하여 CGI 프로그램이 사용할 수 있도록
쿼리 문자열을 환경 변수 QUERY_STRING에 저장 - cgiargs는 클라이언트의 요청에서 받은 CGI 인자
4. 표준 출력 리디렉션:
- Dup2(fd, STDOUT_FILENO)를 호출하여 자식 프로세스의 표준 출력을 클라이언트와의 소켓 연결로
리디렉션함. 즉, CGI 프로그램이 출력하는 내용이 클라이언트에게 직접 전송
5. CGI 프로그램 실행:
- Execve(filename, emptylist, environ)를 호출하여 지정된 CGI 프로그램을 실행
- filename은 실행할 CGI 프로그램의 경로이고, emptylist는 인자를 전달하지 않음을 의미.
environ은 현재 환경 변수를 사용 - Execve가 성공하면, 이후의 코드는 실행되지 않음
6. 부모 프로세스 대기:
- Wait(NULL)을 호출하여 부모 프로세스가 자식 프로세스의 종료를 기다림.
자식 프로세스가 종료될 때까지 부모는 블록됩니다.
완성 코드
https://github.com/whirae/webproxy-jungle/blob/main/tiny/tiny.c
'C언어 > Tiny 웹 서버' 카테고리의 다른 글
Tiny 웹 서버 개요 (0) | 2024.09.17 |
---|---|
network 개요 (2) | 2024.09.16 |