KraftonJungle2기/Today I Learned
[TIL] CSAPP 11장 공부4 - tiny 서버 정리
SooooooooS
2023. 5. 22. 22:34
728x90
1. 아침 문제 풀이
1. 20920번 - 영단어 암기는 괴로워
import sys
N, M = map(int, sys.stdin.readline().split())
words = {} # key = 영어단어 / value = 영어단어 입력 횟수
for _ in range(N) :
w = sys.stdin.readline().strip()
if len(w) >= M :
if w in words :
words[w] += 1
else :
words[w] = 1
# 단어를 사전 순으로 정렬
words = dict(sorted(words.items()))
# 단어를 입력 횟수가 많은 순으로 정렬하되 같으면 길이가 긴 것부터 정렬
words = dict(sorted(words.items(), key=lambda x:(x[1], len(x[0])), reverse=True))
for w in words.keys() :
print(w)
정렬을 어떻게 하느냐가 관건이었던 문제인 것 같다.
우선 우리가 필요한 정보는 단어가 나오는 횟수도 저장을 해 둘 필요가 있기 때문에 dict 을 사용했다.
그리고 정렬의 가장 마지막 기준인 알파벳 사전순으로 먼저 정렬을 했다.
그 후로 자주 나오는 단어, 길이는 모두 큰 것부터 이므로 기준을 같이 주어 정렬한다.
이번에는 딕셔너리를 정렬하는 법에 대해 알 수 있었다.
< 참고 >
https://dogsavestheworld.tistory.com/110
2. Tiny Server
1. doit
한 개의 HTTP Transaction을 처리하는 함수
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_t rio;
//요청 읽어오기
Rio_readinitb(&rio, fd);
Rio_readlineb(&rio, buf, MAXLINE);
printf("Request headers:\n");
printf("%s", buf);
sscanf(buf, "%s %s %s", method, uri, version);
//strcasecmp = 대소문자를 구분하지 않고 두 문자열이 같을 경우 0을 반환한다.
//GET Method가 아닐 경우 오류 발생
if(!(strcasecmp(method, "GET")) && !(strcasecmp(method, "HEAD"))) {
clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
return;
}
read_requesthdrs(&rio); //Read request header
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, method);
}
//동적 콘텐츠일 경우
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, method);
}
}
- GET, HEAD 메소드만 지원하는 코드이다.
- 각각의 오류 상황을 확인하여 적절한 에러를 출력한다.
- 정적 콘텐츠 vs 동적 콘텐츠 구분하여 알맞은 함수 호출하기
2. clienterror
HTTP 요청에 오류가 있을 경우 오류 메세지를 클라이언트에게 전송하는 함수
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
char buf[MAXLINE], body[MAXBUF];
//응답 본체(body) 생성 - HTML 형식의 오류 페이지 작성
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);
//응답 헤더(buf) 생성 후 헤더와 본체 모두 클라이언트에게 전송
sprintf(buf, "HTTP/1.1 %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, body, strlen(body));
}
- 인자로 받은 오류가 발생한 클라이언트 연결 소켓에 인자로 받은 오류 정보를 적절히 보낸다.
3. read_requesthrs
Tiny server는 요청 헤더 내 어떠한 정보도 사용하지 않으므로 읽기만 하고 무시하는 함수
void read_requesthdrs(rio_t *rp) {
char buf[MAXLINE];
Rio_readlineb(rp, buf, MAXLINE); //첫 번째 요청 헤더 읽기
while(strcmp(buf, "\r\n")) { //추가적으로 요청 헤더가 있을 경우
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
}
return;
}
- 요청 헤더는 여러 개일 수도 있고 한 개일 수도 있다.
- 현재는 사용할 일이 없으므로 무시한다.
4. parse_uri
Tiny server의 정책을 가정하는 함수
int parse_uri(char *uri, char *filename, char *cgiargs) {
char *ptr;
//strstr() 함수 = 문자열 내의 특정 문자열이 존재하는지 확인하는 함수
//uri에 cgi-bin가 포함되어 있는지 확인
if(!strstr(uri, "cgi-bin")) { //정적 콘텐츠일 경우 = cgi-bin이 포함되어 있지 않다.
strcpy(cgiargs, "");
strcpy(filename, "."); //현재 directory 경로(?) 복사
strcat(filename, uri);
if (uri[strlen(uri)-1] == '/') {
strcat(filename, "home.html");
}
return 1;
}
else { //동적 콘텐츠일 경우 = cgi-bin이 포함되어 있다.
ptr = index(uri, '?'); //파일이름과 인자를 구분하는 구분자 = ?
if(ptr) {
strcpy(cgiargs, ptr + 1);
*ptr = '\0';
}
else {
strcpy(cgiargs, "");
}
strcpy(filename, ".");
strcat(filename, uri);
return 0;
}
}
- 정적 콘텐츠의 위치 = 자신의 현재 디렉토리
- 동적 콘텐츠의 위치 = cgi-bin 디렉토리
- 정적 콘텐츠일 경우 home.html 파일을 보여주도록 한다.
- 동적 콘텐츠일 경우 입력받은 cgi-bin 속 파일과 인자들의 결과를 보여주도록 한다
- 아래의 동적 콘텐츠 사용해보기에 결과 확인 가능
4. serve_static
Tiny server에서 정적 콘텐츠를 처리하는 함수
void serve_static(int fd, char *filename, int filesize, char *method) {
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXBUF];
get_filetype(filename, filetype); //파일 확장자로 type찾기
//응답 헤더 생성
sprintf(buf, "HTTP/1.1 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 header:\n");
printf("%s", buf);
if(strcasecmp(method, "GET") == 0) {
srcfd = Open(filename, O_RDONLY, 0); //읽기 전용으로 파일 열기
//Mamp() = 메모리 매핑 함수
//파일 안의 내용을 메모리 매핑하여 srcp에 저장
// srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
srcp = (char *)malloc(filesize); //malloc사용하기
rio_readn(srcfd, srcp, filesize);
Close(srcfd);
Rio_writen(fd, srcp, filesize);
//Munmap(srcp, filesize);
free(srcp); //malloc 해제하기
}
}
- 정적 콘텐츠 = HTML, Text, GIF, PNG, JPEG, MP4
- HEAD 메소드도 사용할 수 있도록 하기 위해
- 응답 헤더는 GET, HEAD 모든 메소드에서 출력
- 응답 본체는 GET 메소드일 경우에만 출력
- malloc 함수를 사용해 보았다.
5. get_filetype
입력 파일의 타입을 구하는 함수
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")) { //mp4영상을 위한 타입 설정
strcpy(filetype, "video/mp4");
}
else {
strcpy(filetype, "text/plain");
}
}
- 위의 가능한 정적 콘텐츠의 종류 모두 구분
- mime type을 참고해서 MP4 를 추가했다.
6. serve_dynamic
동적 콘텐츠를 처리하는 함수
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method) {
char buf[MAXLINE], *emptylist[] = { NULL };
//응답 헤더 생성
sprintf(buf, "HTTP/1.1 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) {
setenv("QUERY_STRING", cgiargs, 1); //환경변수 설정
setenv("REQUEST_METHOD", method, 1);
Dup2(fd, STDOUT_FILENO); //표준 출력을 클라이언트 소켓으로 redirection
Execve(filename, emptylist, environ); //CGI 프로그램 실행
}
//부모 프로세스는 자식 프로세스가 종료될 때까지 대기
Wait(NULL);
}
- 자식 프로세서를 생성하여 동적 콘텐츠를 제공한다.
- CGI 프로그램 실행
- 클라이언트가 요청한 url 주소가 스크립트 또는 프로그램을 참조하면
- 웹 서버가 대신 실행 후 결과를 클라이언트에게 전달
- 🔗 http://www.ktword.co.kr/test/view/view.php?nav=2&no=651&sh=cgi
- 동적 콘텐츠 역시 HEAD 메소드를 지원할 시 응답 헤더만 출력할 수 있어야 한다.
- 이를 위해 환경변수 REQUEST_METHOD를 설정해준다.
< 참고 >
🔗 https://straw961030.tistory.com/246
🔗 https://www.codingfactory.net/11877
< fork - SIGCHLD 참고 공부 자료 >
🔗 https://plummmm.tistory.com/67
🔗 https://velog.io/@jungbumwoo/fork-를-알아보자
🔗 https://codetravel.tistory.com/42
🔗 http://egloos.zum.com/youmin3/v/2188455
3. 정적 콘텐츠 - 영상 띄우기
1. HTML 비디오 설정 추가하기
<video autoplay controls loop muted>
<source src="jjangu.mp4" type="video/mp4">
짱구 - 어떡하지
</video>
4. 동적 콘텐츠 사용해보기
< 맥북에서 telnet 사용하기 >
학교를 다니면서 이론적으로만 배웠던 웹 서버의 동작 과정을 알 수 있는 시간이었다.
확실히 처음봤을 때 이해하려고 했지만 외운 내용이었지만 다시 공부하고 직접 해보니 이해하기 수월했다.
728x90