[Week6] Web-Proxy

안나경·2024년 2월 28일

크래프톤정글

목록 보기
39/57

...
뭘 적어야하나 봤는데
이걸 안 적었네

이걸 적은 다음에
12장은 개인적으로 요약해야겠군..

web-proxy lab 과제 pdf

Intro

part 1이 순차적 웹프록시,
part 2가 동시성 웹프록시,
part 3이 캐싱 웹프록시다.

1은 양식에 맞춰 전송, 재전송,
건네받기, 등을 하는 거고,

2는 다중 클라이언트를 감당할 수 있어야하고,

3은 이전에 방문했던 곳이면
캐싱한 페이지를 반환할줄 알아야하며
페이지를 받을때마다 캐싱도 진행해야한다.

나는 2, 3은 하지 않았다.
(찾아보면 3은 대개 링크드 리스트로
구조체를 만들어서 캐싱 리스트를 만들어 관리하고)
(2는 thread 함수를 쓰면 간단히 구현...
은 된다는데 아마 하라는대로 했는데 안 되더라)

심지어 1도
.c파일도 같은 text 파일인데
proxy를 통하면 요청 본문이 한줄도 안 온다.

아무도 나 같은 이슈를 겪지 않아서
참고할 수가 없다. 슬프다.

한번 쭉 쓰고,
솔루션을 대충 확인해서 빼먹은 알고리즘을 고려해
기존 코드를 좀 고친 후 눈물의 디버깅을 했다.
디버깅의 결과는 아름답진 않다.
미련없는 한주는 언제 오지?

Part I: Implementing a sequential web proxy

순차적 프록시 짜기.

프록시의 골자가 그거다.

클라이언트 -> 프록시 -> 서버
클라이언트 <- 프록시 <- 서버

...

그렇다!

순차적 프록시는
(1)클라이언트의 요청을 받아
(2)서버에 넘겨주고
(3)그 응답을 그대로 받아 클라이언트에게 돌려주는 일을 한다.

여기에 고려할 세 가지 이슈가 있다.

  • 요청라인과 헤더를 받을 때,
    적절히 받을 인자를 분리해내기(parse_uri)

  • 이미 받은 헤더는 중복하지 않되
    필요한 헤더는 첨부해서 보내기(request_hdr)

  • 응답을 그대로 받아
    클라이언트에게 전달하기(recieve_pass)

이름은 그냥 임의로 지은 거니
주의깊게 받아들이지말자.

parse_uri 함수

요청라인이

GET http://domain.com:80/path HTTP/1.1
(method) (URI) (version)

로 온다고 치자.
우리가 필요한 인자는

맨 앞의 method, URI다.

대개 포인터로 strstr로 지정해버리는데,
(아마 /기준으로 찾아서 기타 등등으로 되긴하는듯)
(sscanf 의 %[^:] 등으로 :가 나오기 전까지 받아서 할당하는 식도 되긴하는듯.)

아무튼 URI를 분리해야한다.

우리가 필요한건
domain.com
(요청 헤더의 호스트 네임용으로도 쓰고,
endserver에 진입할때 DNS용으로도 쓴다.)
그리고 /path다.

port는 붙어올때도 있고
안 붙어올때도 있으니까 (:80...이거 말이다)
잘 나눠서 분리하자.
이것도 endserver에 진입 시 쓰는데,
만약 없으면 그냥 80으로 보내준다.

/path는
endserver에 정확한 파일로 찾아가기 위해 필요하다.
(어라?
그러고보니 나는 path 지정이 덜 된건가?)
(아닌데...해줬는데...)

request_hdr 함수

요청 라인은 읽고 넘긴다.
(rio에서 전부 요청 라인, 요청 헤더 전부 받아왔어서
다시 처음부터 그걸 읽으려면 넘기는 과정을 해야한다.)

그리고
이거 하기 전에
미리 endserver와 연결해서
(parse_uri에서 인자 받았을 테니까)

User-Agent, Connection, Proxy-Connection,
Host 등 을 그냥 확인하는대로 보내준다.

있으면 그냥 그대로 보내기!
없으면 없는 걸 기억해두고 나중에 보내기.
Host는 만약 없으면 앞에서 받았던 hostname으로 보내준다.

요청 헤더는 순서가 상관없다.

recieve_pass 함수

그다음에
이어진 그 소켓에서
rio를 가져와서

그거를 readline으로 읽는다....

응답라인은 읽는대로 보내고,
응답헤더도 읽는대로 보내되,

Content-Length는 저장해둬서
응답 본문을 보낼때 그 byte에 따라 따로 처리를 할 수는 있지만
그냥 매번 readn으로 끝이 나올때까지
무한히.... 보내도 되긴 한다.
(근데 난 왜 .c는 안 될까?)

...

...

두번째는
thread_create를 해서
특정 thread라는 루틴에 넘기는데
거기서 thread 처리도 해두고,
메인 함수인 doit... 나는 이름을 send로 했는데 아무튼
그것만 받아온 fd로 돌려보내면 된다.

8점짜리 희귀 코드를 보고 싶다면 이걸 보자.
parse_uri 함수는 다른 분 것을 참고했다.
(는 사실 전체적인 개괄 자체도....)

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

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

#if IS_LOCAL_TEST
int is_local_test = 1;
#else
int is_local_test = 0;
#endif

void receive_pass(int clientfd, int serverfd, rio_t *rio_ser);
void Send(int fd);
void read_requesthdrs(rio_t *rp, int fd, char *hostname);
void parse_uri(char *uri, char *hostname, char *port, char *path);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
// void *thread(void *vargp);

/* 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";

int main(int argc, char **argv) {
  printf("%s", user_agent_hdr);

  int listenfd, connfd;
  char hostname[MAXLINE], port[MAXLINE];
  socklen_t clientlen;
  struct sockaddr_storage clientaddr;
//   pthread_t tid;

  /* Check command line args */
  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);  // line:netp:tiny:accept
    Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE,
                0);
    printf("Accepted connection from (%s, %s)\n", hostname, port);
    Send(connfd);   // line:netp:tiny:doit
    Close(connfd);  // line:netp:tiny:close
    // Pthread_create(&tid, NULL, thread, connfdp); 
  }
  return 0;
}

// void *thread(void *vargp)
//     {
//       int connfd = *((int *)vargp);
//       Pthread_detach(pthread_self());
//       Free(vargp);
//       Send(connfd);
//       Close(connfd);
//       return NULL;
//     }

void Send(int fd){

  struct stat sbuf;
  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE];
  char hostname[MAXLINE], path[MAXLINE], port[MAXLINE];
  rio_t rio;
  
  // 요청 라인을 읽고 분석.
  Rio_readinitb(&rio, fd);
  Rio_readlineb(&rio, buf, MAXLINE);
  printf("Request headers:\n");
  printf("%s", buf);

  sscanf(buf, "%s %s", method, uri);

  if(!(strcasecmp(method, "GET") == 0)) {
    clienterror(fd, method, "501","Not implemented","Tiny does not implement this method");
    return;
  }

  // URI 분석하여 도메인, 포트, path를 구분한다.
  parse_uri(uri, hostname, port, path);

  //도메인으로 적절한 웹서버와 연결을 맺는다.
  int proxyfd;
  char buf_ser[MAXLINE];
  rio_t rio_pro;
        
  //fd 받아오고....
  proxyfd = Open_clientfd(hostname, port);

  //읽을 준비중...
  Rio_readinitb(&rio_pro, proxyfd);

  // 요청라인 먼저 보내고..
  sprintf(buf_ser, "%s %s HTTP/1.0\r\n", method, path);
  Rio_writen(proxyfd, buf_ser, strlen(buf_ser));

  // 서버에 요청 헤더 보내는중..
  read_requesthdrs(&rio, proxyfd, hostname);

  // 서버 응답 본문을 클라이언트에게 보내는 중..
  receive_pass(fd, proxyfd, &rio_pro);

  Close(proxyfd);
  exit(0);

}

void receive_pass(int clientfd, int serverfd, rio_t *rio_ser){

  char buf[MAXLINE];
  int size = -1;
  size_t n;
  char tmp_str[MAXBUF];
  // 첫번째 줄 응답 라인
  Rio_readlineb(rio_ser, buf, MAXLINE);
  Rio_writen(clientfd, buf, strlen(buf));

  //응답 헤더 보내야지
  while(strcmp(buf, "\r\n") != 0){
    Rio_readlineb(rio_ser, buf, MAXLINE);
    if(strstr(buf, "Content-Length:")){
      sscanf(buf, "Content-Length: %d", &size);
    }
    Rio_writen(clientfd, buf, strlen(buf));
  }
 
  // 응답 본문 보내야지
  while ((n = Rio_readnb(rio_ser, tmp_str, MAX_CACHE_SIZE)) != 0){
    printf("proxy received %lu bytes,then send\n",n);
    Rio_writen(clientfd, tmp_str, n);
  };

}

void read_requesthdrs(rio_t *rp, int fd, char *hostname){
  char buf[MAXLINE];
  int User_flag = 0, Connect_flag = 0, Procon_flag = 0, Host_flag = 0;
 // 첫번째 줄은 요청 라인이라 먼저 읽음.
    Rio_readlineb(rp, buf, MAXLINE);

    while(strcmp(buf, "\r\n") != 0){
    	Rio_readlineb(rp, buf, MAXLINE);
      
      if(strstr(buf, "Host:")){
        Host_flag = 1;
        sprintf(buf, "Host: %s\r\n", hostname);
        Rio_writen(fd, buf, strlen(buf));
      }
      else if(strstr(buf,"User-Agent:")){
        User_flag = 1;
        sprintf(buf, "User-Agent : %s\r\n", user_agent_hdr);
        Rio_writen(fd, buf, strlen(buf));
      }
      else if(strstr(buf, "Connection:")){
        Connect_flag = 1;
        sprintf(buf, "Connection: close\r\n");
        Rio_writen(fd, buf, strlen(buf));
      }
      else if(strstr(buf, "Proxy-Connection:")){
        Procon_flag = 1;
        sprintf(buf, "Proxy-Connection: close\r\n");
        Rio_writen(fd, buf, strlen(buf));
      }
      else{
        Rio_writen(fd, buf, strlen(buf));
      }
    }
      if(Host_flag == 0){
        sprintf(buf, "Host: %s\r\n", hostname);
        Rio_writen(fd, buf, strlen(buf));
      }
      else if(User_flag == 0){
        sprintf(buf, "User-Agent : %s\r\n", user_agent_hdr);
        Rio_writen(fd, buf, strlen(buf));
      }
      else if(Connect_flag == 0){
        sprintf(buf, "Connection: close\r\n");
        Rio_writen(fd, buf, strlen(buf));
      }
      else if(Procon_flag == 0){
        sprintf(buf, "Proxy-Connection: close\r\n");
        Rio_writen(fd, buf, strlen(buf));
      }
      sprintf(buf, "\r\n");
      Rio_writen(fd, buf, strlen(buf));
    return;
}

void parse_uri(char *uri, char *hostname, char *port, char *path){

//   char port_str[MAXLINE];
// // 포트 파싱
//   if (sscanf(uri, "%*[^:]://%*[^:]:%[^/]/%*s", port_str) == 1) {
//     if (!sscanf(port_str, "%d", port)) {
//         port = 80; // 기본 포트
//     }
//     } 
//   else {
//     port = 80; // 기본 포트
//   }
  

    // host_name의 시작 위치 포인터: '//'가 있으면 //뒤(ptr+2)부터, 없으면 uri 처음부터
  char *hostname_ptr = strstr(uri, "//") ? strstr(uri, "//") + 2 : uri;
  char *port_ptr = strchr(hostname_ptr, ':'); // port 시작 위치 (없으면 NULL)
  char *path_ptr = strchr(hostname_ptr, '/'); // path 시작 위치 (없으면 NULL)
  strcpy(path, path_ptr);

  if (port_ptr) // port 있는 경우
  {
    strncpy(port, port_ptr + 1, path_ptr - port_ptr - 1); 
    port[MAXLINE - 1] = '\0';
    strncpy(hostname, hostname_ptr, port_ptr - hostname_ptr);
    hostname[MAXLINE - 1] = '\0';
  }
  else // port 없는 경우
  {
    if (is_local_test)
      strcpy(port, "80"); // port의 기본 값인 80으로 설정
    else
      strcpy(port, "8000");
    strncpy(hostname, hostname_ptr, path_ptr - hostname_ptr);
  }
}

void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg){

char buf[MAXLINE], body[MAXBUF];
    
    //응답 본체 설계.
    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);
    
    // 응답 출력.
    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(fd, buf, strlen(buf));
    Rio_writen(fd, body,strlen(body));
}
profile
개발자 희망...

0개의 댓글