지난 논함을 계속 하다보니 우리는 Echo 서버, Tiny 서버를 모두 만들 수 있었다. 이제 우리는 진짜 유의미한 무언가를 만들어보기 위해 CS:APP에 있는 Proxy Lab에 대해 논할 것이다. 그리 어렵진 않으면서도 뭔가 막히는 경험을 한다는건 흔하지 않은 것 같다.
2025.05.05 - [구현하기] - Tiny Web Server 개발기록 #3
Tiny Web Server 개발기록 #3
CS:APP 책 프로젝트 중 일부로, 11단원에서 웹 서버를 만드는 내용인 Tiny Web Server를 계속 진행하고있다. 최종 목표는 Proxy Lab을 완료하는 것인데, 이 부분에 있어 천천히 그리고 빠르게 접근해야 할
hyeonistic.tistory.com
나는 다음과 같은 자료들을 참고했고, 약간의 파일럿 Co도 동원했다.
참고자료
https://csapp.cs.cmu.edu/3e/proxylab.pdf
- 요구 조건이 기재되어있는 내용이다.
https://velog.io/@jing93/Week07-proxy-lab
- 난 나의 영어 실력을 못미더워했기 때문에 자연스러운 번역문을 확인했다.
간단히만 이라도 읽고 와주길 바랍니다.
프록시는 서버와 클라이언트 사이에 존재한다. 즉, 클라이언트에게서는 서버의 역할을 해주어야하고, 서버에게서는 클라이언트의 역할을 해주어야한다. 대체 뭘 원하는건가 생각했는데, 그럼 우선 이런 생각을 했다 :
- 프록시의 테스트 환경은 기존에 만들었던 tiny 기반이니 프록시의 서버 연결 대상은 오직 tiny만 생각하면 된다.
- 반면에 클라이언트는 몇 개가 들어올지 모르기도 하고, 또 멀티쓰레딩에 대한 코드 확장 여지도 고려해야한다.
그래서 처음에 이 코드의 main에 서버에 대한 디스크립터와 클라이언트에 대한 디스크립터를 생성하고 무한루프를 돌며 클라이언트를 받자 라고 생각했었다.
어쨌든 서버의 역할을 하기 위해 무한루프를 도는건 맞지만, 서버에 대한 디스크립터는 클라이언트에 요청에 대응하는 식으로 만들고 폐쇄하는게 더 좋다는 결론에 도달했다. 그래서 메인 함수에서 그 내용을 좀 미뤄서 진행하게끔 수정하면서 진행했다.
기존의 Echo와 Tiny를 두 눈 또박 뜨고 코드친게 다행일 정도로 닮은 부분이 많았다. 또 Client 부분은 Echo에서 진짜 별로 안쓰여 있어서, 여러번 로그아닌 로그 printf를 찍어나가며 확인해야 했다.
내가 고난을 겪었던 건 두 개가 더 있었다 :
- 헤더의 요구조건을 뭐 어떻게 하라는거야? 순서도 지켜야 하는건가?
- 받은걸 또 Tiny 서버처럼 메모리를 배정해다가 배치하고 그걸 전달해줘야하나?
고난 #1 해결하기
- 헤더는 순서쌍을 맞춰 구성시키면 된다. A옵션값: 뭐시기
- 그리고, "\r\n" 단위로 끝나야한다는 점을 상기하고 있도록 한다.
고난 #2 해결하기
- 메모리를 배정하진 않고 그냥 받은거 그대로 클라이언트에 다시 갖다주면 된다.
- 그럼 어떻게하냐고요?? 그냥 되나?
- Rio_readnb로 받아와서, Rio_writen으로 쏜다. 끝!
#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";
// Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
void task(int fd);
void fowarding(int fd, rio_t *destServer);
void valueSelection(char *uri, char* link, char *cgiargs);
void read_requesthdrs(rio_t *rp);
void get_filetype(char *filename, char *filetype);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
int main(int argc, char **argv)
{
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE];
socklen_t clientlen;
struct sockaddr_storage clientaddr;
rio_t rio;
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);
task(connfd);
Close(connfd);
}
exit(0);
}
void task(int fd)
{
// fd를 위한 출력 값을 만들어야한다.
// 즉 여기서 tiny에 값을 보내서, 다시 받아오고, 그걸 다시 뿌려주게 된다.
struct stat sbuf;
char buf[MAXLINE], _buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char newHeader[MAXLINE], recieved[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
int destServer_fd;
rio_t fromClientrio, toServerrio;
destServer_fd = Open_clientfd("localhost", "12321");
Rio_readinitb(&toServerrio, destServer_fd);
Rio_readinitb(&fromClientrio, fd);
// 프록시랑 연결된 클라이언트로부터 요청 내용을 긁어온다.
Rio_readlineb(&fromClientrio, buf, MAXLINE);
printf("Request Headers?!\n");
printf("%s", buf);
sscanf(buf, "%s %s %s", method, uri, version);
sprintf(newHeader, "%s %s HTTP/1.0\r\n", method, uri);
sprintf(newHeader, "%sHost: localhost\r\n", newHeader);
sprintf(newHeader, "%sServer: Tiny Web Server\r\n", newHeader);
sprintf(newHeader, "%sUser-Agent: %s",newHeader, user_agent_hdr);
sprintf(newHeader, "%sConnection: close\r\n", newHeader);
sprintf(newHeader, "%sProxy-Connection: close\r\n\r\n", newHeader);
Rio_writen(destServer_fd, newHeader, strlen(newHeader));
// 여기서 클라이언트에 최종 전달한다.
fowarding(fd, &toServerrio);
Close(destServer_fd);
}
void fowarding(int fd, rio_t *destServer)
{
char buf[MAXLINE];
ssize_t n;
while ((n = Rio_readlineb(destServer, buf, MAXLINE)) > 0)
{
Rio_writen(fd, buf, n);
if(strcmp(buf, "\r\n") == 0)
break;
}
while ((n = Rio_readnb(destServer, buf, MAXLINE)) > 0)
{
Rio_writen(fd, buf, n);
}
}
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
char buf[MAXLINE], body[MAXBUF];
/* Build the HTTP response body */
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 the 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(fd, buf, strlen(buf));
Rio_writen(fd, body, strlen(body));
}
// 1차 구현 완료
다음에는 멀티쓰레딩 개념을 적용한 Proxy를 만나보시겠다.
사실 이건 Proxy Lab에서 점수가 안찍힌다. 우리는 2단계부터 점수를 찍기 위해 고민해볼것이다.
'구현하기' 카테고리의 다른 글
Web Proxy #6 : Caching Proxy (0) | 2025.05.08 |
---|---|
Web Proxy #5 : 멀티쓰레딩 Proxy (0) | 2025.05.07 |
Web Proxy #3 : 기본적인 Tiny 완성하기 (0) | 2025.05.05 |
Web Proxy #2 : 기본적인 Echo 서버 만들기 (1) | 2025.05.03 |
Web Proxy #1 : 이론 (0) | 2025.05.03 |