proxy_lab을 진행하며, 웹 서버가 어떤식으로 진행되는지 많이 알게 된 것 같다.
일단 proxy_lab을 진행하기 위한 모든 과정이 끝났으니, 프록시 서버를 만들고 카네기 멜론 교수님의 채점을 차례이다...
뭔가 proxy_lab 진행 과정이 하나하나 준비될게 필요하다 보니, 보스 레이드를 준비하는 것 같다.
그럼 이제 레쓰고...
일단 내가 구현해야 할 것은 정방향 프록시이다.
프록시에 대해 계속 헷갈리고 있을 때, 코치님이 딱 설명을 해주셔서 감이 잡혔었다.
프록시 서버 란 ?
1. 정방향 프록시
클라이언트의 분신 같은 존재이다 !
클라이언트의 분신을 보내서 서버가 어떤 곳인지, 요청을 대신 받아준다.
그리고 그에 대한 결과를 클라이언트 에게 다시 알려준다 !
=> 클라이언트가 누군지 숨길 수 있다 !
2. 리버스 프록시
리버스 프록시에서는 프록시 서버는 서버의 방패막이다 !
서버 측에서 프록시를 운영하며, 클라이언트의 요청들을 선별 제어 하게 된다.
때문에 사용자는 실제 서버가 어디 있는지 모르고, 프록시를 통해 간접적으로 접근하게됨.
=> 서버를 숨길 수 있다 !
나는 리버스 프록시의 개념을 보면서 단단한 사람의 마음의 벽과 비슷하다고 생각이 들었다.
내가 할 수 없는 것은 우회하여 돌리고, 내가 진심으로 수용할 수 있는 말만 받아들인다.
그런 방화벽 역할을 할 수 있는 프록시가 나에게도 생기면 좋겠다.
프록시 서버의 메인
int main(int argc, char *argv[])
{
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE];
socklen_t clientlen;
struct sockaddr_storage clientaddr;
listenfd = open_listenfd(argv[1]);
while(1) {
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
doit(connfd);
Close(connfd);
}
}
프록시 서버의 메인 함수는 다음과 같다.
메인 함수만 보면 이전에 구현했던, tiny server의 메인 함수와 크게 달라 보이지 않는다.
프록시 서버도 서버이기 떄문에 당연하다 !
하지만 서버와 똑같이 listenfd으로 요청을 받고, connfd로 소켓이 생성 된 것을 매핑 해주는 과정 후에, 프록시 서버는 클라이언트 입장이 되어 웹서버와 다시 통신한다.
그러면 프록시 서버에서 필요한 건 어떤 걸까 ?
1. 클라이언트가 요청한uri파싱 => (host와port가져오기)
2. 클라이언트가 요청한 웹 서버와 연결하기 (가져온 인자를 통해서)
3. 서버가 요청한 응답을 클라이언트에게 돌려주기
해당 기능들을 doit() 함수에 내에 구현하였다.
doit 함수 구현
void doit(int clientfd)
{
int proxyfd;
char buf[MAX_OBJECT_SIZE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE], port[MAXLINE], hostname[MAXLINE];
char path[MAXLINE], header_buf[MAX_OBJECT_SIZE];
rio_t rio_server, rio_client;
Rio_readinitb(&rio_client, clientfd);
Rio_readlineb(&rio_client, buf, MAX_OBJECT_SIZE);
sscanf(buf, "%s %s %s", method, uri, version);
parse_url(uri, hostname, path, port);
proxyfd = Open_clientfd(hostname, port);
Rio_readinitb(&rio_server, proxyfd);
make_header(header_buf, hostname, path, user_agent_hdr);
Rio_writen(proxyfd, header_buf, strlen(header_buf));
int n;
while ((n = Rio_readlineb(&rio_server, buf, MAXLINE)) > 0) {
Rio_writen(clientfd, buf, n);
}
Close(proxyfd);
}
이전에 말한 기능들을 하나하나 구현하였다.
doit 함수 기능 요약
1. 클라이언트에게 받은uri를 나눠서 요청한host와port를 알아낸 후에,
해당 정보를 토대로Open_clientfd함수로 웹서버와 연결하는 소켓을 생성한다.
2. 이후 클라이언트에게 받은 인자로 부터HTTP HEADER를 생성하고,
웹서버에게 전송한다 !
3. 그 다음 웹서버가 보낸Respose를 클라이언트에게 전달한다 !
uri를 파싱하고, header를 생성하는 함수는 표준 라이브러리 메서드를 사용하여 구현하였다.
make_header, parse_url 구현
void make_header(char *buf, const char *host, const char *path, const char *user_agent_hdr)
{
sprintf(buf,
"GET %s HTTP/1.0\r\n"
"Host: %s\r\n"
"%s"
"Connection: close\r\n"
"Proxy-Connection: close\r\n"
"\r\n",
path, host, user_agent_hdr);
}
void parse_url(char* uri, char *hostname, char *path, char *port)
{
//"http://localhost:8080/index.html 를 하나하나 파싱한다 생각해보자."
char *hostbegin;
char *hostend;
char *pathbegin;
int len;
if (strncasecmp(uri, "http://", 7) == 0){
hostbegin = uri + 7;
} else {
hostbegin = uri;
}
pathbegin = strchr(hostbegin, '/');
strcpy(path, pathbegin);
len = pathbegin - hostbegin;
char hostport[MAX_OBJECT_SIZE];
strncpy(hostport, hostbegin, len);
hostport[len] = '\0';
char *colon = strchr(hostport, ':');
if(colon) {
*colon = '\0';
strcpy(hostname, hostport);
strcpy(port, colon + 1);
} else {
strcpy(hostname, hostport);
strcpy(port, "80");
}
}

보면 프록시 서버에 보낸 요청을 웹서버에 잘 보내고 응답도 잘 받아서 클라이언트에게 전달해준다 !
그럼 성능은 어떨까 ?

음...
이번엔 카네기멜런 교수님이 살짝 말록랩 때 힘들었지 ?
이러면서, 점수 좀 넉넉하게 주신 것 같다...
프록시 서버를 구현을 완료햇으니,
마지막으로, 동시성과 캐시를 가진 프록시 서버로 업그레이드 해볼꺼다...
화... 화이팅.. !