WEEK07

yeopto·2022년 5월 22일
0

SW사관학교 정글

목록 보기
9/14
post-thumbnail

Tiny Web Server


main()


port 번호를 인자로 받아 클라이언트 요청이 들어 올 때 마다 새로운 연결 소켓을 만들어서 doit() 함수 호출

// 포트번호 인자로 받기
int main(int argc, char **argv) {
  int listenfd, connfd;
  char hostname[MAXLINE], port[MAXLINE];
  socklen_t clientlen;
  struct sockaddr_storage clientaddr; // 클라이언트에서 연결 요청을 보내면 알 수 있는 클라이언트 연결 소켓 주소

  /* Check command line args */
  if (argc != 2) {
    fprintf(stderr, "usage: %s <port>\n", argv[0]);
    exit(1);
  }

  // 해당 포트 번호에 해당하는 listen 소켓 식별자 열어주기
  listenfd = Open_listenfd(argv[1]);
  // 요청이 들어 올 때 마다 새로운 연결 소켓을 만들어 doit() 호출
  while (1) {
    clientlen = sizeof(clientaddr);
    // 서버 연결 식별자 -> connfd
    connfd = Accept(listenfd, (SA *)&clientaddr,
                    &clientlen);  // line:netp:tiny:accept
    // 연결 성공 메세지를 위해서 Getnameinfo를 호출하면서 hostname, portrk 채워짐
    Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE,
                0);
    printf("Accepted connection from (%s, %s)\n", hostname, port);
    doit(connfd);   // line:netp:tiny:doit
    // 서버 연결 식별자 닫아주면 하나의 트랜잭션 끝.
    Close(connfd);  // line:netp:tiny:close
  }
}

doit()


클라이언트의 요청 라인을 확인해 정적, 동적 컨테츠인지 구분하고 서버에 보낸다.

// 클라이언트의 요청 라인을 확인해 정적, 동적컨텐츠인지 구분하고 각각의 서버에 보냄
void doit(int fd) // -> connfd가 인자로 들어옴
{
  int is_static;
  struct stat sbuf;
  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];  // 클라이언트에게서 받은 요청(rio)으로 채워진다.
  char filename[MAXLINE], cgiargs[MAXLINE];  // parse_uri를 통해 채워진다.
  rio_t rio;

  /* Read request line and headers */
  /* 클라이언트가 rio로 보낸 request 라인과 헤더를 읽고 분석한다. */
  Rio_readinitb(&rio, fd); // rio 버퍼와 fd, 여기서는 서버의 connfd를 연결시켜준다.
  Rio_readlineb(&rio, buf, MAXLINE); // 그리고 rio(==connfd)에 있는 string 한 줄(응답 라인)을 모두 buf로 옮긴다.
  printf("Request headers:\n");
  printf("%s", buf);  // 요청 라인 buf = "GET /godzilla.gif HTTP/1.1\0"을 표준 출력만 해줌.
  sscanf(buf, "%s %s %s", method, uri, version); // buf에서 문자열 3개를 읽어와 method, uri, version이라는 문자열에 저장.

  // 요청 method가 GET이 아니면 종료. main으로 가서 연결 닫고 다음 요청 기다림.
  if (strcasecmp(method, "GET")) {  // method 스트링이 GET이 아니면 0이 아닌 값이 나옴
    clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
    return;
  }

  // 요청 라인을 뺀 나머지 요청 헤더들을 무시한다.
  read_requesthdrs(&rio);

  /* Parse URI from GET request */
  /* parse_uri : 클라이언트 요청 라인에서 받아온 uri를 이용해 정적/동적 컨텐츠를 구분한다. */
  is_static = parse_uri(uri, filename, cgiargs); // 정적이면 1 동적이면 0

  /* stat(file, *buffer) : file의 상태를 buffer에 넘긴다. */
  // 여기서 filename은 parse_uri로 만들어준 filename
  if (stat(filename, &sbuf) < 0) {  // 못 넘기면 fail. 파일이 없다. 404.
    clienterror(fd, filename, "404", "Not found", "Tiny couldn't find this file");
    return;
  }

  /* 컨텐츠의 유형(정적, 동적)을 파악한 후 각각의 서버에 보낸다. */
  if (is_static) { /* Serve static content */
    // !(일반 파일이다) or !(읽기 권한이 있다)
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
      clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file");
      return;
    }
    // 정적 컨텐츠면 사이즈를 같이 서버에 보낸다. -> Response header에 Content-length 위해
    serve_static(fd, filename, sbuf.st_size);
  } else { /* Serve dynamic content */
    // !(일반 파일이다) or !(실행 권한이 있다)
    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);
  }
}

clienterror()


에러 메세지와 응답을 서버 소켓을 통해 클라이언트에게 보냄

// 클라이언트 오류 보고
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
  char buf[MAXLINE], body[MAXBUF]; // 에러메세지, 응답 본체
  
  // build HTTP response
  sprintf(body, "<html><title>Tiny Error</title>");
  sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
  sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
  sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
  sprintf(body, "%s<hr><em>The Tiny Web server></em>\r\n", body);
  
  // print HTTP response
  sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "Content-type: text/html\r\n");
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
  
  // Rio_writen으로 buf와 body를 서버 소켓을 통해 클라이언트에게 보냄
  Rio_writen(fd, buf, strlen(buf));
  Rio_writen(fd, body, strlen(body));
}

read_requesthdrs()


tiny는 요청 헤더 내의 어떤 정보도 사용하지 않고 이들을 읽고 무시한다.

// tiny는 요청 헤더 내의 어떤 정보도 사용하지 않고 이들을 읽고 무시
void read_requesthdrs(rio_t *rp) {
  char buf[MAXLINE];

  Rio_readlineb(rp, buf, MAXLINE);
  while(strcmp(buf, "\r\n")) { // EOF(한 줄 전체가 개행문자인 곳) 만날 때 까지 계속 읽기
    Rio_readlineb(rp, buf, MAXLINE);
    printf("%s", buf);
  }
}

parse_uri()


uri를 받아 요청받은 filename(파일이름), cgiarg(인자)를 채워줌.

// uri를 받아 요청받은 filename(파일이름), cgiarg(인자)를 채워줌.
int parse_uri(char *uri, char *filename, char *cgiargs) {
  char *ptr;

  if (!strstr(uri, "cgi-bin")) { // 정적 컨텐츠 요청(uri에 "cgi-bin"이 없으면)
    strcpy(cgiargs, "");
    strcpy(filename, ".");
    strcat(filename, uri); // url '/'로 시작해서 들어옴
    if (uri[strlen(uri) - 1] == '/') {
      /*
        uri : /home.html
        cgiargs : 
        filename : ./home.html
      */
      strcat(filename, "home.html");
    }

    return 1;
  } else { // 동적 컨텐츠 요청
    /*
      uri : /cgi-bin/adder?1234&1234
      cgiargs : 1234&1234
      filename : ./cgi-bin/adder
    */
    ptr = index(uri, '?');
    // '?'가 있으면 cgiargs를 '?' 뒤 인자들과 값으로 채워주고 ?를 NULL로
    if (ptr) {
      strcpy(cgiargs, ptr + 1);
      *ptr = '\0';
    } else { // '?' 없으면 cgiargs에 아무것도 안 넣어줌
      strcpy(cgiargs, "");
    }
    strcpy(filename, ".");
    strcat(filename, uri);

    return 0;
  }
}

serve_static()


클라이언트가 원하는 정적 컨텐츠를 받아와서 응답 라인과 헤더를 작성하고 서버에게 보냄. 그 후 정적 컨텐츠 파일을 읽어 그 응답 바디를 클라이언트에게 보냄.(11.9 숙제 추가)

// 클라이언트가 원하는 정적 컨텐츠를 받아와서 응답 라인과 헤더를 작성하고 서버에게 보냄, 그 후 정적 컨텐츠 파일을 읽어 그 응답 바디를 클라이언트에게 보냄
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, "%sConnection: close\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));  // connfd를 통해 clientfd에게 보냄
  printf("Response headers:\n");
  printf("%s", buf);  // 서버 측에서도 출력한다.

  /* Send response body to client */
  srcfd = Open(filename, O_RDONLY, 0); // filename의 이름을 갖는 파일을 읽기 권한으로 불러온다.
  // srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); -> Mmap방법 : 파일의 메모리를 그대로 가상 메모리에 매핑함.
  srcp = (char *)Malloc(filesize); // 11.9 문제 : mmap()과 달리, 먼저 파일의 크기만큼 메모리를 동적할당 해줌.
  Rio_readn(srcfd, srcp, filesize); // rio_readn을 사용하여 파일의 데이터를 메모리로 읽어옴. ->  srcp에 srcfd의 내용을 매핑해줌
  Close(srcfd); // 파일을 닫는다.
  Rio_writen(fd, srcp, filesize);  // 해당 메모리에 있는 파일 내용들을 fd에 보낸다.
  // Munmap(srcp, filesize); -> Mmap() 방법 : free해주는 느낌
  free(srcp); // malloc 썼으니까 free
}

get_filetype()


filename을 조사해 각각의 식별자에 맞는 MIME 타입을 filetype에 입력해줌.(11.7 숙제 추가)

// filename을 조사해서 filetype을 입력해줌.
void get_filetype(char *filename, char *filetype) {
  if (strstr(filename, ".html"))  // filename 스트링에 ".html" 
    strcpy(filetype, "text/html");
  else if (strstr(filename, ".gif"))
    strcpy(filetype, "image/gif");
  else if (strstr(filename, ".png"))
    strcpy(filetype, "image/png");
  else if (strstr(filename, ".jpg"))
    strcpy(filetype, "image/jpeg");
  else if (strstr(filename, ".mp4"))
    strcpy(filetype, "video/mp4"); // 11.7 문제
  else
    strcpy(filetype, "text/plain");
}

serve_dynamic()


클라이언트가 원하는 동적 컨텐츠를 받아옴. 응답 라인과 헤더를 작성하고 서버에게 보냄. CGI 자식 프로세스를 fork하고 그 프로세스의 표준 출력을 클라이언트 출력과 연결함.

void serve_dynamic(int fd, char *filename, char *cgiargs) {
  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 (Fork() == 0) { /* Child */
    /* Real server would set all CGI vars here */
    setenv("QUERY_STRING", cgiargs, 1);  // 

    // 클라이언트의 표준 출력을 CGI 프로그램의 표준 출력과 연결한다.
    // 이제 CGI 프로그램에서 printf하면 클라이언트에서 출력됨
    Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */
    Execve(filename, emptylist, environ); /* Run CGI program */
  }
  Wait(NULL); /* Parent waits for and reaps child */
}
  • if문에 대해
    • fork()를 실행하면 부모 프로세스와 자식 프로세스가 동시에 실행됨.
    • 만약 fork()의 반환값이 0 이라면, 즉 자식 프로세스라면 if 문을 수행.
    • fork()의 반환값이 0이 아니라면, 즉 부모 프로세스라면 if 문을 건너뛰고 Wait(NULL) 함수로 감. 이 함수는 부모 프로세스가 먼저 도달해도 자식 프로세스가 종료될 때까지 기다리는 함수임.
    • if 문 안에서 setenv 시스템 콜을 수행해 “QUERY_STRING”의 값을 cgiargs로 바꿔준다.
    • dup2 함수를 실행해서 CGI 프로세스의 표준 출력을 fd(서버 연결 소켓 식별자)로 복사한다. 이제 STDOUT_FILENO의 값은 fd이다. 다시 말해, CGI 프로세스에서 표준 출력을 하면 그게 서버 연결 식별자를 거쳐 클라이언트에 출력된다.
    • execuv 함수를 이용해 파일 이름이 filename인 파일을 실행한다.

adder.c


11.10 숙제 추가

/*
 * adder.c - a minimal CGI program that adds two numbers together
 */
/* $begin adder */
#include "csapp.h"

int main(void) {
  char *buf, *p;
  char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
  int n1 = 0, n2 = 0;

  if ((buf = getenv("QUERY_STRING")) != NULL) {
    p = strchr(buf, '&');
    *p = '\0';
    /* 기본 adder.c
    strcpy(arg1, buf);
    strcpy(arg2, p + 1);
    n1 = atoi(arg1);
    n2 = atoi(arg2);
    */

    // 11. 10 문제
    // ex) http://13.209.73.157:8000/cgi-bin/adder?first=13&second=5
    sscanf(buf, "first=%d", &n1); // buf에서 %d를 읽어서 n1에 저장
    sscanf(p + 1, "second=%d", &n2); // p + 1은 second를 가리키게 됨.
  }

  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);

  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);
}
/* $end adder */

HEAD method(11.11 숙제)


HEAD 메서드는 리소스를 GET메서드로 요청했을 때 응답으로 오는 헤더부분만 요청하는 메서드다.

  • HEAD 메서드에 대한 응답은 본문(body)을 가져선 안되며, 본문이 존재하더라도 무시해야한다.
  • 그러나, 응답으로 받는 헤더에는 본문 콘텐츠를 설명하는 개체 헤더는 포함할 수 있음.
  • 개체 헤더는 body에 대한 메타데이터를 필드로 가지고 있는 헤더이다. 여기서, entity-body의 필드로는 Allow, Content-Encoding, Content-Language, Content-Length, Content-Location등이 있다.
  • 쓰는이유 → 본문 포함되는 정보를 모두 얻는 것보다 특정한 목적을 가지고 header 부분만 원한다면 굳이 GET 요청을 통해 body 부분의 포함한 큰 응답 데이터를 받는 것보다는 header부분만 가진 응답을 받는 것이 더 효율적이니까.
  • 11.11 숙제까지 완성한 tiny.c, adder.c 코드

proxy


2022년 5월 17일 VoyagerX 협력사 강연이 있었는데 강연을 듣고 느낀 점이 있다. 7주 동안 “아, 이 정도 이해했으면 되지 않았을까?”라는 생각을 하며 내 자신과 타협 했던 적이 꽤 있었다. 1주일 동안 어렵고 많은 내용을 학습해야하기에 내가 좀 깊게 판다 싶으면 내 자신 스스로 주변 동료들 진도와 비교하며 타협 했던 것 같다. 뭔가 내 자신을 위한 공부라기보다 남들 하니까 하는 공부랄까? 진도따라가기 급급했다. 그래서 그런지 요 몇일은 과제를 언제 다하냐는 압박도 생기고 그로인해 짜증도 많아졌다. 이런 내 자신을 보니 왜?라는 ****의문점을 갖고 공부하지 않고 있었다. 정작 나는 이걸 왜 배우고 있지? 왜 이걸 사용하지? 지금 배우는 키워드의 특징은 무엇일까? 본질적인 생각을 안했다. 그 생각을 갖게 된다면 과제를 즐길 수 있을텐데! 오늘부터는 그 왜? 를 실천해보려한다.

Proxy 란?

  • 프록시는 IP를 우회해서 접속하지 못하는 사이트를 접속할 때 많이 이용한다. 원래 프록시의 기능은 보안을 강화하는 기능이라고 함.
  • 프록시란 컴퓨터 네트워크에서 다른 서버 상의 자원을 찾는 클라이언트로부터 요청을 받아 중계하는 서버를 말함 → 중계해주는 서버다!
  • 프록시라고 말하는 것은 대부분 웹 프록시를 말하는 것! 컴퓨터 네트워크에서 다른 서버로의 자원 요청을 중계하며 분산 시스템 구조를 단순화하여 서비스의 복잡도를 줄일 수 있다.
  • 프록시 서버에는 클라이언트로부터 원격에 요청된 자원들이 캐시되어 임시로 저장되어 있어 이같이 중계자 역할을 할 수 있다. 클라이언트는 자원 재요청 시 원격 서버에 접속할 필요 없고 프록시 서버 내의 정보를 제공받을 수 있으며, 따라서 데이터 전송 시간과 외부 트래픽이 줄어들고 서버 측의 네트워크 병목 현상을 방지할 수 있다.
  • 보안적인 측면 → 프록시 서버 측에서 위험이 예상되는 웹 콘텐츠 및 아성코드를 필터링함으로써 클라이언트 측의 보안을 향상시킬 수 있다.
  • 회사 및 중요 기관에서는 보안 유지를 위해 내부 통신과 외부 데이터 접근을 통제하고 인터넷 이용률 통계를 수집하기 위해 프록시 서버를 사용하기도 한다. → 로그가 남기 때문에 분석을 하는데 사용할 수 있다.
  • 역기능 → 사용자의 입장에서 자신의 웹 서핑 기록을 익명화하기 위해 익명 웹 프록시를 사용한다.

Proxy 서버의 사용목적

  1. 익명으로 컴퓨터를 유지 할 수 있다.
  2. 캐시를 사용하여 리소스로의 접근을 빠르게 하기 위해 사용한다.
  3. 웹 프록시는 웹 서버로부터 웹 페이지를 캐시로 저장하는 데 흔히 쓰이며 캐싱을 통해 콘텐츠를 빠르게 가져올 수 있다.

Proxy.c


main()


프록시 서버에 사용할 포트 번호를 인자로 받아, 프록시 서버가 클라이언트와 연결할 연결 소켓 connfd를 만들고 doit() 함수 실행.

int main(int argc, char **argv) {
  int listenfd, connfd;
  socklen_t clientlen;
  char hostname[MAXLINE], port[MAXLINE];

  struct sockaddr_storage clientaddr;

  if(argc != 2) {
    fprintf(stderr, "usage :%s <port> \n", argv[0]);
    exit(1);
  }

  listenfd = Open_listenfd(argv[1]);
  while(1) {
    clientlen = sizeof(clientaddr);
    connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);

    // 연결 수락 메세지 출력
    Getnameinfo((SA*)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
    printf("Accepted connection from (%s %s).\n",hostname, port);

    // sequential handle
    doit(connfd);
    Close(connfd);
  }
  return 0;
}

void doit(int connfd) {
  
}

doit()


클라이언트의 요청 라인을 파싱해 1) 엔드 서버(tiny.c)의 hostname, path, port를 가져옴. 2) 엔드 서버에 보낼 요청 라인과 헤더를 만들 변수들을 만듦. 3) 프록시 서버와 엔드 서버를 연결하고 엔드 서버의 응답 메세지를 클라이언트에 보내줌.

변수

  • end_serverfd : 프록시 서버와 엔드 서버를 이어주는 프록시 서브 측 클라이언트 소켓
  • hostname, path, port : 프록시 서버가 파싱한 엔드 서버의 정보(클라이언트가 요청한 것들)
  • endserver_http_header : 엔드 서버에 보낼 프록시 서버의 요청 라인과 헤더
void doit(int connfd) {
  int end_serverfd; // 엔드서버(tiny.c) fd

  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
  char endserver_http_header [MAXLINE];
  
  // 요청 인자들
  char hostname[MAXLINE], path[MAXLINE];
  int port;

  rio_t rio, server_rio; /*rio is client's rio,server_rio is endserver's rio*/

  /* 클라이언트가 보낸 요청 헤더에서 method, uri, version을 가져옴.*/
  /* GET http://localhost:8000/home.html HTTP/1.1 */
  Rio_readinitb(&rio, connfd);
  Rio_readlineb(&rio, buf, MAXLINE);
  sscanf(buf, "%s %s %s", method, uri, version); /*read the client request line*/

  if (strcasecmp(method, "GET")) { // 같으면 0을 반환함 즉, 다르다면 출력해라.
    printf("Proxy does not implement the method");
    return;
  }

  /*parse the uri to get hostname,file path ,port*/
  /* 프록시 서버가 엔드 서버로 보낼 정보들을 파싱함. */
  // hostname -> localhost, path -> /home.html, port -> 8000
  parse_uri(uri, hostname, path, &port);

  /*build the http header which will send to the end server*/
  /* 프록시 서버가 엔드 서버로 보낼 요청 헤더들을 만듦. endserver_http_header가 채워진다. */
  build_http_header(endserver_http_header, hostname, path, port, &rio);

  /*connect to the end server*/
  /* 프록시 서버와 엔드 서버를 연결함 */
  end_serverfd = connect_endServer(hostname,port,endserver_http_header);
  // clinetfd connected from proxy to end server at proxy side
  // port: 8000
  if (end_serverfd<0) {
    printf("connection failed\n");
    return;
  }

  /* 엔드 서버에 HTTP 요청 헤더를 보냄 */
  Rio_readinitb(&server_rio,end_serverfd);
  /*write the http header to endserver*/
  Rio_writen(end_serverfd,endserver_http_header,strlen(endserver_http_header));

  /* 엔드 서버로부터 응답 메세지를 받아 클라이언트에 보내줌. */
  /*receive message from end server and send to the client*/
  size_t n;
  while((n=Rio_readlineb(&server_rio,buf,MAXLINE))!=0) {
    printf("proxy received %d bytes,then send\n",n);
    Rio_writen(connfd,buf,n); // connfd -> client와 proxy 연결 소켓. proxy 관점.
  }
  Close(end_serverfd);
}

build_http_header()


클라이언트로부터 받은 요청 헤더를 정제해서 프록시 서버가 엔드 서버에 보낼 요청 헤더를 만듦.

  • request가 “GET [http://localhost:8000/home.html](http://localhost:8000/home.html) HTTP/1.1” 일 때
    • request_hdr = "GET /home.html HTTP/1.0\r\n"
    • host_hdr = "Host: localhost:8000"
    • conn_hdr = "Connection: close\r\n"
    • prox_hdr = "Proxy-Connection: close\r\n"
    • user_agent_hdr = "User-Agent: ...."
    • other_hdr = Connection, Proxy-Connection, User-Agent가 아닌 모든 헤더
void build_http_header(char *http_header,char *hostname,char *path,int port,rio_t *client_rio) {
  char buf[MAXLINE],request_hdr[MAXLINE],other_hdr[MAXLINE],host_hdr[MAXLINE];

  /* 응답 라인 만들기 */
  sprintf(request_hdr, requestlint_hdr_format, path);

  /* 클라이언트 요청 헤더들에서 Host header와 나머지 header들을 구분해서 넣어줌 */
  /*get other request header for client rio and change it */
  while(Rio_readlineb(client_rio, buf, MAXLINE)>0) {
    if (strcmp(buf, endof_hdr) == 0) break; /* EOF, '\r\n' 만나면 끝 */

    /* 호스트 헤더 찾기 */
    if (!strncasecmp(buf, host_key, strlen(host_key))) { /*Host:*/ //일치하는 게 있으면 0
        strcpy(host_hdr, buf);
        continue;
    }
    /* 나머지 헤더 찾기 */
    if (strncasecmp(buf, connection_key, strlen(connection_key))
          && strncasecmp(buf, proxy_connection_key, strlen(proxy_connection_key))
          && strncasecmp(buf, user_agent_key, strlen(user_agent_key))) {
      strcat(other_hdr,buf);
    }
  }
  if (strlen(host_hdr) == 0) {
      sprintf(host_hdr,host_hdr_format,hostname);
  }

  /* 프록시 서버가 엔드 서버로 보낼 요청 헤더 작성 */
  sprintf(http_header,"%s%s%s%s%s%s%s",
          request_hdr,
          host_hdr,
          conn_hdr,
          prox_hdr,
          user_agent_hdr,
          other_hdr,
          endof_hdr);

  return ;
}

connect_endServer()


프록시 서버와 엔드 서버를 연결한다.

inline int connect_endServer(char *hostname,int port,char *http_header) {
    char portStr[100];
    sprintf(portStr,"%d",port);
    return Open_clientfd(hostname, portStr);
}

parst_uri()

클라이언트 uri를 파싱해 서버의 hostname, path, port를 찾는다.

void parse_uri(char *uri,char *hostname,char *path,int *port) {
    *port = 80; // default port
    char* pos = strstr(uri,"//");  /* http://이후의 string들 */

    pos = pos!=NULL? pos+2:uri;  /* http:// 없어도 가능 */ 

    /* port와 path를 파싱 */
    char* pos2 = strstr(pos,":");
    if(pos2!=NULL) {
        *pos2 = '\0';
        sscanf(pos,"%s",hostname);
        sscanf(pos2+1,"%d%s",port,path); // port change from 80 to client-specifying port
    } else {
        pos2 = strstr(pos,"/");
        if(pos2!=NULL) {
            *pos2 = '\0';
            sscanf(pos,"%s",hostname);
            *pos2 = '/';
            sscanf(pos2,"%s",path);
        } else {
            sscanf(pos,"%s",hostname);
        }
    }
    return;
}

매크로, 변수, 함수


#include <stdio.h>
#include "csapp.h"

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400

/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr =
    "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 "
    "Firefox/10.0.3\r\n";
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";
static const char *conn_hdr = "Connection: close\r\n";
static const char *prox_hdr = "Proxy-Connection: close\r\n";
static const char *host_hdr_format = "Host: %s\r\n";
static const char *requestlint_hdr_format = "GET %s HTTP/1.0\r\n";
static const char *endof_hdr = "\r\n";

static const char *connection_key = "Connection";
static const char *user_agent_key= "User-Agent";
static const char *proxy_connection_key = "Proxy-Connection";
static const char *host_key = "Host";

void doit(int connfd);
void parse_uri(char *uri,char *hostname,char *path,int *port);
void build_http_header(char *http_header,char *hostname,char *path,int port, rio_t *client_rio);
int connect_endServer(char *hostname,int port,char *http_header);

참고 - https://sunset-asparagus-5c5.notion.site/Webproxy-1f91d8247c604bce9262bc0209053d16#0aca6f9f85384723860d1516ac2261c6

Sequential proxy까지 내용이다. concurrent, cache는 깃허브 소스코드에 주석으로 달아놨다.


Tiny 서버까진 괜찮았고, 프록시 concurrent까지도 할만했지만 cache proxy는 촉박하게 해서 그런지 굉장히 이해하기 어려웠다. 지금 WEEK08을 진행중인데 이해하기 어려웠던 세마포어, 뮤텍스 개념은 스레드 동기화하면서 느끼고 있지만 이해할만한 것이었다.. pintos 굉장히 어렵다. 헤롱헤롱헤롱헤롱.. pintos 프로젝트가 시작됐는데 이제 시작인 것 같다..


WEEK07 구현 소스코드(cache proxy까지) - https://github.com/yeopto/webproxy

profile
https://yeopto.github.io로 이동했습니다.

0개의 댓글