리눅스 환경에서 CSAPP교재 CH11 네트워크 프로그래밍 부분에 나와 있는 tiny web server와 web proxy server 코드..
처음에는 책에 나와있는 대로 단순히 HTTP protocol 을 통해 데이터를 요청만 가능한 있는 tiny 서버를 만들어보았다.
그리고 ch12 장 동시성 프로그래밍에서 배운 내용을 토대로 pthread 함수를 사용하여 다중 쓰레딩도 가능한 서버를 만들어 보았다.
주석은 달았으나 다른거 공부하느라 시간이 없어서 포스팅내용은 추후에 디벨롭....
// Tiny web server
#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);
void echo(int connfd);
void *thread(void *vargp);
/* 서버의 main function, Client는 browser */
int main(int argc, char **argv) {
int listenfd, connfd; // listen & connected 에 필요한 file descriptor */
char hostname[MAXLINE], port[MAXLINE]; // client hostname, port를 저장할 배열 선언 */
socklen_t clientlen; // unsigned int
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]); // 입력받은 port#로 local에서 passive socket 생성
while (1) {
clientlen = sizeof(clientaddr);
connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); //(SA *)는 addr 표준에 맞게 casting // line:netp:tiny:accept
Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0); // get client information from host name
printf("Accepted connection from (%s, %s)\n", hostname, port);
Pthread_create(&tid, NULL, thread, (void *)connfd); // 다중 접속을 위한 posix thread 기능 구현
}
}
void *thread(void *vargp){ //thread routine
int connfdp = (int)vargp;
Pthread_detach(pthread_self());
doit(connfdp);
Close(connfdp);
}
void doit(int fd){
int is_static;
struct stat sbuf;
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") && strcasecmp(method,"HEAD")){ // GET or HEAD가 아닐 경우 (과제 11번)
clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
return;
}
read_requesthdrs(&rio);
is_static = parse_uri(uri, filename, cgiargs); // request header 파싱
if (stat(filename, &sbuf) < 0) {
clienterror(fd, filename, "404", "Not found", "Tiny couldn’t find this file");
return;
}
/* Parse URI from GET request */
if (is_static){ /* Serve static content */
if (!(S_ISREG(sbuf.st_mode))||!(S_IRUSR & sbuf.st_mode)){
clienterror(fd, filename, "403", "Forbidden","Tiny couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size, method);
}
else{
if(!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)){
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)
{
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;
}
int parse_uri(char *uri, char* filename, char *cgiargs)
{
char *ptr;
if(!strstr(uri, "cgi-bin")){ /*static content*/
strcpy(cgiargs, " ");
strcpy(filename, ".");
strcat(filename, uri);
if (uri[strlen(uri)-1]=='/')
strcat(filename, "home.html");
return 1;
}
else {
ptr=index(uri, '?');
if (ptr) {
strcpy(cgiargs, ptr+1);
*ptr='\0';
}
else
strcpy(cgiargs, "");
strcpy(filename, ".");
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);
if (!strcasecmp(method, "HEAD")){
return;
}
/* Send response body to client */
srcfd = Open(filename, O_RDONLY, 0); //공통
// srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
srcp = (char*)malloc(filesize);
Rio_readn(srcfd, srcp, filesize);
Close(srcfd); //공통
Rio_writen(fd, srcp, filesize); //공통
// Munmap(srcp, filesize); //11.9
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/jpeg");
else if (strstr(filename, ".mp4")) //
strcpy(filetype, "video/mp4"); // 11.7예제
else
strcpy(filetype, "text/plain");
}
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 (!strcasecmp(method,"HEAD")){
return;
}
if (Fork() == 0) { /* Child */
/* Real server would set all CGI vars here */
setenv("QUERY_STRING", cgiargs, 1);
Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */
Execve(filename, emptylist, environ); /* Run CGI program */
}
Wait(NULL); /* Parent waits for and reaps child */
}
// Web proxy
#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";
static const char *conn_hdr = "Connection: close\r\n";
static const char *prox_hdr = "Proxy-Connection: close\r\n";
static const char *host_hdr_format = "Host: %s\r\n";
static const char *requestlint_hdr_format = "GET %s HTTP/1.0\r\n";
static const char *endof_hdr = "\r\n";
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 *thread(void *vargp);
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];
pthread_t tid;
struct sockaddr_storage clientaddr;
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);
Pthread_create(&tid, NULL, thread, (void *)connfd); // 다중 쓰레딩을 통한 conncurrent 접속 구현
/*sequential handle the client transaction*/
}
return 0;
}
void* thread(void *vargp){
int connfd = (int)vargp;
Pthread_detach(pthread_self());
doit(connfd);
Close(connfd);
}
/*handle the client HTTP transaction*/
void doit(int connfd)
{
int end_serverfd;/*the end server file descriptor*/
char buf[MAXLINE],method[MAXLINE],uri[MAXLINE],version[MAXLINE];
char endserver_http_header [MAXLINE];
/*store the request line arguments*/
char hostname[MAXLINE],path[MAXLINE];
int port;
rio_t rio,server_rio;/*rio is client's rio,server_rio is endserver's rio*/
Rio_readinitb(&rio,connfd);
Rio_readlineb(&rio,buf,MAXLINE);
sscanf(buf,"%s %s %s",method,uri,version); /*read the client request line*/
if(strcasecmp(method,"GET")){
printf("Proxy does not implement the method");
return;
}
/*parse the uri to get hostname,file path ,port*/
parse_uri(uri,hostname,path,&port);
/*build the http header which will send to the end server*/
build_http_header(endserver_http_header,hostname,path,port,&rio);
/*connect to the end server*/
end_serverfd = connect_endServer(hostname,port,endserver_http_header);
if(end_serverfd<0){
printf("connection failed\n");
return;
}
Rio_readinitb(&server_rio,end_serverfd);
/*write the http header to endserver*/
Rio_writen(end_serverfd,endserver_http_header,strlen(endserver_http_header));
/*receive message from end server and send to the client*/
size_t n;
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);
}
void build_http_header(char *http_header,char *hostname,char *path,int port,rio_t *client_rio)
{
char buf[MAXLINE],request_hdr[MAXLINE],other_hdr[MAXLINE],host_hdr[MAXLINE];
/*request line*/
sprintf(request_hdr,requestlint_hdr_format,path);
/*get other request header for client rio and change it */
while(Rio_readlineb(client_rio,buf,MAXLINE)>0)
{
if(strcmp(buf,endof_hdr)==0) break;/*EOF*/
if(!strncasecmp(buf,host_key,strlen(host_key)))/*Host:*/
{
strcpy(host_hdr,buf);
continue;
}
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)))
{
strcat(other_hdr,buf);
}
}
if(strlen(host_hdr)==0)
{
sprintf(host_hdr,host_hdr_format,hostname);
}
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 ;
}
/*Connect to the end server*/
inline int connect_endServer(char *hostname,int port,char *http_header){
char portStr[100];
sprintf(portStr,"%d",port);
return Open_clientfd(hostname,portStr);
}
/*parse the uri to get hostname,file path ,port*/
void parse_uri(char *uri,char *hostname,char *path,int *port)
{
*port = 80;
char* pos = strstr(uri,"//");
pos = pos!=NULL? pos+2:uri;
char*pos2 = strstr(pos,":");
if(pos2!=NULL)
{
*pos2 = '\0';
sscanf(pos,"%s",hostname);
sscanf(pos2+1,"%d%s",port,path);
}
else
{
pos2 = strstr(pos,"/");
if(pos2!=NULL)
{
*pos2 = '\0';
sscanf(pos,"%s",hostname);
*pos2 = '/';
sscanf(pos2,"%s",path);
}
else
{
sscanf(pos,"%s",hostname);
}
}
return;
}
'컴퓨터 시스템 > CSAPP' 카테고리의 다른 글
C 입력 버퍼와 출력 버퍼 stdin, stdout (0) | 2021.09.24 |
---|---|
웹 서버 기본 개념 정리(네트워크, TCP/IP , 소켓 프로그래밍) -작성중 (1) | 2021.09.20 |
링킹 (linking) 소스파일과 헤더파일 (0) | 2021.09.14 |
동적 메모리 할당(Dynamic memory allocation), malloc 함수 구현 (malloc lab) (0) | 2021.09.14 |
데이터 세그먼트 간단정리 (0) | 2021.09.06 |