728x90
반응형
1. 웹 서버
웹 클라이언트(브라우저)와 서버는 HTTP(Hypertext Transfer Protocol)라는 텍스트 기반의 프로토콜을 사용하여 상호 연동하고, 웹 콘텐츠는 HTML(Hypertext Markup Language) 언어로 작성된다.
| 웹 콘텐츠
웹 콘텐츠는 MIME(Multipurpose Internet Mail Extensions) 타입을 갖는 바이트 배열이다.
웹 서버는 두 가지 서로 다른 방법으로 클라이언트에게 콘텐츠를 제공한다.
- 정적 콘텐츠
디스크 파일(정적 콘텐츠)을 가져와서 그 내용을 클라이언트에게 전달. 파일을 클라이언트에게 돌려주는 작업은 정적 콘텐츠를 처리한다고 말한다. - 동적 콘텐츠
실행파일을 돌리고, 그 출력을 클라이언트에게 보낸다. 실행파일이 런타임에 만든 출력을 동적 콘텐츠라고 하며, 프로그램을 실행하고 그 결과를 클라이언트에게 보내주는 과정을 동적 콘텐츠를 처리한다고 말한다.
URL(Universal Resource Locator): 웹 서버가 리턴하는 모든 내용들은 서버가 관리하는 파일이고, 이 파일 각각은 URL이라는 고유의 이름을 가진다.
(ex) http://www.google.com:80/index.html
URL은 파일 이름 뒤에 프로그램의 인자를 포함할 수도 있다. '?' 문자는 파일 이름과 인자를 구분, '&' 문자는 인자와 인자를 구분한다.
최소한의 URL은 '/' 문자이며, 모든 서버는 이것을 특정 기본 홈페이지로 확장한다.
2. 소형 웹 서버 Tiny
github 주소: https://github.com/dapsu/week07-webproxy/tree/main/tiny
/* $begin tinymain */
/*
* tiny.c - A simple, iterative HTTP/1.0 Web server that uses the
* GET method to serve static and dynamic content.
*
* Updated 11/2019 droh
* - Fixed sprintf() aliasing issue in serve_static(), and clienterror().
*/
#include "csapp.h"
void doit(int fd);
void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *filename, char *cgiargs);
// void serve_static(int fd, char *filename, int filesize);
// 11.11문제 반영 - HEAD 메소드 지원
void serve_static(int fd, char *filename, int filesize, char *method);
void get_filetype(char *filename, char *filetype);
// void serve_dynamic(int fd, char *filename, char *cgiargs);
// 11.11문제 반영 - HEAD 메소드 지원
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
/*
* tiny는 반복실행 서버. open_listenfd함수 호출하여 listen socket 오픈 후,
tiny는 전형적인 무한 서버 루프 실행, 반복적으로 연결 요청을 접수하고,
트랜잭션을 수행하고, 자신 쪽의 연결 끝을 닫음
*/
int main(int argc, char **argv) {
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE]; // #define MAXLINE 8192 (Max text line length).. why??
socklen_t clientlen; // socklen_t <-- unsigned int..?
struct sockaddr_storage clientaddr; // sockaddr_storage 구조체는 모든 형태의 소켓 주소를 저장하기에 충분
/* Check command line args */
if (argc != 2) { // 실행 시 port 적지 않았다면,
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
listenfd = Open_listenfd(argv[1]); // listen socket open
// 무한 서버 루프 실행
while (1) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); // line:netp:tiny:accept
Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
printf("Accepted connection from (%s, %s)\n", hostname, port);
doit(connfd); // line:netp:tiny:doit
Close(connfd); // line:netp:tiny:close
}
}
/*
* doit 함수: 트랜잭션 처리 함수
요청라인을 읽고 분석(rio_readlineb함수 사용)
tiny는 GET method만 지원
*/
void doit(int fd) {
int is_static;
struct stat sbuf;
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
/*
* Rio Package: 짧은 카운트에서 발생할 수 있는 네트워크 프로그램 같은 응용에서 편리하고,
안정적이고 효율적인 I/O 제공
*/
rio_t rio;
// Read request line and headers
Rio_readinitb(&rio, fd); // open한 식별자마다 한 번 호출. 함수는 식별자 clientfd를 주소 rio에 위치한 rio_t타입의 읽기 버퍼와 연결한다.
Rio_readlineb(&rio, buf, MAXLINE); // 다음 텍스트 줄을 파일 rio(종료 새 줄 문자를 포함)에서 읽고, 이것을 메모리 buf로 복사하고, 텍스트 라인을 NULL(0)문자로 종료시킴
printf("Request headers: \n");
printf("%s", buf); //buf에는 Request headers가 저장되고 method, uri, version의 정보가 들어가 있음
sscanf(buf, "%s %s %s", method, uri, version); // sscanf(): buf에서 argumment-list가 제공하는 위치로 데이터 읽음
// GET이 아닌 다른 method 요청시 에러 메시지 보내고, main루틴으로 돌아오고, 연결을 닫고 다음 연결 요청 기다림
// if (strcasecmp(method, "GET")) { // 대소문자를 구분하지 않고 두 인자 비교. 같으면 0 리턴
if (!(strcasecmp(method, "GET") == 0 || strcasecmp(method, "HEAD") == 0)) { // 대소문자를 구분하지 않고 두 인자 비교. 같으면 0 리턴
clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
return;
}
read_requesthdrs(&rio); // request header 읽기
// Parse URI from GET request
/*
* 요청이 정적 또는 동적 콘텐츠를 위한 것인지 나타내는 플래그 설정
*/
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)) { // 파일이 읽기 가능한지 확인
/*
S_ISREG(mode): mode가 regular file인지 확인
S_IRUSR: read by owner
*/
clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size, method); // 읽기 권한 있다면 클라이언트에게 정적 콘텐츠 제공
}
// 동적 콘텐츠 요구하는 경우
else {
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { // 파일이 실행 가능한지 확인
/*
S_IXUSR: Execute by owner
*/
clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs, method); // 실행 권한 있다면 클라이언트에게 동적 콘텐츠 제공
}
}
/*
* HTTP 응답을 응답 라인에 적절한 상태 코드와 상태 메시지와 함께 클라이언트에 보내며,
브라우저 사용자에게 에러를 설명하는 응답 본체에 HTML 파일도 함께 보냄
(HTML 응답은 본체에서 콘텐츠의 크기와 타입을 나타내야 한다)
*/
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
char buf[MAXLINE], body[MAXBUF]; // MAXBUF 8192(Max I/O buffer size)
// 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
sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
Rio_writen(fd, buf, strlen(buf));
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, buf, strlen(body));
}
// tiny는 요청 헤더 내의 어떤 정보도 사용하지 않음. 이들을 읽고 무시
void read_requesthdrs(rio_t *rp) {
char buf[MAXLINE];
Rio_readlineb(rp, buf, MAXLINE); // MAXLINE까지 읽기
while (strcmp(buf, "\r\n")) { // EOF(한 줄 전체가 개행문자인 곳) 만날 때 까지 계속 읽기
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
}
return;
}
// URI를 파일 이름과 옵션으로 CGI 인자 스트링 분석
int parse_uri(char *uri, char *filename, char *cgiargs) {
char * ptr;
// 요청이 정적 콘텐츠를 위한 것이라면
if (!strstr(uri, "cgi-bin")) {
strcpy(cgiargs, ""); // CGI 인자 스트링 지우고
strcpy(filename, "."); // 상대 리눅스 경로이름으로 변환
strcat(filename, uri); // strcat(*str1, *str2): str2를 str1에 연결하고 NULL 문자로 결과 스트링 종료
if (uri[strlen(uri)-1] == '/') { // URI가 '/'로 끝난다면
strcat(filename, "home.html"); // home.html로 파일이름 추가
}
return 1;
}
// 동적 콘텐츠를 위한 것이라면
else {
// 모든 CGI인자 추출
ptr = index(uri, '?');
if (ptr) {
strcpy(cgiargs, ptr+1); // 물음표 뒤에 인자 붙이기
*ptr = '\0'; // 포인터는 문자열 마지막으로 위치
}
else {
strcpy(cgiargs, "");
}
// 나머지 URI 부분을 상대 리눅스 파일 이름으로 변환
strcpy(filename, ".");
strcat(filename, uri);
return 0;
}
}
void serve_static(int fd, char *filename, int filesize, char *method) {
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXBUF];
// Send response headers to client
get_filetype(filename, filetype); // 파일 타입 결정
// 클라이언트에 응답 줄과 응답 헤더 보낸다.
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);
sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
Rio_writen(fd, buf, strlen(buf));
printf("Response headers: \n");
printf("%s", buf);
// 11.11문제 반영 - HEAD 메소드 지원
if (strcasecmp(method, "HEAD") == 0) {
return;
}
// Send response body to client
srcfd = Open(filename, O_RDONLY, 0); // 읽기 위해서 filename을 오픈하고 식별자 얻어옴
// srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); // mmap함수: 요청한 파일을 가상메모리 영역으로 매핑
// 11.9 문제 반영
srcp = malloc(filesize);
Rio_readn(srcfd, srcp, filesize);
Close(srcfd); // 파일을 메모리로 매핑 후 더이상 이 식별자 필요X -> 파일 닫기
Rio_writen(fd, srcp, filesize); // 실제로 파일을 클라이언트에게 전송
// Munmap(srcp, filesize); // 매핑된 가상메모리 주소를 반환(메모리 누수 방지에 중요)
free(srcp);
}
/*
* get_filetype - derive file type from file name
*/
// 파일 형식 추출하기
void get_filetype(char *filename, char *filetype) {
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 if (strstr(filename, ".mp4")) {
strcpy(filetype, "video/mp4");
}
else {
strcpy(filetype, "text/plain");
}
}
// serve_dynamic함수는 클라이언트에 성공을 알려주는 응답 라인을 보내는 것으로 시작
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method) {
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));
// 응답의 첫 번째 부분을 보낸 후 새로운 자식 프로세스를 fork
if (Fork() == 0) { // Child
// Real sercer would set all CGI vars here
setenv("QUERY_STRING", cgiargs, 1); // QUERY_STRING의 환경변수를 요청 URI의 CGI 인자들로 초기화
// 11.11문제 반영 - HEAD 메소드 지원
setenv("REQUEST_METHOD", method, 1); // REQUEST_METHOD: GET or POST
Dup2(fd, STDOUT_FILENO); // 자식은 자식의 표준 출력은 연결 파일 식별자로 재지정
Execve(filename, emptylist, environ); // CGI프로그램 로드 후 실행
}
Wait(NULL); // 부모는 자식이 종료되어 정리되는 것을 기다리기 위해 wait함수에 블록됨
}
728x90
반응형
'개발자 도전기 > [CS] CSAPP' 카테고리의 다른 글
네트워크 프로그래밍(Network Programming) | 소켓 인터페이스, getaddrinfo함수, addrinfo구조체 (0) | 2021.09.24 |
---|---|
네트워크 프로그래밍(Network Programming) | 클라이언트, 서버, 네트워크, 인터넷, TCP/IP, IP 개념 정리 (0) | 2021.09.23 |
Malloc Lab | 동적 메모리 할당(3) - Implicit - first fit, next fit 코드 구현 (0) | 2021.09.15 |
Malloc Lab | 동적 메모리 할당(2) - 묵시적 가용 리스트(Implicit free list) (0) | 2021.09.14 |
Malloc Lab | 동적 메모리 할당(1) - 개념 정리 (0) | 2021.09.14 |
댓글