미새문지

크래프톤 정글 week06, day52 - Tiny Server Code, 잔디심기 본문

크래프톤 정글/TIL

크래프톤 정글 week06, day52 - Tiny Server Code, 잔디심기

문미새 2024. 2. 29. 14:25
728x90

Tiny Server Code

 

tiny.c

더보기
#include "csapp.h"
void doit(int fd);
void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *filename, char *cgiargs);
void serve_static(int fd, char *filename, int filesize, char *method);
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg,
                 char *longmsg);

// 웹 서버의 주된 동작을 수행하는 Tiny 서버의 메인 함수
int main(int argc, char **argv) {
  // 연결 요청을 듣는 listen함수 소켓, 연결을 관리하는 connect함수 소켓
  int listenfd, connfd;
  // 클라이언트의 호스트 이름과 포트번호를 저장
  char hostname[MAXLINE], port[MAXLINE];
  // 클라이언트 소켓의 주소 길이와, 클라이언트의 소켓 주소 정보를 저장할 구조체
  socklen_t clientlen;
  struct sockaddr_storage clientaddr;
  /* Check command line args */
  // 인자가 2개가 아니라면
  if (argc != 2) {
    // 사용방법을 출력하고 종료
    fprintf(stderr, "usage: %s <port>\n", argv[0]);
    exit(1);
  }
  // listen 소켓을 생성하고 두 번째 인자인 포트 번호에 바인드하여 연결 요청을 받기 시작한다.
  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);
    // csapp 11.6문제 에코 구현
    // 요청한 데이터를 그대로 되돌려주는 에코 함수
    // echo(connfd);
    // 클라이언트 요청을 처리하는 doit함수를 호출
    doit(connfd);   // line:netp:tiny:doit
    // 클라이언트 연결을 종료
    Close(connfd);  // line:netp:tiny:close
  }
}

// csapp 11.6문제 에코 구현
// 클라이언트에서 데이터를 받아 그대로 되돌려주는 함수
void echo(int connfd) {
  // 읽은 바이트 수를 저장할 변수, 읽은 데이터를 저장할 버퍼, 버퍼링된 I/O연산
  size_t n;
  char buf[MAXLINE];
  rio_t rio;
  // rio 구조체를 초기화하고, 클라이언트와 연결한 소켓 파일 디스크립터와 연결한다.
  Rio_readinitb(&rio, connfd);

  // 클라이언트에서 한 줄을 읽어 buf에 저장하고, 읽은 바이트 수를 n에 저장해 클라이언트에서 데이터를 그만 보낼 때까지
  while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
    // 서버가 몇 바이트를 받았는지 출력
    printf("server received %d bytes\n", (int)n);
    // 클라이언트에게 받은 데이터를 다시 돌려준다.
    Rio_writen(connfd, buf, n);
  }
}

// 클라이언트의 HTTP 요청을 처리하는 메인 함수
// 클라이언트와 연결된 파일 디스크립터 fd를 인자로 받는다.
void doit(int fd){
  // is_static:정적 파일의 여부, sbuf:파일정보를 저장
  int is_static;
  struct stat sbuf;
  // HTTP요청라인을 저장, HTTP메소드를 저장, 요청 URI를 저장, HTTP버전을 저장
  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
  // 파일이름 저장, CGI인자를 저장, 클라이언트와 통신을 위한 rio 구조체 선언
  char filename[MAXLINE], cgiargs[MAXLINE];
  rio_t rio;
  // 클라이언트와 연결된 파일 디스크립터를 사용해 rio 구조체를 초기화한다. 
  Rio_readinitb(&rio,fd);
  // 클라이언트로부터 요청 라인을 읽지 못할 때 함수 종료
  if(!Rio_readlineb(&rio,buf,MAXLINE))
    return;
  // 요청 헤더를 출력한다.
  printf("Request headers:\n");
  printf("%s",buf);
  // 요청라인에서 HTTP 메소드, 요청 uri, HTTP 버전을 가져와서 각 변수에 저장한다. 
  sscanf(buf, "%s %s %s",method,uri,version);
  // if(strcasecmp(method,"GET")){
  // csapp 11.11문제 HEAD 메소드
  // HTTP 메소드가 GET이나 HEAD가 아닐 경우 501 에러 메세지를 클라이언트에게 전송하고 종료
  if(!(strcasecmp(method, "GET") == 0 || strcasecmp(method, "HEAD") == 0)) {
    clienterror(fd,method,"501","Not Implemented","Tiny does not implement this method");
    return;
  }

  // 클라이언트로부터 HTTP 요청 헤더를 읽는다.
  read_requesthdrs(&rio);
  // uri를 분석해 파일이름과 CGI 인자를 추출해서 정적인지 동적인지 체크
  is_static = parse_uri(uri,filename,cgiargs);

  // 해당 파일의 상태 정보를 sbuf에 넣고, 파일이 존재하지 않으면 에러 메세지를 전송하고 종료
  // stat 함수는 실패하면 -1을 성공하면 0을 반환하기 때문에 0보다 작으면 실패 했다는 뜻
  if(stat(filename,&sbuf) < 0){
    clienterror(fd,filename,"404","Not found","Tiny couldn't find this file");
    return;
  }

  // 요청이 정적 파일을 대상으로 할 때
  if(is_static){
    // 파일 권한을 나타내는 POSIX 표준에서 정의한 상수
    // 파일이 일반 파일이 아니거나 읽기 권한이 없을 경우
    if(!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)){
      // 403 에러를 전송하고 종료
      clienterror(fd,filename,"403","Forbidden","Tiny couldn't read the file");
      return;
    }

    // 파일이름, 파일크기, HTTP 메소드를 인자로 받아 정적 파일을 처리하는 함수를 실행
    serve_static(fd,filename,sbuf.st_size, method);
  // 만약 요청이 동작 파일을 대상으로 할 때
  }else{
    // 파일이 일반 파일이 아니거나 읽기 권한이 없을 경우
    if(!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)){
      // 403 에러를 전송하고 종료
      clienterror(fd,filename,"403","Forbidden", "Tiny couldn't run the CGI program");
      return;
    }
    // 파일이름, 파일크기, HTTP 메소드를 인자로 받아 동적 파일을 처리하는 함수를 실행
    serve_dynamic(fd,filename,cgiargs, method);
  }
}

// 클라이언트로부터 HTTP 요청 헤더를 읽어들이는 역할을 하는 함수
// rp는 클라이언트에서 HTTP 요청을 읽는 포인터변수
void read_requesthdrs(rio_t *rp){
  // 문자열을 저장할 buf배열을 선언(MAXLINE은 한 줄의 문자열을 받을만한 크기)
  char buf[MAXLINE];
  // 클라이언트에서 한 줄을 읽어와 buf에 저장하고 출력
  // 처음 읽어오는 문자열은 HTTP 요청의 첫 줄인 요청 라인이다.
  Rio_readlineb(rp,buf,MAXLINE);
  printf("%s", buf);

  // 요청 라인 다음에 오는 헤더를 읽는데, HTTP 요청 헤더는 '\r\n'으로 끝나기 때문에
  // buf에 저장된 문자열이 '\r\n'이 아닐 때까지 반복한다.
  while(strcmp(buf,"\r\n")){
    // 클라이언트에서 다음 줄을 읽어서 buf에 저장하고 출력
    Rio_readlineb(rp,buf,MAXLINE);
    printf("%s",buf);
  }
  return;
}

// 주어진 URI를 분석하여 파일명과 CGI 인자 등을 추출하는 함수
int parse_uri(char *uri, char *filename, char *cgiargs){
  // 포인터 변수 선언
  char *ptr;

  // 가져온 URI에 cgi-bin 문자열이 포함되어 있지 않을 경우, 즉 정적 컨텐츠를 요청하는 경우
  // cgi는 동적에서 컨텐츠를 생성하기 위한 기술이기 때문에 없으면 정적 컨텐츠를 요청할 수 밖에 없다.
  if(!strstr(uri,"cgi-bin")){
    // 정적에는 CGI인자가 필요 없기 때문에, cgiargs를 빈 문자열로 초기화한다.
    strcpy(cgiargs,"");
    // 현재 디렉토리에 있기 위해 filename을 .으로 초기화한다.
    strcpy(filename,".");
    // uri를 파일이름에 추가하여 요청된 파일의 경로를 완성한다.
    strcat(filename,uri);

    // uri의 마지막 문자는 null문자인 '\0'로 끝나므로 이것을 제외한(-1) 마지막 문자가 /인 경우
    // uri가 /로 끝나는 경우에, 즉 디렉토리를 가리키는 경우에
    if(uri[strlen(uri)-1] == '/')
      // home.html을 추가한다(해당 페이지로 이동하기 위함)
      strcat(filename,"home.html");
    // 정적 컨텐츠를 요청하는 경우라는 것을 나타내기 위해 1을 반환
    return 1;
  // 문자열이 포함되어 있는 경우
  }else{
    // 포인터에 uri에서 ?문자를 찾아 저장한다.
    // ?뒤에 오는 문자열은 CGI인자이기 때문
    ptr = index(uri, '?');

    // 만약 ?문자가 있는 경우
    if(ptr){
      // ?뒤에 오는 CGI 인자를 cgiargs에 복사한다.
      strcpy(cgiargs,ptr+1);
      // 포인터에 저장된 ?문자를 null('\0') 문자로 바꿔줌으로써 ?전까지의 문자열로 잘리게 된다.
      *ptr = '\0';
    // ?문자가 없는 경우
    }else{
      // cgiargs를 빈 문자열로 초기화한다.
      strcpy(cgiargs,"");
    }
    // 정적 컨텐츠를 요청하는 경우와 동일하게 파일이름을 설정해준다.
    strcpy(filename,".");
    strcat(filename,uri);
    // 동적 컨텐츠를 요청하는 경우라는 것을 나타내기 위해 0을 반환
    return 0;
  }
}

// 파일타입을 확인해 해당 파일의 MIME 타입을 설정하는 함수
// 파일 이름과 파일 타입을 인자로 받는다.
void get_filetype(char *filename,char *filetype){
  // 파일이름이 .html 확장자가 있으면
  if(strstr(filename,".html")){
    // 해당 확장자를 text/html로 설정한다.
    strcpy(filetype,"text/html");

  // 파일이름이 .gif 확장자가 있으면
  }else if(strstr(filename,".gif")){
    // 해당 확장자를 image/gif로 설정한다.
    strcpy(filetype,"image/gif");

  // 파일이름이 .png 확장자가 있으면
  }else if(strstr(filename,".png")){
    // 해당 확장자를 image/png로 설정한다.
    strcpy(filetype,"image/png");

  // 파일이름이 .jpg 확장자가 있으면
  }else if(strstr(filename, ".jpg")){
    // 해당 확장자를 image/jpeg로 설정한다.
    strcpy(filetype,"image/jpeg");

  }else{
    // 없을 때의 기본 확장자를 text/plain로 설정한다.
    strcpy(filetype,"text/plain");
  }
}

// 정적 컨텐츠를 제공하는 static 함수
// fd:연결관리파일디스크립터, filename:클라이언트가 요청한 리소스 파일이름, filesize:리소스파일의크기
// method:클라이언트 HTTP요청메소드 - 11.11문제를 위해 추가
void serve_static(int fd, char *filename, int filesize, char *method){
  // 파일 디스크립터
  int srcfd;
  // 파일의 내용을 가리키는 포인터, 파일 타입 배열, 버퍼를 선언
  // char *srcp, filetype[MAXLINE], buf[MAXLINE];
  // csapp 11.11문제 HEAD 메소드
  // buf의 [MAXLINE]은 한 줄의 텍스트를 저장하는데의 충반한 크기이고 [MAXBUF]는 더 큰 크기의 데이터 저장에 사용
  char *srcp, filetype[MAXLINE], buf[MAXBUF];

  // 파일 이름과 파일 타입을 확인한다.
  get_filetype(filename,filetype);
  // HTTP 응답의 첫 줄, 작성 응답 상태 코드
  sprintf(buf,"HTTP/1.1 200 OK\r\n");
  // 작성한 HTTP 응답의 첫 줄을 클라이언트에 전송
  Rio_writen(fd,buf,strlen(buf));
  // tiny server임을 나타내는 코드
  sprintf(buf,"Server: Tiny Web Server\r\n");
  // 작성한 HTTP 헤더를 클라이언트에 전송
  Rio_writen(fd,buf,strlen(buf));
  // 본문의 길이를 나타내는 filesize 출력
  sprintf(buf,"Content-length: %d\r\n", filesize);
  // 작성한 Content-length 헤더를 클라이언트에 전송
  Rio_writen(fd,buf,strlen(buf));
  // 본문의 미디어 타입을 나타내는 filetype 출력
  sprintf(buf,"Content-type: %s\r\n\r\n",filetype);
  // 작성한 Content-type 헤더를 클라이언트에 전송
  Rio_writen(fd,buf,strlen(buf));

  // csapp 11.11문제 HEAD 메소드
  // buf에 저장된 HTTP 응답 헤더 문자열을 출력
  printf("Response headers:\n");
  printf("%s", buf);

  // strcasecmp(두 문자열이 같은지 비교하는 함수)로 요청 메소드가 HEAD인지 확인해 맞으면 종료
  if(strcasecmp(method, "HEAD") == 0)
    return; // code end
  
  // 요청받은 파일을 읽기전용으로 연다.
  srcfd = Open(filename,O_RDONLY,0);
  // 파일의 내용들을 메모리에 매핑함으로써 메모리 주소를 통해 파일의 내용에 직접 접근 가능하다.
  srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
  // 파일 닫기
  Close(srcfd);
  // 매핑된 메모리 내용을 클라이언트에 전송
  Rio_writen(fd,srcp,filesize);
  // 사용이 끝나면 메모리 사용 효율을 우해 메모리 매핑을 해제
  Munmap(srcp,filesize);
  // csapp 11.9문제 malloc 구현 
  // srcfd = Open(filename, O_RDONLY, 0);
  // 메모리 매핑 대신 malloc을 사용해 메모리를 할당시킨다.
  // srcp = (char *)malloc(sizeof(filesize));
  // 파일의 내용을 읽어와 할당한 메모리에 저장한다.
  // Rio_readn(srcfd, srcp, filesize);
  // Close(srcfd);
  // Rio_writen(fd, srcp, filesize);
  // free(srcp);
}

// 동적 컨텐츠를 제공하는 dynamic 함수
// fd:연결관리파일디스크립터, filename:클라이언트가 요청한 리소스 파일이름, cgiargs:cgi프로그램에 전달될 인자
// method:클라이언트 HTTP요청메소드 - 11.11문제를 위해 추가
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method){
  // 버퍼와 빈 문자열 리스트 선언
  char buf[MAXLINE], *emptylist[] = {NULL};
  // HTTP 응답 코드 첫 줄을 출력
  sprintf(buf,"HTTP/1.1 200 OK\r\n");
  // 작성한 HTTP 응답 코드 첫 줄을 클라이언트에 전송
  Rio_writen(fd,buf,strlen(buf));
  // 이 웹서버가 tiny server임을 알려주는 코드를 출력
  sprintf(buf,"Server: Tiny Web Server\r\n");
  // 작성한 HTTP 헤더를 클라이언트에 전송
  Rio_writen(fd,buf,strlen(buf));
  // 자식 프로세스를 생성하여 현재 프로세스가 자식 프로세스라면
  // 자식을 생성하면 fork()는 부모에게 자식의 PID를 자식에게 0을 반환한다.
  if(Fork() == 0){
    // QUERY_STRING이라는 환경변수를 설정하고 method의 변수값으로 지정
    setenv("QUERY_STRING", cgiargs, 1);
    // csapp 11.11문제 HEAD 메소드
    // 클라이언트의 HTTP 요청 메소드를 전달하는데 사용되는 REQUEST_METHOD를 설정
    setenv("REQUEST_METHOD", method, 1);
    // 클라이언트와 연결된 소켓의 파일 디스크립터를 복제하고, 그 값을 표준 출력으로 설정
    // 이 함수 호출 후에는 프로세스가 표준 출력으로 보내는 모든 데이터가 fd를 통해 전송
    Dup2(fd,STDOUT_FILENO);
    // filename에 지정된 파일을 실행한다.
    // emptylist는 프로그램에 전달되는 파라미터를 지정하고, environ은 프로그램의 환경 변수를 지정한다.
    Execve(filename,emptylist,environ);
  }
  Wait(NULL);
}
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg){
  char buf[MAXLINE];
  sprintf(buf, "HTTP/1.1 %s %s\r\n", errnum, shortmsg);
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "content-type: text/html\r\n\r\n");
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "<html><title>Tiny Error</title>");
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "<body bgcolor=""ffffff"">\r\n");
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "%s: %s\r\n",errnum,shortmsg);
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf,"<p>%s: %s\r\n",longmsg,cause);
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf,"<hr><em>The Tiny Web server</em>\r\n");
  Rio_writen(fd, buf, strlen(buf));
}

 

adder.c

더보기
/*
 * adder.c - a minimal CGI program that adds two numbers together
 */
/* $begin adder */
#include "csapp.h"
int main(void) {
    char *buf, *p, *method;
    char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
    int n1=0, n2=0;
    /* Extract the two arguments */
    if ((buf = getenv("QUERY_STRING")) != NULL) {
        p = strchr(buf, '&');
        *p = '\0';
        sscanf(buf, "n1=%d", &n1 );
        sscanf(p+1, "n2=%d", &n2 );
        // strcpy(arg1, buf);
        // strcpy(arg2, p+1);
        // A = strchr(buf, 'A');
        // B = strchr(buf, 'B');
        // *A = '\0';
        // *B = '\0';
        // strcpy(arg1, A+2);
        // strcpy(arg2, B+2);
        // n1 = atoi(arg1);
        // n2 = atoi(arg2);
    }

    // 11.11 add code
    method = getenv("REQUEST_METHOD");

    /* Make the response body */
    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);
  
    /* Generate the HTTP response */
    printf("Connection: close\r\n");
    printf("Content-length: %d\r\n", (int)strlen(content));
    printf("Content-type: text/html\r\n\r\n");

    // 11.11 add code if
    if(strcasecmp(method, "HEAD") != 0)
        printf("%s", content);
        
    fflush(stdout);
    
    exit(0);
}
/* $end adder */

 

proxy.c(Sequential 만 작성)

더보기
#include <stdio.h>
#include "csapp.h"
/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400

// 웹 브라우저의 신원정보를 나타내는 문자열
// Mozilla Firefox 버전 10.0.3을 사용하는 Linux 시스템
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";
// HTTP의 Connection 헤더를 설정하며, 이 연결은 요청 후 종료된다.
static const char *conn_hdr = "Connection: close\r\n";
// Proxy-Connection 헤더를 설정하며, 이 연결은 요청 후 종료
static const char *prox_hdr = "Proxy-Connection: close\r\n";
// Host 헤더를 설정하며, %s는 호스트 이름으로 치환될 위치를 나타낸다.
static const char *host_hdr_format = "Host: %s\r\n";
// HTTP GET 요청을 설정하는데 사용하며, %s는 호스트 이름으로 치환될 위치를 나타낸다.
static const char *requestlint_hdr_format = "GET %s HTTP/1.0\r\n";
// HTTP 헤더의 끝을 나타낸다.
static const char *endof_hdr = "\r\n";

// Connection, User-Agent, Proxy-Connection, Host 헤더의 키를 나타낸다.
// 이 키를 사용해 HTTP 헤더의 값을 설정하거나 읽어올 수 있다.
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);

// 프록시 서버 메인 함수
int main(int argc,char **argv)
{
    int listenfd,connfd;
    socklen_t  clientlen;
    char hostname[MAXLINE],port[MAXLINE];

    struct sockaddr_storage clientaddr;/*generic sockaddr struct which is 28 Bytes.The same use as sockaddr*/

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

        /*print accepted message*/
        Getnameinfo((SA*)&clientaddr,clientlen,hostname,MAXLINE,port,MAXLINE,0);
        printf("Accepted connection from (%s %s).\n",hostname,port);

        /*sequential handle the client transaction*/
        doit(connfd);

        Close(connfd);
    }
    return 0;
}

// 클라이언트의 HTTP 함수를 처리하는 함수, 연결 파일 디스크립터를 인자로 받는다.
void doit(int connfd)
{
    // 끝 서버의 파일 디스크립터를 저장할 변수 선언
    int end_serverfd;

    // MAXLINE의 크기를 가진 버퍼, 메소드, URI, 버전을 저장할 변수를 선언
    char buf[MAXLINE],method[MAXLINE],uri[MAXLINE],version[MAXLINE];
    // 끝 서버에 보낼 HTTP 헤더를 저장할 문자열 배열을 선언
    char endserver_http_header [MAXLINE];
    /*store the request line arguments*/
    // 호스트이름과 경로를 저장할 변수를 선언, 포트번호 변수 선언
    char hostname[MAXLINE],path[MAXLINE];
    int port;

    // 클라이언트와 끝 서버의 rio구조체 선언
    rio_t rio,server_rio;

    // 클라이언트의 연결 디스크립터에 대한 rio를 초기화
    Rio_readinitb(&rio,connfd);
    // 클라이언트로부터 한 줄을 읽어 buf에 저장
    Rio_readlineb(&rio,buf,MAXLINE);
    // 읽은 줄에서 메소드, uri, 버전을 추출하여 저장한다.
    sscanf(buf,"%s %s %s",method,uri,version);

    // strcasecmp함수는 두 문자열을 비교하는 함수, 0을 반환하면 같다는 의미
    // 만약 메소드가 GET이 아니라면
    if(strcasecmp(method,"GET")){
        // 프록시는 메소드를 구현하지 않는다는 메세지를 출력
        printf("Proxy does not implement the method");
        return;
    }

    // uri를 파싱해서 호스트 이름, 경로 포트번호를 추출한다.
    parse_uri(uri,hostname,path,&port);

    // HTTP 헤더를 작성한다.
    build_http_header(endserver_http_header,hostname,path,port,&rio);

    // 끝 서버에 연결하고, 이 연결의 파일 디스크립터를 end_serverfd에 저장한다.
    end_serverfd = connect_endServer(hostname,port,endserver_http_header);
    // 만약 연결이 실패했으면
    if(end_serverfd<0){
        // 연결 실패 메세지를 출력
        printf("connection failed\n");
        return;
    }

    // 끝 서버의 파일 디스크립터 end_serverfd에 대한 server_rio를 초기화
    Rio_readinitb(&server_rio,end_serverfd);
    // 작성한 HTTP 헤더를 끝 서버에 전송한다.
    Rio_writen(end_serverfd,endserver_http_header,strlen(endserver_http_header));

    // 읽은 바이트 수를 저장할 변수 선언
    size_t n;
    // 끝 서버로부터 한 줄씩 읽어서 buf에 저장하고 읽을 줄이 없을 때 까지 반복
    while((n=Rio_readlineb(&server_rio,buf,MAXLINE))!=0)
    {
        // 프록시가 몇 바이트를 받았는지 출력
        printf("proxy received %d bytes,then send\n",n);
        // 읽은 내용을 클라이언트에 전송한다.
        Rio_writen(connfd,buf,n);
    }
    Close(end_serverfd);
}

// HTTP 헤더를 구성하는 함수
// HTTP헤더, 호스트이름, 경로, 포트, 클라이언트의 rio를 인자로 받는다.
void build_http_header(char *http_header,char *hostname,char *path,int port,rio_t *client_rio)
{
    // 크기가 MAXLINE인 버퍼, 요청 헤더, 다른 헤더, 호스트 헤더를 선언
    char buf[MAXLINE],request_hdr[MAXLINE],other_hdr[MAXLINE],host_hdr[MAXLINE];

    // 요청 라인
    // sprintf 함수는 문자열을 형식에 맞게 생성하고, 이를 변수에 저장하는 역할
    // requestlint_hdr_format 문자열 내의 %s를 path로 치환한 후 결과 문자열을 요청헤더에 저장
    sprintf(request_hdr,requestlint_hdr_format,path);
    
    // 클라이언트의 rio로부터 한 줄씩 buf에 넣으며 읽을 라인이 없을 때까지 반복
    while(Rio_readlineb(client_rio,buf,MAXLINE)>0)
    {
        // 만약 읽은 라인이 헤더의 끝을 나타내는 문자열이면 while문 탈출
        // EOF = End Of File이며, 파일의 끝을 표현하기 위해 정의한 -1값을 가지고 있는 상수
        if(strcmp(buf,endof_hdr)==0) break;/*EOF*/

        // 만약 읽은 라인이 Host:로 시작한다면
        if(!strncasecmp(buf,host_key,strlen(host_key)))/*Host:*/
        {
            // buf의 내용을 호스트 헤더에 저장
            strcpy(host_hdr,buf);
            continue;
        }

        // 만약 읽은 라인이 Connection:, Proxy-Connection:, User-Agent:로 시작하지 않으면
        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)))
        {
            // buf의 내용을 다른 헤더에 추가한다.
            strcat(other_hdr,buf);
        }
    }

    // 만약 호스트헤더가 비어있다면
    if(strlen(host_hdr)==0)
    {
        //host_hdr_format 문자열 내의 %s를 hostname으로 치환한 후 결과 문자열을 호스트헤더에 저장
        sprintf(host_hdr,host_hdr_format,hostname);
    }

    // 모든 헤더를 합쳐서 http_header를 작성한다.
    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 ;
}

// inline 함수는 컴파일러가 함수를 호출하는 대신 코드에 직접 삽입하도록 지시하는 키워드
// 호스트이름과 포트를 사용해 끝 서버에 연결하고, 연결의 파일 디스크립터를 반환하는 함수
inline int connect_endServer(char *hostname,int port,char *http_header){
    // 포트번호를 문자열로 변환해 저장하는 크기가 100인 포트배열 선언
    char portStr[100];
    // port의 정수값을 문자열로 변환하고 포트번호배열에 저장
    sprintf(portStr,"%d",port);
    // 호스트이름과 포트번호배열을 인자로 받아 서버에 연결해서 , 파일 디스크립터를 반환하는 함수
    return Open_clientfd(hostname,portStr);
}

// URI를 분석해 호스트이름, 파일경로, 포트를 추출하는 함수
void parse_uri(char *uri,char *hostname,char *path,int *port)
{
  // HTTP의 기본 포트인 80으로 설정
  *port = 80;
  // URI에서 '//' 문자열을 찾아 시작위치를 pos에 저장
  char* pos = strstr(uri,"//");
  // 만약 '//'이 있다면 pos를 다음 문자로 이동, 없으면 uri을 가리킨다.
  pos = pos!=NULL? pos+2:uri;

  // pos에서 ':'문자열을 찾아 pos2에 저장, 이는 포트 번호를 지정하는데 사용
  char*pos2 = strstr(pos,":");

  // 만약 ':'가 있으면
  if(pos2!=NULL)
  {
    // ':'문자를 null로 바꾼다. 이를 통해 호스트이름과 포트번호를 분리
    *pos2 = '\0';
    // pos에 있는 ':'에서 시작해 문자열 끝까지 호스트이름으로 복사한다.
    sscanf(pos,"%s",hostname);
    // pos2에 있는 ':'다음에서 시작해 숫자를 port로 나머지 문자열을 path로 복사
    sscanf(pos2+1,"%d%s",port,path);
  }
  // ':'문자를 찾기 못했다면
  else
  {
    // pos에서 '/'문자열을 찾아 pos2에 넣는다
    pos2 = strstr(pos,"/");

    // pos2 즉, '/'가 존재한다면
    if(pos2!=NULL)
    {
      // pos2의 '/'를 null로 바꾼다.
      *pos2 = '\0';
      // pos에서 시작해 문자열 끝까지 호스트이름으로 복사한다.
      sscanf(pos,"%s",hostname);
      // pos2에 '/'를 넣어서
      *pos2 = '/';
      // '/'문자부터 시작해 나머지 문자열을 path로 복사한다.
      sscanf(pos2,"%s",path);
    }
    // '/'가 존재하지 않으면
    else
    {
      // pos에서 시작해 나머지 문자열을 호스트이름으로 복사한다.
      sscanf(pos,"%s",hostname);
    }
  }
  return;
}

 


오늘의 잔디심기

백준
4101
자바스크립트
브론즈5
크냐?
const fs = require("fs");
const filePath = process.platform === "linux" ? "/dev/stdin" : "./input.txt";
let input = fs.readFileSync(filePath).toString().split("\n");

const bigger = () => {
    i = 0;
    while(input[i]){
        const [num1, num2] = input[i].split(" ").map(Number);
        
        if(num1 == 0 && num2 == 0) {
            break;
        }

        if(num1 > num2) console.log("Yes");
        else console.log("No");

        i++;
    }
}

bigger();
728x90