brunch

You can make anything
by writing

C.S.Lewis

by zwoo Nov 09. 2022

[정글]여덟째주. 웹서버 구현하기(2)

프록시 서버를 구현해보자

프록시 서버는 클라이언트가 자신을 통해서 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해 주는 컴퓨터 시스템이나 응용 프로그램을 가리킨다. 서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 가리켜 '프록시', 그 중계 기능을 하는 것을 프록시 서버라고 부른다.


프록시 서버를 구현해보자

프록시는 다른 서버로 요청을 보낼 수 있는 서버를 말한다. 프록시 서버의 listenfd 회선으로 어떤 클라이언트의 요청이 들어오면, 프록시는 클라이언트의 요청에 담겨있는 목적지 호스트와 포트를 뽑아내어 목적지 서버와 clientfd 회선을 맺고, 가운데에서 요청과 응답을 전달한다.


https://gist.github.com/yeonwooz/80c56875973321ce2fb7f214938d7903

(전체코드 : https://github.com/yeonwooz/webproxy-lab )


리눅스는 기본적으로 모든 입출력을 파일로 간주하고 처리한다. 이는 어쩌면 당연하다. 리눅스에게는 눈도 없고, 모국어를 배워 한글이나 영어를 구사하는 사용자도 아니다. 리눅스 운영체제에게 명령을 내리기 위한 방법으로 파일 입출력을 선택한 것은 굉장히 자연스럽다. 그래서 리눅스에서는 파일 입출력을 위한 open, close, write, read 메서드를 제공하고 있는데, 컴퓨터 시스템 책인 CSAPP 책의 저자들이 이 함수들을 다양한 목적을 커버할 수 있도록 안정적으로 래핑한 rio 패키지를 개발해두었다. rio 함수들은 기본 네개의 메서드를 처리하되 필요에 맞게 버퍼를 만들어서 1바이트보다 큰 단위로 전달될 수 있도록 해주고, 텍스트가 유실되지 않도록 해준다.


위의 프록시 서버 코드에 doit 함수 안에서 해야 하는 일을 6가지로 구분해두었다. 구현하면서 처음에 들었던 의문은, 클라이언트가 보낸 요청을 그대로 서버로 보내면 안되는가 하는 것이었다. 그에 대한 해답은 다음과 같다. 일단 요청의 첫줄에 담긴 uri에서 host 와 port를 뽑아내야 목적지서버와의 소켓을 맺을 때 사용할 수 있다. 그러므로 우선 클라이언트한테서 첫줄만 먼저 읽어와야 한다. 그리고 프록시에서 보냈다는 헤더도 추가해야 하고, 경우에 따라서는 HTTP 버전도 바꾸어주는 등 보낼 내용을 추가하거나 바꿔주어야 하기 때문에 클라이언트의 요청을 그대로 보내면 안된다. 이번에 진행한 프록시 과제의 요구사항은 프록시가 서버에 HTTP/1.0 버전으로 요청하는 것이었고, 이 버전에서는 keep-alive 헤더를 close로 유지해서 연결을 지속하지 않는 것이 기본 속성이다. 클라이언트로부터 헤더에 close가 담겨 올 경우, 서버는 해당 회선을 재사용할 수 없다. 그러므로, 이번 과제에서는 HTTP/1.0 버전으로만 요청을 보낼 거라서 main함수에서 응답 직후 무조건 close하도록 했지만, 일반적으로는 헤더내용에 따라 분기해주는 것이 맞는 것 같다.


동시에 여러 클라이언트의 요청을 처리해주자 

우리의 프록시는 현재 clientfd를 int 변수로 받고 있기 때문에, 한번에 하나의 회선(clientfd)밖에 맺을 수 없다. 서버가 한번에 사용자 한명의 요청밖에 처리할 수 없는 세상에서 인터넷을 하려면 무한 대기 지옥이 펼쳐지고 말 것이다. 그래서 우리는 들어오는 요청 하나하나에 개별 회선을 지정해줄 필요가 있다.

malloc() 함수를 사용해 각각 개별 메모리에 회선을 넣어서 처리해주도록 하자.


각 클라이언트에게 부여된 회선은 서로 간섭할 수 없도록 개별스레드로 관리하거나 자식 프로세스에서 관리되어야 한다. 그리고 할일이 다 끝나면 메모리 누수를 막기 위해 자식 프로세스를 종료해주거나, thread를 detach시켜서 사용했던 메모리자원을 반납하도록 해주어야 한다.


멀티 스레드

https://gist.github.com/yeonwooz/971b928153cfb9f05fafcfa0735fc0ea



멀티 프로세스

https://gist.github.com/yeonwooz/659853f24a948c65561b680192f00379






Photo by Deb Dowd on Unsplash


TMI1.

이번 과제에서 어려웠던 점은 의외로 포인터 이동을 통한 uri 파싱과 c언어의 기본적인 스트링함수들을 사용하는 부분이었다. c언어 문법에 익숙하지 않다는 것을 크게 실감하고 위기의식을 느꼈다.  



TMI2

다소 오래 전에 만들어진 과제이다보니, proxy와 tiny 코드를 실행해서 채점해주는 driver.sh 스크립트에서 참조하는 nop-server.py 에 적힌셔뱅경로가 #!/usr/bin/python (지금은 사용하지 않는 파이썬 버전) 으로 지정되어있는 문제가 있었다. 컴퓨터 환경 상에 이 경로가 없는 경우 스크립트가 실행되다가 중단되고 말았다. 이를 해결하기 위해서는 nop-server.py 의 셔뱅경로를 #!/usr/bin/python 로 바꿔주어야 했다.



TMI3

과제에 욕심이 컸던 만큼 잘하고 싶어서 조급했던 한 주였다. 동료들과 협력하며 오순도순 과제를 하고 싶은데 점점 과제가 어렵고 많아지다보니 혼자 공부할 내용이 많아서 주변 동료들과 대화를 많이 못하는 것 같다. 실력이 부족하면 각자 분량을 나눠서 공부해봐도 될텐데, 나는 스스로 이해를 하지 못하면 불안해하는 성격이라 그게 잘 안 된다. 



TMI4. 

사실 아직 과제가 덜 끝났는데, 다음 할 일은 프록시가 서버자원을 무조건 요청하지 않고 일부 자원은 직접 캐싱해두도록 하는 것이다. 내일 제출인데 오늘 안에 완성할 수 있을지 모르겠다. 


매거진의 이전글 [정글]여덟째주. 웹서버 구현하기(1)

작품 선택

키워드 선택 0 / 3 0

댓글여부

afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari