proxy-lab 드라이버 매뉴얼에 따라 브라우저는 mozila firefox를 통해 테스트했다.
tiny.c를 확장해서 mp4 파일을 브라우저에서 서비스해보자.
무슨 동영상을 넣을까. 합법적인 딴짓을 통해 적당한 용량의 파일을 골라 tiny폴더에 넣는다.
내가 넣은 동영상은 16초 크기의 우리집 강아지💕 영상이다.
<html>
<head><title>test</title></head>
<body>
<img align="middle" src="godzilla.gif">
<video src="my_puppy.mp4" controls loop width="500px"></video>
Dave O'Hallaron
</body>
</html>
tiny폴더의 home.html에 위 코드처럼 비디오 소스 코드를 넣어주자.
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, ".jpg"))
strcpy(filetype, "image/jpeg");
else if (strstr(filename, ".MP4"))
strcpy(filetype, "image/MP4");
else if (strstr(filename, ".png"))
strcpy(filetype, "image/png");
else if (strstr(filename, ".css"))
strcpy(filetype, "text/css");
else if (strstr(filename, ".js"))
strcpy(filetype, "application/javascript");
else if (strstr(filename, ".ico"))
strcpy(filetype, "image/x-icon");
else
strcpy(filetype, "text/plain");
}
tiny.c파일의 get_filetype에서 'MP4'형식을 추가해주자. (7~8번째 줄 내용)
<html>
<head><title>Tiny server study</title></head>
<body>
<div style="text-align: center; background-color:lightsteelblue; padding: auto; height: auto;">
<header style="font-size: 80px; color:rgba(50,50,231,0.6);"> Tiny Server </header>
<div style= "width:500px; margin: auto; margin-bottom: 20px; ">
<img src="godzilla.gif">
</div>
<div style="margin: auto; border: 1px solid rgba(50,50,231,0.5); width: 400px;border-radius: 20px;">
<h1 style="color:rgba(50,50,231,0.6);"> Add function</h1>
<form action="./cgi-bin/adder" accept-charset="utf-8" name = "add_function" method="get">
<input style="display: inline-block;" type="number" placeholder = "insert any number" id="n1" name="n1">
<p style="display: inline-block;">+</p>
<input style="display: inline-block;" type="number" placeholder = "insert any number" id="n2" name="n2">
<input type="submit" value="submit"/>
</form>
</div>
<div style= "margin-top: 30px; margin-bottom: 30px;">
<video src="my_puppy.mp4" controls loop width="250px"></video>
</div>
<div style="padding: 10px;">
<footer>Jinkyo in Jungle / Dave O'Hallaron</footer>
</div>
</div>
</body>
</html>
tiny.c파일의 정적컨텐츠를 처리할 때, 요청한 파일을 mmap과 rio_readn대신에 malloc, rio_readn, rio_writen을 사용해서 연결 식별자에게 복사하도록 하자.
void serve_static(int fd, char *filename, int filesize)
{
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, "%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);
// /* Send response body to client 이전 코드*/
srcfd = Open(filename, O_RDONLY, 0);
// srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
// Close(srcfd);
// Rio_writen(fd, srcp, filesize);
// Munmap(srcp, filesize);
//changed to malloc 변경 코드
srcp = (char *)malloc(filesize);
Rio_readn(srcfd, srcp, filesize);
Close(srcfd);
Rio_writen(fd, srcp, filesize);
free(srcp);
}
tiny/cgi-bin폴더에 있는 adder.c를 구현해서 우리의 home.html과 상호작용해보자.
<html>
<head><title>Tiny server study</title></head>
<body>
<div style="text-align: center; background-color:lightsteelblue; padding: auto; height: auto;">
<header style="font-size: 80px; color:rgba(50,50,231,0.6);"> Tiny Server </header>
<div style= "width:500px; margin: auto; margin-bottom: 20px; ">
<img src="godzilla.gif">
</div>
<div style="margin: auto; border: 1px solid rgba(50,50,231,0.5); width: 400px;border-radius: 20px;">
<h1 style="color:rgba(50,50,231,0.6);"> Add function</h1>
<form action="./cgi-bin/adder" accept-charset="utf-8" name = "add_function" method="get">
<input style="display: inline-block;" type="number" placeholder = "insert any number" id="n1" name="n1">
<p style="display: inline-block;">+</p>
<input style="display: inline-block;" type="number" placeholder = "insert any number" id="n2" name="n2">
<input type="submit" value="submit"/>
</form>
</div>
<div style= "margin-top: 30px; margin-bottom: 30px;">
<video src="my_puppy.mp4" controls loop width="250px"></video>
</div>
<div style="padding: 10px;">
<footer>Jinkyo in Jungle / Dave O'Hallaron</footer>
</div>
</div>
</body>
</html>
html에 쓸데없는 코드들을 많이 추가 했지만 결국 중요한 코드는 < form > </ form> 태그 안에 있는 코드들이다.
< form >의 action을 통해 cgi-bin/adder 실행파일이 < input >의 submit 버튼을 누르면 실행되도록 하고, method 방식이 "get"이 되도록 지정한다.
숫자를 넣을 input박스 2개를 만들어주는데, "name"은 n1,과 n2로 지정했다.
#include "csapp.h"
#include "stdio.h"
int main(void)
{
char *buf, *p;
char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
int n1 = 0, n2 = 0;
/* Extract the two arguments */
if ((buf = getenv("QUERY_STRING")) != NULL)
{
p = strchr(buf, '&');
*p = '\0';
strcpy(arg1, buf);
strcpy(arg2, p+1);
n1 = atoi(arg1);
n2 = atoi(arg2);
sscanf(buf, "n1=%d", &n1);
sscanf(p + 1, "n2=%d", &n2);
}
/* Make the response body */
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);
/* Generate the HTTP response */
printf("Connection: close\r\n");
printf("Content-length: %d\r\n", (int)strlen(content));
printf("Content-type: text/html\r\n\r\n");
printf("%s", content);
fflush(stdout);
exit(0);
}
adder.c의 기본코드는 CS:APP 책에 있다. 거기에 나는 숫자 input box의 name을 n1, n2로 넣었기 때문에 19,20줄에 sscanf()함수들을 추가해 줬다. sscanf()는 sprintf()함수의 반대라고 생각하면 쉽다. "n1=75"라는 값이 있을 때, 75를 n1의 값에 넣어준다고 생각하면된다.
tiny.c 를 통해 serve_dynamic()함수가 실행되면, 자식프로세스가 생성되며, "127.0.0.1:7000/cgi-bin/adder?n1=75?n2=75"의 URL로 넘어가게 된다. 이때, adder실행파일을 통해 n1, n1값이 sprintf()함수를 통해 content에 넣어지고 content는 client에게 전송된다.
HTTP 메서드 중 가장 간단한 head method를 추가해보자.
위 사진처럼 header의 메타데이터만 가져올 때 http head method를 쓴다고 한다. 따라서 우리도 client 측에서 HEAD라는 요청이 들어오면 서버의 header response 값을 전송해주면 된다.
//client의 http요청을 처리하는 함수
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;
/* Read request line and headers */
Rio_readinitb(&rio, fd);
Rio_readlineb(&rio, buf, MAXLINE);
printf("Request headers:\n");
printf("%s", buf);
sscanf(buf, "%s %s %s", method, uri, version);
// if (strcasecmp(method, "GET")) 이전 코드
if (strcasecmp(method, "GET") && strcasecmp(method, "HEAD")) // 과제 11.11를 위해 수정한 코드
{
clienterror(fd, method, "501", "Not Implemented",
"Tiny does not implement this method");
return;
}
read_requesthdrs(&rio);
/* 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)
{ /* Serve static content */
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
{ /* Serve dynamic content */
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);
}
}
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, "%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);
if (!strcasecmp(method, "HEAD")) // 같으면 0(false) 들어가고 끝냄(HEAD가 맞으면)
return; // void 타입이라 바로 리턴해도 됨(끝내라)
// /* Send response body to client */
srcfd = Open(filename, O_RDONLY, 0);
// srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
// Close(srcfd);
// Rio_writen(fd, srcp, filesize);
// Munmap(srcp, filesize);
//changed to malloc
srcp = (char *)malloc(filesize);
Rio_readn(srcfd, srcp, filesize);
Close(srcfd);
Rio_writen(fd, srcp, filesize);
free(srcp);
}
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));
if (!strcasecmp(method, "HEAD")) // 같으면 0(false) 들어가고 끝냄(HEAD가 맞으면)
return; // void 타입이라 바로 리턴해도 됨(끝내라)
if (Fork() == 0)
{ /* child */
/* Real server would set all CGI vars here */
setenv("QUERY_STRING", cgiargs, 1);
Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */
Execve(filename, emptylist, environ); /* Run CGI program */
}
Wait(NULL); /* Parent waits for and reaps child */
}
사소한 몇가지만 고쳐주면 된다.
첫번째, doit()함수의 기존 "if (strcasecmp(method, "GET")){"501"...}" 의 정의하는 method가 들어오면 501 에러코드를 띄우는 예외처리문을 "HEAD" 메서드가 들어와도 예외처리 되지 않도록 수정하는 것이다. (위 코드 참고)
두번째, 서버의 header response 내용은 serve_static(char method)과 serve_dynamic(char method)에 적혀있다. 따라서 head가 들어오면, header까지만 출력하는 if문을 추가해준다. 이때, 함수 인자에 "char *method"을 해주는 것을 잊지말자.
** 함수 원형선언도 잊지말고 고쳐주자.
아래는 테스트 코드와 결과 창들이다.
0주차 만들었던 웹서버의 태초의 벌거벗은 모습을 본 것같아 재밌는 과제였다. 😊