(종료)
✅☑️❌
최대한 간결하게 가보자.
캐시 서버까지 한 사람들 2명이나 있다고 하길래 5층에 어떻게 공부했는지 물어보러 감.
딱히 다른 건 없었음. 책 보고 이해하는 평범한 절차.
이미 하고 와서 알았으니 잘했던거라고 함.
다들 비슷한듯.
/* 요청 라인 전송 */
void request_line_to_server(int fd)
{
int srcfd; // 파일 식별자 저장
char *srcp, filetype[MAXLINE], buf[MAXBUF];
/* 응답 라인 클라에게 보낸다 */
sprintf(buf, "HTTP/1.0 200 OK\r\n");
rio_writen(fd, buf, strlen(buf)); // 클라로 응답 라인 전송
printf("Response headers:\n"); // 디버깅을 위해 헤더 출력
printf("%s", buf);
}
기존 tiny 서버에서 serve_header만 남겨서
클라한테 요청을 받아서 응답을 건네주는 기본적인 뼈대만 작동시키는데 성공.
클라의 요청을 건드리기 전에,
우선 클라에게 받은 요청을 바로 서버에 전달하고
서버에게 받은 응답을 바로 클라에 전달하는 식으로만 구현해보자.
int serverfd = open_clientfd("localhost", "8080");
open_clientfd()가 정상적으로 작동하는 것은 확인했다.
open_clientfd("localhost", "8081");
을 하면 연결이 성립 안되는 것까지 확인.
이제 클라 -> 프록시 -> 서버 까지는 연결시킬 수 있음.
/* 요청 헤더를 읽는 함수 */
void read_requesthdrs(rio_t *rp, int serverfd)
{
// tiny 서버는 요청 헤더를 실제로 사용하진 않고, 그냥 읽기만 함.
// rp는 클라한테 받은 데이터들 들어있는 버퍼임.
char buf[MAXLINE];
rio_t rio; // 구조체. 버퍼링된 입출력 처리.
rio_readinitb(&rio, serverfd); // 소켓 식별자를 이용한 읽기 작업 준비
rio_readlineb(rp, serverfd, MAXLINE); // rp에 있는걸 한 줄 읽어서 buf에 넣음
while(strcmp(serverfd, "\r\n")) // 버퍼에 빈줄이 나올 때까지 반복
{/home/hutosuto/Jungle/C_Implement/webproxy-lab
rio_readlineb(rp, serverfd, MAXLINE); // rp를 한 줄 읽어서 buf에 넣음
}
return;
}
read_requesthdrs 함수를 수정해서
request headers: 까지는 데이터가 전송되게 함.
다음 단계로 나아가기 전에, 디버깅을 하나 해봄.
char port[4] = "8080";
int serverfd = open_clientfd("localhost", port);
아까 오류를 일으키던 방식대로 바꿔봄.
8080으로 초기화된 char 배열을 인자로 넣어줬음.
다행히도? 다시 오류를 일으킴.
초기화 방식이 문제인가 싶어서 gpt에게 물어봤더니,
char port[5] = "8080";
널 문자도 포함시켜줘야 하니까 공간을 한 개 더 잡으라고 함.
그랬더니 결과가 "8080"을 넣은 것과 똑같이 나와줌. 이건 해결한듯.
rio 패키지가 뭔지도 모르고 마구잡이로 쓰고 있는 것 같아서
일단 사용법이라도 배워보자는 마음에 gpt한테 물어봄.
readinitb의 두 번째 인자에 serverfd를 넣는 건 맞는데,
readlinb의 두 번째 인자에는 buf를 넣는 거였음.
이제까지 rio 패키지에 대해 제대로 알아보지도 않고
대충대충 감으로 해오던 시간들은 전부 낭비한 시간이었던 거임.
아니면 자존심 버리고 gpt한테 진작에 물어봤어야 하는데, 그러지도 않음.
아무튼 이제 segmentation 오류 안 나고 정상적으로 뭔가가 가긴 함.
하지만 문제가 있음.
요청이 2개임.
나는 분명 request_line_to_server
를 1번만 호출했는데,
request headers: 에는 2개가 떠 있음.
처음엔 (클라에서 보낸 요청 라인과 헤더 + 프록시에서 보낸 요청 라인과 헤더) 이렇게 묶인 줄 알았는데,
그렇다고 하더라도 "서버에게 보낸 Response headers:"는 유일해야 함.
request_line_to_server
는 1번만 호출했으니까!
첫 번째 요청: GET / HTTP/1.1 (루트 페이지 요청)
두 번째 요청: GET /favicon.ico HTTP/1.1 (파비콘 요청)
gpt한테 물었더니 원래 그런거라고 함.
구글링 해봐도 맞는 이야기인듯?
안 물어봤으면 또 시간 허투루 쓸 뻔 했다.
버퍼에는 정상적으로 들어온다.
이제 read_requesthdrs
의 버퍼에 들어있는 요청 라인과 요청 헤더들을 서버로 곧장 보내보자.
rio_readinitb
는 버퍼와 파일 디스크립터를 연결한다.
그 후rio_readlineb
를 사용하면 파일 디스크립터에 들어있는 데이터들을 한 줄씩 읽는다.
이 때 파일 디스크립터에 있는 파일 포인터는 앞으로 이동한다. 즉, 버퍼가 소모된다.
lseek(fd, 0, SEEK_SET);
를 하면 파일 포인터를 되돌려서
파일 디스크립터에 들어있던 내용을 처음부터 다시 읽을 수 있다.
/* 요청 헤더를 읽는 함수 */
void read_requesthdrs(rio_t *rp, int serverfd)
{
// 요청 헤더들을 읽어서 바로 서버 소켓으로 보내보자.
// rp는 클라한테 받은 데이터들 들어있는 버퍼임.
char buf[MAXLINE];
rio_t rio; // 구조체. 버퍼링된 입출력 처리.
rio_readinitb(&rio, serverfd); // 소켓 식별자를 이용한 읽기 작업 준비
rio_readlineb(rp, buf, MAXLINE); // 요청 라인을 읽어서 buf에 넣음
while(strcmp(buf, "\r\n")) // 버퍼에 빈줄이 나올 때까지 반복
{
rio_readlineb(rp, buf, MAXLINE); // 요청 헤더들을 읽어서 buf에 넣음
rio_writen(serverfd, buf, strlen(buf)); // buf에 넣은 걸 바로 서버에 냅다 던져
printf("test_line: %s", buf);
}
return;
}
read_requesthdrs
를 수정하여 클라에게서 받은 요청 라인 및 헤더를
서버로 바로 보내도록 하고 싶었다.
그런데 요청 라인이 가는게 아니라 요청 헤더의 첫 줄이 가버리는 현상이 발생함.
/* 클라에게서 받은 요청 라인과 헤더 읽기 */
rio_readinitb(&rio, fd); // 파일 식별자에 있는 데이터를 rio 구조체와 연결
// rio_readlineb(&rio, buf, MAXLINE); // 한 줄 읽어와 버퍼에 저장
// printf("클라에게서 받은 요청 라인:\n");
// printf("%s\n", buf); // 요청 라인 출력
// sscanf(buf, "%s %s %s", method, uri, version); // 버퍼에서 데이터 읽고 method, uri, version에 저장
기존에 있던 코드들이 (정확하게 짚자면 rio_readlineb가) 사악하게
한 줄을 미리 읽어버려서 요청 라인이 빼돌려졌었다.
주석처리를 하니
처음으로 브라우저에 뭔가가 떴다.
뽀록이 터진 줄 알고 좋아하면서 넘어가려고 했는데,
찬찬히 살펴보니
proxy에 남아있던 tiny의 흔적 기관이 나를 속였던 거였다.
사실 필요한 if 문이긴 한데, 이것 때문에 연결된 줄 알고 속았음.
위가 proxy와 연결했었던 기록이고,
아래가 브라우저로 바로 접근해본 기록이다.
Host: localhost:5555
가 어디에서 왔는지 찾아야 한다.
이것도 흔적 기관이 나를 엿먹임.
또 rio_readlineb
한 줄이 요청 라인을 쏙 빼먹어버렸다.
주석처리 하고 돌려봤더니
드디어 정상적으로 요청이 갔다.
이제 서버에서 받은 응답을 클라에 보내기만 하면 된다.
코드로 불꽃놀이 좀 하다가 결국 기계의 노예가 되는 길을 선택함.
/* 서버의 응답을 클라이언트로 전송 */
void response_to_client(int clientfd, int serverfd)
{
char buf[MAXBUF];
rio_t rio;
rio_readinitb(&rio, serverfd); // 서버 소켓에서 들어온 응답을 읽을 준비를 한다
// 서버로부터 데이터를 줄 단위로 계속 읽어옴
ssize_t n; // 읽은 바이트 수를 저장할 변수
while ((n = rio_readlineb(&rio, buf, MAXLINE)) > 0) {
rio_writen(clientfd, buf, n); // 클라이언트로 데이터 전송
printf("%s", buf); // 디버깅을 위해 출력
// 헤더 끝을 인식하고, 이후 본문도 처리하기 위해 루프를 계속 유지
}
printf("클라에게 전송 완료\n");
}
클라 → 프록시 → 서버
클라 ← 프록시 ← 서버
이 2가지의 흐름이 성립했다.
원래라면 감동의 물결이 몰려와야 할텐데
gpt한테 너무 도움 많이 받은 것 같아서 김이 새버렸다.
동적 컨텐츠도 잘 전달해준다.
#include "csapp.h"
void doit(int fd);
// void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *filename, char *cgiargs);
void get_filetype(char *filename, char *filetype);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
/* 소켓 정보 가져오게 하고 할 일 하게하는 main 함수 */
int main(int argc, char **argv)
{
int listenfd, connfd; // 듣기 식별자, 연결 식별자 (서버 소켓과 클라이언트 소켓)
char hostname[MAXLINE], port[MAXLINE]; // 클라 호스트이름, 포트 (혹은 아이피 주소, 서비스 이름)
socklen_t clientlen; // 클라 주소 크기 (sockaddr 구조체 크기)
struct sockaddr_storage clientaddr; // 클라 주소 정보 구조체 (IPv4/IPv6 호환)
/* 인자 맞게 썼는지 확인 */
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);
doit(connfd); // line:netp:tiny:doit // 연결 수립 후 서버에서 할 일
Close(connfd); // line:netp:tiny:close // 할 일 다 하면 연결 끝
}
}
/* 오류 있는지, 컨텐츠가 정적인지 동적인지 확인하고 읽기/실행 */
void doit(int clientfd)
{
int is_static;
struct stat sbuf;
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
rio_t rio;
char port[5] = "8080"; // 포트는 8080으로 고정하라고 적혀있었음
/* 클라에게서 받은 요청 라인과 헤더 읽기 */
rio_readinitb(&rio, clientfd); // 파일 식별자에 있는 데이터를 rio 구조체와 연결
// rio_readlineb(&rio, buf, MAXLINE); // 한 줄 읽어와 버퍼에 저장
// printf("클라에게서 받은 요청 라인:\n");
// printf("%s\n", buf); // 요청 라인 출력
// sscanf(buf, "%s %s %s", method, uri, version); // 버퍼에서 데이터 읽고 method, uri, version에 저장
// if (strcasecmp(method, "GET")) // GET 요청인지 확인
// {
// clienterror(clientfd, method, "501", "Not Implemented", "Tiny does not implement this method"); // 아니면 꺼지라고 함
// return;
// }
int serverfd = open_clientfd("localhost", port); // 서버와 소켓 연결
request_to_server(&rio, serverfd); // 클라에게서 받은 요청을 서버로 넘긴다
// parse_uri(uri, filename, cgiargs); // URI에서 데이터 추출
response_to_client(clientfd, serverfd); // 서버에게서 받은 응답을 클라로 넘긴다
}
/* 클라의 요청을 서버로 전송 */
void request_to_server(rio_t *rp, int serverfd)
{
// 요청 헤더들을 읽어서 바로 서버 소켓으로 보내보자.
// rp는 클라한테 받은 데이터들 들어있는 버퍼임.
char buf[MAXLINE];
rio_t rio; // 구조체. 버퍼링된 입출력 처리.
rio_readinitb(&rio, serverfd); // 소켓 식별자를 이용한 읽기 작업 준비
// rio_readlineb(rp, buf, MAXLINE); // 요청 라인을 읽어서 buf에 넣음
while(strcmp(buf, "\r\n")) // 버퍼에 빈줄이 나올 때까지 반복
{
rio_readlineb(rp, buf, MAXLINE); // 요청 헤더들을 읽어서 buf에 넣음
rio_writen(serverfd, buf, strlen(buf)); // buf에 넣은 걸 바로 서버에 냅다 던져
printf("test_line: %s", buf);
}
return;
}
/* 서버의 응답을 클라이언트로 전송 */
void response_to_client(int clientfd, int serverfd)
{
char buf[MAXBUF];
rio_t rio;
rio_readinitb(&rio, serverfd); // 서버 소켓에서 들어온 응답을 읽을 준비를 한다
// 서버로부터 데이터를 줄 단위로 계속 읽어옴
ssize_t n; // 읽은 바이트 수를 저장할 변수
while ((n = rio_readlineb(&rio, buf, MAXLINE)) > 0) {
rio_writen(clientfd, buf, n); // 클라이언트로 데이터 전송
printf("%s", buf); // 디버깅을 위해 출력
// 헤더 끝을 인식하고, 이후 본문도 처리하기 위해 루프를 계속 유지
}
printf("클라에게 전송 완료\n");
}
/* 에러 메시지 띄우는 함수 */
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
char buf[MAXLINE], body[MAXBUF];
/* HTTP 응답 중 body 만들기 */
// body를 다시 추가해서 안 넣어주면 마지막 빼고 다 사라져버림.
// sprintf는 기존에 있던 데이터에 추가되는 방식이 아니라 덮어씌워버리기 때문.
// 응답을 문자열 하나로 만들어서 크기를 쉽게 알 수 있게 함.
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);
/* HTTP 응답 출력 */
// 응답 시작 라인, 응답 헤더 뿌려준 후에 응답 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));
}
아직 uri를 뜯어서 필요한 걸로만 바꾸는 것도 못 했고,
정상적인 요청인지 아닌지 필터링하는 것도 못했다.
대놓고 요구 사항이 명시되어 있는데
연결 하나만 구현하는 것도 힘들어서 시간 너무 오래 잡아먹음.
애초에 백지에 가까운 상태에서 구현했으면 시간 더 적게 걸렸을수도.