[Network] proxy 서버 제작 (1)

Laska·2025년 5월 8일
post-thumbnail

proxy_lab을 진행하며, 웹 서버가 어떤식으로 진행되는지 많이 알게 된 것 같다.

일단 proxy_lab을 진행하기 위한 모든 과정이 끝났으니, 프록시 서버를 만들고 카네기 멜론 교수님의 채점을 차례이다...

뭔가 proxy_lab 진행 과정이 하나하나 준비될게 필요하다 보니, 보스 레이드를 준비하는 것 같다.

그럼 이제 레쓰고...


proxy server ?


일단 내가 구현해야 할 것은 정방향 프록시이다.
프록시에 대해 계속 헷갈리고 있을 때, 코치님이 딱 설명을 해주셔서 감이 잡혔었다.


프록시 서버 란 ?

1. 정방향 프록시

클라이언트의 분신 같은 존재이다 !

클라이언트의 분신을 보내서 서버가 어떤 곳인지, 요청을 대신 받아준다.
그리고 그에 대한 결과를 클라이언트 에게 다시 알려준다 !
=> 클라이언트가 누군지 숨길 수 있다 !


2. 리버스 프록시

리버스 프록시에서는 프록시 서버는 서버의 방패막이다 !

서버 측에서 프록시를 운영하며, 클라이언트의 요청들을 선별 제어 하게 된다.
때문에 사용자는 실제 서버가 어디 있는지 모르고, 프록시를 통해 간접적으로 접근하게됨.
=> 서버를 숨길 수 있다 !


나는 리버스 프록시의 개념을 보면서 단단한 사람의 마음의 벽과 비슷하다고 생각이 들었다.

내가 할 수 없는 것은 우회하여 돌리고, 내가 진심으로 수용할 수 있는 말만 받아들인다.

그런 방화벽 역할을 할 수 있는 프록시가 나에게도 생기면 좋겠다.


proxy server - Main


프록시 서버의 메인

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 파싱 => (hostport 가져오기)
2. 클라이언트가 요청한 웹 서버와 연결하기 (가져온 인자를 통해서)
3. 서버가 요청한 응답을 클라이언트에게 돌려주기

해당 기능들을 doit() 함수에 내에 구현하였다.


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를 나눠서 요청한 hostport를 알아낸 후에,
해당 정보를 토대로 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");
  }
}

result


구현을 마쳤고 이제 결과를 봐야겠지...

보면 프록시 서버에 보낸 요청을 웹서버에 잘 보내고 응답도 잘 받아서 클라이언트에게 전달해준다 !


그럼 성능은 어떨까 ?

음...

이번엔 카네기멜런 교수님이 살짝 말록랩 때 힘들었지 ?
이러면서, 점수 좀 넉넉하게 주신 것 같다...

프록시 서버를 구현을 완료햇으니,

마지막으로, 동시성과 캐시를 가진 프록시 서버로 업그레이드 해볼꺼다...

화... 화이팅.. !

profile
똑똑해지고 싶어요

0개의 댓글