Programming Assignment #5: HTTP/1.0 Web server SWE2007: Software Experiment II (Fall 2014) Due: 23rd Dec. (Tue), 11:59 PM, HARD DEADLINE 1. Introduction 이번과제는본과목의마지막과제로, HTTP/1.0 을따르는멀티쓰레드웹서버를제작하는것이 다. 이번과제를수행함으로써소켓프로그래밍과쓰레드프로그래밍을연습한다. 또한, 웹서버 의동작원리에대해서배울수있다. 이번과제는본과목의기말고사로간주되며, 제출하지않으면큰불이익을받는다. 또한, 성적 처리를하기위해이번과제의지연제출은허용되지않는다. PA #4에서작성했던서버-클라이언트프로그램의서버를확장해 Google Chrome과같은상용웹브라우저에서접속시킬수있는웹서버를제작한다. 해당웹서버는 HTTP 프로토콜을준수하여클라이언트로부터들어오는요청에대한적절한응답을하고, 멀티쓰레드를이용해동시에여러요청을처리한다. 2. Background 2.1. HTTP (Hypertext transfer protocol) HTTP는인터넷에서가장널리쓰이는프로토콜의하나로, WWW(world wide web) 에서 HTML(hypertext markup language) 문서를전달하기위해이용한다. HTTP는 TCP나 UDP의 80번포트로데이터를주고받는다. 웹브라우저로도메인주소 http://csl.skku.edu/ 에접속한상태를소켓연결의측면으로간단히보면다음과같다. 1. 웹브라우저프로세스 ( 이하클라이언트 ) 가새로운 TCP 소켓을만든다. 2. csl.skku.edu 서버 ( 이하서버 ) 의 TCP 80번포트에접속해연결을맺는다. 3. 클라이언트는서버로 / 에위치한웹문서를달라는 HTTP 요청메시지를보낸다. 4. 서버는클라이언트의요청에대응하는 / 에해당하는 HTML 문서를찾거나동적으로만들고, 적절한 HTTP 응답헤더와 HTML 문서를클라이언트에게전송한다. 5. 소켓연결을종료하고, 새로운사용자요청이발생하면 1로되돌아간다. HTTP에관한더구체적인정보는, 다음링크와교재 11.5장을참고한다.
http://en.wikipedia.org/wiki/hypertext_transfer_protocol 2.2. Example of a HTTP Request/Response 요청 ( 여기서빨간색 / 은서버의상대주소 / 에위치한문서를요청하는것이다 ) GET / HTTP/1.0 Host: csl.skku.edu (NULL LINE) 응답 ( 굵게처리된부분을잘확인할것 ) HTTP/1.1 200 OK Date: Mon, 02 Dec 2014 05:27:18 GMT Server: Apache/2.2.16 (Debian) X-Powered-By: PHP/5.3.3-7+squeeze19 Expires: Tue, 01 Jan 2002 00:00:00 GMT Cache-Control: no-store, no-cache, must-revalidate Vary: Accept-Encoding Content-Length: 7212 Connection: close Content-Type: text/html; charset=utf-8 (NULL LINE) <!DOCTYPE html PUBLIC " "> <html> [[ 이하생략 ]] HTTP response header HTTP body (html, png, ) HTTP 정의에따르면, HTTP 헤더 ( 응답과요청모두 ) 는개행문자로 <CR><LF> 를사용한다. NULL LINE 은 HTTP 의개행문자 (<CR><LF>) 로만이루어진라인이다. HTTP 요청메시지의경우요청의 끝을알리며, HTTP 응답에선 HTTP 응답헤더와 HTML 문서본문의경계를나눠주는역할을한다. 이를확인하는간단한테스트코드는다음과같다. (sockfd는 csl.skku.edu:80 에연결된소켓 ) char request[] = "GET / HTTP/1.0\r\nHost: csl.skku.edu\r\n\r\n"; char response[5000]; write(sockfd, request, strlen(request)); int read_bytes = read(sockefd, response, 5000); write(1, response, read_bytes); read_bytes = read(sockefd, response, 5000); write(1, response, read_bytes);
2.3. HTTP version HTTP는 1991년에 V0.9, 1996년에 V1.0, 1999년에 V1.1이발표되었고, 현재거의모든상용프로그램은 V1.1을따르고있다 ( 통신예제응답의 HTTP/1.1 이란문구 ). V1.0에서 V1.1으로발전하면서개선된사항엔, 한 TCP 연결에서하나이상의요청 / 응답을처리하도록하는 Persistent connections, 여러요청 / 응답을하나로묶어서한꺼번에전송하는 Pipelining 등이있다. 하지만이런기능들을구현하는것은복잡하니, 본과제에선 HTTP/1.0 을따르는서버를구현한다. 위의통신예시를보면, 임의로구현한 HTTP/1.0 요청에대해, CSL 의웹서버 ( 아파치 ) 는 HTTP/1.1 로응답을한것이다. 본과제에선서버를구현하는것이니, 모든요청에대해 HTTP/1.0 으로응답을해야한다. 참고로, 상용웹브라우저는 HTTP/1.1 로요청을전송할것이다. 2.4. Request methods 다음 HTTP 요청의시작을보면, GET 이란문자열로요청이시작함을알수있다. GET / HTTP/1.0 이 GET이란문자열은 HTTP 요청의한 method로써, 주로데이터를받아올때사용하는것이다. 다른방식은 POST, HEAD, PUT 등의다른 methods가있는데, 이번과제의입력으로는 GET 요청만입력된다고가정한다. GET을받으면, 특별한처리없이요청받은해당파일만전송하도록한다. 상용웹브라우저상에서서버상으로웹페이지를호출하면, GET으로요청이전송될것이다. http://en.wikipedia.org/wiki/hypertext_transfer_protocol#request_methods 2.5. Response headers 2.2절의예제를보면 10줄의응답헤더가존재한다. 제각각의미가있는헤더이지만, 이번절에선본과제에서사용할세가지중요한헤더만설명한다. 다른헤더에관심이있다면, 다음페이지를참조할수있다 : http://en.wikipedia.org/wiki/list_of_http_header_fields#response_fields 2.5.1. Status code 응답헤더의첫줄을보면다음과같이시작한다. HTTP/1.1 200 OK HTTP/1.1 은 HTTP V1.1 방식으로응답을한다는선언이고, 200 OK 는 HTTP 요청을정상적으로처 리해응답한다는코드이다. 다른코드의종류와의미는다음웹페이지를참조한다. http://en.wikipedia.org/wiki/list_of_http_status_codes 본과제에서사용하는다른응답코드로는 403 Forbidden, 404 Not Found 이있다. 제각각문자
그대로의의미를뜻하며, 4 절에서상태코드를전송하는규칙을확인하도록한다. 참고로, 본과제 에선 HTTP/1.1 이아닌 HTTP/1.0 으로응답을시작할것이다. 2.5.2. Content-Length 이헤더는 HTTP 응답헤더를제외한, HTTP 응답본문, 즉해당파일의크기를뜻한다. 예제에서보면, Content-Length로 7212란길이가명시되었으니, HTTP body는정확히 7212바이트이다. 참고로, 헤더의종료는 NULL LINE이등장하는것으로확인할수있다. 2.5.3. Content-Type 이헤더는해당 HTTP body, 즉파일의종류를뜻하고 MIME type으로기술된다. 예시의 text/html는 text 형태의 html 파일임을뜻한다. 이필드를참조함으로써브라우저는해당파일을적절한방식으로그려낼수있다. 왜냐하면파일의형식에따라그리는방식도다르기때문이다. 본과제에서쓰이는것과달리실제웹페이지주소엔확장자란개념이존재하지않기때문에이렇게 MIME type을지정하면브라우저가파일의형식을잘파악할수있다. http://en.wikipedia.org/wiki/internet_media_type 2.6. Handling sockets 소켓 read/write 시엔, 내가요청한크기만큼실제로송신된다는보장이없다. 예를들어, 받는쪽과보내는쪽의 read/write 처리속도가다를경우, 소켓버퍼에처리되지못한데이터가쌓이다어느순간가득차게된다. 이때 write를수행하면요청한크기보다작은양만큼수행될것이다. 다음은어떤파일을읽어서소켓에쓰는예시이다. long length = st.st_size; // file length while (length > 0) long sent = 0; long bytes_in_buffer = read(fd, buffer, min(length, sizeof buffer)); if (bytes_in_buffer == 0) // EOF break; do { sent += write(sk, buffer + sent, bytes_in_buffer sent); } while (sent < bytes_in_buffer); length = sent; } 내가 100 바이트를전송하고싶다면, loop 를돌며 write 를시도하여정확히 100 바이트가전송됨 을확인하여야한다.
3. Overview 웹서버를그림으로그린예시는다음과같다. 위그림의브라우저처럼출력되는 html 문서의코드는다음상자에있다. <html> <body> <img src="./naver.gif"><br> <img src="./daum.png"> </body> </html> 서버로부터위와같은 html 문서를받으면, 웹브라우저는해당파일의위에서아래로해석하며우리가보는화면을그린다. 거의모든 html 문서는독자적으로완성되지않고다른외부파일을같이이용한다. 예를들면, javascript, css, 그림등이있고위코드에선붉은색으로강조된그림파일이이에해당한다. 이런외부자원을만나면브라우저는해당서버로요청을보내파일을다운로드받는다. 모든외부자원을받은다음, 이모든파일을조합하여하나의페이지를그려내어사용자에게보여준다. 아래그림은해당 html 코드를그리기위하여브라우저와서버사이에일어나는통신의순서를 나타낸그림이다. 그림과같이, 어떤요청에대한응답도중에새로운요청에대한응답을시작 할수있어야할것이다. (Multi-processing)
본과제에서는이런멀티 - 프로세싱을구현하기위해쓰레드란개념을이용하도록한다. 또한, 교 재 11.5 장과 11.6 장에 HTTP 에대한설명과예제웹서버가있으니참고할수있다. 4. Problem specification 과제를단순화하기위해, 웹브라우저로접속하면화면에 DEFAULT 란문자열을찍도록하는웹 서버가스켈레톤코드로제공된다. < 서버생성 > 서버의기본포트는 1234이고, 프로그램인자가 1개존재하면그숫자를포트로쓴다. 소켓에접속요청이들어오면, 연결을맺고새로운쓰레드를생성하여해당연결을전담하여처리하게하고, 메인쓰레드는바로다음요청을처리하기위해대기한다. 쓰레드를생성할때, 쓰레드간원치않는공유가발생하지않도록한다. (e.g., socket descriptors, etc.)
< 요청의처리 > 소켓에서부터클라이언트의요청을전부읽어낸다. ( 참고 ) 채점시서버로들어오는한요청의크기는 1024바이트를넘지않는다. 요청의첫째줄을분석해적절한문서를찾는다. 요청의시작은 GET /test.html HTTP/1.0 과같은형식이며, /test.html 이란상대주소를구하고이를서버의 working directory에서부터찾는다. 만약상대주소가 / 였다면, /index.html 이란상대주소를받았다고본다. 파일확장자는 html, jpg, png, gif, js, css만허용한다. ( 다른확장자는이용할권한이없다 ) 아래수행기록은예외로처리한다. < 응답의전송 > 클라이언트요청에대응하는응답을해당소켓으로전송한다. HTTP 응답은 <HTTP 헤더 > + <NULL LINE> + < 파일의내용 > 으로구성된다. HTTP 헤더는다음정보를이용해만든다. <Status code, Content-type, Content-length> 세가지필드만만들어전송한다. 스켈레톤코드 http.c 파일의 make_http_header() 함수를이용하면 HTTP 헤더로사용할수있는문자열을동적으로생성한다. 이헤더를사용한후반드시메모리를해제한다. Status code는아래세가지경우만고려한다. 권한과파일존재는서버에서 open 시도시발생하는에러로판별한다. 정상일때 해당파일에접근할권한이없을때 파일이존재하지않을때 OK E_NOT_ALLOWED E_NO_FILE Content-type은해당파일의확장자에따라다음값을이용한다. HTML 파일 TYPE_HTML JPG 파일 TYPE_JPG PNG 파일 TYPE_PNG GIF 파일 TYPE_GIF JS 파일 TYPE_JS CSS 파일 TYPE_CSS 에러발생 TYPE_ERROR Content-length 는해당파일의크기이다. < 파일의내용 > 의크기와 Content-length 는 일치하여야한다. 전송을마친뒤소켓을닫는다.
< 연결종료이후 > 메인쓰레드는스스로종료하지않고무한히연결을기다린다. 다른쓰레드는수행기록을남기고종료한다. 다른쓰레드들은종료직전엔반드시 detached 상태이어야한다. < 수행기록 > 전송이력을메모리에기록하고, 사용자의요청에따라이정보를전송한다. 서버쓰레드가종료되기전, 다음과같이수행기록을남긴다. ( 전송을완료한다음, 다른말로소켓을닫고종료되기직전에업데이트할것 ) 발생한모든연결의개수 헤더를제외한보낸데이터크기의합 (byte), Content-length의합이라생각해도좋음. 각파일확장자마다, 에러가발생하지않고정상적으로전송한횟수의합. 여러쓰레드가동시에수행기록을남길때동기화시키기위해 lock 등을이용한다. /stat 이란주소를요청받으면, make_report() 함수를호출해수행기록에대한리 포트를만든다. (HTML 파일로취급할것 ) char *make_report(int connections, long long int sent_bytes, int html, int jpg, int png, int gif, int js, int css, int error) http.c 에구현된 make_report() 함수에인자를넣으면수행기록에대한리포트 를동적으로만든다. /stat 이요청된시점에커넥션이 3 번발생했고, 총 2400 바이트를전송했으며, 각 각 html, jpg, hwp 파일을요청받았다면다음과같이 make_report() 함수를호출 한다. make_report(3, 2400, 1, 1, 0, 0, 0, 0, 1); connections = html + jpg + png + gif + js + css + error 식은항상 성립한다. make_report() 로만들어진문자열을 < 파일의내용 > 으로클라이언트에게전송한다. 이상황은 HTML 요청을정상적으로전송했다고간주한다. 파일의크기는 make_report() 함수가생성한문자열의길이이다. 문자열을전송하기전적절한헤더를생성하여보내줘야할것이다. 전송이후자기자신에대한수행기록을업데이트한뒤, free() 로할당받은메모리를 해제하고쓰레드를종료한다. /reset 주소를요청받으면, 다음과같이처리한다. (HTML 파일로취급 ) 새로운서버쓰레드를생성하지않고, ( 메인쓰레드는 accept 이후그냥대기 ) 다른서버쓰레드가수행기록을남기고전부종료될때까지기다린다음, 현재까지전송한데이터크기의합을 0으로초기화한뒤, (sent_bytes)
/stat 을요청받은것과같이현재까지수행기록을클라이언트에게전송한다. ( 전송이후자기자신의기록을업데이트하는것까지포함 ) 수행기록을남긴뒤, 새로운서버쓰레드를생성할수있도록서버를정상화한뒤쓰레드를종료한다. 5. Skeleton codes 이번과제수행을위해다음 9 개의파일이주어진다 : Makefile: GNU make 도구를위해필요한파일 webserver.c: 기초적인웹서버가구현된파일 http.c: 제공함수들이구현된파일 http.h: 프로그램에서이용하는헤더 string_sw.c: 문자열처리함수를구현한 C 파일 string_sw.h: 문자열처리함수가선언된헤더파일 index.html: 서버를테스트할수있는웹페이지파일 daum.png: index.html을그리기위해포함된그림 ( 다음로고 ) naver.gif: index.html을그리기위해포함된그림 ( 네이버로고 ) 프로그램을컴파일하기위해 make 명령을사용하면 webserver 프로그램이생성된다. http.c 에담긴함수들은전부 thread-safe 하다. 6. Restrictions 과제구현을위해여태까지배운리눅스시스템콜 / 라이브러리함수를이용한다. 동적메모리할당함수를제외한다른라이브러리함수는사용할수없다. 필요하다면직접구현하여사용한다. 쉬운디버깅을하기위해라이브러리함수를사용할수있다. 다만, 제출한파일엔해당함수들이직접사용되지않아야한다. ( 주석등으로처리해도괜찮음 ) 어떤자원을동적으로할당받았다면, 프로그램종료전에반드시해제해야한다. 여기서자원이란파일, 메모리, 소켓등을뜻한다. 내가할당받지않은자원을해제하지않도록조심한다. 프로그램컴파일시발생하는모든 warning을잡는다. 7. Hand in instruction 작성한코드상단의주석에이름과학번을작성한다. make 명령을통해프로그램이제대로만들어지는지확인한다.
제출한코드가컴파일되지않을경우, 경우에따라서해당제출은채점되지않거나심한페널티를받게될것이다. webserver.c 파일의이름을학번.c 로명명한다. 본과제수행시구현방법과디자인을설명하는보고서를 PDF 포맷으로작성하여 " 학번.pdf" 이란이름을붙인다. ( 가능하면 PDF가가장좋지만, 대중적인문서포맷은다른포맷도괜찮음 ) 과제를제출하기위해 [wooyeong at csl.skku.edu] 주소로메일을보낸다. 메일전송시전술한코드파일과문서파일을각각첨부하고 ( 압축하지말것!), 메일제목은다음과같이명명한다 : [SWE2007] PA #5, 학번, 이름 8. Logistics 본과제는혼자수행한다. 제출상태는과목홈페이지 http://csl.skku.edu/swe2007f14/projects 에즉각적으로공지될것이다. 본과제의지연제출은허용되지않는다. 본과제에선문서의평가비중이높으니, 문서를잘작성하도록한다. 동기화등의이슈를해결하기위해어떤디자인을이용했는지설명한다. 다른사람의과제를 copy할경우, 개입한사람전부해당과제에대해 0점처리되고, 교수님께보고되며, 성적산정에불이익이있다. 또한, copy가두차례이상적발될경우 F 학점이부여될수있다. Have fun! 정우영, 담당조교 컴퓨터시스템연구실 성균관대학교