해당 내용은 정확하지 않아 개발 공부에 도움이 되지 않습니다.
귀한 시간 내주시어 지적해 주시면 감사 드리겠습니다.
1. Tiny서버
1) CGI
- 서버와 어플리케이션 간에 데이터를 주고 받는 방식, 또는 컨벤션
2) 소켓
- 데이터를 보내거나 받을때 창구 역할을 한다.
- 데이터 송신의 Abstraction
3) 라우팅과 라우트
- 라우트는 경로, 라우팅은 그 경로를 찾아가기 위한 과정
4) Tiny 서버에서 사용되는 함수 정리
- strlen() : 문자열 길이를 반환한다.
- open() : std input, std output, std err 파일을 기본적으로 열어 줄 수 있다.
- close() : file descriptor(fd)를 받아 닫는다.
- Read() : 읽은 바이트의 크기를 리턴
- write() : 쓴 바이트 수를 리턴
- mmap() : fd로 지정된 디바이스 파일에서 offset에 해당하는 물리주소에서 시작하여, length바이트 만큼을 start주소로 대응
- strcasecmp() : 대소문자를 구별하지 않고 스트링을 비교 해준다.
/* $begin tinymain */
/*
* tiny.c - A simple, iterative HTTP/1.0 Web server that uses the
* GET method to serve static and dynamic content.
*
* Updated 11/2019 droh
* - Fixed sprintf() aliasing issue in serve_static(), and clienterror().
*/
#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);
/*doit, 한 개의 HTTP 트랜잭션을 처리한다.*/
void doit(int fd)// fd = file descriptor = 파일 식별자
{
int is_static;
struct stat sbuf;
/*MAXLINE : 8192, 2^23, 8kbyte*/
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
rio_t rio;
/* Read request line and headers*/
Rio_readinitb(&rio, fd);
Rio_readlineb(&rio, buf, MAXLINE); // 요청라인을 읽고 분석
printf("Request headers: \n");
printf("%s", buf);
sscanf(buf, "%s %s %s", method, uri, version);
if (!(strcasecmp(method, "GET") == 0 || strcasecmp(method, "HEAD") == 0)) // POST만 받을꺼야
{
clienterror(fd, method, "501", "Not impemented",
"Tiny does not implement this method");
return;
}
read_requesthdrs(&rio); // POST면 무시
/*Parse URI from GET request*/
is_static = parse_uri(uri, filename, cgiargs); // 정적인지 동적인지(정적이면 참?)
if (stat(filename, &sbuf) < 0) // 디스크에 파일이 없다면 리턴
{
clienterror(fd, filename, "404", "Not found",
"Tiny dees not implement this method");
return;
}
if (is_static) //Serve static content
{
if(!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))//보통 파일이 아니면서 GET 권한을 가지고 있지 않는
{
clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size, method); // 다 참이면 정적 주셈
}
else // Serve dynamic content
{
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))//보통 파일이 아니면서 POST 권한을 가지고 있지 않는
{
clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs, method); // 다 참이면 동적 주셈
}
}
/*에러 처리 함수*/
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
//MAXBUF : 8192
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));
}
/*요청헤더 내 정보를 읽고 무시한다?*/
void read_requesthdrs(rio_t *rp)
{
char buf[MAXLINE];
Rio_readlineb(rp, buf, MAXLINE);
while(strcmp(buf, "\r\n"))
{
rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
}
return;
}
/*HTTP URI를 분석한다. */
int parse_uri(char *uri, char *filename, char *cgiargs)
{
char *ptr;
if (!strstr(uri, "cgi-bin")) //static content
{
strcpy(cgiargs, ""); // cgi인자 스트링을 지우고
strcpy(filename, "."); // URIfmf ./index.html 같은 상대 리눅스 경로이름으로 변환
strcat(filename, uri); // 까지
if (uri[strlen(uri)-1] == '/') //만약에 uri가 /로 끝난다면
strcat(filename, "home.html"); // 기본 파일 이름을 추가
return 1;
}
else //Dynamic content
{
ptr = index(uri, '?'); // 동적이라면
if (ptr)
{
strcpy(cgiargs, ptr+1); // CGI 모든 인자를 추출
*ptr = '\0';
}
else
strcpy(cgiargs, ""); // 까지
strcpy(filename, "."); // 나머지 URI 부분을 상대 리눅스 파일 이름으로 변환
strcat(filename, uri); // 까지
return 0;
}
}
/*정적 컨텐츠를 클라이언트에게 제공*/
void serve_static(int fd, char *filename, int filesize, char *method)
{
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)); //까지,
printf("Response headers: \n");
printf("%s", buf); //헤더 종료
/*
Send response body to client
요청한 파일의 내용을 연결 식별자 fd로 복사해서 응답 본체를 보낸다.
*/
if (strcasecmp(method, "GET") == 0)
{
srcfd = Open(filename, O_RDONLY, 0); //읽기위해서 filename을 오픈하고 식별자를 얻어옴
// srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); //리눅스mmap함수를 요청한 파일을 가상메모리 영역으로 매핑
// Close(srcfd); //파일을 메모리로 맵핑한 후 이 식별자는 필요 없어 닫는다.(메모리 누수 방지)
// Rio_writen(fd, srcp, filesize); //실제로 파일을 클라이언트에 전송(주소 srcp에서 시작하는 filesize바이트를 클라이언트의 연결식별자로 복사)
// Munmap(srcp, filesize); //맵핑된 가상 메모리 주소를 반환(메모리 누수 방지)
/*mmap대신 malloc으로 구현*/
srcp = (char*)Malloc(filesize);
Rio_readn(srcfd, srcp, filesize);
Close(srcfd);
Rio_writen(fd, srcp, filesize);
free(srcp);
}
}
/*get_filetype - Derive file type from filename*/
void get_filetype(char *filename, char *filetype)
{
if (strstr(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/jpg");
else if (strstr(filename, ".mp4"))
strcpy(filetype, "video/mp4");
else
strcpy(filetype, "text/plain");
}
/*
TINY 자식프로세스를 fork하고, 그 후에 CGI 프로그램을 자식의 컨텍스트에서 실행하며 모든 종류의 동적 컨텐츠를 제공한다.
클라이언트에 성공을 알려주는 응답 라인을 보내는 것으로 시작한다.
CGI프로그램은 응답의 나머지 부분을 보내야 한다.
*/
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method)
{
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) // 새로운 자식을 Fork(고른다?) 한다. 골랐을때 0!
{
/*Real server would set all CGI vars here*/
setenv("QUERY_STRING", cgiargs, 1); //QUERY_STRING 환경변수를 요청URI의 CGI 인자들로 초기화(다른 CGI환경변수들도 마찬가지로 설정!)
setenv("REQUEST_METHOD", method, 1);
Dup2(fd, STDOUT_FILENO); //자식은 자식의 표준 출력을 연결 파일 식별자로 재지정(파일을 복사하는거(앞에꺼를 뒤에로))
Execve(filename, emptylist, environ); //CGI프로그램을 로드하고 실행
}
Wait(NULL); //부모는 자식이 종료되어 정리되는 것을 기다리기 위해 wait함수에서 블록
}
/*Tiny는 반복실행 서버로 명령줄에서 넘겨받은 포트로의 연결 요청을 듣는다.*/
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);
}
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); // 트랜잭션 수행
Close(connfd); // 자신쪽(서버?)의 연결 끝을 닫는다.
}
}
2. Proxy + Tiny
#include <stdio.h>
#include "csapp.h"
void doit(int fd);
void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *request_ip, char *port, char *filename);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg,
char *longmsg);
void header_make(char *method, char *request_ip, char *user_agent_hdr, char *version, int clientfd, char *filename);
void serve_static(int clientfd, int fd);
/* 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";
/*doit, 한 개의 HTTP 트랜잭션을 처리한다.*/
void doit(int fd)// fd = file descriptor = 파일 식별자(connfd 받아온거임)
{
int clientfd;
// MAXLINE : 8192, 2^23, 8kbyte
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char request_ip[MAXLINE], port[MAXLINE], filename[MAXLINE];
rio_t rio;
rio_t rio_com;
Rio_readinitb(&rio, fd); //fd의 주소값을 rio로!
Rio_readlineb(&rio, buf, MAXLINE); //한줄을 읽어 오는거 (GET /godzilla.gif HTTP/1.1)
printf("Request headers: \n");
printf("%s", buf);
//자르기 시작
sscanf(buf, "%s http://%s %s", method, uri, version);
//이상한 요청 거르기
if (!(strcasecmp(method, "GET") == 0 || strcasecmp(method, "HEAD") == 0))
{
clienterror(fd, method, "501", "Not impemented",
"Tiny does not implement this method");
return;
}
//파싱 시작
parse_uri(uri, request_ip, port, filename);
//파싱 완료
//헤더랑 응답라인
clientfd = Open_clientfd(request_ip, port);
header_make(method, request_ip, user_agent_hdr, version, clientfd, filename);
//헤더 만들고 tiny로 보냈음
//서버에서 왔음
serve_static(clientfd, fd);
Close(clientfd);
}
/*클라이언트에서 온 헤더 첫 줄 파싱*/
/*
URI 파싱 조건필요한거 :
1. method : GET (이미 있음)
2. request_ip : naver.com/ (:// 뒤에부터 다음 / 나올때까지)
3. filename : index.html (request_ip 포인터 다음 부터 : 나올때까지)
4. port : 8000 (filename 포인터 부터 공백까지)
5. version : HTTP/1.0 (마지막 글자 0으로 수정 필요)
6. 포트가 없을 수도 있음
7. 파일이 없어서 포트 뒤에 '/'가 없을 수도 있음
*/
int parse_uri(char *uri, char *request_ip, char *port, char *filename)
{
char *ptr;
ptr = strchr(uri, 58); // ':'아스키 코드
if(ptr != NULL) //port가 있을때
{
*ptr = '\0';
strcpy(request_ip, uri);// : 앞에 ip 가져온다.
strcpy(port, ptr+1); // : 뒤로 다 가져온다 8000/index.html
ptr = strchr(port, 47); // '/'아스키 코드
if (ptr != NULL){ // '/' 있다면
strcpy(filename, ptr);
*ptr = '\0';
strcpy(port, port);
}else{ // '/' 없다면
strcpy(port, port);
}
}
else //port가 없을때
{
strcpy(request_ip, uri);
ptr = strchr(request_ip, 47);
strcpy(filename, ptr+1);
port = "80";
}
}
void header_make(char *method, char *request_ip, char *user_agent_hdr, char *version, int clientfd, char *filename)
{
char buf[MAXLINE]; //임시 버프
/*자른 데이터를 헤더로 만들어 buf로 tiny에게 전달*/
//앞줄을 하나씩 물고와요 한다.
sprintf(buf, "%s %s %s\r\n", method, filename, "HTTP/1.0");
sprintf(buf, "%sHost: %s\r\n", buf, request_ip);
sprintf(buf, "%s%s", buf, user_agent_hdr);
sprintf(buf, "%sConnection: %s\r\n", buf, "close");
sprintf(buf, "%sProxy-Connection: %s\r\n\r\n", buf, "close");
//서버로 보낸다.
Rio_writen(clientfd, buf, strlen(buf));
}
/*정적 컨텐츠를 클라이언트에게 제공*/
void serve_static(int clientfd, int fd)
{
int src_size;
char *srcp, *p, content_length[MAXLINE], buf[MAXBUF];
rio_t server_rio;
Rio_readinitb(&server_rio, clientfd); //rio_t의 읽기 버퍼와 fd를 연결
Rio_readlineb(&server_rio, buf, MAXLINE); //rio_t에서 읽어서 버퍼에 저장(한줄씩 >_-)
Rio_writen(fd, buf, strlen(buf)); //fd에 써서 보내준다.(클라이언트로)
while(strcmp(buf, "\r\n")) //우리의 헤더는 가장 끝에 빈줄이 있으므로 그걸 만날때까지 읽어보자
{
if (strncmp(buf, "Content-length:", 15)==0) //strncmp함수는 두 문자열의 길이를 비교한다. 참이면 같다!
{
p = index(buf, 32); //32은 아스키 코드 공백, 공백의 포인터 할당
strcpy(content_length, p+1); //공백뒤에 있는 '텍스트'를 임시 변수에 할당
src_size = atoi(content_length); //정수로 바꾸어 src_size에 사이즈 저장
}
Rio_readlineb(&server_rio, buf, MAXLINE); //읽으면서 보내(클라이언트로
Rio_writen(fd, buf, strlen(buf));
}
/*헤더끝*/
/*body 보내기 시작*/
/*mmap대신 malloc으로 구현*/
srcp = malloc(src_size); //바디 사이즈 할당
Rio_readnb(&server_rio, srcp, src_size); //읽고
Rio_writen(fd, srcp, src_size); //보내
free(srcp); //malloc 프리
}
/*에러 처리 함수*/
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
//MAXBUF : 8192
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));
}
/*argc: 함수의 전달된 인자의 개수, argv: 가변적인 개수의 문자열*/
int main(int argc, char **argv) {
int listenfd, connfd; // listenfd: 프록시 듣기식별자, connfd: 프록시연결 식별자
char hostname[MAXLINE], port[MAXLINE]; // 클라이언트에게 받은 uil 정보를 담음 공간
socklen_t clientlen; // 소켓 길이를 저장할 구조체
struct sockaddr_storage clientaddr; // 소켓 구조체(clientaddress 들어감)
if (argc != 2) { //입력인자가 2개인지 확인
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(1);
}
listenfd = Open_listenfd(argv[1]); // listen 소켓 오픈
/*무한서버 루프, 반복적으로 연결 요청을 접수*/
while (1) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); // 연결 요청 접수, 소켓 어드래스(SA)
//-> 포트번호는 내가 정하고 있고 사용자가 주소를 치면 받아서 비교 후 트억셉 -> 연결!
Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
// 받은걸로 호스트 네임과 포트를 저장
// hostname: 143.248.204.8(내아이피) , port: 49981
printf("Accepted connection from (%s, %s)\n", hostname, port);
doit(connfd); // 이제 connfd로 연결하러 doit!
Close(connfd); // 자신쪽(서버?)의 연결 끝을 닫는다.
}
}
3. Proxy + Thead + Tiny
#include <stdio.h>
#include "csapp.h"
#include "sbuf.h"
/*
사전쓰레드된 동시성 서버구현
- 한 개의 생산자와 다수의 소비자를 갖는 생산자-소비자
* Client Proxy(thread) Server
* ---------- ---------- ----------
* | Clientfd | -------> | Connfd | | Connfd |
* ---------- ---------- ----------
* | | | Proxy | doit() | |
* | | | Clientfd | ---------> | |
* | | | | request_to_server() | Listenfd |
* | | | Listenfd | | |
* | | <------- | | <--------- | |
* ---------- Close() ---------- response_from_server() ----------
* | CacheMem |
* ----------
*/
void *thread(void *vargp);
int sbuf_remove(sbuf_t *sp);
void sbuf_init(sbuf_t *sp, int n);
void doit(int fd);
void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *request_ip, char *port, char *filename);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg,
char *longmsg);
void header_make(char *method, char *request_ip, char *user_agent_hdr, char *version, int clientfd, char *filename);
void serve_static(int clientfd, int fd);
sbuf_t sbuf;
/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define NTHREADS 4
#define SBUFSIZE 16
/* 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";
/*doit, 한 개의 HTTP 트랜잭션을 처리한다.*/
void doit(int fd)// fd = file descriptor = 파일 식별자(connfd 받아온거임)
{
int clientfd;
// MAXLINE : 8192, 2^23, 8kbyte
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char request_ip[MAXLINE], port[MAXLINE], filename[MAXLINE];
rio_t rio;
rio_t rio_com;
Rio_readinitb(&rio, fd); //fd의 주소값을 rio로!
Rio_readlineb(&rio, buf, MAXLINE); //한줄을 읽어 오는거 (GET /godzilla.gif HTTP/1.1)
printf("Request headers: \n");
printf("%s", buf);
//자르기 시작
sscanf(buf, "%s http://%s %s", method, uri, version);
//이상한 요청 거르기
if (!(strcasecmp(method, "GET") == 0 || strcasecmp(method, "HEAD") == 0))
{
clienterror(fd, method, "501", "Not impemented",
"Tiny does not implement this method");
return;
}
//파싱 시작
parse_uri(uri, request_ip, port, filename);
//파싱 완료
//헤더랑 응답라인
clientfd = Open_clientfd(request_ip, port);
header_make(method, request_ip, user_agent_hdr, version, clientfd, filename);
//헤더 만들고 tiny로 보냈음
//서버에서 왔음
serve_static(clientfd, fd);
Close(clientfd);
}
/*클라이언트에서 온 헤더 첫 줄 파싱*/
/*
URI 파싱 조건필요한거 :
1. method : GET (이미 있음)
2. request_ip : naver.com/ (:// 뒤에부터 다음 / 나올때까지)
3. filename : index.html (request_ip 포인터 다음 부터 : 나올때까지)
4. port : 8000 (filename 포인터 부터 공백까지)
5. version : HTTP/1.0 (마지막 글자 0으로 수정 필요)
6. 포트가 없을 수도 있음
7. 파일이 없어서 포트 뒤에 '/'가 없을 수도 있음
*/
int parse_uri(char *uri, char *request_ip, char *port, char *filename)
{
char *ptr;
ptr = strchr(uri, 58); // ':'아스키 코드
if(ptr != NULL) //port가 있을때
{
*ptr = '\0';
strcpy(request_ip, uri);// : 앞에 ip 가져온다.
strcpy(port, ptr+1); // : 뒤로 다 가져온다 8000/index.html
ptr = strchr(port, 47); // '/'아스키 코드
if (ptr != NULL){ // '/' 있다면
strcpy(filename, ptr);
*ptr = '\0';
strcpy(port, port);
}else{ // '/' 없다면
strcpy(port, port);
}
}
else //port가 없을때
{
strcpy(request_ip, uri);
ptr = strchr(request_ip, 47);
strcpy(filename, ptr+1);
port = "80";
}
}
void header_make(char *method, char *request_ip, char *user_agent_hdr, char *version, int clientfd, char *filename)
{
char buf[MAXLINE]; //임시 버프
/*자른 데이터를 헤더로 만들어 buf로 tiny에게 전달*/
//앞줄을 하나씩 물고와요 한다.
sprintf(buf, "%s %s %s\r\n", method, filename, "HTTP/1.0");
sprintf(buf, "%sHost: %s\r\n", buf, request_ip);
sprintf(buf, "%s%s", buf, user_agent_hdr);
sprintf(buf, "%sConnection: %s\r\n", buf, "close");
sprintf(buf, "%sProxy-Connection: %s\r\n\r\n", buf, "close");
//서버로 보낸다.
Rio_writen(clientfd, buf, strlen(buf));
}
/*정적 컨텐츠를 클라이언트에게 제공*/
void serve_static(int clientfd, int fd)
{
int src_size;
char *srcp, *p, content_length[MAXLINE], buf[MAXBUF];
rio_t server_rio;
Rio_readinitb(&server_rio, clientfd); //rio_t의 읽기 버퍼와 fd를 연결
Rio_readlineb(&server_rio, buf, MAXLINE); //rio_t에서 읽어서 버퍼에 저장(한줄씩 >_-)
Rio_writen(fd, buf, strlen(buf)); //fd에 써서 보내준다.(클라이언트로)
while(strcmp(buf, "\r\n")) //우리의 헤더는 가장 끝에 빈줄이 있으므로 그걸 만날때까지 읽어보자
{
if (strncmp(buf, "Content-length:", 15)==0) //strncmp함수는 두 문자열의 길이를 비교한다. 참이면 같다!
{
p = index(buf, 32); //32은 아스키 코드 공백, 공백의 포인터 할당
strcpy(content_length, p+1); //공백뒤에 있는 '텍스트'를 임시 변수에 할당
src_size = atoi(content_length); //정수로 바꾸어 src_size에 사이즈 저장
}
Rio_readlineb(&server_rio, buf, MAXLINE); //읽으면서 보내(클라이언트로
Rio_writen(fd, buf, strlen(buf));
}
/*헤더끝*/
/*body 보내기 시작*/
/*mmap대신 malloc으로 구현*/
srcp = malloc(src_size); //바디 사이즈 할당
Rio_readnb(&server_rio, srcp, src_size); //읽고
Rio_writen(fd, srcp, src_size); //보내
free(srcp); //malloc 프리
}
/*에러 처리 함수*/
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
//MAXBUF : 8192
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));
}
/*argc: 함수의 전달된 인자의 개수, argv: 가변적인 개수의 문자열*/
int main(int argc, char **argv) {
int listenfd, connfd; // listenfd: 프록시 듣기식별자, connfd: 프록시연결 식별자
char hostname[MAXLINE], port[MAXLINE]; // 클라이언트에게 받은 uil 정보를 담음 공간
socklen_t clientlen; // 소켓 길이를 저장할 구조체
struct sockaddr_storage clientaddr; // 소켓 구조체(clientaddress 들어감)
pthread_t tid;
if (argc != 2) { //입력인자가 2개인지 확인
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
listenfd = Open_listenfd(argv[1]); // listen 소켓 오픈
/*스레드*/
sbuf_init(&sbuf, SBUFSIZE); //버퍼 생성
for (int i = 0; i < NTHREADS; i++) //
{
Pthread_create(&tid, NULL, thread, NULL); //호출되는 프로세스에서 새로운 쓰레드 시작
}
/*무한서버 루프, 반복적으로 연결 요청을 접수*/
while (1) {
clientlen = sizeof(struct sockaddr_storage);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); // 연결 요청 접수, 소켓 어드래스(SA)
//-> 포트번호는 내가 정하고 있고 사용자가 주소를 치면 받아서 비교 후 트억셉 -> 연결!
sbuf_insert(&sbuf, connfd);
}
}
/*thread 함수*/
void *thread(void *vargp)
{
//Pthread_detach 함수는 pthread_self가 종료되면 쓰레드를 반환 하거나 분리해야함
//pthread_self는 스레드의 아이디를 반환해줌
Pthread_detach(pthread_self());
while (1)
{
int connfd = sbuf_remove(&sbuf); //가용아이템을 받으면 connfd로 연결
doit(connfd);
Close(connfd);
}
}
배운 점
1. 네트워크 7계층(OSI)에 대해서 정말 많이 이해 하였고 어떻한 키워드 별로 어떤 계층인지 어느정도 구분을 할 수 있다.
2. Proxy서버를 구현 하면서 정확한 흐름과 역할, 특징에 대해 알게 되었다.
3. TCP/IP에 대해 자세히 알게 되었다.
4. DOM에 대해 더 공부 해야 겠다.
반응형
'Develop > SW사관학교 정글5기' 카테고리의 다른 글
[WEEK09]Pintos_Project2_User Program (0) | 2022.11.29 |
---|---|
[WEEK08]PintOS_Project1_Threads (0) | 2022.11.17 |
[WEEK06]malloc함수 구현_C (0) | 2022.11.11 |
[WEEK05]RedBlackTree 구현_C (0) | 2022.11.11 |
[SW사관학교 정글]5기_WEEK03_회고 (0) | 2022.10.15 |